documentation: fixed a heap o' typos
[platform/upstream/gstreamer.git] / gst-libs / gst / vulkan / win32 / gstvkwindow_win32.c
1 /*
2  * GStreamer
3  * Copyright (C) 2008 Julien Isorce <julien.isorce@gmail.com>
4  * Copyright (C) 2012 Matthew Waters <ystreet00@gmail.com>
5  * Copyright (C) 2019 Seungha Yang <seungha.yang@navercorp.com>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26
27 #include "gstvkwindow_win32.h"
28
29 LRESULT CALLBACK window_proc (HWND hWnd, UINT uMsg, WPARAM wParam,
30     LPARAM lParam);
31 LRESULT FAR PASCAL sub_class_proc (HWND hWnd, UINT uMsg, WPARAM wParam,
32     LPARAM lParam);
33
34 enum
35 {
36   PROP_0
37 };
38
39 struct _GstVulkanWindowWin32Private
40 {
41   GIOChannel *msg_io_channel;
42
43   gint preferred_width;
44   gint preferred_height;
45 };
46
47 #define GST_CAT_DEFAULT gst_vulkan_window_win32_debug
48 GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
49
50 #define DEBUG_INIT \
51   GST_DEBUG_CATEGORY_GET (GST_CAT_DEFAULT, "vulkanwindow");
52 #define gst_vulkan_window_win32_parent_class parent_class
53 G_DEFINE_TYPE_WITH_CODE (GstVulkanWindowWin32, gst_vulkan_window_win32,
54     GST_TYPE_VULKAN_WINDOW, G_ADD_PRIVATE (GstVulkanWindowWin32) DEBUG_INIT);
55
56 static void gst_vulkan_window_win32_set_window_handle (GstVulkanWindow * window,
57     guintptr handle);
58 static VkSurfaceKHR
59 gst_vulkan_window_win32_get_surface (GstVulkanWindow * window, GError ** error);
60 static gboolean
61 gst_vulkan_window_win32_get_presentation_support (GstVulkanWindow * window,
62     GstVulkanDevice * device, guint32 queue_family_idx);
63 static gboolean gst_vulkan_window_win32_open (GstVulkanWindow * window,
64     GError ** error);
65 static void gst_vulkan_window_win32_close (GstVulkanWindow * window);
66 static void release_parent_win_id (GstVulkanWindowWin32 * window_win32);
67 static void gst_vulkan_window_win32_show (GstVulkanWindowWin32 * window);
68
69 static void
70 gst_vulkan_window_win32_class_init (GstVulkanWindowWin32Class * klass)
71 {
72   GstVulkanWindowClass *window_class = (GstVulkanWindowClass *) klass;
73
74   window_class->set_window_handle =
75       GST_DEBUG_FUNCPTR (gst_vulkan_window_win32_set_window_handle);
76   window_class->get_surface =
77       GST_DEBUG_FUNCPTR (gst_vulkan_window_win32_get_surface);
78   window_class->open = GST_DEBUG_FUNCPTR (gst_vulkan_window_win32_open);
79   window_class->close = GST_DEBUG_FUNCPTR (gst_vulkan_window_win32_close);
80   window_class->get_presentation_support =
81       GST_DEBUG_FUNCPTR (gst_vulkan_window_win32_get_presentation_support);
82 }
83
84 static void
85 gst_vulkan_window_win32_init (GstVulkanWindowWin32 * window)
86 {
87   window->priv = gst_vulkan_window_win32_get_instance_private (window);
88
89   window->priv->preferred_width = 320;
90   window->priv->preferred_height = 240;
91 }
92
93 GstVulkanWindowWin32 *
94 gst_vulkan_window_win32_new (GstVulkanDisplay * display)
95 {
96   GstVulkanWindowWin32 *window;
97
98   if ((gst_vulkan_display_get_handle_type (display) &
99           GST_VULKAN_DISPLAY_TYPE_WIN32) == 0)
100     /* we require an win32 display to create win32 windows */
101     return NULL;
102
103   window = g_object_new (GST_TYPE_VULKAN_WINDOW_WIN32, NULL);
104   gst_object_ref_sink (window);
105
106   return window;
107 }
108
109 static gboolean
110 msg_cb (GIOChannel * source, GIOCondition condition, gpointer data)
111 {
112   MSG msg;
113
114   if (!PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
115     return G_SOURCE_CONTINUE;
116
117   GST_TRACE ("handle message");
118   TranslateMessage (&msg);
119   DispatchMessage (&msg);
120
121   return G_SOURCE_CONTINUE;
122 }
123
124 typedef struct
125 {
126   GstVulkanWindowWin32 *self;
127   GError **error;
128   gboolean fired;
129   gboolean ret;
130   GMutex lock;
131   GCond cond;
132 } CreateWindowData;
133
134 static gboolean
135 _create_window (CreateWindowData * data)
136 {
137   data->ret = gst_vulkan_window_win32_create_window (data->self, data->error);
138
139   g_mutex_lock (&data->lock);
140   data->fired = TRUE;
141   g_cond_signal (&data->cond);
142   g_mutex_unlock (&data->lock);
143
144   return G_SOURCE_REMOVE;
145 }
146
147 static gboolean
148 gst_vulkan_window_win32_open (GstVulkanWindow * window, GError ** error)
149 {
150   GstVulkanWindowWin32 *window_win32 = GST_VULKAN_WINDOW_WIN32 (window);
151   GstVulkanDisplay *display;
152   GMainContext *context;
153   CreateWindowData data = { 0, };
154
155   if (!GST_VULKAN_WINDOW_CLASS (parent_class)->open (window, error))
156     return FALSE;
157
158   display = gst_vulkan_window_get_display (window);
159   context = g_main_context_ref (display->main_context);
160   gst_object_unref (display);
161
162   window_win32->priv->msg_io_channel = g_io_channel_win32_new_messages (0);
163   window_win32->msg_source =
164       g_io_create_watch (window_win32->priv->msg_io_channel, G_IO_IN);
165   g_source_set_callback (window_win32->msg_source, (GSourceFunc) msg_cb, NULL,
166       NULL);
167   g_source_attach (window_win32->msg_source, context);
168
169   data.self = window_win32;
170   data.error = error;
171   g_mutex_init (&data.lock);
172   g_cond_init (&data.cond);
173
174   g_main_context_invoke (context, (GSourceFunc) _create_window, &data);
175   g_mutex_lock (&data.lock);
176   while (!data.fired)
177     g_cond_wait (&data.cond, &data.lock);
178   g_mutex_unlock (&data.lock);
179
180   g_mutex_clear (&data.lock);
181   g_cond_clear (&data.cond);
182   g_main_context_unref (context);
183
184   if (!data.ret)
185     return FALSE;
186
187   gst_vulkan_window_win32_show (window_win32);
188
189   return TRUE;
190 }
191
192 static void
193 gst_vulkan_window_win32_close (GstVulkanWindow * window)
194 {
195   GstVulkanWindowWin32 *window_win32 = GST_VULKAN_WINDOW_WIN32 (window);
196
197   release_parent_win_id (window_win32);
198
199   if (window_win32->internal_win_id) {
200     RemoveProp (window_win32->internal_win_id, "vulkan_window");
201     ShowWindow (window_win32->internal_win_id, SW_HIDE);
202     SetParent (window_win32->internal_win_id, NULL);
203     if (!DestroyWindow (window_win32->internal_win_id))
204       GST_WARNING ("failed to destroy window %" G_GUINTPTR_FORMAT
205           ", 0x%x", (guintptr) window_win32->internal_win_id,
206           (unsigned int) GetLastError ());
207   }
208
209   g_source_destroy (window_win32->msg_source);
210   g_source_unref (window_win32->msg_source);
211   window_win32->msg_source = NULL;
212   g_io_channel_unref (window_win32->priv->msg_io_channel);
213   window_win32->priv->msg_io_channel = NULL;
214
215   GST_VULKAN_WINDOW_CLASS (parent_class)->close (window);
216 }
217
218 static void
219 set_parent_win_id (GstVulkanWindowWin32 * window_win32)
220 {
221   WNDPROC window_parent_proc;
222   RECT rect;
223
224   if (!window_win32->parent_win_id) {
225     /* no parent so the internal window needs borders and system menu */
226     SetWindowLongPtr (window_win32->internal_win_id, GWL_STYLE,
227         WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW);
228     SetParent (window_win32->internal_win_id, NULL);
229     return;
230   }
231
232   window_parent_proc =
233       (WNDPROC) GetWindowLongPtr (window_win32->parent_win_id, GWLP_WNDPROC);
234
235   GST_DEBUG ("set parent %" G_GUINTPTR_FORMAT,
236       (guintptr) window_win32->parent_win_id);
237
238   SetProp (window_win32->parent_win_id, "vulkan_window_id",
239       window_win32->internal_win_id);
240   SetProp (window_win32->parent_win_id, "vulkan_window_parent_proc",
241       (WNDPROC) window_parent_proc);
242   SetWindowLongPtr (window_win32->parent_win_id, GWLP_WNDPROC,
243       (LONG_PTR) sub_class_proc);
244
245   SetWindowLongPtr (window_win32->internal_win_id, GWL_STYLE,
246       WS_CHILD | WS_MAXIMIZE);
247   SetParent (window_win32->internal_win_id, window_win32->parent_win_id);
248
249   /* take changes into account: SWP_FRAMECHANGED */
250   GetClientRect (window_win32->parent_win_id, &rect);
251   SetWindowPos (window_win32->internal_win_id, HWND_TOP, rect.left, rect.top,
252       rect.right, rect.bottom,
253       SWP_ASYNCWINDOWPOS | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
254       SWP_FRAMECHANGED | SWP_NOACTIVATE);
255   MoveWindow (window_win32->internal_win_id, rect.left, rect.top, rect.right,
256       rect.bottom, FALSE);
257 }
258
259 static void
260 release_parent_win_id (GstVulkanWindowWin32 * window_win32)
261 {
262   WNDPROC parent_proc;
263
264   if (!window_win32->parent_win_id)
265     return;
266
267   parent_proc =
268       GetProp (window_win32->parent_win_id, "vulkan_window_parent_proc");
269   if (!parent_proc)
270     return;
271
272   GST_DEBUG ("release parent %" G_GUINTPTR_FORMAT,
273       (guintptr) window_win32->parent_win_id);
274
275   SetWindowLongPtr (window_win32->parent_win_id, GWLP_WNDPROC,
276       (LONG_PTR) parent_proc);
277
278   RemoveProp (window_win32->parent_win_id, "vulkan_window_parent_proc");
279 }
280
281 static gboolean
282 gst_vulkan_window_win32_create_window (GstVulkanWindowWin32 * window_win32,
283     GError ** error)
284 {
285   WNDCLASSEX wc;
286   ATOM atom = 0;
287   HINSTANCE hinstance = GetModuleHandle (NULL);
288
289   static gint x = 0;
290   static gint y = 0;
291
292   GST_LOG ("Attempting to create a win32 window");
293
294   x += 20;
295   y += 20;
296
297   atom = GetClassInfoEx (hinstance, "GSTVULKAN", &wc);
298
299   if (atom == 0) {
300     ZeroMemory (&wc, sizeof (WNDCLASSEX));
301
302     wc.cbSize = sizeof (WNDCLASSEX);
303     wc.lpfnWndProc = window_proc;
304     wc.cbClsExtra = 0;
305     wc.cbWndExtra = 0;
306     wc.hInstance = hinstance;
307     wc.hIcon = LoadIcon (NULL, IDI_WINLOGO);
308     wc.hIconSm = NULL;
309     wc.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
310     wc.hCursor = LoadCursor (NULL, IDC_ARROW);
311     wc.hbrBackground = (HBRUSH) GetStockObject (BLACK_BRUSH);
312     wc.lpszMenuName = NULL;
313     wc.lpszClassName = "GSTVULKAN";
314
315     atom = RegisterClassEx (&wc);
316
317     if (atom == 0) {
318       g_set_error (error, GST_VULKAN_WINDOW_ERROR,
319           GST_VULKAN_WINDOW_ERROR_RESOURCE_UNAVAILABLE,
320           "Failed to register window class 0x%x\n",
321           (unsigned int) GetLastError ());
322       goto failure;
323     }
324   }
325
326   window_win32->internal_win_id = 0;
327   window_win32->device = 0;
328   window_win32->visible = FALSE;
329
330   window_win32->internal_win_id = CreateWindowEx (0,
331       "GSTVULKAN",
332       "Vulkan renderer",
333       WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW,
334       x, y, 0, 0, (HWND) NULL, (HMENU) NULL, hinstance, window_win32);
335
336   if (!window_win32->internal_win_id) {
337     g_set_error (error, GST_VULKAN_WINDOW_ERROR,
338         GST_VULKAN_WINDOW_ERROR_RESOURCE_UNAVAILABLE,
339         "failed to create vulkan window");
340     goto failure;
341   }
342
343   GST_DEBUG ("vulkan window created: %" G_GUINTPTR_FORMAT,
344       (guintptr) window_win32->internal_win_id);
345
346   /* device is set in the window_proc */
347   if (!window_win32->device) {
348     g_set_error (error, GST_VULKAN_WINDOW_ERROR,
349         GST_VULKAN_WINDOW_ERROR_RESOURCE_UNAVAILABLE,
350         "failed to create device");
351     goto failure;
352   }
353
354   ShowCursor (TRUE);
355
356   GST_LOG ("Created a win32 window");
357
358   /* The window has been created as if it had no parent, so there is nothing
359    * else to do in that case. Even if user has already set a window,
360    * parent_win_id could still be 0 at this point, and in that case calling
361    * set_parent_win_id() here would steal focus from the parent window. */
362   if (window_win32->parent_win_id)
363     set_parent_win_id (window_win32);
364
365   return TRUE;
366
367 failure:
368   return FALSE;
369 }
370
371 static VkSurfaceKHR
372 gst_vulkan_window_win32_get_surface (GstVulkanWindow * window, GError ** error)
373 {
374   GstVulkanWindowWin32 *window_win32 = GST_VULKAN_WINDOW_WIN32 (window);
375   VkWin32SurfaceCreateInfoKHR info = { 0, };
376   VkSurfaceKHR ret;
377   VkResult err;
378
379   info.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
380   info.pNext = NULL;
381   info.flags = 0;
382   info.hinstance = GetModuleHandle (NULL);
383   info.hwnd = window_win32->parent_win_id ? window_win32->parent_win_id :
384       window_win32->internal_win_id;
385
386   if (!window_win32->CreateWin32Surface)
387     window_win32->CreateWin32Surface =
388         gst_vulkan_instance_get_proc_address (window->display->instance,
389         "vkCreateWin32SurfaceKHR");
390   if (!window_win32->CreateWin32Surface) {
391     g_set_error_literal (error, GST_VULKAN_ERROR, VK_ERROR_FEATURE_NOT_PRESENT,
392         "Could not retrieve \"vkCreateWin32SurfaceKHR\" function pointer");
393     return NULL;
394   }
395
396   err =
397       window_win32->CreateWin32Surface (window->display->instance->instance,
398       &info, NULL, &ret);
399
400   if (gst_vulkan_error_to_g_error (err, error, "vkCreateWin32SurfaceKHR") < 0)
401     return NULL;
402
403   return ret;
404 }
405
406 static gboolean
407 gst_vulkan_window_win32_get_presentation_support (GstVulkanWindow * window,
408     GstVulkanDevice * device, guint32 queue_family_idx)
409 {
410   GstVulkanWindowWin32 *window_win32 = GST_VULKAN_WINDOW_WIN32 (window);
411   VkPhysicalDevice gpu;
412
413   if (!window_win32->GetPhysicalDeviceWin32PresentationSupport)
414     window_win32->GetPhysicalDeviceWin32PresentationSupport =
415         gst_vulkan_instance_get_proc_address (window->display->instance,
416         "vkGetPhysicalDeviceWin32PresentationSupportKHR");
417   if (!window_win32->GetPhysicalDeviceWin32PresentationSupport) {
418     GST_WARNING_OBJECT (window, "Could not retrieve "
419         "\"GetPhysicalDeviceWin32PresentationSupport\" " "function pointer");
420     return FALSE;
421   }
422
423   gpu = gst_vulkan_device_get_physical_device (device);
424   if (window_win32->GetPhysicalDeviceWin32PresentationSupport (gpu,
425           queue_family_idx))
426     return TRUE;
427
428   return FALSE;
429 }
430
431
432 static void
433 gst_vulkan_window_win32_set_window_handle (GstVulkanWindow * window,
434     guintptr id)
435 {
436   GstVulkanWindowWin32 *window_win32;
437
438   window_win32 = GST_VULKAN_WINDOW_WIN32 (window);
439
440   if (!window_win32->internal_win_id) {
441     window_win32->parent_win_id = (HWND) id;
442     return;
443   }
444
445   if (window_win32->visible) {
446     ShowWindow (window_win32->internal_win_id, SW_HIDE);
447     window_win32->visible = FALSE;
448   }
449
450   release_parent_win_id (window_win32);
451   window_win32->parent_win_id = (HWND) id;
452   set_parent_win_id (window_win32);
453 }
454
455 static void
456 gst_vulkan_window_win32_show (GstVulkanWindowWin32 * window)
457 {
458   gint width = window->priv->preferred_width;
459   gint height = window->priv->preferred_height;
460
461   if (!window->visible) {
462     HWND parent_id = window->parent_win_id;
463
464     /* if no parent the real size has to be set now because this has not been done
465      * when at window creation */
466     if (!parent_id) {
467       RECT rect;
468       GetClientRect (window->internal_win_id, &rect);
469       width += 2 * GetSystemMetrics (SM_CXSIZEFRAME);
470       height +=
471           2 * GetSystemMetrics (SM_CYSIZEFRAME) +
472           GetSystemMetrics (SM_CYCAPTION);
473       MoveWindow (window->internal_win_id, rect.left, rect.top, width,
474           height, FALSE);
475     }
476
477     ShowWindowAsync (window->internal_win_id, SW_SHOW);
478     window->visible = TRUE;
479   }
480 }
481
482 /* PRIVATE */
483
484 LRESULT CALLBACK
485 window_proc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
486 {
487   GstVulkanWindowWin32 *window_win32;
488   LRESULT ret = 0;
489
490   if (uMsg == WM_CREATE) {
491     window_win32 =
492         GST_VULKAN_WINDOW_WIN32 (((LPCREATESTRUCT) lParam)->lpCreateParams);
493
494     GST_TRACE ("WM_CREATE");
495
496     window_win32->device = GetDC (hWnd);
497     /* Do this, otherwise we hang on exit. We can still use it (due to the
498      * CS_OWNDC flag in the WindowClass) after we have Released.
499      */
500     ReleaseDC (hWnd, window_win32->device);
501
502     SetProp (hWnd, "vulkan_window", window_win32);
503   } else if (GetProp (hWnd, "vulkan_window")) {
504     GstVulkanWindow *window;
505
506     window_win32 = GST_VULKAN_WINDOW_WIN32 (GetProp (hWnd, "vulkan_window"));
507     window = GST_VULKAN_WINDOW (window_win32);
508
509     g_assert (window_win32->internal_win_id == hWnd);
510
511     switch (uMsg) {
512       case WM_SIZE:
513         gst_vulkan_window_resize (window, LOWORD (lParam), HIWORD (lParam));
514         break;
515       case WM_PAINT:
516       {
517         PAINTSTRUCT ps;
518
519         BeginPaint (hWnd, &ps);
520         gst_vulkan_window_redraw (window);
521         EndPaint (hWnd, &ps);
522         break;
523       }
524       case WM_CLOSE:
525       {
526         ShowWindowAsync (window_win32->internal_win_id, SW_HIDE);
527
528         gst_vulkan_window_win32_close (window);
529         break;
530       }
531       case WM_ERASEBKGND:
532       {
533         ret = TRUE;
534         break;
535       }
536       default:
537       {
538         /* transmit messages to the parent (ex: mouse/keyboard input) */
539         HWND parent_id = window_win32->parent_win_id;
540         if (parent_id)
541           PostMessage (parent_id, uMsg, wParam, lParam);
542         ret = DefWindowProc (hWnd, uMsg, wParam, lParam);
543         break;
544       }
545     }
546   } else {
547     ret = DefWindowProc (hWnd, uMsg, wParam, lParam);
548   }
549
550   return ret;
551 }
552
553 LRESULT FAR PASCAL
554 sub_class_proc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
555 {
556   WNDPROC window_parent_proc = GetProp (hWnd, "vulkan_window_parent_proc");
557
558   if (uMsg == WM_SIZE) {
559     HWND vulkan_window_id = GetProp (hWnd, "vulkan_window_id");
560     MoveWindow (vulkan_window_id, 0, 0, LOWORD (lParam), HIWORD (lParam),
561         FALSE);
562   }
563
564   return CallWindowProc (window_parent_proc, hWnd, uMsg, wParam, lParam);
565 }