anvil: create and render to multiple outputs independently

This commit is contained in:
Victor Brekenfeld 2020-06-11 12:15:31 +02:00
parent 9fd8dd9cec
commit d7f800c76d
2 changed files with 174 additions and 86 deletions

View File

@ -410,12 +410,13 @@ impl<F: GLGraphicsBackend + 'static> GliumDrawer<F> {
// redraw the frame, in a simple but inneficient way
{
let screen_dimensions = self.borrow().get_framebuffer_dimensions();
window_map.with_windows_from_bottom_to_top(|toplevel_surface, initial_place, bounding_box| {
window_map.with_windows_from_bottom_to_top(|toplevel_surface, mut initial_place, bounding_box| {
// skip windows that do not overlap with a given output
if let Some(output) = output_rect {
if !output.overlaps(bounding_box) {
return;
}
initial_place.0 -= output.x;
}
if let Some(wl_surface) = toplevel_surface.get_surface() {
// this surface is a root of a subsurface tree that needs to be drawn

View File

@ -45,15 +45,16 @@ use smithay::{
encoder::Info as EncoderInfo,
},
},
image::{ImageBuffer, Rgba},
image::{ImageBuffer, Rgba, Pixel},
input::Libinput,
nix::{fcntl::OFlag, sys::stat::dev_t},
wayland_server::{
protocol::{wl_output, wl_surface},
Display,
Display, Global,
},
},
signaling::{Linkable, SignalToken, Signaler},
utils::Rectangle,
wayland::{
compositor::CompositorToken,
output::{Mode, Output, PhysicalProperties},
@ -137,12 +138,15 @@ pub fn run_udev(
let bytes = include_bytes!("../resources/cursor2.rgba");
let udev_backend = UdevBackend::new(state.seat_name.clone(), log.clone()).map_err(|_| ())?;
let output_map = Rc::new(RefCell::new(Vec::new()));
let mut udev_handler = UdevHandlerImpl {
compositor_token: state.ctoken,
#[cfg(feature = "egl")]
egl_buffer_reader,
session: state.session.clone().unwrap(),
backends: HashMap::new(),
output_map,
display: display.clone(),
primary_gpu,
window_map: state.window_map.clone(),
@ -158,34 +162,7 @@ pub fn run_udev(
/*
* Initialize a fake output (we render one screen to every device in this example)
*/
let (output, _output_global) = Output::new(
&mut display.borrow_mut(),
"Drm".into(),
PhysicalProperties {
width: 0,
height: 0,
subpixel: wl_output::Subpixel::Unknown,
make: "Smithay".into(),
model: "Generic DRM".into(),
},
log.clone(),
);
let (w, h) = (1920, 1080); // Hardcode full-hd res
output.change_current_state(
Some(Mode {
width: w as i32,
height: h as i32,
refresh: 60_000,
}),
None,
None,
);
output.set_preferred(Mode {
width: w as i32,
height: h as i32,
refresh: 60_000,
});
/*
* Initialize libinput backend
@ -250,6 +227,62 @@ pub fn run_udev(
Ok(())
}
struct MyOutput {
device_id: dev_t,
crtc: crtc::Handle,
size: (u32, u32),
_wl: Output,
global: Option<Global<wl_output::WlOutput>>,
}
impl MyOutput {
fn new(display: &mut Display, device_id: dev_t, crtc: crtc::Handle, conn: ConnectorInfo, logger: ::slog::Logger) -> MyOutput {
let (output, global) = Output::new(
display,
format!("{:?}", conn.interface()),
PhysicalProperties {
width: conn.size().unwrap_or((0, 0)).0 as i32,
height: conn.size().unwrap_or((0, 0)).1 as i32,
subpixel: wl_output::Subpixel::Unknown,
make: "Smithay".into(),
model: "Generic DRM".into(),
},
logger,
);
let mode = conn.modes()[0];
let (w, h) = mode.size();
output.change_current_state(
Some(Mode {
width: w as i32,
height: h as i32,
refresh: (mode.vrefresh() * 1000) as i32,
}),
None,
None,
);
output.set_preferred(Mode {
width: w as i32,
height: h as i32,
refresh: (mode.vrefresh() * 1000) as i32,
});
MyOutput {
device_id,
crtc,
size: (w as u32, h as u32),
_wl: output,
global: Some(global),
}
}
}
impl Drop for MyOutput {
fn drop(&mut self) {
self.global.take().unwrap().destroy();
}
}
struct BackendData {
_restart_token: SignalToken,
event_source: Source<Generic<RenderDevice>>,
@ -265,6 +298,7 @@ struct UdevHandlerImpl<Data: 'static> {
display: Rc<RefCell<Display>>,
primary_gpu: Option<PathBuf>,
window_map: Rc<RefCell<MyWindowMap>>,
output_map: Rc<RefCell<Vec<MyOutput>>>,
pointer_location: Rc<RefCell<(f64, f64)>>,
pointer_image: ImageBuffer<Rgba<u8>, Vec<u8>>,
cursor_status: Arc<Mutex<CursorImageStatus>>,
@ -279,6 +313,8 @@ impl<Data: 'static> UdevHandlerImpl<Data> {
pub fn scan_connectors(
device: &mut RenderDevice,
egl_buffer_reader: Rc<RefCell<Option<EGLBufferReader>>>,
display: &mut Display,
output_map: &mut Vec<MyOutput>,
logger: &::slog::Logger,
) -> HashMap<crtc::Handle, Rc<GliumDrawer<RenderSurface>>> {
// Get a set of all modesetting resource handles (excluding planes):
@ -313,6 +349,7 @@ impl<Data: 'static> UdevHandlerImpl<Data> {
egl_buffer_reader.clone(),
logger.clone(),
);
output_map.push(MyOutput::new(display, device.device_id(), crtc, connector_info, logger.clone()));
entry.insert(Rc::new(renderer));
break 'outer;
@ -327,6 +364,8 @@ impl<Data: 'static> UdevHandlerImpl<Data> {
#[cfg(not(feature = "egl"))]
pub fn scan_connectors(
device: &mut RenderDevice,
display: &mut Display,
output_map: &mut Vec<MyOutput>,
logger: &::slog::Logger,
) -> HashMap<crtc::Handle, Rc<GliumDrawer<RenderSurface>>> {
// Get a set of all modesetting resource handles (excluding planes):
@ -356,6 +395,7 @@ impl<Data: 'static> UdevHandlerImpl<Data> {
if !backends.contains_key(&crtc) {
let renderer =
GliumDrawer::init(device.create_surface(crtc).unwrap(), logger.clone());
output_map.push(MyOutput::new(display, device.device_id(), crtc, connector_info, logger.clone()));
backends.insert(crtc, Rc::new(renderer));
break 'outer;
@ -369,7 +409,7 @@ impl<Data: 'static> UdevHandlerImpl<Data> {
}
impl<Data: 'static> UdevHandlerImpl<Data> {
fn device_added(&mut self, _device: dev_t, path: PathBuf) {
fn device_added(&mut self, device_id: dev_t, path: PathBuf) {
// Try to open the device
if let Some(mut device) = self
.session
@ -421,12 +461,16 @@ impl<Data: 'static> UdevHandlerImpl<Data> {
let backends = Rc::new(RefCell::new(UdevHandlerImpl::<Data>::scan_connectors(
&mut device,
self.egl_buffer_reader.clone(),
&mut *self.display.borrow_mut(),
&mut *self.output_map.borrow_mut(),
&self.logger,
)));
#[cfg(not(feature = "egl"))]
let backends = Rc::new(RefCell::new(UdevHandlerImpl::<Data>::scan_connectors(
&mut device,
&mut *self.display.borrow_mut(),
&mut *self.output_map.borrow_mut(),
&self.logger,
)));
@ -434,10 +478,13 @@ impl<Data: 'static> UdevHandlerImpl<Data> {
// Note: if you replicate this (very simple) structure, it is rather easy
// to introduce reference cycles with Rc. Be sure about your drop order
let renderer = Rc::new(DrmRenderer {
device_id,
compositor_token: self.compositor_token,
backends: backends.clone(),
window_map: self.window_map.clone(),
output_map: self.output_map.clone(),
pointer_location: self.pointer_location.clone(),
pointer_image: self.pointer_image.clone(),
cursor_status: self.cursor_status.clone(),
dnd_icon: self.dnd_icon.clone(),
logger: self.logger.clone(),
@ -462,12 +509,6 @@ impl<Data: 'static> UdevHandlerImpl<Data> {
.unwrap();
for renderer in backends.borrow_mut().values() {
// create cursor
renderer
.borrow()
.set_cursor_representation(&self.pointer_image, (2, 2))
.unwrap();
// render first frame
schedule_initial_render(renderer.clone(), &self.loop_handle);
}
@ -487,26 +528,31 @@ impl<Data: 'static> UdevHandlerImpl<Data> {
//quick and dirty, just re-init all backends
if let Some(ref mut backend_data) = self.backends.get_mut(&device) {
let logger = &self.logger;
let pointer_image = &self.pointer_image;
let egl_buffer_reader = self.egl_buffer_reader.clone();
let loop_handle = self.loop_handle.clone();
let mut display = self.display.borrow_mut();
let mut output_map = self.output_map.borrow_mut();
output_map.retain(|output| output.device_id != device);
self.loop_handle
.with_source(&backend_data.event_source, |source| {
let mut backends = backend_data.surfaces.borrow_mut();
#[cfg(feature = "egl")]
let new_backends =
UdevHandlerImpl::<Data>::scan_connectors(&mut source.file, egl_buffer_reader, logger);
UdevHandlerImpl::<Data>::scan_connectors(
&mut source.file,
egl_buffer_reader,
&mut *display,
&mut *output_map,
logger);
#[cfg(not(feature = "egl"))]
let new_backends = UdevHandlerImpl::<Data>::scan_connectors(&mut source.file, logger);
let new_backends = UdevHandlerImpl::<Data>::scan_connectors(
&mut source.file,
&mut *display,
&mut *output_map,
logger);
*backends = new_backends;
for renderer in backends.values() {
// create cursor
renderer
.borrow()
.set_cursor_representation(pointer_image, (2, 2))
.unwrap();
// render first frame
schedule_initial_render(renderer.clone(), &loop_handle);
}
@ -520,6 +566,8 @@ impl<Data: 'static> UdevHandlerImpl<Data> {
// drop surfaces
backend_data.surfaces.borrow_mut().clear();
debug!(self.logger, "Surfaces dropped");
// clear outputs
self.output_map.borrow_mut().retain(|output| output.device_id != device);
let device = self.loop_handle.remove(backend_data.event_source).unwrap();
@ -568,10 +616,13 @@ impl<Data: 'static> DrmRendererSessionListener<Data> {
}
pub struct DrmRenderer {
device_id: dev_t,
compositor_token: CompositorToken<Roles>,
backends: Rc<RefCell<HashMap<crtc::Handle, Rc<GliumDrawer<RenderSurface>>>>>,
window_map: Rc<RefCell<MyWindowMap>>,
output_map: Rc<RefCell<Vec<MyOutput>>>,
pointer_location: Rc<RefCell<(f64, f64)>>,
pointer_image: ImageBuffer<Rgba<u8>, Vec<u8>>,
cursor_status: Arc<Mutex<CursorImageStatus>>,
dnd_icon: Arc<Mutex<Option<wl_surface::WlSurface>>>,
logger: ::slog::Logger,
@ -590,12 +641,16 @@ impl DrmRenderer {
evt_handle: Option<&LoopHandle<Data>>,
) {
if let Some(drawer) = self.backends.borrow().get(&crtc) {
{
let (x, y) = *self.pointer_location.borrow();
let _ = drawer
.borrow()
.set_cursor_position(x.trunc().abs() as u32, y.trunc().abs() as u32);
}
// get output coordinates
let (x, y) = self.output_map.borrow()
.iter()
.take_while(|output| output.device_id != self.device_id || output.crtc != crtc)
.fold((0u32, 0u32), |pos, output| (pos.0 + output.size.0, pos.1));
let (width, height) = self.output_map.borrow()
.iter()
.find(|output| output.device_id == self.device_id && output.crtc == crtc)
.map(|output| output.size)
.unwrap_or((0, 0)); // in this case the output will be removed.
// and draw in sync with our monitor
let mut frame = drawer.draw();
@ -604,10 +659,29 @@ impl DrmRenderer {
drawer.draw_windows(
&mut frame,
&*self.window_map.borrow(),
None,
Some(Rectangle {
x: x as i32, y: y as i32,
width: width as i32,
height: height as i32,
}),
self.compositor_token,
);
let (x, y) = *self.pointer_location.borrow();
// get pointer coordinates
let (ptr_x, ptr_y) = *self.pointer_location.borrow();
let ptr_x = ptr_x.trunc().abs() as i32 - x as i32;
let ptr_y = ptr_y.trunc().abs() as i32 - y as i32;
// set cursor
// TODO hack, should be possible to clear the cursor
let empty = ImageBuffer::from_fn(self.pointer_image.width(), self.pointer_image.height(), |_, _| Rgba::from_channels(0, 0, 0, 0));
if ptr_x > 0 && ptr_x < width as i32 && ptr_y > 0 && ptr_y < height as i32 {
let _ = drawer
.borrow()
.set_cursor_position(ptr_x as u32, ptr_y as u32);
let mut software_cursor = false;
// draw the dnd icon if applicable
{
let guard = self.dnd_icon.lock().unwrap();
@ -616,9 +690,10 @@ impl DrmRenderer {
drawer.draw_dnd_icon(
&mut frame,
surface,
(x as i32, y as i32),
(ptr_x, ptr_y),
self.compositor_token,
);
software_cursor = true;
}
}
}
@ -634,17 +709,28 @@ impl DrmRenderer {
*guard = CursorImageStatus::Default;
}
if let CursorImageStatus::Image(ref surface) = *guard {
drawer.draw_cursor(&mut frame, surface, (x as i32, y as i32), self.compositor_token);
drawer.draw_cursor(&mut frame, surface, (ptr_x, ptr_y), self.compositor_token);
software_cursor = true;
}
}
let result = frame.finish();
if result.is_ok() {
// Send frame events so that client start drawing their next frame
self.window_map.borrow().send_frames(SCOUNTER.next_serial());
// TODO: this is wasteful, only do this on change (cache previous software cursor state)
if software_cursor {
if let Err(err) = drawer.borrow().set_cursor_representation(&empty, (2, 2)) {
error!(self.logger, "Error setting cursor: {}", err);
}
} else {
if let Err(err) = drawer.borrow().set_cursor_representation(&self.pointer_image, (2, 2)) {
error!(self.logger, "Error setting cursor: {}", err);
}
}
} else {
if let Err(err) = drawer.borrow().set_cursor_representation(&empty, (2, 2)) {
error!(self.logger, "Error setting cursor: {}", err);
}
}
if let Err(err) = result {
if let Err(err) = frame.finish() {
warn!(self.logger, "Error during rendering: {:?}", err);
let reschedule = match err {
SwapBuffersError::AlreadySwapped => false,
@ -697,6 +783,7 @@ impl DrmRenderer {
}
}
} else {
// TODO: only send drawn windows the frames callback
// Send frame events so that client start drawing their next frame
self.window_map.borrow().send_frames(SCOUNTER.next_serial());
}