crosvm: GPU and display arg parsing for Windows.

This replaces the handwritten arg parser with the serde_keyvalue
based parser. Eventually we will unify the arg parsing with Unix.

BUG=b:233676779
TEST=cargo b --features all-msvc64,gpu --no-default-features
TEST=cargo t -p vm_control --features all-msvc64,gpu
     --no-default-features
TEST=cargo t -p crosvm sys::windows::config::
     --features all-msvc64,gpu --no-default-features

Change-Id: I36a563be9767c7e5cbd3ab44f6a9ba23cd64cdb6
Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/3939033
Commit-Queue: Pujun Lun <lunpujun@google.com>
Reviewed-by: Alexandre Courbot <acourbot@chromium.org>
This commit is contained in:
Pujun Lun 2022-10-06 23:35:27 -07:00 committed by crosvm LUCI
parent 11a7826e0d
commit b849ebd0e1
16 changed files with 359 additions and 542 deletions

1
Cargo.lock generated
View file

@ -1990,6 +1990,7 @@ dependencies = [
"sync",
"thiserror",
"vm_memory",
"winapi",
]
[[package]]

View file

@ -46,6 +46,7 @@ disk = { path = "../disk" }
downcast-rs = { version = "1.2.0", optional = true }
enumn = "0.1.0"
ffmpeg = { path = "../media/ffmpeg", optional = true }
gpu_display = { path = "../gpu_display", optional = true }
rutabaga_gfx = { path = "../rutabaga_gfx" }
hypervisor = { path = "../hypervisor" }
kvm_sys = { path = "../kvm_sys" }
@ -82,7 +83,6 @@ vp8 = { path = "../media/vp8", optional = true }
[target.'cfg(unix)'.dependencies]
fuse = {path = "../fuse" }
gpu_display = { path = "../gpu_display", optional = true }
libcras = { version = "*", optional = true }
minijail = "*"
p9 = "*"

View file

