🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
use std::{
sync::{atomic::Ordering, Mutex},
time::Duration,
};
use crate::{
drawing::*,
render::*,
state::{take_presentation_feedback, AnvilState, Backend},
};
#[cfg(feature = "egl")]
use smithay::backend::renderer::ImportEgl;
#[cfg(feature = "debug")]
use smithay::backend::{allocator::Fourcc, renderer::ImportMem};
use smithay::{
backend::{
allocator::{
dmabuf::{Dmabuf, DmabufAllocator},
gbm::{GbmAllocator, GbmBufferFlags},
vulkan::{ImageUsageFlags, VulkanAllocator},
},
egl::{EGLContext, EGLDisplay},
renderer::{
damage::OutputDamageTracker, element::AsRenderElements, gles::GlesRenderer, Bind, ImportDma,
ImportMemWl,
},
vulkan::{version::Version, Instance, PhysicalDevice},
x11::{WindowBuilder, X11Backend, X11Event, X11Surface},
},
delegate_dmabuf,
input::{
keyboard::LedState,
pointer::{CursorImageAttributes, CursorImageStatus},
},
output::{Mode, Output, PhysicalProperties, Subpixel},
reexports::{
ash::ext,
calloop::EventLoop,
gbm,
wayland_protocols::wp::presentation_time::server::wp_presentation_feedback,
wayland_server::{protocol::wl_surface, Display},
},
utils::{DeviceFd, IsAlive, Scale},
wayland::{
compositor,
dmabuf::{
DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier,
},
presentation::Refresh,
},
};
use tracing::{error, info, trace, warn};
pub const OUTPUT_NAME: &str = "x11";
#[derive(Debug)]
pub struct X11Data {
render: bool,
mode: Mode,
// FIXME: If GlesRenderer is dropped before X11Surface, then the MakeCurrent call inside Gles2Renderer will
// fail because the X11Surface is keeping gbm alive.
renderer: GlesRenderer,
damage_tracker: OutputDamageTracker,
surface: X11Surface,
dmabuf_state: DmabufState,
_dmabuf_global: DmabufGlobal,
_dmabuf_default_feedback: DmabufFeedback,
#[cfg(feature = "debug")]
fps: fps_ticker::Fps,
}
impl DmabufHandler for AnvilState<X11Data> {
fn dmabuf_state(&mut self) -> &mut DmabufState {
&mut self.backend_data.dmabuf_state
}
fn dmabuf_imported(&mut self, _global: &DmabufGlobal, dmabuf: Dmabuf, notifier: ImportNotifier) {
if self.backend_data.renderer.import_dmabuf(&dmabuf, None).is_ok() {
let _ = notifier.successful::<AnvilState<X11Data>>();
} else {
notifier.failed();
}
}
}
delegate_dmabuf!(AnvilState<X11Data>);
impl Backend for X11Data {
fn seat_name(&self) -> String {
"x11".to_owned()
}
fn reset_buffers(&mut self, _output: &Output) {
self.surface.reset_buffers();
}
fn early_import(&mut self, _surface: &wl_surface::WlSurface) {}
fn update_led_state(&mut self, _led_state: LedState) {}
}
pub fn run_x11() {
let mut event_loop = EventLoop::try_new().unwrap();
let display = Display::new().unwrap();
let mut display_handle = display.handle();
let backend = X11Backend::new().expect("Failed to initilize X11 backend");
let handle = backend.handle();
// Obtain the DRM node the X server uses for direct rendering.
let (node, fd) = handle
.drm_node()
.expect("Could not get DRM node used by X server");
// Create the gbm device for buffer allocation.
let device = gbm::Device::new(DeviceFd::from(fd)).expect("Failed to create gbm device");
// Initialize EGL using the GBM device.
let egl = unsafe { EGLDisplay::new(device.clone()).expect("Failed to create EGLDisplay") };
// Create the OpenGL context
let context = EGLContext::new(&egl).expect("Failed to create EGLContext");
let window = WindowBuilder::new()
.title("Anvil")
.build(&handle)
.expect("Failed to create first window");
let skip_vulkan = std::env::var("ANVIL_NO_VULKAN")
.map(|x| {
x == "1" || x.to_lowercase() == "true" || x.to_lowercase() == "yes" || x.to_lowercase() == "y"
})
.unwrap_or(false);
let vulkan_allocator = if !skip_vulkan {
Instance::new(Version::VERSION_1_2, None)
.ok()
.and_then(|instance| {
PhysicalDevice::enumerate(&instance).ok().and_then(|devices| {
devices
.filter(|phd| phd.has_device_extension(ext::physical_device_drm::NAME))
.find(|phd| {
phd.primary_node().unwrap() == Some(node)
|| phd.render_node().unwrap() == Some(node)
})
})
})
.and_then(|physical_device| {
VulkanAllocator::new(
&physical_device,
ImageUsageFlags::COLOR_ATTACHMENT | ImageUsageFlags::SAMPLED,
)
.ok()
})
} else {
None
};
let surface = match vulkan_allocator {
// Create the surface for the window.
Some(vulkan_allocator) => handle
.create_surface(
&window,
DmabufAllocator(vulkan_allocator),
context
.dmabuf_render_formats()
.iter()
.map(|format| format.modifier),
)
.expect("Failed to create X11 surface"),
None => handle
.create_surface(
&window,
DmabufAllocator(GbmAllocator::new(device, GbmBufferFlags::RENDERING)),
context
.dmabuf_render_formats()
.iter()
.map(|format| format.modifier),
)
.expect("Failed to create X11 surface"),
};
#[cfg_attr(not(feature = "egl"), allow(unused_mut))]
let mut renderer = unsafe { GlesRenderer::new(context) }.expect("Failed to initialize renderer");
#[cfg(feature = "egl")]
if renderer.bind_wl_display(&display.handle()).is_ok() {
info!("EGL hardware-acceleration enabled");
}
let dmabuf_formats = renderer.dmabuf_formats();
let dmabuf_default_feedback = DmabufFeedbackBuilder::new(node.dev_id(), dmabuf_formats)
.build()
.unwrap();
let mut dmabuf_state = DmabufState::new();
let dmabuf_global = dmabuf_state.create_global_with_default_feedback::<AnvilState<X11Data>>(
&display.handle(),
&dmabuf_default_feedback,
);
let size = {
let s = window.size();
(s.w as i32, s.h as i32).into()
};
let mode = Mode {
size,
refresh: 60_000,
};
#[cfg(feature = "debug")]
#[allow(deprecated)]
let fps_image =
image::io::Reader::with_format(std::io::Cursor::new(FPS_NUMBERS_PNG), image::ImageFormat::Png)
.decode()
.unwrap();
#[cfg(feature = "debug")]
let fps_texture = renderer
.import_memory(
&fps_image.to_rgba8(),
Fourcc::Abgr8888,
(fps_image.width() as i32, fps_image.height() as i32).into(),
false,
)
.expect("Unable to upload FPS texture");
#[cfg(feature = "debug")]
let mut fps_element = FpsElement::new(fps_texture);
let output = Output::new(
OUTPUT_NAME.to_string(),
PhysicalProperties {
size: (0, 0).into(),
subpixel: Subpixel::Unknown,
make: "Smithay".into(),
model: "X11".into(),
},
);
let _global = output.create_global::<AnvilState<X11Data>>(&display.handle());
output.change_current_state(Some(mode), None, None, Some((0, 0).into()));
output.set_preferred(mode);
let damage_tracker = OutputDamageTracker::from_output(&output);
let data = X11Data {
render: true,
mode,
surface,
renderer,
damage_tracker,
dmabuf_state,
_dmabuf_global: dmabuf_global,
_dmabuf_default_feedback: dmabuf_default_feedback,
#[cfg(feature = "debug")]
fps: fps_ticker::Fps::default(),
};
let mut state = AnvilState::init(display, event_loop.handle(), data, true);
state
.shm_state
.update_formats(state.backend_data.renderer.shm_formats());
state.space.map_output(&output, (0, 0));
let output_clone = output.clone();
event_loop
.handle()
.insert_source(backend, move |event, _, data| match event {
X11Event::CloseRequested { .. } => {
data.running.store(false, Ordering::SeqCst);
}
X11Event::Resized { new_size, .. } => {
let output = &output_clone;
let size = { (new_size.w as i32, new_size.h as i32).into() };
data.backend_data.mode = Mode {
size,
refresh: 60_000,
};
output.delete_mode(output.current_mode().unwrap());
output.change_current_state(Some(data.backend_data.mode), None, None, None);
output.set_preferred(data.backend_data.mode);
crate::shell::fixup_positions(&mut data.space, data.pointer.current_location());
data.backend_data.render = true;
}
X11Event::PresentCompleted { .. } | X11Event::Refresh { .. } => {
data.backend_data.render = true;
}
X11Event::Input { event, .. } => data.process_input_event_windowed(event, OUTPUT_NAME),
X11Event::Focus { focused: false, .. } => {
data.release_all_keys();
}
_ => {}
})
.expect("Failed to insert X11 Backend into event loop");
#[cfg(feature = "xwayland")]
state.start_xwayland();
info!("Initialization completed, starting the main loop.");
let mut pointer_element = PointerElement::default();
while state.running.load(Ordering::SeqCst) {
if state.backend_data.render {
profiling::scope!("render_frame");
let now = state.clock.now();
let frame_target = now
+ output
.current_mode()
.map(|mode| Duration::from_secs_f64(1_000f64 / mode.refresh as f64))
.unwrap_or_default();
state.pre_repaint(&output, frame_target);
let backend_data = &mut state.backend_data;
// We need to borrow everything we want to refer to inside the renderer callback otherwise rustc is unhappy.
#[cfg(feature = "debug")]
let fps = backend_data.fps.avg().round() as u32;
#[cfg(feature = "debug")]
fps_element.update_fps(fps);
let (mut buffer, age) = backend_data.surface.buffer().expect("gbm device was destroyed");
let mut fb = match backend_data.renderer.bind(&mut buffer) {
Ok(fb) => fb,
Err(err) => {
error!("Error while binding buffer: {}", err);
profiling::finish_frame!();
continue;
}
};
#[cfg(feature = "debug")]
if let Some(renderdoc) = state.renderdoc.as_mut() {
renderdoc.start_frame_capture(
backend_data.renderer.egl_context().get_context_handle(),
std::ptr::null(),
);
}
let mut elements: Vec<CustomRenderElements<GlesRenderer>> = Vec::new();
// draw the cursor as relevant
// reset the cursor if the surface is no longer alive
let mut reset = false;
if let CursorImageStatus::Surface(ref surface) = state.cursor_status {
reset = !surface.alive();
}
if reset {
state.cursor_status = CursorImageStatus::default_named();
}
let cursor_visible = !matches!(state.cursor_status, CursorImageStatus::Surface(_));
let scale = Scale::from(output.current_scale().fractional_scale());
let cursor_hotspot = if let CursorImageStatus::Surface(ref surface) = state.cursor_status {
compositor::with_states(surface, |states| {
states
.data_map
.get::<Mutex<CursorImageAttributes>>()
.unwrap()
.lock()
.unwrap()
.hotspot
})
} else {
(0, 0).into()
};
let cursor_pos = state.pointer.current_location();
pointer_element.set_status(state.cursor_status.clone());
elements.extend(
pointer_element.render_elements(
&mut backend_data.renderer,
(cursor_pos - cursor_hotspot.to_f64())
.to_physical(scale)
.to_i32_round(),
scale,
1.0,
),
);
// draw the dnd icon if any
if let Some(icon) = state.dnd_icon.as_ref() {
let dnd_icon_pos = (cursor_pos + icon.offset.to_f64())
.to_physical(scale)
.to_i32_round();
if icon.surface.alive() {
elements.extend(AsRenderElements::<GlesRenderer>::render_elements(
&smithay::desktop::space::SurfaceTree::from_surface(&icon.surface),
&mut backend_data.renderer,
dnd_icon_pos,
scale,
1.0,
));
}
}
#[cfg(feature = "debug")]
elements.push(CustomRenderElements::Fps(fps_element.clone()));
let render_res = render_output(
&output,
&state.space,
elements,
&mut backend_data.renderer,
&mut fb,
&mut backend_data.damage_tracker,
age.into(),
state.show_window_preview,
);
match render_res {
Ok(render_output_result) => {
trace!("Finished rendering");
let submitted = if let Err(err) = backend_data.surface.submit() {
backend_data.surface.reset_buffers();
warn!("Failed to submit buffer: {}. Retrying", err);
false
} else {
true
};
let states = render_output_result.states;
#[cfg(feature = "debug")]
let rendered = render_output_result.damage.is_some();
if render_output_result.damage.is_some() {
let mut output_presentation_feedback =
take_presentation_feedback(&output, &state.space, &states);
output_presentation_feedback.presented(
frame_target,
output
.current_mode()
.map(|mode| {
Refresh::fixed(Duration::from_secs_f64(1_000f64 / mode.refresh as f64))
})
.unwrap_or(Refresh::Unknown),
0,
wp_presentation_feedback::Kind::Vsync,
)
}
#[cfg(feature = "debug")]
if rendered {
if let Some(renderdoc) = state.renderdoc.as_mut() {
renderdoc.end_frame_capture(
state.backend_data.renderer.egl_context().get_context_handle(),
std::ptr::null(),
);
}
} else if let Some(renderdoc) = state.renderdoc.as_mut() {
renderdoc.discard_frame_capture(
state.backend_data.renderer.egl_context().get_context_handle(),
std::ptr::null(),
);
}
state.backend_data.render = !submitted;
// Send frame events so that client start drawing their next frame
state.post_repaint(&output, frame_target, None, &states);
}
Err(err) => {
#[cfg(feature = "debug")]
if let Some(renderdoc) = state.renderdoc.as_mut() {
renderdoc.discard_frame_capture(
backend_data.renderer.egl_context().get_context_handle(),
std::ptr::null(),
);
}
backend_data.surface.reset_buffers();
error!("Rendering error: {}", err);
// TODO: convert RenderError into SwapBuffersError and skip temporary (will retry) and panic on ContextLost or recreate
}
}
#[cfg(feature = "debug")]
state.backend_data.fps.tick();
window.set_cursor_visible(cursor_visible);
profiling::finish_frame!();
}
let result = event_loop.dispatch(Some(Duration::from_millis(16)), &mut state);
if result.is_err() {
state.running.store(false, Ordering::SeqCst);
} else {
state.space.refresh();
state.popups.cleanup();
display_handle.flush_clients().unwrap();
}
}
}