xref: /aosp_15_r20/external/crosvm/src/crosvm/gpu_config.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1 // Copyright 2022 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 use base::warn;
6 use devices::virtio::gpu::VIRTIO_GPU_MAX_SCANOUTS;
7 use devices::virtio::GpuDisplayMode;
8 use devices::virtio::GpuDisplayParameters;
9 #[cfg(feature = "gfxstream")]
10 use devices::virtio::GpuMode;
11 use devices::virtio::GpuParameters;
12 use vm_control::gpu::DEFAULT_DPI;
13 
14 use crate::crosvm::cmdline::FixedGpuDisplayParameters;
15 use crate::crosvm::cmdline::FixedGpuParameters;
16 use crate::crosvm::config::Config;
17 
18 #[cfg(feature = "gfxstream")]
default_use_vulkan() -> bool19 fn default_use_vulkan() -> bool {
20     !cfg!(windows)
21 }
22 
fixup_gpu_options( mut gpu_params: GpuParameters, ) -> Result<FixedGpuParameters, String>23 pub(crate) fn fixup_gpu_options(
24     mut gpu_params: GpuParameters,
25 ) -> Result<FixedGpuParameters, String> {
26     // Fix up `gpu_params.display_params` parsed from command-line.
27     gpu_params.display_params = gpu_params
28         .display_params
29         .into_iter()
30         .map(|p| fixup_gpu_display_options(p).map(|p| p.0))
31         .collect::<Result<Vec<_>, _>>()?;
32 
33     if gpu_params.__width_compat.is_some() || gpu_params.__height_compat.is_some() {
34         warn!("'width' and 'height' in '--gpu' are deprecated; please use `displays=[...]`");
35     }
36 
37     match (
38         gpu_params.__width_compat.take(),
39         gpu_params.__height_compat.take(),
40     ) {
41         (Some(width), Some(height)) => {
42             let display_mode = GpuDisplayMode::Windowed(width, height);
43             gpu_params
44                 .display_params
45                 .push(GpuDisplayParameters::default_with_mode(display_mode));
46         }
47         (None, None) => {}
48         _ => {
49             return Err("must include both 'width' and 'height' if either is supplied".to_string())
50         }
51     }
52 
53     #[cfg(feature = "gfxstream")]
54     if gpu_params.mode == GpuMode::ModeGfxstream {
55         if gpu_params.use_vulkan.is_none() {
56             gpu_params.use_vulkan = Some(default_use_vulkan());
57         }
58     } else {
59         #[cfg(windows)]
60         return Err(format!(
61             "backend type {:?} is deprecated, please use gfxstream",
62             gpu_params.mode
63         ));
64     }
65 
66     Ok(FixedGpuParameters(gpu_params))
67 }
68 
69 /// Fixes `GpuDisplayParameters` after parsing using serde.
70 ///
71 /// The `dpi` field is guaranteed to be populated after this is called.
fixup_gpu_display_options( mut display_params: GpuDisplayParameters, ) -> Result<FixedGpuDisplayParameters, String>72 pub(crate) fn fixup_gpu_display_options(
73     mut display_params: GpuDisplayParameters,
74 ) -> Result<FixedGpuDisplayParameters, String> {
75     let (horizontal_dpi_compat, vertical_dpi_compat) = (
76         display_params.__horizontal_dpi_compat.take(),
77         display_params.__vertical_dpi_compat.take(),
78     );
79     if horizontal_dpi_compat.is_some() || vertical_dpi_compat.is_some() {
80         warn!("'horizontal-dpi' and 'vertical-dpi' are deprecated; please use `dpi=[...]`");
81     }
82     // Make sure `display_params.dpi` is always populated.
83     display_params.dpi = Some(match display_params.dpi {
84         Some(dpi) => {
85             if horizontal_dpi_compat.is_some() || vertical_dpi_compat.is_some() {
86                 return Err(
87                     "if 'dpi' is supplied, 'horizontal-dpi' and 'vertical-dpi' must not be supplied"
88                         .to_string(),
89                 );
90             }
91             dpi
92         }
93         None => (
94             horizontal_dpi_compat.unwrap_or(DEFAULT_DPI),
95             vertical_dpi_compat.unwrap_or(DEFAULT_DPI),
96         ),
97     });
98 
99     Ok(FixedGpuDisplayParameters(display_params))
100 }
101 
validate_gpu_config(cfg: &mut Config) -> Result<(), String>102 pub(crate) fn validate_gpu_config(cfg: &mut Config) -> Result<(), String> {
103     if let Some(gpu_parameters) = cfg.gpu_parameters.as_mut() {
104         if !gpu_parameters.pci_bar_size.is_power_of_two() {
105             return Err(format!(
106                 "`pci-bar-size` must be a power of two but is {}",
107                 gpu_parameters.pci_bar_size
108             ));
109         }
110 
111         if gpu_parameters.max_num_displays < 1
112             || gpu_parameters.max_num_displays > VIRTIO_GPU_MAX_SCANOUTS as u32
113         {
114             return Err(format!(
115                 "`max_num_displays` must be in range [1, {}]",
116                 VIRTIO_GPU_MAX_SCANOUTS
117             ));
118         }
119         if gpu_parameters.display_params.len() as u32 > gpu_parameters.max_num_displays {
120             return Err(format!(
121                 "Provided more `display_params` ({}) than `max_num_displays` ({})",
122                 gpu_parameters.display_params.len(),
123                 gpu_parameters.max_num_displays
124             ));
125         }
126 
127         // Add a default display if no display is specified.
128         if gpu_parameters.display_params.is_empty() {
129             gpu_parameters.display_params.push(Default::default());
130         }
131 
132         let is_4k_uhd_enabled = false;
133         let (width, height) =
134             gpu_parameters.display_params[0].get_virtual_display_size_4k_uhd(is_4k_uhd_enabled);
135         cfg.display_input_width = Some(width);
136         cfg.display_input_height = Some(height);
137     }
138     Ok(())
139 }
140 
141 #[cfg(test)]
142 mod tests {
143     use argh::FromArgs;
144     #[cfg(feature = "gfxstream")]
145     use devices::virtio::GpuWsi;
146 
147     use super::*;
148     use crate::crosvm::config::from_key_values;
149 
get_backend_name() -> &'static str150     const fn get_backend_name() -> &'static str {
151         if cfg!(feature = "gfxstream") {
152             "gfxstream"
153         } else if cfg!(feature = "virgl_renderer") {
154             "virglrenderer"
155         } else {
156             "2d"
157         }
158     }
159 
160     /// Parses and fix up a `GpuParameters` from a command-line option string.
parse_gpu_options(s: &str) -> Result<GpuParameters, String>161     fn parse_gpu_options(s: &str) -> Result<GpuParameters, String> {
162         from_key_values::<FixedGpuParameters>(s).map(|p| p.0)
163     }
164 
parse_gpu_display_options(s: &str) -> Result<GpuDisplayParameters, String>165     fn parse_gpu_display_options(s: &str) -> Result<GpuDisplayParameters, String> {
166         from_key_values::<GpuDisplayParameters>(s)
167     }
168 
169     #[test]
parse_gpu_options_max_num_displays()170     fn parse_gpu_options_max_num_displays() {
171         {
172             let gpu_params = parse_gpu_options("").unwrap();
173             assert_eq!(gpu_params.max_num_displays, VIRTIO_GPU_MAX_SCANOUTS as u32);
174         }
175         {
176             let gpu_params = parse_gpu_options("max-num-displays=5").unwrap();
177             assert_eq!(gpu_params.max_num_displays, 5);
178         }
179         {
180             let command = crate::crosvm::cmdline::RunCommand::from_args(
181                 &[],
182                 &["--gpu", "max-num-displays=0", "/dev/null"],
183             )
184             .unwrap();
185             assert!(Config::try_from(command).is_err());
186         }
187         {
188             let command = crate::crosvm::cmdline::RunCommand::from_args(
189                 &[],
190                 &[
191                     "--gpu",
192                     format!("max-num-displays={}", VIRTIO_GPU_MAX_SCANOUTS + 1).as_str(),
193                     "/dev/null",
194                 ],
195             )
196             .unwrap();
197             assert!(Config::try_from(command).is_err());
198         }
199         // TODO(b/332910955): Remove the single display restriction on Windows and enable this test.
200         #[cfg(any(target_os = "android", target_os = "linux"))]
201         {
202             let command = crate::crosvm::cmdline::RunCommand::from_args(
203                 &[],
204                 &[
205                     "--gpu",
206                     "max-num-displays=1,displays=[[mode=windowed[1920,1080]],\
207                     [mode=windowed[1280,720]]]",
208                     "/dev/null",
209                 ],
210             )
211             .unwrap();
212             assert!(Config::try_from(command).is_err());
213         }
214         #[cfg(any(target_os = "android", target_os = "linux"))]
215         {
216             let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
217                 &[],
218                 &[
219                     "--gpu",
220                     "max-num-displays=3,displays=[[mode=windowed[1920,1080]],\
221                     [mode=windowed[1280,720]]]",
222                     "/dev/null",
223                 ],
224             )
225             .unwrap()
226             .try_into()
227             .unwrap();
228 
229             let gpu_params = config.gpu_parameters.unwrap();
230 
231             assert_eq!(gpu_params.max_num_displays, 3);
232             assert_eq!(gpu_params.display_params.len(), 2);
233             assert_eq!(
234                 gpu_params.display_params[0].mode,
235                 GpuDisplayMode::Windowed(1920, 1080)
236             );
237             assert_eq!(
238                 gpu_params.display_params[1].mode,
239                 GpuDisplayMode::Windowed(1280, 720)
240             );
241         }
242     }
243 
244     #[test]
parse_gpu_options_mode()245     fn parse_gpu_options_mode() {
246         use devices::virtio::gpu::GpuMode;
247 
248         let gpu_params = parse_gpu_options("backend=2d").unwrap();
249         assert_eq!(gpu_params.mode, GpuMode::Mode2D);
250 
251         let gpu_params = parse_gpu_options("backend=2D").unwrap();
252         assert_eq!(gpu_params.mode, GpuMode::Mode2D);
253 
254         #[cfg(feature = "virgl_renderer")]
255         {
256             let gpu_params = parse_gpu_options("backend=3d").unwrap();
257             assert_eq!(gpu_params.mode, GpuMode::ModeVirglRenderer);
258 
259             let gpu_params = parse_gpu_options("backend=3D").unwrap();
260             assert_eq!(gpu_params.mode, GpuMode::ModeVirglRenderer);
261 
262             let gpu_params = parse_gpu_options("backend=virglrenderer").unwrap();
263             assert_eq!(gpu_params.mode, GpuMode::ModeVirglRenderer);
264         }
265 
266         #[cfg(feature = "gfxstream")]
267         {
268             let gpu_params = parse_gpu_options("backend=gfxstream").unwrap();
269             assert_eq!(gpu_params.mode, GpuMode::ModeGfxstream);
270         }
271     }
272 
273     #[test]
parse_gpu_options_flags()274     fn parse_gpu_options_flags() {
275         macro_rules! assert_default {
276             ($p:ident.$a:ident) => {
277                 assert_eq!($p.$a, GpuParameters::default().$a)
278             };
279         }
280 
281         let gpu_params = parse_gpu_options("").unwrap();
282         assert_default!(gpu_params.renderer_use_egl);
283         assert_default!(gpu_params.renderer_use_gles);
284         assert_default!(gpu_params.renderer_use_glx);
285         assert_default!(gpu_params.renderer_use_surfaceless);
286         assert_default!(gpu_params.use_vulkan);
287         assert_default!(gpu_params.udmabuf);
288 
289         let gpu_params = parse_gpu_options("egl=false,gles=false").unwrap();
290         assert_eq!(gpu_params.renderer_use_egl, false);
291         assert_eq!(gpu_params.renderer_use_gles, false);
292         assert_default!(gpu_params.renderer_use_glx);
293         assert_default!(gpu_params.renderer_use_surfaceless);
294         assert_default!(gpu_params.use_vulkan);
295         assert_default!(gpu_params.udmabuf);
296 
297         let gpu_params = parse_gpu_options("surfaceless=false,glx").unwrap();
298         assert_default!(gpu_params.renderer_use_egl);
299         assert_default!(gpu_params.renderer_use_gles);
300         assert_eq!(gpu_params.renderer_use_surfaceless, false);
301         assert_eq!(gpu_params.renderer_use_glx, true);
302         assert_default!(gpu_params.use_vulkan);
303         assert_default!(gpu_params.udmabuf);
304 
305         let gpu_params = parse_gpu_options("vulkan,udmabuf").unwrap();
306         assert_default!(gpu_params.renderer_use_egl);
307         assert_default!(gpu_params.renderer_use_gles);
308         assert_default!(gpu_params.renderer_use_glx);
309         assert_default!(gpu_params.renderer_use_surfaceless);
310         assert_eq!(gpu_params.use_vulkan, Some(true));
311         assert_eq!(gpu_params.udmabuf, true);
312 
313         assert!(parse_gpu_options("egl=false,gles=true,foomatic").is_err());
314     }
315 
316     #[cfg(feature = "gfxstream")]
317     #[test]
parse_gpu_options_gfxstream_with_wsi_specified()318     fn parse_gpu_options_gfxstream_with_wsi_specified() {
319         {
320             let gpu_params = parse_gpu_options("backend=gfxstream,wsi=vk").unwrap();
321             assert!(matches!(gpu_params.wsi, Some(GpuWsi::Vulkan)));
322         }
323         {
324             let gpu_params = parse_gpu_options("wsi=vk,backend=gfxstream").unwrap();
325             assert!(matches!(gpu_params.wsi, Some(GpuWsi::Vulkan)));
326         }
327         {
328             assert!(parse_gpu_options("backend=gfxstream,wsi=invalid_value").is_err());
329         }
330         {
331             assert!(parse_gpu_options("wsi=invalid_value,backend=gfxstream").is_err());
332         }
333     }
334 
335     #[test]
parse_gpu_options_default_vulkan_support()336     fn parse_gpu_options_default_vulkan_support() {
337         let gpu_params = parse_gpu_options("backend=2d").unwrap();
338         assert_eq!(gpu_params.use_vulkan, None);
339 
340         #[cfg(feature = "virgl_renderer")]
341         {
342             let gpu_params = parse_gpu_options("backend=virglrenderer").unwrap();
343             assert_eq!(gpu_params.use_vulkan, None);
344         }
345 
346         #[cfg(feature = "gfxstream")]
347         {
348             let gpu_params = parse_gpu_options("backend=gfxstream").unwrap();
349             assert_eq!(gpu_params.use_vulkan, Some(default_use_vulkan()));
350         }
351     }
352 
353     #[test]
parse_gpu_options_with_vulkan_specified()354     fn parse_gpu_options_with_vulkan_specified() {
355         const BACKEND: &str = get_backend_name();
356         {
357             let gpu_params = parse_gpu_options("vulkan=true").unwrap();
358             assert_eq!(gpu_params.use_vulkan, Some(true));
359         }
360         {
361             let gpu_params =
362                 parse_gpu_options(format!("backend={},vulkan=true", BACKEND).as_str()).unwrap();
363             assert_eq!(gpu_params.use_vulkan, Some(true));
364         }
365         {
366             let gpu_params =
367                 parse_gpu_options(format!("vulkan=true,backend={}", BACKEND).as_str()).unwrap();
368             assert_eq!(gpu_params.use_vulkan, Some(true));
369         }
370         {
371             let gpu_params = parse_gpu_options("vulkan=false").unwrap();
372             assert_eq!(gpu_params.use_vulkan, Some(false));
373         }
374         {
375             let gpu_params =
376                 parse_gpu_options(format!("backend={},vulkan=false", BACKEND).as_str()).unwrap();
377             assert_eq!(gpu_params.use_vulkan, Some(false));
378         }
379         {
380             let gpu_params =
381                 parse_gpu_options(format!("vulkan=false,backend={}", BACKEND).as_str()).unwrap();
382             assert_eq!(gpu_params.use_vulkan, Some(false));
383         }
384         {
385             assert!(parse_gpu_options(
386                 format!("backend={},vulkan=invalid_value", BACKEND).as_str()
387             )
388             .is_err());
389         }
390         {
391             assert!(parse_gpu_options(
392                 format!("vulkan=invalid_value,backend={}", BACKEND).as_str()
393             )
394             .is_err());
395         }
396     }
397 
398     #[test]
parse_gpu_options_context_types()399     fn parse_gpu_options_context_types() {
400         use rutabaga_gfx::RUTABAGA_CAPSET_CROSS_DOMAIN;
401         use rutabaga_gfx::RUTABAGA_CAPSET_VIRGL;
402 
403         let gpu_params = parse_gpu_options("context-types=virgl:cross-domain").unwrap();
404         assert_eq!(
405             gpu_params.capset_mask,
406             (1 << RUTABAGA_CAPSET_VIRGL) | (1 << RUTABAGA_CAPSET_CROSS_DOMAIN)
407         );
408     }
409 
410     #[test]
parse_gpu_options_cache()411     fn parse_gpu_options_cache() {
412         let gpu_params = parse_gpu_options("cache-path=/path/to/cache,cache-size=16384").unwrap();
413         assert_eq!(gpu_params.cache_path, Some("/path/to/cache".into()));
414         assert_eq!(gpu_params.cache_size, Some("16384".into()));
415     }
416 
417     #[test]
parse_gpu_options_pci_bar()418     fn parse_gpu_options_pci_bar() {
419         let gpu_params = parse_gpu_options("pci-bar-size=0x100000").unwrap();
420         assert_eq!(gpu_params.pci_bar_size, 0x100000);
421     }
422 
423     #[test]
parse_gpu_options_no_display_specified()424     fn parse_gpu_options_no_display_specified() {
425         let display_params = parse_gpu_options("").unwrap().display_params;
426         assert!(display_params.is_empty());
427     }
428 
429     #[test]
parse_gpu_options_display_size_valid()430     fn parse_gpu_options_display_size_valid() {
431         const WIDTH: u32 = 1720;
432         const HEIGHT: u32 = 1800;
433 
434         let display_params = parse_gpu_options(format!("width={},height=720", WIDTH).as_str())
435             .unwrap()
436             .display_params;
437         assert_eq!(display_params.len(), 1);
438         assert!(
439             matches!(display_params[0].mode, GpuDisplayMode::Windowed(width, _) if width == WIDTH)
440         );
441 
442         let display_params = parse_gpu_options(format!("width=1280,height={}", HEIGHT).as_str())
443             .unwrap()
444             .display_params;
445         assert_eq!(display_params.len(), 1);
446         assert!(
447             matches!(display_params[0].mode, GpuDisplayMode::Windowed(_, height) if height == HEIGHT)
448         );
449     }
450 
451     #[test]
parse_gpu_options_display_size_incomplete()452     fn parse_gpu_options_display_size_incomplete() {
453         assert!(parse_gpu_options("width=1280").is_err());
454         assert!(parse_gpu_options("height=720").is_err());
455     }
456 
457     #[test]
parse_gpu_options_display_size_duplicated()458     fn parse_gpu_options_display_size_duplicated() {
459         assert!(parse_gpu_options("width=1280,width=1280,height=720").is_err());
460         assert!(parse_gpu_options("width=1280,height=720,height=720").is_err());
461     }
462 
463     #[test]
parse_gpu_display_options_mode()464     fn parse_gpu_display_options_mode() {
465         let display_params = parse_gpu_display_options("mode=windowed[1280,720]").unwrap();
466         assert!(matches!(
467             display_params.mode,
468             GpuDisplayMode::Windowed(_, _)
469         ));
470 
471         #[cfg(windows)]
472         {
473             let display_params = parse_gpu_display_options("mode=borderless_full_screen").unwrap();
474             assert!(matches!(
475                 display_params.mode,
476                 GpuDisplayMode::BorderlessFullScreen(_)
477             ));
478         }
479 
480         assert!(parse_gpu_display_options("mode=invalid_mode").is_err());
481     }
482 
483     #[test]
parse_gpu_display_options_mode_duplicated()484     fn parse_gpu_display_options_mode_duplicated() {
485         assert!(
486             parse_gpu_display_options("mode=windowed[1280,720],mode=windowed[1280,720]").is_err()
487         );
488     }
489 
490     #[cfg(windows)]
491     #[test]
parse_gpu_display_options_borderless_full_screen_should_not_be_specified_with_size()492     fn parse_gpu_display_options_borderless_full_screen_should_not_be_specified_with_size() {
493         assert!(parse_gpu_display_options("mode=borderless_full_screen[1280,720]").is_err());
494     }
495 
496     #[test]
parse_gpu_display_options_windowed_with_size()497     fn parse_gpu_display_options_windowed_with_size() {
498         const WIDTH: u32 = 1720;
499         const HEIGHT: u32 = 1800;
500 
501         let display_params =
502             parse_gpu_display_options(format!("mode=windowed[{},720]", WIDTH).as_str()).unwrap();
503         assert!(matches!(
504             display_params.mode,
505             GpuDisplayMode::Windowed(width, _) if width == WIDTH
506         ));
507 
508         let display_params =
509             parse_gpu_display_options(format!("mode=windowed[1280,{}]", HEIGHT).as_str()).unwrap();
510         assert!(matches!(
511             display_params.mode,
512             GpuDisplayMode::Windowed(_, height) if height == HEIGHT
513         ));
514 
515         assert!(parse_gpu_display_options("mode=windowed[]").is_err());
516         assert!(parse_gpu_display_options("mode=windowed[1280]").is_err());
517     }
518 
519     #[test]
parse_gpu_display_options_hidden()520     fn parse_gpu_display_options_hidden() {
521         let display_params = parse_gpu_display_options("hidden").unwrap();
522         assert!(display_params.hidden);
523 
524         let display_params = parse_gpu_display_options("hidden=true").unwrap();
525         assert!(display_params.hidden);
526 
527         let display_params = parse_gpu_display_options("hidden=false").unwrap();
528         assert!(!display_params.hidden);
529     }
530 
531     #[test]
parse_gpu_display_options_hidden_duplicated()532     fn parse_gpu_display_options_hidden_duplicated() {
533         assert!(parse_gpu_display_options("hidden,hidden").is_err());
534     }
535 
536     #[test]
parse_gpu_display_options_refresh_rate()537     fn parse_gpu_display_options_refresh_rate() {
538         const REFRESH_RATE: u32 = 30;
539 
540         let display_params =
541             parse_gpu_display_options(format!("refresh-rate={}", REFRESH_RATE).as_str()).unwrap();
542         assert_eq!(display_params.refresh_rate, REFRESH_RATE);
543     }
544 
545     #[test]
parse_gpu_display_options_refresh_rate_duplicated()546     fn parse_gpu_display_options_refresh_rate_duplicated() {
547         assert!(parse_gpu_display_options("refresh-rate=30,refresh-rate=60").is_err());
548     }
549 
550     #[test]
parse_gpu_display_options_dpi()551     fn parse_gpu_display_options_dpi() {
552         const HORIZONTAL_DPI: u32 = 160;
553         const VERTICAL_DPI: u32 = 25;
554 
555         let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
556             &[],
557             &[
558                 "--gpu-display",
559                 format!("dpi=[{},{}]", HORIZONTAL_DPI, VERTICAL_DPI).as_str(),
560                 "/dev/null",
561             ],
562         )
563         .unwrap()
564         .try_into()
565         .unwrap();
566 
567         let gpu_params = config.gpu_parameters.unwrap();
568 
569         assert_eq!(gpu_params.display_params.len(), 1);
570         assert_eq!(
571             gpu_params.display_params[0].horizontal_dpi(),
572             HORIZONTAL_DPI
573         );
574         assert_eq!(gpu_params.display_params[0].vertical_dpi(), VERTICAL_DPI);
575     }
576 
577     #[test]
parse_gpu_display_options_default_dpi()578     fn parse_gpu_display_options_default_dpi() {
579         {
580             let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
581                 &[],
582                 &["--gpu-display", "mode=windowed[800,600]", "/dev/null"],
583             )
584             .unwrap()
585             .try_into()
586             .unwrap();
587 
588             let gpu_params = config.gpu_parameters.unwrap();
589 
590             assert_eq!(gpu_params.display_params.len(), 1);
591             assert_eq!(gpu_params.display_params[0].horizontal_dpi(), DEFAULT_DPI);
592             assert_eq!(gpu_params.display_params[0].vertical_dpi(), DEFAULT_DPI);
593         }
594 
595         {
596             let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
597                 &[],
598                 &["--gpu", "displays=[[mode=windowed[800,600]]]", "/dev/null"],
599             )
600             .unwrap()
601             .try_into()
602             .unwrap();
603 
604             let gpu_params = config.gpu_parameters.unwrap();
605 
606             assert_eq!(gpu_params.display_params.len(), 1);
607             assert_eq!(gpu_params.display_params[0].horizontal_dpi(), DEFAULT_DPI);
608             assert_eq!(gpu_params.display_params[0].vertical_dpi(), DEFAULT_DPI);
609         }
610     }
611 
612     #[test]
parse_gpu_display_options_dpi_compat()613     fn parse_gpu_display_options_dpi_compat() {
614         const HORIZONTAL_DPI: u32 = 160;
615         const VERTICAL_DPI: u32 = 25;
616 
617         let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
618             &[],
619             &[
620                 "--gpu-display",
621                 format!(
622                     "horizontal-dpi={},vertical-dpi={}",
623                     HORIZONTAL_DPI, VERTICAL_DPI
624                 )
625                 .as_str(),
626                 "/dev/null",
627             ],
628         )
629         .unwrap()
630         .try_into()
631         .unwrap();
632 
633         let gpu_params = config.gpu_parameters.unwrap();
634 
635         assert_eq!(gpu_params.display_params.len(), 1);
636         assert_eq!(
637             gpu_params.display_params[0].horizontal_dpi(),
638             HORIZONTAL_DPI
639         );
640         assert_eq!(gpu_params.display_params[0].vertical_dpi(), VERTICAL_DPI);
641     }
642 
643     #[test]
parse_gpu_display_options_dpi_duplicated()644     fn parse_gpu_display_options_dpi_duplicated() {
645         assert!(crate::crosvm::cmdline::RunCommand::from_args(
646             &[],
647             &[
648                 "--gpu-display",
649                 "horizontal-dpi=160,horizontal-dpi=320",
650                 "/dev/null",
651             ],
652         )
653         .is_err());
654 
655         assert!(crate::crosvm::cmdline::RunCommand::from_args(
656             &[],
657             &[
658                 "--gpu-display",
659                 "vertical-dpi=25,vertical-dpi=50",
660                 "/dev/null",
661             ],
662         )
663         .is_err());
664 
665         assert!(crate::crosvm::cmdline::RunCommand::from_args(
666             &[],
667             &[
668                 "--gpu-display",
669                 "dpi=[160,320],horizontal-dpi=160,vertical-dpi=25",
670                 "/dev/null",
671             ],
672         )
673         .is_err());
674     }
675 
676     #[test]
parse_gpu_options_single_display()677     fn parse_gpu_options_single_display() {
678         {
679             let gpu_params = parse_gpu_options("displays=[[mode=windowed[800,600]]]").unwrap();
680             assert_eq!(gpu_params.display_params.len(), 1);
681             assert_eq!(
682                 gpu_params.display_params[0].mode,
683                 GpuDisplayMode::Windowed(800, 600)
684             );
685         }
686 
687         #[cfg(windows)]
688         {
689             let gpu_params = parse_gpu_options("displays=[[mode=borderless_full_screen]]").unwrap();
690             assert_eq!(gpu_params.display_params.len(), 1);
691             assert!(matches!(
692                 gpu_params.display_params[0].mode,
693                 GpuDisplayMode::BorderlessFullScreen(_)
694             ));
695         }
696     }
697 
698     #[test]
parse_gpu_options_multi_display()699     fn parse_gpu_options_multi_display() {
700         {
701             let gpu_params =
702                 parse_gpu_options("displays=[[mode=windowed[500,600]],[mode=windowed[700,800]]]")
703                     .unwrap();
704             assert_eq!(gpu_params.display_params.len(), 2);
705             assert_eq!(
706                 gpu_params.display_params[0].mode,
707                 GpuDisplayMode::Windowed(500, 600)
708             );
709             assert_eq!(
710                 gpu_params.display_params[1].mode,
711                 GpuDisplayMode::Windowed(700, 800)
712             );
713         }
714 
715         #[cfg(windows)]
716         {
717             let gpu_params = parse_gpu_options(
718                 "displays=[[mode=windowed[800,600]],[mode=borderless_full_screen]]",
719             )
720             .unwrap();
721             assert_eq!(gpu_params.display_params.len(), 2);
722             assert_eq!(
723                 gpu_params.display_params[0].mode,
724                 GpuDisplayMode::Windowed(800, 600)
725             );
726             assert!(matches!(
727                 gpu_params.display_params[1].mode,
728                 GpuDisplayMode::BorderlessFullScreen(_)
729             ));
730         }
731     }
732 
733     #[test]
parse_gpu_options_single_display_compat()734     fn parse_gpu_options_single_display_compat() {
735         const BACKEND: &str = get_backend_name();
736 
737         let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
738             &[],
739             &[
740                 "--gpu",
741                 format!("backend={},width=500,height=600", BACKEND,).as_str(),
742                 "/dev/null",
743             ],
744         )
745         .unwrap()
746         .try_into()
747         .unwrap();
748 
749         let gpu_params = config.gpu_parameters.unwrap();
750 
751         assert_eq!(gpu_params.display_params.len(), 1);
752         assert_eq!(
753             gpu_params.display_params[0].mode,
754             GpuDisplayMode::Windowed(500, 600)
755         );
756 
757         let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
758             &[],
759             &[
760                 "--gpu",
761                 format!("backend={}", BACKEND,).as_str(),
762                 "--gpu-display",
763                 "mode=windowed[700,800]",
764                 "/dev/null",
765             ],
766         )
767         .unwrap()
768         .try_into()
769         .unwrap();
770 
771         let gpu_params = config.gpu_parameters.unwrap();
772 
773         assert_eq!(gpu_params.display_params.len(), 1);
774         assert_eq!(
775             gpu_params.display_params[0].mode,
776             GpuDisplayMode::Windowed(700, 800)
777         );
778     }
779 
780     #[cfg(any(target_os = "android", target_os = "linux"))]
781     #[test]
parse_gpu_options_and_gpu_display_options_multi_display_supported_on_unix()782     fn parse_gpu_options_and_gpu_display_options_multi_display_supported_on_unix() {
783         {
784             let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
785                 &[],
786                 &[
787                     "--gpu",
788                     format!(
789                         "backend={},width=500,height=600,displays=[[mode=windowed[700,800]]]",
790                         get_backend_name()
791                     )
792                     .as_str(),
793                     "--gpu-display",
794                     "mode=windowed[900,1000]",
795                     "/dev/null",
796                 ],
797             )
798             .unwrap()
799             .try_into()
800             .unwrap();
801 
802             let gpu_params = config.gpu_parameters.unwrap();
803 
804             assert_eq!(gpu_params.display_params.len(), 3);
805             assert_eq!(
806                 gpu_params.display_params[0].mode,
807                 GpuDisplayMode::Windowed(700, 800)
808             );
809             assert_eq!(
810                 gpu_params.display_params[1].mode,
811                 GpuDisplayMode::Windowed(500, 600)
812             );
813             assert_eq!(
814                 gpu_params.display_params[2].mode,
815                 GpuDisplayMode::Windowed(900, 1000)
816             );
817         }
818     }
819 
820     #[cfg(windows)]
821     #[test]
parse_gpu_options_and_gpu_display_options_multi_display_unsupported_on_windows()822     fn parse_gpu_options_and_gpu_display_options_multi_display_unsupported_on_windows() {
823         let command = crate::crosvm::cmdline::RunCommand::from_args(
824             &[],
825             &[
826                 "--gpu-display",
827                 "mode=borderless_full_screen",
828                 "--gpu-display",
829                 "mode=borderless_full_screen",
830                 "/dev/null",
831             ],
832         )
833         .unwrap();
834         assert!(Config::try_from(command).is_err());
835 
836         let command = crate::crosvm::cmdline::RunCommand::from_args(
837             &[],
838             &[
839                 "--gpu",
840                 "width=1280,height=720",
841                 "--gpu-display",
842                 "mode=borderless_full_screen",
843                 "/dev/null",
844             ],
845         )
846         .unwrap();
847         assert!(Config::try_from(command).is_err());
848 
849         let command = crate::crosvm::cmdline::RunCommand::from_args(
850             &[],
851             &[
852                 "--gpu",
853                 "displays=[[mode=windowed[1280,720]]]",
854                 "--gpu-display",
855                 "mode=borderless_full_screen",
856                 "/dev/null",
857             ],
858         )
859         .unwrap();
860         assert!(Config::try_from(command).is_err());
861 
862         let command = crate::crosvm::cmdline::RunCommand::from_args(
863             &[],
864             &[
865                 "--gpu",
866                 "displays=[[mode=windowed[500,600]],[mode=windowed[700,800]]]",
867                 "/dev/null",
868             ],
869         )
870         .unwrap();
871         assert!(Config::try_from(command).is_err());
872     }
873 
874     #[test]
parse_gpu_options_cache_size()875     fn parse_gpu_options_cache_size() {
876         {
877             let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
878                 &[],
879                 &[
880                     "--gpu",
881                     "vulkan=false,cache-path=/some/path,cache-size=50M",
882                     "/dev/null",
883                 ],
884             )
885             .unwrap()
886             .try_into()
887             .unwrap();
888 
889             let gpu_params = config.gpu_parameters.unwrap();
890             assert_eq!(gpu_params.cache_path, Some("/some/path".into()));
891             assert_eq!(gpu_params.cache_size, Some("50M".into()));
892         }
893     }
894 }
895