xref: /aosp_15_r20/external/crosvm/gpu_display/src/gpu_display_win/surface.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 std::ops::ControlFlow;
6 use std::ops::Deref;
7 #[cfg(feature = "gfxstream")]
8 use std::os::raw::c_int;
9 #[cfg(feature = "gfxstream")]
10 use std::os::raw::c_void;
11 use std::rc::Rc;
12 use std::sync::Arc;
13 use std::sync::Weak;
14 use std::time::Instant;
15 
16 use anyhow::Context;
17 use anyhow::Result;
18 use base::error;
19 use base::info;
20 use base::warn;
21 use base::Tube;
22 use euclid::size2;
23 use euclid::Box2D;
24 use euclid::Size2D;
25 use metrics::sys::windows::Metrics;
26 use sync::Mutex;
27 use vm_control::gpu::DisplayMode;
28 use vm_control::gpu::DisplayParameters;
29 use win_util::keys_down;
30 use winapi::shared::minwindef::HIWORD;
31 use winapi::shared::minwindef::LOWORD;
32 use winapi::shared::minwindef::LPARAM;
33 use winapi::shared::minwindef::LRESULT;
34 use winapi::shared::minwindef::TRUE;
35 use winapi::shared::minwindef::WPARAM;
36 use winapi::um::winuser::VK_F4;
37 use winapi::um::winuser::VK_MENU;
38 use winapi::um::winuser::WM_CLOSE;
39 
40 use super::keyboard_input_manager::KeyboardInputManager;
41 use super::math_util::Size2DCheckedCast;
42 use super::mouse_input_manager::MouseInputManager;
43 use super::virtual_display_manager::NoopVirtualDisplayManager as VirtualDisplayManager;
44 #[cfg(feature = "gfxstream")]
45 use super::window::BasicWindow;
46 use super::window::GuiWindow;
47 use super::window_manager::NoopWindowManager as WindowManager;
48 use super::window_message_processor::GeneralMessage;
49 use super::window_message_processor::SurfaceResources;
50 use super::window_message_processor::WindowMessage;
51 use super::window_message_processor::WindowPosMessage;
52 use super::window_message_processor::HANDLE_WINDOW_MESSAGE_TIMEOUT;
53 use super::HostWindowSpace;
54 use super::MouseMode;
55 use super::VulkanDisplayWrapper;
56 use crate::EventDeviceKind;
57 
58 #[cfg(feature = "gfxstream")]
59 #[link(name = "gfxstream_backend")]
60 extern "C" {
gfxstream_backend_setup_window( hwnd: *const c_void, window_x: c_int, window_y: c_int, window_width: c_int, window_height: c_int, fb_width: c_int, fb_height: c_int, )61     fn gfxstream_backend_setup_window(
62         hwnd: *const c_void,
63         window_x: c_int,
64         window_y: c_int,
65         window_width: c_int,
66         window_height: c_int,
67         fb_width: c_int,
68         fb_height: c_int,
69     );
70 }
71 
72 // Updates the rectangle in the window's client area to which gfxstream renders.
update_virtual_display_projection( #[allow(unused)] vulkan_display: impl Deref<Target = VulkanDisplayWrapper>, #[allow(unused)] window: &GuiWindow, #[allow(unused)] projection_box: &Box2D<i32, HostWindowSpace>, )73 fn update_virtual_display_projection(
74     #[allow(unused)] vulkan_display: impl Deref<Target = VulkanDisplayWrapper>,
75     #[allow(unused)] window: &GuiWindow,
76     #[allow(unused)] projection_box: &Box2D<i32, HostWindowSpace>,
77 ) {
78     #[cfg(feature = "vulkan_display")]
79     if let VulkanDisplayWrapper::Initialized(ref vulkan_display) = *vulkan_display {
80         if let Err(err) = vulkan_display
81             .move_window(&projection_box.cast_unit())
82             .with_context(|| "move the subwindow")
83         {
84             error!("{:?}", err);
85         }
86         #[cfg(feature = "gfxstream")]
87         return;
88     }
89 
90     // Safe because `Window` object won't outlive the HWND.
91     #[cfg(feature = "gfxstream")]
92     unsafe {
93         gfxstream_backend_setup_window(
94             window.handle() as *const c_void,
95             projection_box.min.x,
96             projection_box.min.y,
97             projection_box.width(),
98             projection_box.height(),
99             projection_box.width(),
100             projection_box.height(),
101         );
102     }
103 }
104 
105 #[allow(dead_code)]
106 #[derive(Clone)]
107 pub(crate) struct DisplayProperties {
108     pub start_hidden: bool,
109     pub is_fullscreen: bool,
110     pub window_width: u32,
111     pub window_height: u32,
112 }
113 
114 impl From<&DisplayParameters> for DisplayProperties {
from(params: &DisplayParameters) -> Self115     fn from(params: &DisplayParameters) -> Self {
116         let is_fullscreen = matches!(params.mode, DisplayMode::BorderlessFullScreen(_));
117         let (window_width, window_height) = params.get_window_size();
118 
119         Self {
120             start_hidden: params.hidden,
121             is_fullscreen,
122             window_width,
123             window_height,
124         }
125     }
126 }
127 
128 pub struct Surface {
129     surface_id: u32,
130     mouse_input: MouseInputManager,
131     window_manager: WindowManager,
132     virtual_display_manager: VirtualDisplayManager,
133     #[allow(dead_code)]
134     gpu_main_display_tube: Option<Rc<Tube>>,
135     vulkan_display: Arc<Mutex<VulkanDisplayWrapper>>,
136 }
137 
138 impl Surface {
new( surface_id: u32, window: &GuiWindow, _metrics: Option<Weak<Metrics>>, display_params: &DisplayParameters, resources: SurfaceResources, vulkan_display: Arc<Mutex<VulkanDisplayWrapper>>, ) -> Result<Self>139     pub fn new(
140         surface_id: u32,
141         window: &GuiWindow,
142         _metrics: Option<Weak<Metrics>>,
143         display_params: &DisplayParameters,
144         resources: SurfaceResources,
145         vulkan_display: Arc<Mutex<VulkanDisplayWrapper>>,
146     ) -> Result<Self> {
147         static CONTEXT_MESSAGE: &str = "When creating Surface";
148         info!(
149             "Creating surface {} to associate with scanout {}",
150             surface_id,
151             window.scanout_id()
152         );
153 
154         let initial_host_viewport_size = window.get_client_rect().context(CONTEXT_MESSAGE)?.size;
155         let virtual_display_size = {
156             let (width, height) = display_params.get_virtual_display_size();
157             size2(width, height).checked_cast()
158         };
159         let virtual_display_manager =
160             VirtualDisplayManager::new(&initial_host_viewport_size, &virtual_display_size);
161         // This will make gfxstream initialize the child window to which it will render.
162         update_virtual_display_projection(
163             vulkan_display.lock(),
164             window,
165             &virtual_display_manager.get_virtual_display_projection_box(),
166         );
167 
168         let SurfaceResources {
169             display_event_dispatcher,
170             gpu_main_display_tube,
171         } = resources;
172 
173         let mouse_input = MouseInputManager::new(
174             window,
175             *virtual_display_manager.get_host_to_guest_transform(),
176             virtual_display_size.checked_cast(),
177             display_event_dispatcher,
178         );
179 
180         Ok(Surface {
181             surface_id,
182             mouse_input,
183             window_manager: WindowManager::new(
184                 window,
185                 &display_params.into(),
186                 initial_host_viewport_size,
187                 gpu_main_display_tube.clone(),
188             )
189             .context(CONTEXT_MESSAGE)?,
190             virtual_display_manager,
191             gpu_main_display_tube,
192             vulkan_display,
193         })
194     }
195 
surface_id(&self) -> u32196     pub fn surface_id(&self) -> u32 {
197         self.surface_id
198     }
199 
handle_key_event( &mut self, window: &GuiWindow, _key_down: bool, w_param: WPARAM, _l_param: LPARAM, )200     fn handle_key_event(
201         &mut self,
202         window: &GuiWindow,
203         _key_down: bool,
204         w_param: WPARAM,
205         _l_param: LPARAM,
206     ) {
207         // Since we handle WM_SYSKEYDOWN we have to handle Alt-F4 ourselves.
208         if (w_param == VK_MENU as usize || w_param == VK_F4 as usize)
209             && keys_down(&[VK_MENU, VK_F4])
210         {
211             info!("Got alt-F4 w_param={}, posting WM_CLOSE", w_param);
212             if let Err(e) =
213                 window.post_message(WM_CLOSE, /* w_param */ 0, /* l_param */ 0)
214             {
215                 error!("Failed to post WM_CLOSE: {:?}", e);
216             }
217         }
218     }
219 
set_mouse_mode(&mut self, window: &GuiWindow, mouse_mode: MouseMode)220     fn set_mouse_mode(&mut self, window: &GuiWindow, mouse_mode: MouseMode) {
221         self.mouse_input
222             .handle_change_mouse_mode_request(window, mouse_mode);
223     }
224 
update_host_viewport_size( &mut self, window: &GuiWindow, host_viewport_size: &Size2D<i32, HostWindowSpace>, )225     fn update_host_viewport_size(
226         &mut self,
227         window: &GuiWindow,
228         host_viewport_size: &Size2D<i32, HostWindowSpace>,
229     ) {
230         info!("Updating host viewport size to {:?}", host_viewport_size);
231         let start = Instant::now();
232 
233         self.virtual_display_manager
234             .update_host_guest_transforms(host_viewport_size);
235         let virtual_display_projection_box = self
236             .virtual_display_manager
237             .get_virtual_display_projection_box();
238         update_virtual_display_projection(
239             self.vulkan_display.lock(),
240             window,
241             &virtual_display_projection_box,
242         );
243         self.mouse_input.update_host_to_guest_transform(
244             *self.virtual_display_manager.get_host_to_guest_transform(),
245         );
246 
247         let elapsed = start.elapsed();
248         let elapsed_millis = elapsed.as_millis();
249         if elapsed < HANDLE_WINDOW_MESSAGE_TIMEOUT {
250             info!(
251                 "Finished updating host viewport size in {}ms",
252                 elapsed_millis
253             );
254         } else {
255             warn!(
256                 "Window might have been hung since updating host viewport size took \
257                         too long ({}ms)!",
258                 elapsed_millis
259             );
260         }
261     }
262 
263     /// Called once when it is safe to assume all future messages targeting `window` will be
264     /// dispatched to this `Surface`.
on_message_dispatcher_attached(&mut self, window: &GuiWindow)265     fn on_message_dispatcher_attached(&mut self, window: &GuiWindow) {
266         // `WindowManager` relies on window messages to properly set initial window pos.
267         // We might see a suboptimal UI if any error occurs here, such as having black bars. Instead
268         // of crashing the emulator, we would just log the error and still allow the user to
269         // experience the app.
270         if let Err(e) = self.window_manager.set_initial_window_pos(window) {
271             error!("Failed to set initial window pos: {:#?}", e);
272         }
273     }
274 
275     /// Called whenever any window message is retrieved. Returns None if `DefWindowProcW()` should
276     /// be called after our processing.
277     #[inline]
handle_window_message( &mut self, window: &GuiWindow, message: WindowMessage, ) -> Option<LRESULT>278     pub fn handle_window_message(
279         &mut self,
280         window: &GuiWindow,
281         message: WindowMessage,
282     ) -> Option<LRESULT> {
283         if let ControlFlow::Break(ret) = self.mouse_input.handle_window_message(window, &message) {
284             return ret;
285         }
286 
287         // Just return 0 for most of the messages we processed.
288         let mut ret: Option<LRESULT> = Some(0);
289         match message {
290             WindowMessage::Key {
291                 is_sys_key: _,
292                 is_down,
293                 w_param,
294                 l_param,
295             } => self.handle_key_event(window, is_down, w_param, l_param),
296             WindowMessage::WindowPos(window_pos_msg) => {
297                 ret = self.handle_window_pos_message(window, window_pos_msg)
298             }
299             WindowMessage::DisplayChange => self.window_manager.handle_display_change(window),
300             WindowMessage::HostViewportChange { l_param } => {
301                 self.on_host_viewport_change(window, l_param)
302             }
303             // The following messages are handled by other modules.
304             WindowMessage::WindowActivate { .. }
305             | WindowMessage::Mouse(_)
306             | WindowMessage::KeyboardFocus => (),
307             WindowMessage::Other(..) => {
308                 // Request default processing for messages that we don't explicitly handle.
309                 ret = None;
310             }
311         }
312         ret
313     }
314 
315     #[inline]
handle_general_message( &mut self, window: &GuiWindow, message: &GeneralMessage, keyboard_input_manager: &KeyboardInputManager, )316     pub fn handle_general_message(
317         &mut self,
318         window: &GuiWindow,
319         message: &GeneralMessage,
320         keyboard_input_manager: &KeyboardInputManager,
321     ) {
322         match message {
323             GeneralMessage::MessageDispatcherAttached => {
324                 self.on_message_dispatcher_attached(window)
325             }
326             GeneralMessage::RawInputEvent(raw_input) => {
327                 self.mouse_input.handle_raw_input_event(window, *raw_input)
328             }
329             GeneralMessage::GuestEvent {
330                 event_device_kind,
331                 event,
332             } => {
333                 if let EventDeviceKind::Keyboard = event_device_kind {
334                     keyboard_input_manager.handle_guest_event(window, *event);
335                 }
336             }
337             GeneralMessage::SetMouseMode(mode) => self.set_mouse_mode(window, *mode),
338         }
339     }
340 
341     /// Returns None if `DefWindowProcW()` should be called after our processing.
342     #[inline]
handle_window_pos_message( &mut self, window: &GuiWindow, message: WindowPosMessage, ) -> Option<LRESULT>343     fn handle_window_pos_message(
344         &mut self,
345         window: &GuiWindow,
346         message: WindowPosMessage,
347     ) -> Option<LRESULT> {
348         self.window_manager
349             .handle_window_pos_message(window, &message);
350         match message {
351             WindowPosMessage::WindowPosChanged { .. } => {
352                 // Request default processing, otherwise `WM_SIZE` and `WM_MOVE` won't be sent.
353                 // https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-windowposchanged#remarks
354                 return None;
355             }
356             // "An application should return TRUE if it processes this message."
357             WindowPosMessage::WindowSizeChanging { .. } => return Some(TRUE as LRESULT),
358             _ => (),
359         }
360         Some(0)
361     }
362 
363     #[inline]
on_host_viewport_change(&mut self, window: &GuiWindow, l_param: LPARAM)364     fn on_host_viewport_change(&mut self, window: &GuiWindow, l_param: LPARAM) {
365         let new_size = size2(LOWORD(l_param as u32) as i32, HIWORD(l_param as u32) as i32);
366         self.update_host_viewport_size(window, &new_size);
367     }
368 }
369