Skip to content

Introduce a display subsystem for GPU output #319

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

mtjhrc
Copy link
Collaborator

@mtjhrc mtjhrc commented Apr 23, 2025

This PR adds initial support for the more traditional display output from the VM by basically having virtual displays (until now we only had cross-domain/wayland).

This implementes 2 display backends:

  • DisplayBackendNoop - used by default, pretty much returns an error for every operation
  • DisplayBackendGtk - implementation using GTK (works on Linux, to be tested on macOS)

This introduces 2 new public API function:

  • int32_t krun_set_display_backend_gtk(uint32_t ctx_id)
  • int32_t krun_set_display(uint32_t ctx_id, uint32_t display_id, uint32_t width, uint32_t height)

Current limitations to be addressed by future PRs:

  • add a virtio-input device and forward the events from the window to the guest
  • improve the performance by using dmabufs instead copying it to the CPU
  • send display resize events to the guest

You can test this using the newly added --display option in chroot_vm and running something graphical in the guest - the simplest option is probably kmscube:
$ ./chroot_vm --display=0:1920:1080 rootfs_fedora kmscube

@mtjhrc mtjhrc force-pushed the gtk-display branch 10 times, most recently from 38c4a1a to 7f463ef Compare April 29, 2025 11:11
@mtjhrc mtjhrc marked this pull request as ready for review April 29, 2025 11:58
mtjhrc added 5 commits April 30, 2025 10:30
This is similar concept to the mpsc::channel but uses an EventFd to notify the
reader when data is availible. This seems like it could be generally useful,
when we need to use Epoll to also wait for other file descriptor events.

This is just a simple implementation using EventFd and could maybe be optimized.
(maybe even using a unix pipe directly)

Signed-off-by: Matej Hrica <[email protected]>
We wrongly expected an extra virtio_gpu_ctrl_hdr for VIRTIO_GPU_CMD_GET_DISPLAY_INFO
The command doesn't have any body and we have already read the ctrl_header.

Signed-off-by: Matej Hrica <[email protected]>
Introduce a DisplayBackend trait and a DisplayBackendNoop implementation. Hook
up the gpu device to use the display backend. For now this only hooks up the
simplest and slowest option of display output - the output is copied to the
CPU using the transfer_read operation and passed to the display backend. The
DisplayBackendNoop doesn't provide any displays, so this doesn't display
anything yet.

Introduce a new public API function krun_set_display() for configuring VM
displays.

The virtio-gpu code is based on rust-vmm/vhost-device-gpu.

Signed-off-by: Matej Hrica <[email protected]>
Add a DisplayBackend implementation using GTK. This also introduces a new public
API function krun_set_display_backend_gtk() for enabling the gtk display backend.

Signed-off-by: Matej Hrica <[email protected]>
The --display option can be provided multiple times. Specyfing at least one
display automatically enables the GTK display backend.

Signed-off-by: Matej Hrica <[email protected]>
@slp
Copy link
Collaborator

slp commented Apr 30, 2025

On macOS I'm getting this:

thread 'gtk display' panicked at /Users/slopezpa/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/gtk4-0.9.6/src/rt.rs:106:9:
assertion `left != right` failed: Attempted to initialize GTK on OSX from non-main thread
  left: 0
 right: 0
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

I haven't investigated further yet.

Copy link
Member

@jakecorrenti jakecorrenti left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't speak to the logic of the subsystem since I'm not familiar with what's best practices here, but the code itself LGTM.

Could use a rebase, though.

Copy link

@bilelmoussaoui bilelmoussaoui left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some comments here and there


fn build_overlay(window: &Window) -> Overlay {
let overlay_bar = HeaderBar::builder()
.visible(true)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.visible(true)

all widgets are visible by default


let overlay_unfullscreen_btn = Button::builder()
.tooltip_text("Exit fullscreen mode")
.icon_name("view-restore")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where this icon comes from?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked at this list, and picked icon names: https://specifications.freedesktop.org/icon-naming-spec/latest/#names
(but looking at it again, it seems this is from 2007, so I'm not sure if I should be rely on this 😄 )
Is it a bad assumption, that these icons will always exist?


pub fn gtk_display_main_loop(rx: PollableChannelReciever<DisplayEvent>, displays: DisplayInfoList) {
gtk4::init().expect("Failed to initialize GTK");
let main_loop = glib::MainLoop::new(None, false);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason for running a manual main loop instead of using gtk::Application?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, originally I also wanted to also use a custom MainContext and only use gtk from this worker thread. This is technically a library and theoretically other glib/gtk things could be running (but more of a theoretical than a real problem I guess). I feel like the Rust bindings always want you to use the global context anyway (for example the source::unix_fd_add_local), or maybe I just don't understand, how to use it properly. I'm not really familiar with glib/gtk C API anyway.

But it looks like this might be a moot point anyway, because on macOS it seems gtk has to be initialized on the main thread anyway...

@@ -29,6 +30,8 @@ virtio-bindings = "0.2.0"
vm-memory = { version = ">=0.13", features = ["backend-mmap"] }
zerocopy = { version = "0.6.3", optional = true }
zerocopy-derive = { version = "0.6.3", optional = true }
gtk4 = { version = "0.9.6", features = ["v4_14"], optional = true }
glib = { version = "0.20.9", optional = true }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is already pulled by gtk4, no need for a specific dep.

height: i32,
format: gdk::MemoryFormat,
) -> Self {
let header_bar = HeaderBar::builder().build();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let header_bar = HeaderBar::builder().build();
let header_bar = HeaderBar::default();

is enough i guess

// Enforce a minimum window size:
.height_request(64)
.titlebar(&header_bar)
.visible(true)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as other widgets

.build();

let actions = SimpleActionGroup::new();
actions.add_action_entries([

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why use a separate action group ? i would recommend using a gtk::Window which is an gio::ActionMap already

]);
window.insert_action_group("scanout", Some(&actions));

let fullscreen_btn = gtk4::Button::builder()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

weird that some types are imported, some are not ;-)


// Unset the width/height after the window is created to allow resizing the window to
// be smaller
picture.set_size_request(-1, -1);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that looks like a hack.

Copy link
Collaborator Author

@mtjhrc mtjhrc May 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it is. Any idea how to do this properly, though?

I wanted to set the default window width/height, I see Window.set_default_size exits, but what to set the dimensions to? The window height should be the height of the image + height of the header bar, which I don't know how to obtain, and even if I did calculating this manually feels even more hacky and error prone.

I don't see a way to set the default, but not minimal dimensions of the Picture widget (which would automatically create the window with correct size).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You will have to create a custom paintable, see https://github.com/gtk-rs/gtk4-rs/tree/main/examples/custom_paintable or other custom paintables examples.

use gtk4::{
gdk,
gio::{ActionEntry, Cancellable, SimpleActionGroup},
prelude::{ActionMapExtManual, GtkWindowExt, WidgetExt},

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
prelude::{ActionMapExtManual, GtkWindowExt, WidgetExt},
prelude::*,

as stuff can move from ExtManual to Ext with time, so avoid potential code fixing with version bumps

Comment on lines +149 to +156
fn build_kill_vm_btn() -> Button {
let kill_vm_btn = Button::builder()
.label("Kill VM")
.action_name("scanout.kill")
.build();
kill_vm_btn.set_css_classes(&["destructive-action"]);
kill_vm_btn
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems too much to do this in a separate function. Also use add_css_class, the function you are using removes all the pre-existing CSS classes in the widget if any.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants