kms: Allow updating the primary node
Add more sophisticated code to handle the primary node disappearing. Also overhaul the selection logic to respect our allow/deny-list and prefer devices with built-in connectors before using the boot gpu. This will also allow triggering a primary node switch at runtime for debugging purposes in the future.
This commit is contained in:
parent
4c0c61e94b
commit
8194be30c6
8 changed files with 153 additions and 93 deletions
|
|
@ -4,7 +4,7 @@ use crate::{
|
|||
backend::render::{output_elements, CursorMode, GlMultiRenderer, CLEAR_COLOR},
|
||||
config::{AdaptiveSync, EdidProduct, OutputConfig, OutputState, ScreenFilter},
|
||||
shell::Shell,
|
||||
utils::prelude::*,
|
||||
utils::{env::dev_list_var, prelude::*},
|
||||
wayland::protocols::screencopy::Frame,
|
||||
};
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ use smithay::{
|
|||
compositor::{FrameError, FrameFlags},
|
||||
exporter::gbm::GbmFramebufferExporter,
|
||||
output::DrmOutputManager,
|
||||
DrmDevice, DrmDeviceFd, DrmEvent, DrmNode,
|
||||
DrmDevice, DrmDeviceFd, DrmEvent, DrmNode, NodeType,
|
||||
},
|
||||
egl::{context::ContextPriority, EGLContext, EGLDevice, EGLDisplay},
|
||||
session::Session,
|
||||
|
|
@ -312,7 +312,7 @@ impl State {
|
|||
{
|
||||
for (conn, maybe_crtc) in connectors {
|
||||
match device.connector_added(
|
||||
self.backend.kms().primary_node.as_ref(),
|
||||
self.backend.kms().primary_node.clone(),
|
||||
conn,
|
||||
maybe_crtc,
|
||||
(w, 0),
|
||||
|
|
@ -336,7 +336,14 @@ impl State {
|
|||
|
||||
// TODO atomic commit all surfaces together and drop surfaces, if it fails due to bandwidth
|
||||
|
||||
self.backend.kms().drm_devices.insert(drm_node, device);
|
||||
let kms = self.backend.kms();
|
||||
let was_empty = kms.drm_devices.is_empty();
|
||||
kms.drm_devices.insert(drm_node, device);
|
||||
if was_empty {
|
||||
if let Err(err) = kms.select_primary_gpu(dh) {
|
||||
warn!("Failed to determine a new primary gpu: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.common
|
||||
|
|
@ -406,7 +413,7 @@ impl State {
|
|||
|
||||
for (conn, maybe_crtc) in changes.added {
|
||||
match device.connector_added(
|
||||
backend.primary_node.as_ref(),
|
||||
backend.primary_node.clone(),
|
||||
conn,
|
||||
maybe_crtc,
|
||||
(w, 0),
|
||||
|
|
@ -466,7 +473,7 @@ impl State {
|
|||
let drm_node = DrmNode::from_dev_id(dev)?;
|
||||
let mut outputs_removed = Vec::new();
|
||||
let backend = self.backend.kms();
|
||||
if let Some(mut device) = backend.drm_devices.remove(&drm_node) {
|
||||
if let Some(mut device) = backend.drm_devices.shift_remove(&drm_node) {
|
||||
if let Some(mut leasing_global) = device.leasing_global.take() {
|
||||
leasing_global.disable_global::<State>();
|
||||
}
|
||||
|
|
@ -483,6 +490,12 @@ impl State {
|
|||
.destroy_global::<State>(dh, socket.dmabuf_global);
|
||||
dh.remove_global::<State>(socket.drm_global);
|
||||
}
|
||||
let was_primary = *backend.primary_node.read().unwrap() == Some(device.render_node);
|
||||
if was_primary {
|
||||
if let Err(err) = backend.select_primary_gpu(dh) {
|
||||
warn!("Failed to determine a new primary gpu: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.common
|
||||
.output_configuration_state
|
||||
|
|
@ -558,7 +571,7 @@ impl Device {
|
|||
|
||||
pub fn connector_added(
|
||||
&mut self,
|
||||
primary_node: Option<&DrmNode>,
|
||||
primary_node: Arc<RwLock<Option<DrmNode>>>,
|
||||
conn: connector::Handle,
|
||||
maybe_crtc: Option<crtc::Handle>,
|
||||
position: (u32, u32),
|
||||
|
|
@ -623,7 +636,7 @@ impl Device {
|
|||
&output,
|
||||
crtc,
|
||||
conn,
|
||||
primary_node.copied().unwrap_or(self.render_node),
|
||||
primary_node,
|
||||
self.dev_node,
|
||||
self.render_node,
|
||||
evlh,
|
||||
|
|
|
|||
|
|
@ -4,11 +4,12 @@ use crate::{
|
|||
config::{AdaptiveSync, OutputState, ScreenFilter},
|
||||
shell::Shell,
|
||||
state::BackendData,
|
||||
utils::prelude::*,
|
||||
utils::{env::dev_var, prelude::*},
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use calloop::LoopSignal;
|
||||
use indexmap::IndexMap;
|
||||
use render::gles::GbmGlowBackend;
|
||||
use smithay::{
|
||||
backend::{
|
||||
|
|
@ -22,13 +23,13 @@ use smithay::{
|
|||
libinput::{LibinputInputBackend, LibinputSessionInterface},
|
||||
renderer::{glow::GlowRenderer, multigpu::GpuManager},
|
||||
session::{libseat::LibSeatSession, Event as SessionEvent, Session},
|
||||
udev::{all_gpus, primary_gpu, UdevBackend, UdevEvent},
|
||||
udev::{primary_gpu, UdevBackend, UdevEvent},
|
||||
},
|
||||
output::Output,
|
||||
reexports::{
|
||||
calloop::{Dispatcher, EventLoop, LoopHandle},
|
||||
drm::{
|
||||
control::{crtc, Device as _},
|
||||
control::{connector::Interface, crtc, Device as _},
|
||||
Device as _,
|
||||
},
|
||||
input::{self, Libinput},
|
||||
|
|
@ -65,9 +66,9 @@ use super::render::{init_shaders, output_elements, CursorMode, CLEAR_COLOR};
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct KmsState {
|
||||
pub drm_devices: HashMap<DrmNode, Device>,
|
||||
pub drm_devices: IndexMap<DrmNode, Device>,
|
||||
pub input_devices: HashMap<String, input::Device>,
|
||||
pub primary_node: Option<DrmNode>,
|
||||
pub primary_node: Arc<RwLock<Option<DrmNode>>>,
|
||||
// Mesa llvmpipe renderer, if supported and there are no render nodes
|
||||
pub software_renderer: Option<GlowRenderer>,
|
||||
pub api: GpuManager<GbmGlowBackend<DrmDeviceFd>>,
|
||||
|
|
@ -90,24 +91,6 @@ pub fn init_backend(
|
|||
let libinput_context = init_libinput(dh, &session, &event_loop.handle())
|
||||
.context("Failed to initialize libinput backend")?;
|
||||
|
||||
// get our primary gpu
|
||||
let primary = determine_primary_gpu(session.seat());
|
||||
if let Some(primary) = primary.as_ref() {
|
||||
info!("Using {} as primary gpu for rendering.", primary);
|
||||
}
|
||||
|
||||
let software_renderer = if primary.is_none() {
|
||||
match software_renderer() {
|
||||
Ok(renderer) => Some(renderer),
|
||||
Err(err) => {
|
||||
error!(?err, "Failed to initialize software EGL renderer.");
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// watch for gpu events
|
||||
let udev_dispatcher = init_udev(session.seat(), &event_loop.handle())
|
||||
.context("Failed to initialize udev connection")?;
|
||||
|
|
@ -134,10 +117,10 @@ pub fn init_backend(
|
|||
|
||||
// finish backend initialization
|
||||
state.backend = BackendData::Kms(KmsState {
|
||||
drm_devices: HashMap::new(),
|
||||
drm_devices: IndexMap::new(),
|
||||
input_devices: HashMap::new(),
|
||||
primary_node: primary,
|
||||
software_renderer,
|
||||
primary_node: Arc::new(RwLock::new(None)),
|
||||
software_renderer: None,
|
||||
api: GpuManager::new(GbmGlowBackend::new()).context("Failed to initialize gpu backend")?,
|
||||
|
||||
session,
|
||||
|
|
@ -146,9 +129,6 @@ pub fn init_backend(
|
|||
syncobj_state: None,
|
||||
});
|
||||
|
||||
// start x11
|
||||
state.launch_xwayland(primary);
|
||||
|
||||
// manually add already present gpus
|
||||
for (dev, path) in udev_dispatcher.as_source_ref().device_list() {
|
||||
if let Err(err) = state.device_added(dev, path.into(), dh) {
|
||||
|
|
@ -156,24 +136,9 @@ pub fn init_backend(
|
|||
}
|
||||
}
|
||||
|
||||
if !crate::utils::env::bool_var("COSMIC_DISABLE_SYNCOBJ").unwrap_or(false) {
|
||||
let kms = match &mut state.backend {
|
||||
BackendData::Kms(kms) => kms,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
if let Some(primary_node) = kms
|
||||
.primary_node
|
||||
.and_then(|node| node.node_with_type(NodeType::Primary).and_then(|x| x.ok()))
|
||||
{
|
||||
if let Some(device) = kms.drm_devices.get(&primary_node) {
|
||||
let import_device = device.drm.device().device_fd().clone();
|
||||
if supports_syncobj_eventfd(&import_device) {
|
||||
let syncobj_state = DrmSyncobjState::new::<State>(&dh, import_device);
|
||||
kms.syncobj_state = Some(syncobj_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// start x11
|
||||
let primary = state.backend.kms().primary_node.read().unwrap().clone();
|
||||
state.launch_xwayland(primary);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -217,32 +182,51 @@ fn init_libinput(
|
|||
Ok(libinput_context)
|
||||
}
|
||||
|
||||
fn determine_primary_gpu(seat: String) -> Option<DrmNode> {
|
||||
if let Some(node) = std::env::var("COSMIC_RENDER_DEVICE")
|
||||
fn determine_boot_gpu(seat: String) -> Option<DrmNode> {
|
||||
let primary_node = primary_gpu(&seat)
|
||||
.ok()
|
||||
.and_then(|x| DrmNode::from_path(x).ok())
|
||||
{
|
||||
Some(node)
|
||||
} else {
|
||||
let primary_node = primary_gpu(&seat)
|
||||
.ok()
|
||||
.flatten()
|
||||
.and_then(|x| DrmNode::from_path(x).ok());
|
||||
primary_node
|
||||
.and_then(|x| x.node_with_type(NodeType::Render).and_then(Result::ok))
|
||||
.or_else(|| {
|
||||
for dev in all_gpus(&seat).expect("Failed to query gpus") {
|
||||
if let Some(node) = DrmNode::from_path(dev)
|
||||
.ok()
|
||||
.and_then(|x| x.node_with_type(NodeType::Render).and_then(Result::ok))
|
||||
{
|
||||
return Some(node);
|
||||
}
|
||||
}
|
||||
.flatten()
|
||||
.and_then(|x| DrmNode::from_path(x).ok());
|
||||
primary_node.and_then(|x| x.node_with_type(NodeType::Render).and_then(Result::ok))
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
fn determine_primary_gpu(
|
||||
drm_devices: &IndexMap<DrmNode, Device>,
|
||||
seat: String,
|
||||
) -> Result<Option<DrmNode>> {
|
||||
if let Some(device) = dev_var("COSMIC_RENDER_DEVICE") {
|
||||
if let Some(node) = drm_devices
|
||||
.values()
|
||||
.find_map(|dev| device.matches(&dev.render_node).then_some(dev.render_node))
|
||||
{
|
||||
return Ok(Some(node));
|
||||
}
|
||||
}
|
||||
|
||||
// try to find builtin display
|
||||
for dev in drm_devices.values() {
|
||||
if dev.surfaces.values().any(|s| {
|
||||
if let Some(conn_info) = dev.drm.device().get_connector(s.connector, false).ok() {
|
||||
let i = conn_info.interface();
|
||||
i == Interface::EmbeddedDisplayPort || i == Interface::LVDS || i == Interface::DSI
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}) {
|
||||
return Ok(Some(dev.render_node));
|
||||
}
|
||||
}
|
||||
|
||||
// else try to find the boot gpu
|
||||
let boot = determine_boot_gpu(seat);
|
||||
if let Some(boot) = boot {
|
||||
if drm_devices.values().any(|dev| dev.render_node == boot) {
|
||||
return Ok(Some(boot));
|
||||
}
|
||||
}
|
||||
|
||||
// else just take the first
|
||||
Ok(drm_devices.values().next().map(|dev| dev.render_node))
|
||||
}
|
||||
|
||||
/// Create `GlowRenderer` for `EGL_MESA_device_software` device, if present
|
||||
|
|
@ -375,6 +359,55 @@ impl State {
|
|||
}
|
||||
|
||||
impl KmsState {
|
||||
fn select_primary_gpu(&mut self, dh: &DisplayHandle) -> Result<()> {
|
||||
// We don't have to check the allow/blocklist here,
|
||||
// as any disallowed devices won't be in `self.drm_devices`.
|
||||
|
||||
let mut primary_node = self.primary_node.write().unwrap();
|
||||
let _ = primary_node.take(); // if we error don't leave an old node in place
|
||||
*primary_node = determine_primary_gpu(&self.drm_devices, self.session.seat())?;
|
||||
|
||||
if let Some(node) = *primary_node {
|
||||
info!("Using {} as primary gpu for rendering.", node);
|
||||
self.software_renderer.take();
|
||||
} else if self.software_renderer.is_none() {
|
||||
info!("Failed to find a suitable gpu, using software renderingr");
|
||||
self.software_renderer = match software_renderer() {
|
||||
Ok(renderer) => Some(renderer),
|
||||
Err(err) => {
|
||||
error!(?err, "Failed to initialize software EGL renderer.");
|
||||
None
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if !crate::utils::env::bool_var("COSMIC_DISABLE_SYNCOBJ").unwrap_or(false) {
|
||||
if let Some(primary_node) = primary_node
|
||||
.as_ref()
|
||||
.and_then(|node| node.node_with_type(NodeType::Primary).and_then(|x| x.ok()))
|
||||
{
|
||||
if let Some(device) = self.drm_devices.get(&primary_node) {
|
||||
let import_device = device.drm.device().device_fd().clone();
|
||||
if supports_syncobj_eventfd(&import_device) {
|
||||
if let Some(state) = self.syncobj_state.as_mut() {
|
||||
state.update_device(import_device);
|
||||
} else {
|
||||
let syncobj_state = DrmSyncobjState::new::<State>(&dh, import_device);
|
||||
self.syncobj_state = Some(syncobj_state);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(old_state) = self.syncobj_state.take() {
|
||||
dh.remove_global::<State>(old_state.into_global());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn switch_vt(&mut self, num: i32) -> Result<(), anyhow::Error> {
|
||||
self.session.change_vt(num).map_err(Into::into)
|
||||
}
|
||||
|
|
@ -459,7 +492,7 @@ impl KmsState {
|
|||
let mut used_devices = HashSet::new();
|
||||
|
||||
for device in self.drm_devices.values_mut() {
|
||||
if device.in_use(self.primary_node.as_ref()) {
|
||||
if device.in_use(self.primary_node.read().unwrap().as_ref()) {
|
||||
if device.egl.is_none() {
|
||||
let egl = init_egl(&device.gbm).context("Failed to create EGL context")?;
|
||||
let mut renderer = unsafe {
|
||||
|
|
@ -495,7 +528,7 @@ impl KmsState {
|
|||
}
|
||||
|
||||
// trigger re-evaluation... urgh
|
||||
if let Some(primary_node) = self.primary_node.as_ref() {
|
||||
if let Some(primary_node) = self.primary_node.read().unwrap().as_ref() {
|
||||
let _ = self.api.single_renderer(primary_node);
|
||||
}
|
||||
|
||||
|
|
@ -654,7 +687,7 @@ impl KmsState {
|
|||
if !test_only {
|
||||
for (conn, crtc) in new_pairings {
|
||||
let (output, _) = device.connector_added(
|
||||
self.primary_node.as_ref(),
|
||||
self.primary_node.clone(),
|
||||
conn,
|
||||
Some(crtc),
|
||||
(w, 0),
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ pub struct Surface {
|
|||
pub struct SurfaceThreadState {
|
||||
// rendering
|
||||
api: GpuManager<GbmGlowBackend<DrmDeviceFd>>,
|
||||
primary_node: DrmNode,
|
||||
primary_node: Arc<RwLock<Option<DrmNode>>>,
|
||||
target_node: DrmNode,
|
||||
active: Arc<AtomicBool>,
|
||||
vrr_mode: AdaptiveSync,
|
||||
|
|
@ -228,7 +228,7 @@ impl Surface {
|
|||
output: &Output,
|
||||
crtc: crtc::Handle,
|
||||
connector: connector::Handle,
|
||||
primary_node: DrmNode,
|
||||
primary_node: Arc<RwLock<Option<DrmNode>>>,
|
||||
dev_node: DrmNode,
|
||||
target_node: DrmNode,
|
||||
evlh: &LoopHandle<'static, State>,
|
||||
|
|
@ -469,7 +469,7 @@ impl Drop for Surface {
|
|||
|
||||
fn surface_thread(
|
||||
output: Output,
|
||||
primary_node: DrmNode,
|
||||
primary_node: Arc<RwLock<Option<DrmNode>>>,
|
||||
target_node: DrmNode,
|
||||
shell: Arc<RwLock<Shell>>,
|
||||
active: Arc<AtomicBool>,
|
||||
|
|
@ -956,7 +956,11 @@ impl SurfaceThreadState {
|
|||
|
||||
let render_node = render_node_for_output(
|
||||
self.mirroring.as_ref().unwrap_or(&self.output),
|
||||
&self.primary_node,
|
||||
self.primary_node
|
||||
.read()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.unwrap_or(&self.target_node),
|
||||
&self.target_node,
|
||||
&*self.shell.read().unwrap(),
|
||||
);
|
||||
|
|
@ -1821,6 +1825,8 @@ fn source_node_for_surface(w: &WlSurface) -> Option<DrmNode> {
|
|||
.flatten()
|
||||
}
|
||||
|
||||
// TODO: Introduce can_shared_dmabuf_framebuffer for cases where we might select another gpu
|
||||
// and composite on target if not possible to finally get rid of "primary"
|
||||
fn render_node_for_output(
|
||||
output: &Output,
|
||||
primary_node: &DrmNode,
|
||||
|
|
|
|||
|
|
@ -671,7 +671,7 @@ impl State {
|
|||
ClientState {
|
||||
compositor_client_state: CompositorClientState::default(),
|
||||
advertised_drm_node: match &self.backend {
|
||||
BackendData::Kms(kms_state) => kms_state.primary_node,
|
||||
BackendData::Kms(kms_state) => *kms_state.primary_node.read().unwrap(),
|
||||
_ => None,
|
||||
},
|
||||
privileged: !enable_wayland_security(),
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ pub fn screenshot_window(state: &mut State, surface: &CosmicSurface) {
|
|||
.backend
|
||||
.offscreen_renderer(|kms| {
|
||||
advertised_node_for_surface(&wl_surface, &state.common.display_handle)
|
||||
.or(kms.primary_node)
|
||||
.or(*kms.primary_node.read().unwrap())
|
||||
})
|
||||
.with_context(|| "Failed to get renderer for screenshot")
|
||||
.and_then(|renderer| match renderer {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ impl BufferHandler for State {
|
|||
if let BackendData::Kms(kms_state) = &mut self.backend {
|
||||
for device in kms_state.drm_devices.values_mut() {
|
||||
if device.active_buffers.remove(&buffer.downgrade()) {
|
||||
if !device.in_use(kms_state.primary_node.as_ref()) {
|
||||
if !device.in_use(kms_state.primary_node.read().unwrap().as_ref()) {
|
||||
if let Err(err) = kms_state.refresh_used_devices() {
|
||||
warn!(?err, "Failed to init devices.");
|
||||
};
|
||||
|
|
|
|||
|
|
@ -355,7 +355,10 @@ fn constraints_for_output(output: &Output, backend: &mut BackendData) -> Option<
|
|||
};
|
||||
|
||||
let mut renderer = backend
|
||||
.offscreen_renderer(|kms| kms.target_node_for_output(&output).or(kms.primary_node))
|
||||
.offscreen_renderer(|kms| {
|
||||
kms.target_node_for_output(&output)
|
||||
.or(*kms.primary_node.read().unwrap())
|
||||
})
|
||||
.unwrap();
|
||||
Some(constraints_for_renderer(mode, renderer.as_mut()))
|
||||
}
|
||||
|
|
@ -376,7 +379,7 @@ fn constraints_for_toplevel(
|
|||
})
|
||||
.flatten();
|
||||
|
||||
dma_node.or(kms.primary_node)
|
||||
dma_node.or(*kms.primary_node.read().unwrap())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
|
|
|
|||
|
|
@ -337,7 +337,9 @@ pub fn render_workspace_to_buffer(
|
|||
let common = &mut state.common;
|
||||
|
||||
let renderer = match state.backend.offscreen_renderer(|kms| {
|
||||
let render_node = kms.target_node_for_output(&output).or(kms.primary_node)?;
|
||||
let render_node = kms
|
||||
.target_node_for_output(&output)
|
||||
.or(*kms.primary_node.read().unwrap())?;
|
||||
let target_node = get_dmabuf(&buffer)
|
||||
.ok()
|
||||
.and_then(|dma| dma.node())
|
||||
|
|
@ -591,7 +593,7 @@ pub fn render_window_to_buffer(
|
|||
})
|
||||
.flatten()
|
||||
})
|
||||
.or(kms.primary_node)
|
||||
.or(*kms.primary_node.read().unwrap())
|
||||
}) {
|
||||
Ok(renderer) => renderer,
|
||||
Err(err) => {
|
||||
|
|
@ -745,7 +747,10 @@ pub fn render_cursor_to_buffer(
|
|||
}
|
||||
|
||||
let common = &mut state.common;
|
||||
let renderer = match state.backend.offscreen_renderer(|kms| kms.primary_node) {
|
||||
let renderer = match state
|
||||
.backend
|
||||
.offscreen_renderer(|kms| *kms.primary_node.read().unwrap())
|
||||
{
|
||||
Ok(renderer) => renderer,
|
||||
Err(err) => {
|
||||
warn!(?err, "Couldn't use node for screencopy");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue