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
[dependencies]
derivative = "2.2.0"
zbus = "2.0.0-beta"
derivative = "2.1.3"
zvariant = "2.4.0"
zvariant = { version = "2.4.0", features = ["serde_bytes"] }
libc = "0.2.86"
glib = { git = "https://github.com/gtk-rs/gtk-rs", optional = true }
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_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::Arc;
use derivative::Derivative;
use zbus::{dbus_interface, export::zvariant::Fd};
#[derive(Debug)]
#[derive(Derivative)]
#[derivative(Debug)]
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 width: u32,
pub height: u32,
@ -17,7 +42,7 @@ pub struct Scanout {
pub y0_top: bool,
}
impl Drop for Scanout {
impl Drop for ScanoutDMABUF {
fn drop(&mut self) {
if self.fd >= 0 {
unsafe {
@ -30,11 +55,10 @@ impl Drop for Scanout {
// TODO: replace events mpsc with async traits
#[derive(Debug)]
pub enum Event {
Switch {
width: i32,
height: i32,
},
Update {
Scanout(Scanout),
Update(Update),
ScanoutDMABUF(ScanoutDMABUF),
UpdateDMABUF {
x: i32,
y: i32,
w: i32,
@ -52,7 +76,6 @@ pub enum Event {
hot_y: i32,
data: Vec<u8>,
},
Scanout(Scanout),
Disconnected,
}
@ -82,18 +105,46 @@ pub(crate) struct Listener<E: EventSender> {
#[dbus_interface(name = "org.qemu.Display1.Listener")]
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(
&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,
fd: Fd,
width: u32,
@ -104,7 +155,7 @@ impl<E: 'static + EventSender> Listener<E> {
y0_top: bool,
) {
let fd = unsafe { libc::dup(fd.as_raw_fd()) };
self.send(Event::Scanout(Scanout {
self.send(Event::ScanoutDMABUF(ScanoutDMABUF {
fd,
width,
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) {
self.send(Event::MouseSet { x, y, on })
}

View File

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

View File

@ -169,7 +169,19 @@ impl QemuConsole {
clone!(@weak self as con => move |t| {
let priv_ = imp::QemuConsole::from_instance(&con);
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);
// we don't simply queue_render, as we want a copy immediately
priv_.area.make_current();
@ -181,10 +193,6 @@ impl QemuConsole {
};
priv_.area.queue_draw();
}
Event::Scanout(s) => {
priv_.label.set_label(&format!("{:?}", s));
priv_.area.set_scanout(s);
}
Event::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);
priv_.area.set_cursor(Some(&cur));
}
_ => ()
t => { dbg!(t); }
}
Continue(true)
}),

View File

@ -10,7 +10,7 @@ use std::ffi::{CStr, CString};
use crate::egl;
use crate::error::*;
use gl::{self, types::*};
use qemu_display_listener::Scanout;
use qemu_display_listener::{Scanout, ScanoutDMABUF, Update};
mod imp {
use super::*;
@ -22,7 +22,7 @@ mod imp {
pub texture_blit_vao: Cell<GLuint>,
pub texture_blit_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)>,
}
@ -229,6 +229,58 @@ mod imp {
pub fn set_scanout(&self, widget: &super::QemuConsoleArea, s: Scanout) {
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_dpy = if let Ok(dpy) = widget.get_display().downcast::<gdk_wl::WaylandDisplay>()
@ -315,6 +367,18 @@ impl QemuConsoleArea {
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) {
let priv_ = imp::QemuConsoleArea::from_instance(self);

View File

@ -1,12 +1,15 @@
use std::error::Error;
use std::net::{TcpListener, TcpStream};
use std::{thread, time, io};
use std::sync::{Arc, Mutex};
use std::{io, thread, time};
use qemu_display_listener::{Console, Event};
use zbus::Connection;
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)]
pub struct SocketAddrArgs {
@ -30,7 +33,6 @@ struct Cli {
address: SocketAddrArgs,
}
struct ServerInner {
width: u16,
height: u16,
@ -43,10 +45,7 @@ struct Server {
impl Server {
fn new(width: u16, height: u16) -> Self {
Self {
inner: Arc::new(Mutex::new(ServerInner {
width,
height,
}))
inner: Arc::new(Mutex::new(ServerInner { width, height })),
}
}
@ -65,11 +64,13 @@ impl Server {
Ok(e) => e,
Err(VncError::Io(ref e)) if e.kind() == io::ErrorKind::WouldBlock => {
continue;
},
}
Err(VncError::Disconnected) => {
return Ok(());
}
Err(e) => { return Err(e.into()); }
Err(e) => {
return Err(e.into());
}
};
match event {
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 _thread = thread::spawn(move || {
match rx.recv().unwrap() {
Event::Scanout(s) => {
let _thread = thread::spawn(move || match rx.recv().unwrap() {
Event::ScanoutDMABUF(s) => {
dbg!(&s);
unsafe {
libc::close(s.fd);
}
let _ = ack.send(());
},
e => { dbg!(e); },
}
e => {
dbg!(e);
}
});
for stream in listener.incoming() {