The open source OpenXR runtime
1// Copyright 2019-2022, Collabora, Ltd.
2// SPDX-License-Identifier: BSL-1.0
3/*!
4 * @file
5 * @brief Microsoft Windows window code.
6 * @author Rylie Pavlik <rylie.pavlik@collabora.com>
7 * @author Lubosz Sarnecki <lubosz.sarnecki@collabora.com>
8 * @author Jakob Bornecrantz <jakob@collabora.com>
9 * @ingroup comp_main
10 */
11
12#include <stdlib.h>
13#include <string.h>
14#include "xrt/xrt_compiler.h"
15#include "main/comp_window.h"
16#include "util/u_misc.h"
17#include "os/os_threading.h"
18
19
20#undef ALLOW_CLOSING_WINDOW
21
22/*
23 *
24 * Private structs.
25 *
26 */
27
28/*!
29 * A Microsoft Windows window.
30 *
31 * @implements comp_target_swapchain
32 */
33struct comp_window_mswin
34{
35 struct comp_target_swapchain base;
36 struct os_thread_helper oth;
37
38 ATOM window_class;
39 HINSTANCE instance;
40 HWND window;
41
42
43 bool fullscreen_requested;
44 bool should_exit;
45 bool thread_started;
46 bool thread_exited;
47};
48
49static WCHAR szWindowClass[] = L"Monado";
50static WCHAR szWindowData[] = L"MonadoWindow";
51
52#define COMP_ERROR_GETLASTERROR(C, MSG_WITH_PLACEHOLDER, MSG_WITHOUT_PLACEHOLDER) \
53 do { \
54 DWORD err = GetLastError(); \
55 char *buf = NULL; \
56 if (0 != FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, \
57 LANG_SYSTEM_DEFAULT, (LPSTR)&buf, 256, NULL)) { \
58 COMP_ERROR(C, MSG_WITH_PLACEHOLDER, buf); \
59 LocalFree(buf); \
60 } else { \
61 COMP_ERROR(C, MSG_WITHOUT_PLACEHOLDER); \
62 } \
63 } while (0)
64/*
65 *
66 * Functions.
67 *
68 */
69
70static void
71draw_window(HWND hWnd, struct comp_window_mswin *cwm)
72{
73 ValidateRect(hWnd, NULL);
74}
75
76static LRESULT CALLBACK
77WndProc(HWND hWnd, unsigned int message, WPARAM wParam, LPARAM lParam)
78{
79 struct comp_window_mswin *cwm = GetPropW(hWnd, szWindowData);
80
81 if (!cwm) {
82 // This is before we've set up our window, or for some other helper window...
83 // We might want to handle messages differently in here.
84 return DefWindowProcW(hWnd, message, wParam, lParam);
85 }
86 struct comp_compositor *c = cwm->base.base.c;
87 switch (message) {
88 case WM_PAINT:
89 // COMP_INFO(c, "WM_PAINT");
90 draw_window(hWnd, cwm);
91 break;
92 case WM_QUIT:
93 // COMP_INFO(c, "WM_QUIT");
94 PostQuitMessage(0);
95 break;
96 case WM_CLOSE:
97 // COMP_INFO(c, "WM_CLOSE");
98 cwm->should_exit = true;
99 DestroyWindow(hWnd);
100 cwm->window = NULL;
101 break;
102 case WM_DESTROY:
103 // COMP_INFO(c, "WM_DESTROY");
104 // Post a quit message and return.
105 PostQuitMessage(0);
106 break;
107 default: return DefWindowProcW(hWnd, message, wParam, lParam);
108 }
109 return 0;
110}
111
112
113static inline struct vk_bundle *
114get_vk(struct comp_window_mswin *cwm)
115{
116 return &cwm->base.base.c->base.vk;
117}
118
119static void
120comp_window_mswin_destroy(struct comp_target *ct)
121{
122 struct comp_window_mswin *cwm = (struct comp_window_mswin *)ct;
123
124 // Stop the Windows thread first, destroy also stops the thread.
125 os_thread_helper_destroy(&cwm->oth);
126
127 comp_target_swapchain_cleanup(&cwm->base);
128
129 //! @todo
130
131 free(ct);
132}
133
134static void
135comp_window_mswin_update_window_title(struct comp_target *ct, const char *title)
136{
137 struct comp_window_mswin *cwm = (struct comp_window_mswin *)ct;
138 //! @todo
139}
140
141static void
142comp_window_mswin_fullscreen(struct comp_window_mswin *w)
143{
144 //! @todo
145}
146
147static VkResult
148comp_window_mswin_create_surface(struct comp_window_mswin *w, VkSurfaceKHR *out_surface)
149{
150 struct vk_bundle *vk = get_vk(w);
151 VkResult ret;
152
153 VkWin32SurfaceCreateInfoKHR surface_info = {
154 .sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR,
155 .hinstance = w->instance,
156 .hwnd = w->window,
157 };
158
159 VkSurfaceKHR surface = VK_NULL_HANDLE;
160 ret = vk->vkCreateWin32SurfaceKHR( //
161 vk->instance, //
162 &surface_info, //
163 NULL, //
164 &surface); //
165 if (ret != VK_SUCCESS) {
166 COMP_ERROR(w->base.base.c, "vkCreateWin32SurfaceKHR: %s", vk_result_string(ret));
167 return ret;
168 }
169
170 VK_NAME_SURFACE(vk, surface, "comp_window_mswin surface");
171 *out_surface = surface;
172
173 return VK_SUCCESS;
174}
175
176static bool
177comp_window_mswin_init_swapchain(struct comp_target *ct, uint32_t width, uint32_t height)
178{
179 struct comp_window_mswin *cwm = (struct comp_window_mswin *)ct;
180 VkResult ret;
181
182 ret = comp_window_mswin_create_surface(cwm, &cwm->base.surface.handle);
183 if (ret != VK_SUCCESS) {
184 COMP_ERROR(ct->c, "Failed to create surface '%s'!", vk_result_string(ret));
185 return false;
186 }
187
188 //! @todo
189
190 return true;
191}
192
193
194static void
195comp_window_mswin_flush(struct comp_target *ct)
196{
197 struct comp_window_mswin *cwm = (struct comp_window_mswin *)ct;
198}
199
200static void
201comp_window_mswin_window_loop(struct comp_window_mswin *cwm)
202{
203 struct comp_target *ct = &cwm->base.base;
204 RECT rc = {0, 0, (LONG)(ct->width), (LONG)ct->height};
205
206 COMP_INFO(ct->c, "Creating window");
207 cwm->window =
208 CreateWindowExW(0, szWindowClass, L"Monado (Windowed)", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
209 rc.right - rc.left, rc.bottom - rc.top, NULL, NULL, cwm->instance, NULL);
210 if (cwm->window == NULL) {
211 COMP_ERROR_GETLASTERROR(ct->c, "Failed to create window: %s", "Failed to create window");
212 // parent thread will be notified (by caller) that we have exited.
213 return;
214 }
215
216 COMP_INFO(ct->c, "Setting window properties and showing window");
217 SetPropW(cwm->window, szWindowData, cwm);
218 SetWindowLongPtr(cwm->window, GWLP_USERDATA, (LONG_PTR)(cwm));
219 ShowWindow(cwm->window, SW_SHOWDEFAULT);
220 UpdateWindow(cwm->window);
221
222 COMP_INFO(ct->c, "Unblocking parent thread");
223 // Unblock the parent thread now that we're successfully running.
224 {
225 os_thread_helper_lock(&cwm->oth);
226 cwm->thread_started = true;
227 os_thread_helper_signal_locked(&cwm->oth);
228 os_thread_helper_unlock(&cwm->oth);
229 }
230 COMP_INFO(ct->c, "Starting the Windows window message loop");
231
232 bool done = false;
233 while (os_thread_helper_is_running(&cwm->oth)) {
234 // force handling messages.
235 MSG msg;
236 while (PeekMessageW(&msg, cwm->window, 0, 0, PM_REMOVE)) {
237 TranslateMessage(&msg);
238 DispatchMessageW(&msg);
239#ifdef ALLOW_CLOSING_WINDOW
240 /// @todo We need to bubble this up to multi-compositor
241 /// and the state tracker (as "instance lost")
242 if (msg.message == WM_QUIT) {
243 COMP_INFO(cwm->base.base.c, "Got WM_QUIT message");
244 return;
245 }
246 if (msg.message == WM_DESTROY) {
247 COMP_INFO(cwm->base.base.c, "Got WM_DESTROY message");
248 return;
249 }
250 if (cwm->should_exit) {
251 COMP_INFO(cwm->base.base.c, "Got 'should_exit' flag.");
252 return;
253 }
254#endif
255 }
256 }
257 if (cwm->window != NULL) {
258 // Got shut down by app code, not by a window message, so we still need to clean up our window.
259 if (0 == DestroyWindow(cwm->window)) {
260 COMP_ERROR_GETLASTERROR(ct->c, "DestroyWindow failed: %s", "DestroyWindow failed");
261 }
262 cwm->window = NULL;
263 }
264}
265
266static void
267comp_window_mswin_mark_exited(struct comp_window_mswin *cwm)
268{
269 // Unblock the parent thread to advise of our exit
270 os_thread_helper_lock(&cwm->oth);
271 cwm->thread_exited = true;
272 os_thread_helper_signal_locked(&cwm->oth);
273 os_thread_helper_unlock(&cwm->oth);
274}
275
276static void
277comp_window_mswin_thread(struct comp_window_mswin *cwm)
278{
279 struct comp_target *ct = &cwm->base.base;
280
281 RECT rc = {0, 0, (LONG)(ct->width), (LONG)ct->height};
282
283 WNDCLASSEXW wcex;
284 U_ZERO(&wcex);
285 wcex.cbSize = sizeof(WNDCLASSEXW);
286 wcex.style = CS_HREDRAW | CS_VREDRAW;
287
288 wcex.lpfnWndProc = WndProc;
289 wcex.cbClsExtra = 0;
290 wcex.cbWndExtra = 0;
291 wcex.hInstance = cwm->instance;
292 wcex.lpszClassName = szWindowClass;
293 wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
294//! @todo icon
295#if 0
296 wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_SAMPLEGUI));
297 wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_SAMPLEGUI);
298 wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
299#endif
300 COMP_INFO(ct->c, "Registering window class");
301 ATOM window_class = RegisterClassExW(&wcex);
302 if (!window_class) {
303 COMP_ERROR_GETLASTERROR(ct->c, "Failed to register window class: %s",
304 "Failed to register window class");
305 comp_window_mswin_mark_exited(cwm);
306 return;
307 }
308
309 comp_window_mswin_window_loop(cwm);
310
311 COMP_INFO(ct->c, "Unregistering window class");
312 if (0 == UnregisterClassW((LPCWSTR)window_class, NULL)) {
313 COMP_ERROR_GETLASTERROR(ct->c, "Failed to unregister window class: %s",
314 "Failed to unregister window class");
315 }
316
317 comp_window_mswin_mark_exited(cwm);
318}
319
320static void *
321comp_window_mswin_thread_func(void *ptr)
322{
323
324 struct comp_window_mswin *cwm = (struct comp_window_mswin *)ptr;
325 os_thread_helper_name(&(cwm->oth), "Compositor Window Message Thread");
326
327 comp_window_mswin_thread(cwm);
328 os_thread_helper_signal_stop(&cwm->oth);
329 COMP_INFO(cwm->base.base.c, "Windows window message thread now exiting.");
330 return NULL;
331}
332
333static bool
334comp_window_mswin_init(struct comp_target *ct)
335{
336 struct comp_window_mswin *cwm = (struct comp_window_mswin *)ct;
337 cwm->instance = GetModuleHandle(NULL);
338
339 ct->width = 1280;
340 ct->height = 720;
341
342 if (os_thread_helper_start(&cwm->oth, comp_window_mswin_thread_func, cwm) != 0) {
343 COMP_ERROR(ct->c, "Failed to start Windows window message thread");
344 return false;
345 }
346
347 // Wait for thread to start, create window, etc.
348 os_thread_helper_lock(&cwm->oth);
349 while (!cwm->thread_started && !cwm->thread_exited) {
350 os_thread_helper_wait_locked(&cwm->oth);
351 }
352 bool ret = cwm->thread_started && !cwm->thread_exited;
353 os_thread_helper_unlock(&cwm->oth);
354 return ret;
355}
356
357static void
358comp_window_mswin_configure(struct comp_window_mswin *w, int32_t width, int32_t height)
359{
360 if (w->base.base.c->settings.fullscreen && !w->fullscreen_requested) {
361 COMP_DEBUG(w->base.base.c, "Setting full screen");
362 comp_window_mswin_fullscreen(w);
363 w->fullscreen_requested = true;
364 }
365}
366
367#ifdef ALLOW_CLOSING_WINDOW
368/// @todo This is somehow triggering crashes in the multi-compositor, which is trying to run without things it needs,
369/// even though it didn't do this when we called the parent impl instead of inlining it.
370static bool
371comp_window_mswin_configure_check_ready(struct comp_target *ct)
372{
373 struct comp_window_mswin *cwm = (struct comp_window_mswin *)ct;
374 return os_thread_helper_is_running(&cwm->oth) && cwm->base.swapchain.handle != VK_NULL_HANDLE;
375}
376#endif
377
378struct comp_target *
379comp_window_mswin_create(struct comp_compositor *c)
380{
381 struct comp_window_mswin *w = U_TYPED_CALLOC(struct comp_window_mswin);
382 if (os_thread_helper_init(&w->oth) != 0) {
383 COMP_ERROR(c, "Failed to init Windows window message thread");
384 free(w);
385 return NULL;
386 }
387
388 // The display timing code hasn't been tested on Windows and may be broken.
389 comp_target_swapchain_init_and_set_fnptrs(&w->base, COMP_TARGET_FORCE_FAKE_DISPLAY_TIMING);
390
391 w->base.base.name = "MS Windows";
392 w->base.display = VK_NULL_HANDLE;
393 w->base.base.destroy = comp_window_mswin_destroy;
394 w->base.base.flush = comp_window_mswin_flush;
395 w->base.base.init_pre_vulkan = comp_window_mswin_init;
396 w->base.base.init_post_vulkan = comp_window_mswin_init_swapchain;
397 w->base.base.set_title = comp_window_mswin_update_window_title;
398#ifdef ALLOW_CLOSING_WINDOW
399 w->base.base.check_ready = comp_window_mswin_configure_check_ready;
400#endif
401 w->base.base.c = c;
402
403 return &w->base.base;
404}
405
406
407/*
408 *
409 * Factory
410 *
411 */
412
413static const char *instance_extensions[] = {
414 VK_KHR_WIN32_SURFACE_EXTENSION_NAME,
415};
416
417static bool
418detect(const struct comp_target_factory *ctf, struct comp_compositor *c)
419{
420 return false;
421}
422
423static bool
424create_target(const struct comp_target_factory *ctf, struct comp_compositor *c, struct comp_target **out_ct)
425{
426 struct comp_target *ct = comp_window_mswin_create(c);
427 if (ct == NULL) {
428 return false;
429 }
430
431 *out_ct = ct;
432
433 return true;
434}
435
436const struct comp_target_factory comp_target_factory_mswin = {
437 .name = "Microsoft Windows(TM)",
438 .identifier = "mswin",
439 .requires_vulkan_for_create = false,
440 .is_deferred = true,
441 .required_instance_version = 0,
442 .required_instance_extensions = instance_extensions,
443 .required_instance_extension_count = ARRAY_SIZE(instance_extensions),
444 .optional_device_extensions = NULL,
445 .optional_device_extension_count = 0,
446 .detect = detect,
447 .create_target = create_target,
448};