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