@ -83,6 +83,7 @@ pub enum GpuMode {
Mode2D,
#[serde(rename = "virglrenderer", alias = "3d", alias = "3D")]
ModeVirglRenderer,
#[cfg(feature = "gfxstream")]
#[serde(rename = "gfxstream")]
ModeGfxstream,
}
@ -1108,6 +1109,7 @@ impl Gpu {
let component = match gpu_parameters.mode {
GpuMode::Mode2D => RutabagaComponentType::Rutabaga2D,
GpuMode::ModeVirglRenderer => RutabagaComponentType::VirglRenderer,
#[cfg(feature = "gfxstream")]
GpuMode::ModeGfxstream => RutabagaComponentType::Gfxstream,
};
@ -1126,12 +1128,15 @@ impl Gpu {
.set_use_glx(gpu_parameters.renderer_use_glx)
.set_use_surfaceless(gpu_parameters.renderer_use_surfaceless)
.set_use_vulkan(gpu_parameters.use_vulkan.unwrap_or_default())
.set_use_guest_angle(gpu_parameters.gfxstream_use_guest_angle.unwrap_or_default())
.set_support_gles31(gpu_parameters.gfxstream_support_gles31)
.set_wsi(gpu_parameters.wsi.as_ref())
.set_use_external_blob(external_blob)
.set_use_render_server(use_render_server);
#[cfg(feature = "gfxstream")]
let rutabaga_builder = rutabaga_builder
.set_use_guest_angle(gpu_parameters.gfxstream_use_guest_angle.unwrap_or_default())
.set_support_gles31(gpu_parameters.gfxstream_support_gles31);
Gpu {
exit_evt_wrtube,
#[cfg(unix)]

View file

@ -57,11 +57,13 @@ pub struct GpuParameters {
pub renderer_use_glx: bool,
#[serde(rename = "surfaceless")]
pub renderer_use_surfaceless: bool,
#[cfg(feature = "gfxstream")]
#[serde(rename = "angle")]
pub gfxstream_use_guest_angle: Option<bool>,
#[serde(rename = "vulkan")]
pub use_vulkan: Option<bool>,
#[serde(skip)]
#[cfg(feature = "gfxstream")]
#[serde(rename = "gles31")]
pub gfxstream_support_gles31: bool,
pub wsi: Option<RutabagaWsi>,
pub udmabuf: bool,
@ -82,6 +84,7 @@ impl Default for GpuParameters {
renderer_use_gles: true,
renderer_use_glx: false,
renderer_use_surfaceless: true,
#[cfg(feature = "gfxstream")]
gfxstream_use_guest_angle: None,
use_vulkan: None,
mode: if cfg!(feature = "virgl_renderer") {
@ -89,6 +92,7 @@ impl Default for GpuParameters {
} else {
GpuMode::Mode2D
},
#[cfg(feature = "gfxstream")]
gfxstream_support_gles31: true,
wsi: None,
cache_path: None,

View file

@ -22,6 +22,8 @@ use rutabaga_gfx::ResourceCreate3D;
use rutabaga_gfx::ResourceCreateBlob;
use rutabaga_gfx::Rutabaga;
use rutabaga_gfx::RutabagaBuilder;
#[cfg(windows)]
use rutabaga_gfx::RutabagaError;
use rutabaga_gfx::RutabagaFence;
use rutabaga_gfx::RutabagaFenceHandler;
use rutabaga_gfx::RutabagaHandle;

View file

@ -27,6 +27,8 @@ mod virtio_pci_device;
pub mod block;
pub mod console;
#[cfg(feature = "gpu")]
pub mod gpu;
pub mod resource_bridge;
#[cfg(feature = "audio")]
pub mod snd;
@ -38,6 +40,8 @@ pub use self::block::*;
pub use self::console::*;
pub use self::descriptor_utils::Error as DescriptorError;
pub use self::descriptor_utils::*;
#[cfg(feature = "gpu")]
pub use self::gpu::*;
pub use self::input::*;
pub use self::interrupt::*;
pub use self::iommu::*;
@ -54,15 +58,11 @@ cfg_if::cfg_if! {
if #[cfg(unix)] {
mod p9;
mod pmem;
pub mod wl;
pub mod wl;
pub mod fs;
#[cfg(feature = "gpu")]
pub mod gpu;
pub mod net;
#[cfg(feature = "gpu")]
pub use self::gpu::*;
pub use self::iommu::sys::unix::vfio_wrapper;
pub use self::net::*;
pub use self::p9::*;

View file

@ -36,6 +36,8 @@ use metrics::Metrics;
pub use surface::NoopSurface as Surface;
#[cfg(feature = "kiwi")]
use sync::Mutex;
use vm_control::gpu::DisplayMode;
use vm_control::gpu::DisplayParameters;
use window_message_processor::DisplaySendToWndProc;
pub use window_procedure_thread::WindowProcedureThread;
@ -62,6 +64,20 @@ pub struct DisplayProperties {
pub gpu_main_display_tube: Option<Arc<Mutex<Tube>>>,
}
impl From<&DisplayParameters> for DisplayProperties {
fn from(params: &DisplayParameters) -> Self {
let is_fullscreen = matches!(params.mode, DisplayMode::BorderlessFullScreen(_));
let (window_width, window_height) = params.get_window_size();
Self {
start_hidden: params.hidden,
is_fullscreen,
window_width,
window_height,
}
}
}
pub struct DisplayWin {
wndproc_thread: WindowProcedureThread<Surface>,
display_closed_event: Event,

View file

@ -7,8 +7,6 @@ cfg_if::cfg_if! {
use std::net;
use base::RawDescriptor;
#[cfg(feature = "gpu")]
use vm_control::gpu::DisplayParameters as GpuDisplayParameters;
use devices::virtio::vhost::user::device::parse_wayland_sock;
use super::sys::config::{
@ -45,6 +43,8 @@ use devices::SerialParameters;
use devices::StubPciParameters;
use hypervisor::ProtectionType;
use resources::AddressRange;
#[cfg(feature = "gpu")]
use vm_control::gpu::DisplayParameters as GpuDisplayParameters;
#[cfg(feature = "gpu")]
use super::sys::config::parse_gpu_options;
@ -708,7 +708,6 @@ pub struct RunCommand {
/// initially hidden (default: false).
/// refresh-rate=INT - Force a specific vsync generation
/// rate in hertz on the guest (default: 60)
#[cfg(unix)]
pub gpu_display: Vec<GpuDisplayParameters>,
#[cfg(feature = "gpu")]
#[argh(option, long = "gpu", from_str_fn(parse_gpu_options))]
@ -1720,6 +1719,23 @@ impl TryFrom<RunCommand> for super::config::Config {
#[cfg(feature = "gpu")]
{
cfg.gpu_parameters = cmd.gpu_params;
if !cmd.gpu_display.is_empty() {
cfg.gpu_parameters
.get_or_insert_with(Default::default)
.display_params
.extend(cmd.gpu_display);
}
#[cfg(windows)]
if let Some(gpu_parameters) = &cfg.gpu_parameters {
let num_displays = gpu_parameters.display_params.len();
if num_displays > 1 {
return Err(format!(
"Only one display is supported (supplied {})",
num_displays
));
}
}
}
#[cfg(unix)]
@ -1770,16 +1786,6 @@ impl TryFrom<RunCommand> for super::config::Config {
.pivot_root = p;
}
#[cfg(feature = "gpu")]
{
if !cmd.gpu_display.is_empty() {
cfg.gpu_parameters
.get_or_insert_with(Default::default)
.display_params
.extend(cmd.gpu_display);
}
}
cfg.net_vq_pairs = cmd.net_vq_pairs;
}

View file

@ -446,8 +446,11 @@ mod tests {
let gpu_params: GpuParameters = from_key_values("backend=virglrenderer").unwrap();
assert_eq!(gpu_params.mode, GpuMode::ModeVirglRenderer);
let gpu_params: GpuParameters = from_key_values("backend=gfxstream").unwrap();
assert_eq!(gpu_params.mode, GpuMode::ModeGfxstream);
#[cfg(feature = "gfxstream")]
{
let gpu_params: GpuParameters = from_key_values("backend=gfxstream").unwrap();
assert_eq!(gpu_params.mode, GpuMode::ModeGfxstream);
}
}
#[cfg(feature = "gpu")]

View file

@ -143,8 +143,12 @@ mod tests {
"568",
]);
}
if cfg!(feature = "gpu") {
if cfg!(all(feature = "gpu", feature = "gfxstream")) {
args.extend(["--gpu", "angle=true,backend=gfxstream,egl=true,gles=false,glx=false,refresh_rate=60,surfaceless=false,vulkan=true,wsi=vk,display_mode=borderless_full_screen,hidden"]);
args.extend([
"--gpu-display",
"mode=borderless_full_screen,hidden,refresh-rate=60",
]);
}
if cfg!(feature = "audio") {
args.extend(["--ac97", "backend=win_audio"]);

View file

@ -4,32 +4,22 @@
use std::str::FromStr;
#[cfg(feature = "gpu")]
use base::info;
#[cfg(all(feature = "prod-build", feature = "kiwi"))]
use devices::serial_device::SerialType;
#[cfg(feature = "gpu")]
use devices::virtio::GpuDisplayMode;
#[cfg(feature = "gpu")]
use devices::virtio::GpuDisplayParameters;
#[cfg(feature = "gpu")]
#[cfg(all(feature = "gpu", feature = "gfxstream"))]
use devices::virtio::GpuMode;
#[cfg(feature = "gpu")]
use devices::virtio::GpuParameters;
#[cfg(feature = "gpu")]
use devices::virtio::DEFAULT_REFRESH_RATE;
#[cfg(feature = "audio")]
use devices::Ac97Parameters;
use devices::SerialParameters;
#[cfg(feature = "gpu")]
use rutabaga_gfx::calculate_context_mask;
#[cfg(feature = "gpu")]
use rutabaga_gfx::RutabagaWsi;
use serde::Deserialize;
use serde::Serialize;
#[cfg(feature = "gpu")]
use crate::crosvm::argument;
use crate::crosvm::config::Config;
#[cfg(feature = "audio")]
@ -41,16 +31,6 @@ pub fn parse_ac97_options(
Err(format!("unknown ac97 parameter {} {}", key, value))
}
#[cfg(feature = "gpu")]
pub fn is_gpu_backend_deprecated(backend: &str) -> bool {
match backend {
"2d" | "2D" | "3d" | "3D" | "virglrenderer" => {
cfg!(feature = "gfxstream")
}
_ => false,
}
}
#[cfg(feature = "gfxstream")]
pub fn use_vulkan() -> bool {
false
@ -80,392 +60,43 @@ pub fn validate_config(_cfg: &mut Config) -> std::result::Result<(), String> {
#[cfg(feature = "gpu")]
pub fn parse_gpu_options(s: &str) -> Result<GpuParameters, String> {
parse_gpu_options_inner(s).map_err(|e| e.to_string())
}
use crate::crosvm::config::from_key_values;
let mut gpu_params: GpuParameters = from_key_values(s)?;
#[cfg(feature = "gpu")]
fn parse_gpu_options_inner(s: &str) -> argument::Result<GpuParameters> {
let mut gpu_params: GpuParameters = Default::default();
#[cfg(feature = "gfxstream")]
let mut vulkan_specified = false;
#[cfg(feature = "gfxstream")]
let mut gles31_specified = false;
#[cfg(feature = "gfxstream")]
let mut angle_specified = false;
let mut width: Option<u32> = None;
let mut height: Option<u32> = None;
let mut dpi: Option<u32> = None;
let mut display_mode: Option<String> = None;
#[cfg(feature = "gfxstream")]
let mut refresh_rate: Option<u32> = None;
let opts = s
.split(',')
.map(|frag| frag.split('='))
.map(|mut kv| (kv.next().unwrap_or(""), kv.next().unwrap_or("")));
let mut hidden: Option<bool> = None;
for (k, v) in opts {
match k {
"backend" => match v {
"2d" | "2D" => {
if crate::crosvm::sys::config::is_gpu_backend_deprecated(v) {
return Err(argument::Error::InvalidValue {
value: v.to_string(),
expected: String::from(
"this backend type is deprecated, please use gfxstream.",
),
});
} else {
gpu_params.mode = GpuMode::Mode2D;
}
}
"3d" | "3D" | "virglrenderer" => {
if crate::crosvm::sys::config::is_gpu_backend_deprecated(v) {
return Err(argument::Error::InvalidValue {
value: v.to_string(),
expected: String::from(
"this backend type is deprecated, please use gfxstream.",
),
});
} else {
gpu_params.mode = GpuMode::ModeVirglRenderer;
}
}
#[cfg(feature = "gfxstream")]
"gfxstream" => {
gpu_params.mode = GpuMode::ModeGfxstream;
}
_ => {
return Err(argument::Error::InvalidValue {
value: v.to_string(),
expected: String::from(
#[cfg(feature = "gfxstream")]
"gpu parameter 'backend' should be one of (2d|virglrenderer|gfxstream)",
#[cfg(not(feature = "gfxstream"))]
"gpu parameter 'backend' should be one of (2d|3d)",
),
});
}
},
"egl" => match v {
"true" | "" => {
gpu_params.renderer_use_egl = true;
}
"false" => {
gpu_params.renderer_use_egl = false;
}
_ => {
return Err(argument::Error::InvalidValue {
value: v.to_string(),
expected: String::from("gpu parameter 'egl' should be a boolean"),
});
}
},
"gles" => match v {
"true" | "" => {
gpu_params.renderer_use_gles = true;
}
"false" => {
gpu_params.renderer_use_gles = false;
}
_ => {
return Err(argument::Error::InvalidValue {
value: v.to_string(),
expected: String::from("gpu parameter 'gles' should be a boolean"),
});
}
},
"glx" => match v {
"true" | "" => {
gpu_params.renderer_use_glx = true;
}
"false" => {
gpu_params.renderer_use_glx = false;
}
_ => {
return Err(argument::Error::InvalidValue {
value: v.to_string(),
expected: String::from("gpu parameter 'glx' should be a boolean"),
});
}
},
"surfaceless" => match v {
"true" | "" => {
gpu_params.renderer_use_surfaceless = true;
}
"false" => {
gpu_params.renderer_use_surfaceless = false;
}
_ => {
return Err(argument::Error::InvalidValue {
value: v.to_string(),
expected: String::from("gpu parameter 'surfaceless' should be a boolean"),
});
}
},
#[cfg(feature = "gfxstream")]
"angle" => {
angle_specified = true;
match v {
"true" | "" => {
gpu_params.gfxstream_use_guest_angle = true;
}
"false" => {
gpu_params.gfxstream_use_guest_angle = false;
}
_ => {
return Err(argument::Error::InvalidValue {
value: v.to_string(),
expected: String::from("gpu parameter 'angle' should be a boolean"),
});
}
}
}
"vulkan" => {
#[cfg(feature = "gfxstream")]
{
vulkan_specified = true;
}
match v {
"true" | "" => {
gpu_params.use_vulkan = true;
}
"false" => {
gpu_params.use_vulkan = false;
}
_ => {
return Err(argument::Error::InvalidValue {
value: v.to_string(),
expected: String::from("gpu parameter 'vulkan' should be a boolean"),
});
}
}
}
#[cfg(feature = "gfxstream")]
"gles3.1" => {
gles31_specified = true;
match v {
"true" | "" => {
gpu_params.gfxstream_support_gles31 = true;
}
"false" => {
gpu_params.gfxstream_support_gles31 = false;
}
_ => {
return Err(argument::Error::InvalidValue {
value: v.to_string(),
expected: String::from("gpu parameter 'gles3.1' should be a boolean"),
});
}
}
}
"wsi" => match v {
"vk" => {
gpu_params.wsi = Some(RutabagaWsi::Vulkan);
}
_ => {
return Err(argument::Error::InvalidValue {
value: v.to_string(),
expected: String::from("gpu parameter 'wsi' should be vk"),
});
}
},
"width" => {
if let Some(width) = width {
return Err(argument::Error::TooManyArguments(format!(
"width was already specified: {}",
width
)));
}
width = Some(
v.parse::<u32>()
.map_err(|_| argument::Error::InvalidValue {
value: v.to_string(),
expected: String::from("gpu parameter 'width' must be a valid integer"),
})?,
);
}
"height" => {
if let Some(height) = height {
return Err(argument::Error::TooManyArguments(format!(
"height was already specified: {}",
height
)));
}
height = Some(
v.parse::<u32>()
.map_err(|_| argument::Error::InvalidValue {
value: v.to_string(),
expected: String::from(
"gpu parameter 'height' must be a valid integer",
),
})?,
);
}
"dpi" => {
if let Some(dpi) = dpi {
return Err(argument::Error::TooManyArguments(format!(
"dpi was already specified: {}",
dpi
)));
}
dpi = Some(
v.parse::<u32>()
.map_err(|_| argument::Error::InvalidValue {
value: v.to_string(),
expected: String::from("gpu parameter 'dpi' must be a valid integer"),
})?,
);
}
#[cfg(feature = "gfxstream")]
"refresh_rate" => {
if let Some(refresh_rate) = refresh_rate {
return Err(argument::Error::TooManyArguments(format!(
"refresh_rate was already specified: {}",
refresh_rate
)));
}
refresh_rate =
Some(
v.parse::<u32>()
.map_err(|_| argument::Error::InvalidValue {
value: v.to_string(),
expected: String::from(
"gpu parameter 'refresh_rate' must be a valid integer",
),
})?,
);
}
"display_mode" => {
if let Some(display_mode) = display_mode {
return Err(argument::Error::TooManyArguments(format!(
"display_mode was already specified: {}",
display_mode
)));
}
display_mode = Some(String::from(v));
}
"hidden" => match v {
"true" | "" => {
hidden = Some(true);
}
"false" => {
hidden = Some(false);
}
_ => {
return Err(argument::Error::InvalidValue {
value: v.to_string(),
expected: String::from("gpu parameter 'hidden' should be a boolean"),
});
}
},
"cache-path" => gpu_params.cache_path = Some(v.to_string()),
"cache-size" => gpu_params.cache_size = Some(v.to_string()),
"udmabuf" => match v {
"true" | "" => {
gpu_params.udmabuf = true;
}
"false" => {
gpu_params.udmabuf = false;
}
_ => {
return Err(argument::Error::InvalidValue {
value: v.to_string(),
expected: String::from("gpu parameter 'udmabuf' should be a boolean"),
});
}
},
"context-types" => {
let context_types: Vec<String> = v.split(':').map(|s| s.to_string()).collect();
gpu_params.context_mask = calculate_context_mask(context_types);
}
"" => {}
_ => {
return Err(argument::Error::UnknownArgument(format!(
"gpu parameter {}",
k
)));
}
match (
gpu_params.__width_compat.take(),
gpu_params.__height_compat.take(),
) {
(Some(width), Some(height)) => {
let display_mode = GpuDisplayMode::Windowed(width, height);
gpu_params
.display_params
.push(GpuDisplayParameters::default_with_mode(display_mode));
}
}
let mut display_param = match display_mode.as_deref() {
Some("windowed") => GpuDisplayParameters::default_windowed(),
Some("borderless_full_screen") => GpuDisplayParameters::default_borderless_full_screen(),
None => Default::default(),
Some(display_mode) => {
return Err(argument::Error::InvalidValue {
value: display_mode.to_string(),
expected: String::from(
"gpu parameter 'display_mode' must be either 'borderless_full_screen' \
or 'windowed'",
),
})
}
};
if let Some(hidden) = hidden {
display_param.hidden = hidden;
}
#[cfg(feature = "gfxstream")]
{
if let Some(refresh_rate) = refresh_rate {
gpu_params.refresh_rate = refresh_rate;
}
}
match display_param.display_mode {
GpuDisplayMode::Windowed {
width: ref mut width_in_params,
height: ref mut height_in_params,
dpi: ref mut dpi_in_params,
} => {
if let Some(width) = width {
*width_in_params = width;
}
if let Some(height) = height {
*height_in_params = height;
}
if let Some(dpi) = dpi {
*dpi_in_params = dpi;
}
}
GpuDisplayMode::BorderlessFullScreen(_) => {
if width.is_some() || height.is_some() || dpi.is_some() {
return Err(argument::Error::UnknownArgument(
"width, height, or dpi is only supported for windowed display mode".to_string(),
));
}
(None, None) => {}
_ => {
return Err("must include both 'width' and 'height' if either is supplied".to_string())
}
}
#[cfg(feature = "gfxstream")]
{
if !vulkan_specified && gpu_params.mode == GpuMode::ModeGfxstream {
gpu_params.use_vulkan = crate::crosvm::sys::config::use_vulkan();
}
if angle_specified || gles31_specified {
match gpu_params.mode {
GpuMode::ModeGfxstream => {}
_ => {
return Err(argument::Error::UnknownArgument(
"gpu parameters angle and gles3.1 are only supported for gfxstream backend"
.to_string(),
));
}
}
if matches!(gpu_params.mode, GpuMode::ModeGfxstream) {
if gpu_params.use_vulkan.is_none() {
gpu_params.use_vulkan = Some(use_vulkan());
}
} else {
return Err(format!(
"backend type {:?} is deprecated, please use gfxstream",
gpu_params.mode
));
}
gpu_params.display_params = vec![display_param];
Ok(gpu_params)
}
#[cfg(feature = "gpu")]
pub(crate) fn validate_gpu_config(cfg: &mut Config) -> Result<(), String> {
if let Some(gpu_parameters) = cfg.gpu_parameters.as_ref() {
if let Some(gpu_parameters) = cfg.gpu_parameters.as_mut() {
if gpu_parameters.display_params.is_empty() {
gpu_parameters.display_params.push(Default::default());
}
@ -477,10 +108,6 @@ pub(crate) fn validate_gpu_config(cfg: &mut Config) -> Result<(), String> {
for virtio_single_touch in cfg.virtio_single_touch.iter_mut() {
virtio_single_touch.set_default_size(width, height);
}
let dpi = gpu_parameters.display_params[0].get_dpi();
info!("using dpi {} on the Android guest", dpi);
cfg.params.push(format!("androidboot.lcd_density={}", dpi));
}
Ok(())
}
@ -541,14 +168,25 @@ impl FromStr for HypervisorKind {
#[cfg(test)]
mod tests {
#[cfg(feature = "gpu")]
use argh::FromArgs;
#[cfg(feature = "gpu")]
use devices::virtio::gpu::GpuDisplayMode;
#[cfg(all(feature = "gpu", feature = "gfxstream"))]
use rutabaga_gfx::RutabagaWsi;
#[cfg(any(feature = "audio", feature = "gpu"))]
use super::*;
#[cfg(feature = "gpu")]
use crate::crosvm::config::from_key_values;
#[cfg(feature = "gpu")]
use crate::crosvm::sys::config::parse_gpu_options;
#[cfg(feature = "gpu")]
fn parse_gpu_display_options(s: &str) -> Result<GpuDisplayParameters, String> {
from_key_values::<GpuDisplayParameters>(s)
}
#[cfg(all(feature = "gpu", feature = "gfxstream"))]
#[test]
fn parse_gpu_options_gfxstream_with_wsi_specified() {
@ -578,49 +216,24 @@ mod tests {
#[cfg(all(feature = "gpu"))]
#[test]
fn parse_gpu_options_default_vulkan_support() {
#[cfg(unix)]
assert!(
!parse_gpu_options("backend=virglrenderer")
.unwrap()
.use_vulkan
);
#[cfg(feature = "gfxstream")]
assert!(!parse_gpu_options("backend=gfxstream").unwrap().use_vulkan);
#[cfg(all(feature = "gfxstream", unix))]
assert!(parse_gpu_options("backend=gfxstream").unwrap().use_vulkan);
assert_eq!(
parse_gpu_options("backend=gfxstream").unwrap().use_vulkan,
Some(false)
);
}
#[cfg(all(feature = "gpu"))]
#[test]
fn parse_gpu_options_with_vulkan_specified() {
assert!(parse_gpu_options("vulkan=true").unwrap().use_vulkan);
#[cfg(unix)]
assert!(
parse_gpu_options("backend=virglrenderer,vulkan=true")
.unwrap()
.use_vulkan
assert_eq!(
parse_gpu_options("vulkan=true").unwrap().use_vulkan,
Some(true)
);
#[cfg(unix)]
assert!(
parse_gpu_options("vulkan=true,backend=virglrenderer")
.unwrap()
.use_vulkan
assert_eq!(
parse_gpu_options("vulkan=false").unwrap().use_vulkan,
Some(false)
);
assert!(!parse_gpu_options("vulkan=false").unwrap().use_vulkan);
#[cfg(unix)]
assert!(
!parse_gpu_options("backend=virglrenderer,vulkan=false")
.unwrap()
.use_vulkan
);
#[cfg(unix)]
assert!(
!parse_gpu_options("vulkan=false,backend=virglrenderer")
.unwrap()
.use_vulkan
);
#[cfg(unix)]
assert!(parse_gpu_options("backend=virglrenderer,vulkan=invalid_value").is_err());
assert!(parse_gpu_options("vulkan=invalid_value,backend=virglrenderer").is_err());
}
@ -628,130 +241,248 @@ mod tests {
#[test]
fn parse_gpu_options_gfxstream_with_gles31_specified() {
assert!(
parse_gpu_options("backend=gfxstream,gles3.1=true")
parse_gpu_options("backend=gfxstream,gles31=true")
.unwrap()
.gfxstream_support_gles31
);
assert!(
parse_gpu_options("gles3.1=true,backend=gfxstream")
parse_gpu_options("gles31=true,backend=gfxstream")
.unwrap()
.gfxstream_support_gles31
);
assert!(
!parse_gpu_options("backend=gfxstream,gles3.1=false")
!parse_gpu_options("backend=gfxstream,gles31=false")
.unwrap()
.gfxstream_support_gles31
);
assert!(
!parse_gpu_options("gles3.1=false,backend=gfxstream")
!parse_gpu_options("gles31=false,backend=gfxstream")
.unwrap()
.gfxstream_support_gles31
);
assert!(parse_gpu_options("backend=gfxstream,gles3.1=invalid_value").is_err());
assert!(parse_gpu_options("gles3.1=invalid_value,backend=gfxstream").is_err());
assert!(parse_gpu_options("backend=gfxstream,gles31=invalid_value").is_err());
assert!(parse_gpu_options("gles31=invalid_value,backend=gfxstream").is_err());
}
#[cfg(all(feature = "gpu", feature = "gfxstream"))]
#[test]
fn parse_gpu_options_not_gfxstream_with_gles31_specified() {
assert!(parse_gpu_options("backend=virglrenderer,gles3.1=true").is_err());
assert!(parse_gpu_options("gles3.1=true,backend=virglrenderer").is_err());
assert!(parse_gpu_options("backend=virglrenderer,gles31=true").is_err());
assert!(parse_gpu_options("gles31=true,backend=virglrenderer").is_err());
}
#[cfg(feature = "gpu")]
#[test]
fn parse_gpu_options_gpu_display_mode() {
let display_params = parse_gpu_options("display_mode=windowed")
fn parse_gpu_options_no_display_specified() {
let display_params = parse_gpu_options("").unwrap().display_params;
assert!(display_params.is_empty());
}
#[cfg(feature = "gpu")]
#[test]
fn parse_gpu_options_display_size_valid() {
const WIDTH: u32 = 1720;
const HEIGHT: u32 = 1800;
let display_params = parse_gpu_options(format!("width={},height=720", WIDTH).as_str())
.unwrap()
.display_params;
assert_eq!(display_params.len(), 1);
assert!(
matches!(display_params[0].mode, GpuDisplayMode::Windowed(width, _) if width == WIDTH)
);
let display_params = parse_gpu_options(format!("width=1280,height={}", HEIGHT).as_str())
.unwrap()
.display_params;
assert_eq!(display_params.len(), 1);
assert!(
matches!(display_params[0].mode, GpuDisplayMode::Windowed(_, height) if height == HEIGHT)
);
}
#[cfg(feature = "gpu")]
#[test]
fn parse_gpu_options_display_size_incomplete() {
assert!(parse_gpu_options("width=1280").is_err());
assert!(parse_gpu_options("height=720").is_err());
}
#[cfg(feature = "gpu")]
#[test]
fn parse_gpu_options_display_size_duplicated() {
assert!(parse_gpu_options("width=1280,width=1280,height=720").is_err());
assert!(parse_gpu_options("width=1280,height=720,height=720").is_err());
}
#[cfg(feature = "gpu")]
#[test]
fn parse_gpu_display_options_mode() {
let display_params = parse_gpu_display_options("mode=windowed[1280,720]").unwrap();
assert!(matches!(
display_params[0].display_mode,
GpuDisplayMode::Windowed { .. }
display_params.mode,
GpuDisplayMode::Windowed(_, _)
));
let display_params = parse_gpu_options("display_mode=borderless_full_screen")
.unwrap()
.display_params;
let display_params = parse_gpu_display_options("mode=borderless_full_screen").unwrap();
assert!(matches!(
display_params[0].display_mode,
display_params.mode,
GpuDisplayMode::BorderlessFullScreen(_)
));
assert!(parse_gpu_options("display_mode=invalid_mode").is_err());
assert!(parse_gpu_display_options("mode=invalid_mode").is_err());
}
#[cfg(feature = "gpu")]
#[test]
fn parse_gpu_options_gpu_display_mode_duplicated() {
assert!(parse_gpu_options("display_mode=windowed,display_mode=windowed").is_err());
fn parse_gpu_display_options_mode_duplicated() {
assert!(
parse_gpu_display_options("mode=windowed[1280,720],mode=windowed[1280,720]").is_err()
);
}
#[cfg(feature = "gpu")]
#[test]
fn parse_gpu_options_borderless_full_screen_shouldnt_be_specified_with_size() {
assert!(parse_gpu_options("display_mode=borderless_full_screen,width=1280").is_err());
assert!(parse_gpu_options("display_mode=borderless_full_screen,height=720").is_err());
fn parse_gpu_display_options_borderless_full_screen_should_not_be_specified_with_size() {
assert!(parse_gpu_display_options("mode=borderless_full_screen[1280,720]").is_err());
}
#[cfg(feature = "gpu")]
#[test]
fn parse_gpu_options_windowed_with_size() {
fn parse_gpu_display_options_windowed_with_size() {
const WIDTH: u32 = 1720;
const HEIGHT: u32 = 1800;
const DPI: u32 = 1808;
let display_params =
parse_gpu_options(format!("display_mode=windowed,width={}", WIDTH).as_str())
.unwrap()
.display_params;
assert!(
matches!(display_params[0].display_mode, GpuDisplayMode::Windowed { width, .. } if width == WIDTH)
);
parse_gpu_display_options(format!("mode=windowed[{},720]", WIDTH).as_str()).unwrap();
assert!(matches!(
display_params.mode,
GpuDisplayMode::Windowed(width, _) if width == WIDTH
));
let display_params =
parse_gpu_options(format!("display_mode=windowed,height={}", HEIGHT).as_str())
.unwrap()
.display_params;
assert!(
matches!(display_params[0].display_mode, GpuDisplayMode::Windowed { height, .. } if height == HEIGHT)
);
parse_gpu_display_options(format!("mode=windowed[1280,{}]", HEIGHT).as_str()).unwrap();
assert!(matches!(
display_params.mode,
GpuDisplayMode::Windowed(_, height) if height == HEIGHT
));
let display_params =
parse_gpu_options(format!("display_mode=windowed,dpi={}", DPI).as_str())
.unwrap()
.display_params;
assert!(
matches!(display_params[0].display_mode, GpuDisplayMode::Windowed { dpi, .. } if dpi == DPI)
);
assert!(parse_gpu_display_options("mode=windowed[]").is_err());
assert!(parse_gpu_display_options("mode=windowed[1280]").is_err());
}
#[cfg(feature = "gpu")]
#[test]
fn parse_gpu_options_hidden() {
let display_params = parse_gpu_options("hidden=true").unwrap().display_params;
assert!(display_params[0].hidden);
fn parse_gpu_display_options_hidden() {
let display_params = parse_gpu_display_options("hidden").unwrap();
assert!(display_params.hidden);
let display_params = parse_gpu_options("hidden=false").unwrap().display_params;
assert!(matches!(display_params[0].hidden, false));
let display_params = parse_gpu_display_options("hidden=true").unwrap();
assert!(display_params.hidden);
let display_params = parse_gpu_display_options("hidden=false").unwrap();
assert!(!display_params.hidden);
}
#[cfg(feature = "gpu")]
#[test]
fn parse_gpu_options_refresh_rate() {
const REFRESH_RATE: u32 = 120;
let display_params = parse_gpu_options(format!("refresh_rate={}", REFRESH_RATE).as_str())
.unwrap()
.display_params;
fn parse_gpu_display_options_hidden_duplicated() {
assert!(parse_gpu_display_options("hidden,hidden").is_err());
}
#[cfg(feature = "gpu")]
#[test]
fn parse_gpu_display_options_refresh_rate() {
const REFRESH_RATE: u32 = 30;
let display_params =
parse_gpu_display_options(format!("refresh-rate={}", REFRESH_RATE).as_str()).unwrap();
assert_eq!(display_params.refresh_rate, REFRESH_RATE);
assert!(parse_gpu_options(format!("refresh_rate=invalid_value").as_str()).is_err());
}
#[cfg(feature = "gpu")]
#[test]
fn parse_gpu_options_size_duplicated() {
assert!(parse_gpu_options("width=1280,width=1280").is_err());
assert!(parse_gpu_options("height=1280,height=1280").is_err());
assert!(parse_gpu_options("dpi=1280,dpi=1280").is_err());
fn parse_gpu_display_options_refresh_rate_duplicated() {
assert!(parse_gpu_display_options("refresh-rate=30,refresh-rate=60").is_err());
}
#[cfg(feature = "gpu")]
#[test]
fn parse_gpu_options_and_gpu_display_options_valid() {
const WIDTH: u32 = 1720;
const HEIGHT: u32 = 1800;
const EXPECTED_DISPLAY_MODE: GpuDisplayMode = GpuDisplayMode::Windowed(WIDTH, HEIGHT);
const BACKEND: &str = if cfg!(feature = "gfxstream") {
"gfxstream"
} else {
"2d"
};
let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
&[],
&[
"--gpu",
format!("backend={}", BACKEND).as_str(),
"--gpu-display",
format!("windowed[{},{}]", WIDTH, HEIGHT).as_str(),
"/dev/null",
],
)
.unwrap()
.try_into()
.unwrap();
let gpu_params = config.gpu_parameters.unwrap();
assert_eq!(gpu_params.display_params.len(), 1);
assert_eq!(gpu_params.display_params[0].mode, EXPECTED_DISPLAY_MODE);
// `width` and `height` in GPU options are supported for CLI backward compatibility.
let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
&[],
&[
"--gpu",
format!("backend={},width={},height={}", BACKEND, WIDTH, HEIGHT).as_str(),
"/dev/null",
],
)
.unwrap()
.try_into()
.unwrap();
let gpu_params = config.gpu_parameters.unwrap();
assert_eq!(gpu_params.display_params.len(), 1);
assert_eq!(gpu_params.display_params[0].mode, EXPECTED_DISPLAY_MODE);
}
#[cfg(feature = "gpu")]
#[test]
fn parse_gpu_options_and_gpu_display_options_multi_display_unsupported() {
let command = crate::crosvm::cmdline::RunCommand::from_args(
&[],
&[
"--gpu-display",
"mode=borderless_full_screen",
"--gpu-display",
"mode=borderless_full_screen",
"/dev/null",
],
)
.unwrap();
assert!(Config::try_from(command).is_err());
let command = crate::crosvm::cmdline::RunCommand::from_args(
&[],
&[
"--gpu",
"width=1280,height=720",
"--gpu-display",
"mode=borderless_full_screen",
"/dev/null",
],
)
.unwrap();
assert!(Config::try_from(command).is_err());
}
}

View file

@ -28,3 +28,6 @@ serde_keyvalue = { path = "../serde_keyvalue", features = ["argh_derive"] }
sync = { path = "../common/sync" }
thiserror = "*"
vm_memory = { path = "../vm_memory" }
[target.'cfg(windows)'.dependencies]
winapi = "*"

View file

@ -5,8 +5,6 @@
use std::collections::BTreeMap as Map;
use std::fmt;
use std::fmt::Display;
#[cfg(windows)]
use std::marker::PhantomData;
use std::path::Path;
use serde::Deserialize;
@ -32,7 +30,14 @@ fn default_dpi() -> u32 {
}
/// Trait that the platform-specific type `DisplayMode` needs to implement.
pub trait DisplayModeTrait {
pub(crate) trait DisplayModeTrait {
/// Returns the initial host window size.
fn get_window_size(&self) -> (u32, u32);
/// Returns the virtual display size used for creating the display device.
///
/// This may be different from the initial host window size since different display backends may
/// have different alignment requirements on it.
fn get_virtual_display_size(&self) -> (u32, u32);
}
@ -42,7 +47,7 @@ impl Default for DisplayMode {
}
}
#[derive(Clone, Debug, PartialEq, Deserialize, FromKeyValues, Serialize)]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, FromKeyValues)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct DisplayParameters {
#[serde(default)]
@ -78,6 +83,10 @@ impl DisplayParameters {
Self::new(mode, false, DEFAULT_REFRESH_RATE, DEFAULT_DPI, DEFAULT_DPI)
}
pub fn get_window_size(&self) -> (u32, u32) {
self.mode.get_window_size()
}
pub fn get_virtual_display_size(&self) -> (u32, u32) {
self.mode.get_virtual_display_size()
}

View file

@ -13,7 +13,7 @@ cfg_if::cfg_if! {
pub mod windows;
pub use windows as platform;
#[cfg(feature = "gpu")]
pub type DisplayMode = platform::gpu::WinDisplayMode<windows::DisplayDataProvider>;
pub type DisplayMode = platform::gpu::WinDisplayMode<platform::gpu::DisplayDataProvider>;
} else {
compile_error!("Unsupported platform");
}

View file

@ -14,9 +14,13 @@ pub enum UnixDisplayMode {
}
impl DisplayModeTrait for UnixDisplayMode {
fn get_virtual_display_size(&self) -> (u32, u32) {
fn get_window_size(&self) -> (u32, u32) {
match self {
Self::Windowed(width, height) => (*width, *height),
}
}
fn get_virtual_display_size(&self) -> (u32, u32) {
self.get_window_size()
}
}

View file

@ -16,43 +16,36 @@ use crate::gpu::DisplayModeTrait;
const DISPLAY_WIDTH_SOFT_MAX: u32 = 1920;
const DISPLAY_HEIGHT_SOFT_MAX: u32 = 1080;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum WinDisplayMode<T> {
Windowed(u32, u32),
BorderlessFullScreen(PhantomData<T>),
BorderlessFullScreen(#[serde(skip)] PhantomData<T>),
}
impl<T> DisplayModeTrait for WinDisplayMode<T> {
fn get_virtual_display_size(&self) -> (u32, u32) {
let (width, height) = match self {
impl<T: ProvideDisplayData> DisplayModeTrait for WinDisplayMode<T> {
fn get_window_size(&self) -> (u32, u32) {
match self {
Self::Windowed(width, height) => (*width, *height),
Self::BorderlessFullScreen(_) => {
let (width, height) = DisplayDataProvider::get_host_display_size();
adjust_virtual_display_size(width, height)
}
};
Self::BorderlessFullScreen(_) => T::get_host_display_size(),
}
}
fn get_virtual_display_size(&self) -> (u32, u32) {
let (width, height) = self.get_window_size();
let (width, height) = adjust_virtual_display_size(width, height);
info!("Guest display size: {}x{}", width, height);
(width, height)
}
}
impl<T> From<WinDisplayMode<T>> for WinDisplayModeArg {
fn from(mode: WinDisplayMode<T>) -> WinDisplayModeArg {
match mode {
WinDisplayMode::Windowed { .. } => WinDisplayModeArg::Windowed,
WinDisplayMode::BorderlessFullScreen(_) => WinDisplayModeArg::BorderlessFullScreen,
}
}
}
/// Trait for returning host display data such as resolution. Tests may overwrite this to specify
/// display data rather than rely on properties of the actual display device.
trait ProvideDisplayData {
fn get_host_display_size() -> (u32, u32);
}
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct DisplayDataProvider;
impl ProvideDisplayData for DisplayDataProvider {
@ -78,3 +71,39 @@ fn adjust_virtual_display_size(width: u32, height: u32) -> (u32, u32) {
let width = width - (width % 8);
(width, height)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn borderless_full_screen_virtual_window_width_should_be_multiple_of_8() {
struct MockDisplayDataProvider;
impl ProvideDisplayData for MockDisplayDataProvider {
fn get_host_display_size() -> (u32, u32) {
(1366, 768)
}
}
let mode = WinDisplayMode::<MockDisplayDataProvider>::BorderlessFullScreen(PhantomData);
let (width, _) = mode.get_virtual_display_size();
assert_eq!(width % 8, 0);
}
#[test]
fn borderless_full_screen_virtual_window_size_should_be_smaller_than_soft_max() {
struct MockDisplayDataProvider;
impl ProvideDisplayData for MockDisplayDataProvider {
fn get_host_display_size() -> (u32, u32) {
(DISPLAY_WIDTH_SOFT_MAX + 1, DISPLAY_HEIGHT_SOFT_MAX + 1)
}
}
let mode = WinDisplayMode::<MockDisplayDataProvider>::BorderlessFullScreen(PhantomData);
let (width, height) = mode.get_virtual_display_size();
assert!(width <= DISPLAY_WIDTH_SOFT_MAX);
assert!(height <= DISPLAY_HEIGHT_SOFT_MAX);
}
}