Handle 2D scanouts

This commit is contained in:
Marc-André Lureau 2021-02-28 23:34:52 +04:00
parent eeea1ce3d7
commit e0320ca4ab
6 changed files with 186 additions and 52 deletions

View File

@ -7,11 +7,12 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
derivative = "2.2.0"
zbus = "2.0.0-beta" zbus = "2.0.0-beta"
derivative = "2.1.3" zvariant = { version = "2.4.0", features = ["serde_bytes"] }
zvariant = "2.4.0"
libc = "0.2.86" libc = "0.2.86"
glib = { git = "https://github.com/gtk-rs/gtk-rs", optional = true } glib = { git = "https://github.com/gtk-rs/gtk-rs", optional = true }
enumflags2 = { version = "0.6.4", features = ["serde"] } enumflags2 = { version = "0.6.4", features = ["serde"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0.123", features = ["derive"] }
serde_repr = "0.1.6" serde_repr = "0.1.6"
serde_bytes = "0.11.5"

View File

@ -4,10 +4,35 @@ use std::os::unix::io::{AsRawFd, RawFd};
use std::sync::mpsc::{Receiver, RecvError, SendError, Sender}; use std::sync::mpsc::{Receiver, RecvError, SendError, Sender};
use std::sync::Arc; use std::sync::Arc;
use derivative::Derivative;
use zbus::{dbus_interface, export::zvariant::Fd}; use zbus::{dbus_interface, export::zvariant::Fd};
#[derive(Debug)] #[derive(Derivative)]
#[derivative(Debug)]
pub struct Scanout { pub struct Scanout {
pub width: u32,
pub height: u32,
pub stride: u32,
pub format: u32,
#[derivative(Debug = "ignore")]
pub data: Vec<u8>,
}
#[derive(Derivative)]
#[derivative(Debug)]
pub struct Update {
pub x: i32,
pub y: i32,
pub w: i32,
pub h: i32,
pub stride: u32,
pub format: u32,
#[derivative(Debug = "ignore")]
pub data: Vec<u8>,
}
#[derive(Debug)]
pub struct ScanoutDMABUF {
pub fd: RawFd, pub fd: RawFd,
pub width: u32, pub width: u32,
pub height: u32, pub height: u32,
@ -17,7 +42,7 @@ pub struct Scanout {
pub y0_top: bool, pub y0_top: bool,
} }
impl Drop for Scanout { impl Drop for ScanoutDMABUF {
fn drop(&mut self) { fn drop(&mut self) {
if self.fd >= 0 { if self.fd >= 0 {
unsafe { unsafe {
@ -30,11 +55,10 @@ impl Drop for Scanout {
// TODO: replace events mpsc with async traits // TODO: replace events mpsc with async traits
#[derive(Debug)] #[derive(Debug)]
pub enum Event { pub enum Event {
Switch { Scanout(Scanout),
width: i32, Update(Update),
height: i32, ScanoutDMABUF(ScanoutDMABUF),
}, UpdateDMABUF {
Update {
x: i32, x: i32,
y: i32, y: i32,
w: i32, w: i32,
@ -52,7 +76,6 @@ pub enum Event {
hot_y: i32, hot_y: i32,
data: Vec<u8>, data: Vec<u8>,
}, },
Scanout(Scanout),
Disconnected, Disconnected,
} }
@ -82,18 +105,46 @@ pub(crate) struct Listener<E: EventSender> {
#[dbus_interface(name = "org.qemu.Display1.Listener")] #[dbus_interface(name = "org.qemu.Display1.Listener")]
impl<E: 'static + EventSender> Listener<E> { impl<E: 'static + EventSender> Listener<E> {
fn switch(&mut self, width: i32, height: i32) {
self.send(Event::Switch { width, height })
}
fn update(&mut self, x: i32, y: i32, w: i32, h: i32) {
self.send(Event::Update { x, y, w, h });
if let Err(e) = self.wait() {
eprintln!("update returned error: {}", e)
}
}
fn scanout( fn scanout(
&mut self,
width: u32,
height: u32,
stride: u32,
format: u32,
data: serde_bytes::ByteBuf,
) {
self.send(Event::Scanout(Scanout {
width,
height,
stride,
format,
data: data.into_vec(),
}))
}
fn update(
&mut self,
x: i32,
y: i32,
w: i32,
h: i32,
stride: u32,
format: u32,
data: serde_bytes::ByteBuf,
) {
self.send(Event::Update(Update {
x,
y,
w,
h,
stride,
format,
data: data.into_vec(),
}))
}
#[dbus_interface(name = "ScanoutDMABUF")]
fn scanout_dmabuf(
&mut self, &mut self,
fd: Fd, fd: Fd,
width: u32, width: u32,
@ -104,7 +155,7 @@ impl<E: 'static + EventSender> Listener<E> {
y0_top: bool, y0_top: bool,
) { ) {
let fd = unsafe { libc::dup(fd.as_raw_fd()) }; let fd = unsafe { libc::dup(fd.as_raw_fd()) };
self.send(Event::Scanout(Scanout { self.send(Event::ScanoutDMABUF(ScanoutDMABUF {
fd, fd,
width, width,
height, height,
@ -115,6 +166,14 @@ impl<E: 'static + EventSender> Listener<E> {
})) }))
} }
#[dbus_interface(name = "UpdateDMABUF")]
fn update_dmabuf(&mut self, x: i32, y: i32, w: i32, h: i32) {
self.send(Event::UpdateDMABUF { x, y, w, h });
if let Err(e) = self.wait() {
eprintln!("update returned error: {}", e)
}
}
fn mouse_set(&mut self, x: i32, y: i32, on: i32) { fn mouse_set(&mut self, x: i32, y: i32, on: i32) {
self.send(Event::MouseSet { x, y, on }) self.send(Event::MouseSet { x, y, on })
} }

View File

@ -17,6 +17,7 @@ khronos-egl = { version = "3.0.0", features = ["dynamic"] }
libloading = "0.6" libloading = "0.6"
gl = "0.14.0" gl = "0.14.0"
glib = { git = "https://github.com/gtk-rs/gtk-rs", optional = true } glib = { git = "https://github.com/gtk-rs/gtk-rs", optional = true }
derivative = "2.2.0"
[dependencies.gtk] [dependencies.gtk]
package = "gtk4" package = "gtk4"

View File

@ -169,7 +169,19 @@ impl QemuConsole {
clone!(@weak self as con => move |t| { clone!(@weak self as con => move |t| {
let priv_ = imp::QemuConsole::from_instance(&con); let priv_ = imp::QemuConsole::from_instance(&con);
match t { match t {
Event::Update { .. } => { Event::Scanout(s) => {
priv_.area.set_scanout(s);
priv_.area.queue_render();
}
Event::Update(u) => {
priv_.area.update(u);
priv_.area.queue_render();
}
Event::ScanoutDMABUF(s) => {
priv_.label.set_label(&format!("{:?}", s));
priv_.area.set_scanout_dmabuf(s);
}
Event::UpdateDMABUF { .. } => {
priv_.wait_rendering.set(priv_.wait_rendering.get() + 1); priv_.wait_rendering.set(priv_.wait_rendering.get() + 1);
// we don't simply queue_render, as we want a copy immediately // we don't simply queue_render, as we want a copy immediately
priv_.area.make_current(); priv_.area.make_current();
@ -181,10 +193,6 @@ impl QemuConsole {
}; };
priv_.area.queue_draw(); priv_.area.queue_draw();
} }
Event::Scanout(s) => {
priv_.label.set_label(&format!("{:?}", s));
priv_.area.set_scanout(s);
}
Event::Disconnected => { Event::Disconnected => {
priv_.label.set_label("Console disconnected!"); priv_.label.set_label("Console disconnected!");
} }
@ -194,7 +202,7 @@ impl QemuConsole {
let cur = gdk::Cursor::from_texture(&tex, hot_x, hot_y, None); let cur = gdk::Cursor::from_texture(&tex, hot_x, hot_y, None);
priv_.area.set_cursor(Some(&cur)); priv_.area.set_cursor(Some(&cur));
} }
_ => () t => { dbg!(t); }
} }
Continue(true) Continue(true)
}), }),

View File

@ -10,7 +10,7 @@ use std::ffi::{CStr, CString};
use crate::egl; use crate::egl;
use crate::error::*; use crate::error::*;
use gl::{self, types::*}; use gl::{self, types::*};
use qemu_display_listener::Scanout; use qemu_display_listener::{Scanout, ScanoutDMABUF, Update};
mod imp { mod imp {
use super::*; use super::*;
@ -22,7 +22,7 @@ mod imp {
pub texture_blit_vao: Cell<GLuint>, pub texture_blit_vao: Cell<GLuint>,
pub texture_blit_prog: Cell<GLuint>, pub texture_blit_prog: Cell<GLuint>,
pub texture_blit_flip_prog: Cell<GLuint>, pub texture_blit_flip_prog: Cell<GLuint>,
pub scanout: Cell<Option<Scanout>>, pub scanout: Cell<Option<ScanoutDMABUF>>,
pub scanout_size: Cell<(u32, u32)>, pub scanout_size: Cell<(u32, u32)>,
} }
@ -229,6 +229,58 @@ mod imp {
pub fn set_scanout(&self, widget: &super::QemuConsoleArea, s: Scanout) { pub fn set_scanout(&self, widget: &super::QemuConsoleArea, s: Scanout) {
widget.make_current(); widget.make_current();
if s.format != 0x20020888 {
todo!();
}
unsafe {
gl::BindTexture(gl::TEXTURE_2D, self.tex_id());
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as _);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as _);
gl::PixelStorei(gl::UNPACK_ROW_LENGTH, s.stride as i32 / 4);
gl::TexImage2D(
gl::TEXTURE_2D,
0,
gl::RGB as _,
s.width as _,
s.height as _,
0,
gl::BGRA,
gl::UNSIGNED_BYTE,
s.data.as_ptr() as _,
);
}
self.scanout_size.set((s.width, s.height));
}
pub fn update(&self, widget: &super::QemuConsoleArea, u: Update) {
widget.make_current();
if u.format != 0x20020888 {
todo!();
}
unsafe {
gl::BindTexture(gl::TEXTURE_2D, self.tex_id());
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as _);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as _);
gl::PixelStorei(gl::UNPACK_ROW_LENGTH, u.stride as i32 / 4);
gl::TexSubImage2D(
gl::TEXTURE_2D,
0,
u.x,
u.y,
u.w,
u.h,
gl::BGRA,
gl::UNSIGNED_BYTE,
u.data.as_ptr() as _,
);
}
}
pub fn set_scanout_dmabuf(&self, widget: &super::QemuConsoleArea, s: ScanoutDMABUF) {
widget.make_current();
let egl = egl::egl(); let egl = egl::egl();
let egl_dpy = if let Ok(dpy) = widget.get_display().downcast::<gdk_wl::WaylandDisplay>() let egl_dpy = if let Ok(dpy) = widget.get_display().downcast::<gdk_wl::WaylandDisplay>()
@ -315,6 +367,18 @@ impl QemuConsoleArea {
priv_.set_scanout(self, s); priv_.set_scanout(self, s);
} }
pub fn update(&self, u: Update) {
let priv_ = imp::QemuConsoleArea::from_instance(self);
priv_.update(self, u);
}
pub fn set_scanout_dmabuf(&self, s: ScanoutDMABUF) {
let priv_ = imp::QemuConsoleArea::from_instance(self);
priv_.set_scanout_dmabuf(self, s);
}
pub fn save_to_png(&self, filename: &str) { pub fn save_to_png(&self, filename: &str) {
let priv_ = imp::QemuConsoleArea::from_instance(self); let priv_ = imp::QemuConsoleArea::from_instance(self);

View File

@ -1,12 +1,15 @@
use std::error::Error; use std::error::Error;
use std::net::{TcpListener, TcpStream}; use std::net::{TcpListener, TcpStream};
use std::{thread, time, io};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::{io, thread, time};
use qemu_display_listener::{Console, Event};
use zbus::Connection;
use clap::Clap; use clap::Clap;
use vnc::{server::FramebufferUpdate, server::Event as VncEvent, PixelFormat, Rect, Server as VncServer, Error as VncError}; use qemu_display_listener::{Console, Event};
use vnc::{
server::Event as VncEvent, server::FramebufferUpdate, Error as VncError, PixelFormat, Rect,
Server as VncServer,
};
use zbus::Connection;
#[derive(Clap, Debug)] #[derive(Clap, Debug)]
pub struct SocketAddrArgs { pub struct SocketAddrArgs {
@ -30,7 +33,6 @@ struct Cli {
address: SocketAddrArgs, address: SocketAddrArgs,
} }
struct ServerInner { struct ServerInner {
width: u16, width: u16,
height: u16, height: u16,
@ -43,10 +45,7 @@ struct Server {
impl Server { impl Server {
fn new(width: u16, height: u16) -> Self { fn new(width: u16, height: u16) -> Self {
Self { Self {
inner: Arc::new(Mutex::new(ServerInner { inner: Arc::new(Mutex::new(ServerInner { width, height })),
width,
height,
}))
} }
} }
@ -65,11 +64,13 @@ impl Server {
Ok(e) => e, Ok(e) => e,
Err(VncError::Io(ref e)) if e.kind() == io::ErrorKind::WouldBlock => { Err(VncError::Io(ref e)) if e.kind() == io::ErrorKind::WouldBlock => {
continue; continue;
}, }
Err(VncError::Disconnected) => { Err(VncError::Disconnected) => {
return Ok(()); return Ok(());
} }
Err(e) => { return Err(e.into()); } Err(e) => {
return Err(e.into());
}
}; };
match event { match event {
VncEvent::FramebufferUpdateRequest { .. } => { VncEvent::FramebufferUpdateRequest { .. } => {
@ -108,16 +109,16 @@ fn main() -> Result<(), Box<dyn Error>> {
let server = Server::new(console.width()? as u16, console.height()? as u16); let server = Server::new(console.width()? as u16, console.height()? as u16);
let _thread = thread::spawn(move || { let _thread = thread::spawn(move || match rx.recv().unwrap() {
match rx.recv().unwrap() { Event::ScanoutDMABUF(s) => {
Event::Scanout(s) => {
dbg!(&s); dbg!(&s);
unsafe { unsafe {
libc::close(s.fd); libc::close(s.fd);
} }
let _ = ack.send(()); let _ = ack.send(());
}, }
e => { dbg!(e); }, e => {
dbg!(e);
} }
}); });
for stream in listener.incoming() { for stream in listener.incoming() {