glwindow/winrt: Add window resize event handler
authorSeungha Yang <seungha@centricular.com>
Thu, 9 Jul 2020 13:23:33 +0000 (22:23 +0900)
committerGStreamer Merge Bot <gitlab-merge-bot@gstreamer-foundation.org>
Fri, 10 Jul 2020 11:46:56 +0000 (11:46 +0000)
GstGLWindow implmentaion should be able to report native window size
and also it need to handle resize event for glimagesink.

Note that GstD3D11Window implementation was referenced for this change.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-base/-/merge_requests/745>

gst-libs/gst/gl/meson.build
gst-libs/gst/gl/winrt/gstglwindow_winrt_egl.cpp
gst-libs/gst/gl/winrt/gstglwindow_winrt_egl.h

index 5c43bd3..ef48507 100644 (file)
@@ -718,13 +718,15 @@ if need_win_winrt != 'no' and host_system == 'windows'
   if egl_dep.found()
     windows_graphics_h = cc.has_header('windows.graphics.h')
     windows_app_dep = cc.find_library('WindowsApp', required: false)
+    runtimeobject_lib = cc.find_library('runtimeobject', required: false)
 
-    if windows_graphics_h and windows_app_dep.found()
+    if windows_graphics_h and windows_app_dep.found() and runtimeobject_lib.found()
       enabled_gl_winsys += 'winrt'
       glconf.set10('GST_GL_HAVE_WINDOW_WINRT', 1)
       gl_sources += [
         'winrt/gstglwindow_winrt_egl.cpp'
       ]
+      gl_winsys_deps += runtimeobject_lib
     elif need_win_winrt == 'yes'
       error('WinRT is enabled, but headers/libraries were not found')
     endif
index 9e8d386..4dbeaa9 100644 (file)
@@ -1,6 +1,7 @@
 /*
  * GStreamer
  * Copyright (C) 2019 Nirbheek Chauhan <nirbheek@centricular.com>
+ * Copyright (C) 2020 Seungha Yang <seungha@centricular.com>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
 #include "gstglwindow_winrt_egl.h"
 #include "../gstglwindow_private.h"
 
+/* workaround for GetCurrentTime collision */
+#ifdef GetCurrentTime
+#undef GetCurrentTime
+#endif
+#include <windows.ui.xaml.h>
+#include <windows.ui.xaml.media.dxinterop.h>
+#include <windows.applicationmodel.core.h>
+#include <windows.graphics.display.h>
+#include <wrl.h>
+#include <wrl/wrappers/corewrappers.h>
+
+using namespace Microsoft::WRL;
+using namespace Microsoft::WRL::Wrappers;
+using namespace ABI::Windows::UI;
+using namespace ABI::Windows::Foundation;
+using namespace ABI::Windows::Foundation::Collections;
+using namespace ABI::Windows::Graphics;
+
 #define GST_CAT_DEFAULT gst_gl_window_debug
 
-#define GST_GL_WINDOW_WINRT_EGL_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
-    GST_TYPE_GL_WINDOW_WINRT_EGL, GstGLWindowWinRTEGLPrivate))
+/* timeout to wait busy UI thread */
+#define DEFAULT_ASYNC_TIMEOUT (5 * 1000)
+
+static void gst_gl_window_winrt_egl_on_resize (GstGLWindow * window,
+    guint width, guint height);
+
+typedef enum
+{
+  GST_GL_WINDOW_WINRT_NATIVE_TYPE_NONE = 0,
+  GST_GL_WINDOW_WINRT_NATIVE_TYPE_CORE_WINDOW,
+  GST_GL_WINDOW_WINRT_NATIVE_TYPE_SWAP_CHAIN_PANEL,
+} GstGLWindowWinRTNativeType;
+
+template <typename CB>
+static HRESULT
+run_async (const ComPtr<Core::ICoreDispatcher> &dispatcher, DWORD timeout,
+    CB &&cb)
+{
+  ComPtr<IAsyncAction> async_action;
+  HRESULT hr;
+  HRESULT async_hr;
+  boolean can_now;
+  DWORD wait_ret;
+
+  hr = dispatcher->get_HasThreadAccess (&can_now);
+
+  if (FAILED (hr))
+    return hr;
+
+  if (can_now)
+    return cb ();
+
+  Event event (CreateEventEx (NULL, NULL, CREATE_EVENT_MANUAL_RESET,
+      EVENT_ALL_ACCESS));
+
+  if (!event.IsValid())
+    return E_FAIL;
+
+  auto handler =
+      Callback<Implements<RuntimeClassFlags<ClassicCom>,
+          Core::IDispatchedHandler, FtmBase>>([&async_hr, &cb, &event] {
+        async_hr = cb ();
+        SetEvent (event.Get ());
+        return S_OK;
+      });
+
+  hr = dispatcher->RunAsync (Core::CoreDispatcherPriority_Normal,
+      handler.Get (), &async_action);
+
+  if (FAILED (hr))
+    return hr;
+
+  wait_ret = WaitForSingleObjectEx (event.Get (), timeout, true);
+  if (wait_ret != WAIT_OBJECT_0)
+    return E_FAIL;
+
+  return async_hr;
+}
+
+/* ICoreWindow resize event handler */
+typedef ABI::Windows::Foundation::
+    __FITypedEventHandler_2_Windows__CUI__CCore__CCoreWindow_Windows__CUI__CCore__CWindowSizeChangedEventArgs_t
+        IWindowSizeChangedEventHandler;
+
+static float
+get_logical_dpi (void)
+{
+  ComPtr<Display::IDisplayPropertiesStatics> properties;
+  HRESULT hr;
+  HStringReference str_ref =
+      HStringReference (RuntimeClass_Windows_Graphics_Display_DisplayProperties);
+
+  hr = GetActivationFactory (str_ref.Get(), properties.GetAddressOf());
+
+  if (SUCCEEDED (hr)) {
+    float dpi = 96.0f;
+
+    hr = properties->get_LogicalDpi (&dpi);
+    if (SUCCEEDED (hr))
+      return dpi;
+  }
+
+  return 96.0f;
+}
+
+static inline float dip_to_pixel (float dip, float logical_dpi)
+{
+  /* https://docs.microsoft.com/en-us/windows/win32/learnwin32/dpi-and-device-independent-pixels */
+  return dip * logical_dpi / 96.0f;
+}
+
+class CoreResizeHandler
+    : public RuntimeClass<RuntimeClassFlags<ClassicCom>,
+                          IWindowSizeChangedEventHandler>
+{
+public:
+  CoreResizeHandler () {}
+  HRESULT RuntimeClassInitialize (GstGLWindow * window)
+  {
+    if (!window)
+      return E_INVALIDARG;
+
+    listener_ = window;
+    return S_OK;
+  }
+
+  IFACEMETHOD(Invoke)
+  (Core::ICoreWindow * sender, Core::IWindowSizeChangedEventArgs * args)
+  {
+    Size new_size;
+    HRESULT hr;
+
+    if (!listener_)
+      return S_OK;
+
+    hr = args->get_Size(&new_size);
+    if (SUCCEEDED (hr)) {
+      gint width, height;
+      float dpi;
+
+      dpi = get_logical_dpi ();
+
+      width = (guint) dip_to_pixel (new_size.Width, dpi);
+      height = (guint) dip_to_pixel (new_size.Height, dpi);
+
+      gst_gl_window_winrt_egl_on_resize (listener_, width, height);
+    }
+
+    return S_OK;
+  }
+
+private:
+  GstGLWindow * listener_;
+};
+
+class PanelResizeHandler
+    : public RuntimeClass<RuntimeClassFlags<ClassicCom>,
+        Xaml::ISizeChangedEventHandler>
+{
+public:
+  PanelResizeHandler () {}
+  HRESULT RuntimeClassInitialize (GstGLWindow * window)
+  {
+    if (!window)
+      return E_INVALIDARG;
+
+    listener_ = window;
+    return S_OK;
+  }
+
+  IFACEMETHOD(Invoke)
+  (IInspectable * sender, Xaml::ISizeChangedEventArgs * args)
+  {
+    Size new_size;
+    HRESULT hr;
+
+    if (!listener_)
+      return S_OK;
+
+    hr = args->get_NewSize(&new_size);
+    if (SUCCEEDED(hr)) {
+      gst_gl_window_winrt_egl_on_resize (listener_,
+          (guint) new_size.Width, (guint) new_size.Height);
+    }
+
+    return S_OK;
+  }
+
+private:
+  GstGLWindow * listener_;
+};
+
+class GstGLWindowWinRTEGLResizeHandler
+{
+public:
+  GstGLWindowWinRTEGLResizeHandler(IInspectable * native_handle,
+      GstGLWindow * listener)
+    : native_type_ (GST_GL_WINDOW_WINRT_NATIVE_TYPE_NONE)
+    , isValid_ (false)
+  {
+    ComPtr<IInspectable> window;
+    HRESULT hr = E_FAIL;
+
+    if (!native_handle) {
+      GST_WARNING ("Null handler");
+      return;
+    }
+
+    window = native_handle;
+    if (SUCCEEDED (window.As (&core_window_))) {
+      GST_INFO ("Valid ICoreWindow");
+      native_type_ = GST_GL_WINDOW_WINRT_NATIVE_TYPE_CORE_WINDOW;
+      core_window_->get_Dispatcher (&dispatcher_);
+    } else if (SUCCEEDED (window.As (&panel_))) {
+      ComPtr<Xaml::IDependencyObject> dependency_obj;
+
+      GST_INFO ("Valid ISwapChainPanel");
+      native_type_ = GST_GL_WINDOW_WINRT_NATIVE_TYPE_SWAP_CHAIN_PANEL;
+      hr = panel_.As (&dependency_obj);
+      if (FAILED (hr)) {
+        GST_WARNING ("Couldn't get IDependencyObject interface");
+        return;
+      }
+
+      dependency_obj->get_Dispatcher (&dispatcher_);
+    } else {
+      GST_ERROR ("Invalid window handle");
+      return;
+    }
+
+    if (!dispatcher_) {
+      GST_WARNING ("ICoreDispatcher is unavailable");
+      return;
+    }
+
+    switch (native_type_) {
+      case GST_GL_WINDOW_WINRT_NATIVE_TYPE_CORE_WINDOW:
+        if (!registerSizeChangedHandlerForCoreWindow (listener)) {
+          GST_WARNING
+              ("Couldn't install size changed event handler for corewindow");
+          return;
+        }
+        break;
+      case GST_GL_WINDOW_WINRT_NATIVE_TYPE_SWAP_CHAIN_PANEL:
+        if (!registerSizeChangedHandlerForSwapChainPanel (listener)) {
+          GST_WARNING
+              ("Couldn't install size changed event handler for swapchainpanel");
+          return;
+        }
+        break;
+      default:
+        g_assert_not_reached ();
+        return;
+    }
+
+    isValid_ = true;
+  }
+
+  ~GstGLWindowWinRTEGLResizeHandler()
+  {
+    if (!isValid_ || !dispatcher_)
+      return;
+
+    switch (native_type_) {
+      case GST_GL_WINDOW_WINRT_NATIVE_TYPE_CORE_WINDOW:
+        unregisterSizeChangedHandlerForCoreWindow ();
+        break;
+      case GST_GL_WINDOW_WINRT_NATIVE_TYPE_SWAP_CHAIN_PANEL:
+        unregisterSizeChangedHandlerForSwapChainPanel ();
+        break;
+      default:
+        g_assert_not_reached ();
+        return;
+    }
+  }
+
+  bool
+  IsValid (void)
+  {
+    return isValid_;
+  }
+
+  bool
+  GetWindowSize (guint * width, guint * height)
+  {
+    bool ret = true;
+
+    if (!isValid_)
+      return false;
+
+    switch (native_type_) {
+      case GST_GL_WINDOW_WINRT_NATIVE_TYPE_CORE_WINDOW:
+        return getWindowSizeForCoreWindow (width, height);
+      case GST_GL_WINDOW_WINRT_NATIVE_TYPE_SWAP_CHAIN_PANEL:
+        return getWindowSizeForSwapChainPanel (width, height);
+      default:
+        g_assert_not_reached ();
+        break;
+    }
+
+    return false;
+  }
 
+private:
+  bool
+  registerSizeChangedHandlerForCoreWindow (GstGLWindow * window)
+  {
+    ComPtr<IWindowSizeChangedEventHandler> resize_handler;
+    HRESULT hr;
 
-G_DEFINE_TYPE (GstGLWindowWinRTEGL, gst_gl_window_winrt_egl,
+    hr = MakeAndInitialize<CoreResizeHandler>(&resize_handler, window);
+    if (FAILED (hr)) {
+      GST_WARNING ("Couldn't creat resize handler object");
+      return false;
+    }
+
+    hr = run_async (dispatcher_, DEFAULT_ASYNC_TIMEOUT,
+        [this, resize_handler] {
+          return core_window_->add_SizeChanged (resize_handler.Get(),
+              &event_token_);
+        });
+
+    if (FAILED (hr)) {
+      GST_WARNING ("Couldn't install resize handler");
+      return false;
+    }
+
+    return true;
+  }
+
+  bool
+  registerSizeChangedHandlerForSwapChainPanel (GstGLWindow * window)
+  {
+    ComPtr<Xaml::ISizeChangedEventHandler> resize_handler;
+    ComPtr<Xaml::IFrameworkElement> framework;
+    HRESULT hr;
+
+    hr = MakeAndInitialize<PanelResizeHandler>(&resize_handler, window);
+    if (FAILED (hr)) {
+      GST_WARNING ("Couldn't creat resize handler object");
+      return false;
+    }
+
+    hr = panel_.As (&framework);
+    if (FAILED (hr)) {
+      GST_WARNING ("Couldn't get IFrameworkElement interface");
+      return false;
+    }
+
+    hr = run_async (dispatcher_, DEFAULT_ASYNC_TIMEOUT,
+      [this, framework, resize_handler] {
+        return framework->add_SizeChanged (resize_handler.Get(),
+            &event_token_);
+      });
+
+    if (FAILED (hr)) {
+      GST_WARNING ("Couldn't install resize handler");
+      return false;
+    }
+
+    return true;
+  }
+
+  void
+  unregisterSizeChangedHandlerForCoreWindow (void)
+  {
+    run_async (dispatcher_, DEFAULT_ASYNC_TIMEOUT,
+        [this] {
+          core_window_->remove_SizeChanged(event_token_);
+          return S_OK;
+        });
+  }
+
+  void
+  unregisterSizeChangedHandlerForSwapChainPanel (void)
+  {
+    ComPtr<Xaml::IFrameworkElement> framework;
+    HRESULT hr;
+
+    hr = panel_.As (&framework);
+    if (SUCCEEDED (hr)) {
+      run_async (dispatcher_, DEFAULT_ASYNC_TIMEOUT,
+          [this, framework] {
+            return framework->remove_SizeChanged (event_token_);
+          });
+    }
+  }
+
+  bool
+  getWindowSizeForCoreWindow (guint * width, guint * height)
+  {
+    HRESULT hr;
+
+    hr = run_async (dispatcher_, DEFAULT_ASYNC_TIMEOUT,
+      [this, width, height] {
+        HRESULT async_hr;
+        Rect bounds;
+
+        async_hr = core_window_->get_Bounds (&bounds);
+        if (SUCCEEDED (async_hr)) {
+          float dpi;
+
+          dpi = get_logical_dpi ();
+
+          *width = (guint) dip_to_pixel (bounds.Width, dpi);
+          *height = (guint) dip_to_pixel (bounds.Height, dpi);
+        }
+
+        return async_hr;
+      });
+
+    return SUCCEEDED (hr);
+  }
+
+  bool
+  getWindowSizeForSwapChainPanel (guint * width, guint * height)
+  {
+    HRESULT hr;
+    ComPtr<Xaml::IUIElement> ui;
+
+    hr = panel_.As (&ui);
+    if (FAILED (hr))
+      return false;
+
+    hr = run_async (dispatcher_, DEFAULT_ASYNC_TIMEOUT,
+        [ui, width, height] {
+          Size size;
+          HRESULT async_hr;
+
+          async_hr = ui->get_RenderSize (&size);
+          if (SUCCEEDED (async_hr)) {
+            *width = (guint) size.Width;
+            *height = (guint) size.Height;
+          }
+
+          return async_hr;
+        });
+
+    return SUCCEEDED (hr);
+  }
+
+private:
+  GstGLWindowWinRTNativeType native_type_;
+  ComPtr<Core::ICoreDispatcher> dispatcher_;
+
+  ComPtr<Core::ICoreWindow> core_window_;
+  ComPtr<Xaml::Controls::ISwapChainPanel> panel_;
+
+  EventRegistrationToken event_token_;
+  bool isValid_;
+};
+
+struct _GstGLWindowWinRTEGLPrivate
+{
+  GstGLWindowWinRTEGLResizeHandler *resize_handler;
+  guint surface_width;
+  guint surface_height;
+  GMutex event_lock;
+};
+
+#define gst_gl_window_winrt_egl_parent_class parent_class
+G_DEFINE_TYPE_WITH_PRIVATE (GstGLWindowWinRTEGL, gst_gl_window_winrt_egl,
     GST_TYPE_GL_WINDOW);
 
+static void gst_gl_window_winrt_egl_dispose (GObject * object);
+static void gst_gl_window_winrt_egl_finalize (GObject * object);
 static guintptr gst_gl_window_winrt_egl_get_display (GstGLWindow * window);
 static guintptr gst_gl_window_winrt_egl_get_window_handle (GstGLWindow *
     window);
 static void gst_gl_window_winrt_egl_set_window_handle (GstGLWindow * window,
     guintptr handle);
+static void gst_gl_window_winrt_egl_show (GstGLWindow * window);
 
 static void
 gst_gl_window_winrt_egl_class_init (GstGLWindowWinRTEGLClass * klass)
 {
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
   GstGLWindowClass *window_class = (GstGLWindowClass *) klass;
 
+  gobject_class->dispose = gst_gl_window_winrt_egl_dispose;
+  gobject_class->finalize = gst_gl_window_winrt_egl_finalize;
+
   window_class->get_display =
       GST_DEBUG_FUNCPTR (gst_gl_window_winrt_egl_get_display);
   window_class->get_window_handle =
       GST_DEBUG_FUNCPTR (gst_gl_window_winrt_egl_get_window_handle);
   window_class->set_window_handle =
       GST_DEBUG_FUNCPTR (gst_gl_window_winrt_egl_set_window_handle);
+  window_class->show =
+      GST_DEBUG_FUNCPTR (gst_gl_window_winrt_egl_show);
 }
 
 static void
 gst_gl_window_winrt_egl_init (GstGLWindowWinRTEGL * window_winrt)
 {
+  window_winrt->priv = (GstGLWindowWinRTEGLPrivate *)
+      gst_gl_window_winrt_egl_get_instance_private (window_winrt);
+
+  g_mutex_init (&window_winrt->priv->event_lock);
+}
+
+static void
+gst_gl_window_winrt_egl_dispose (GObject * object)
+{
+  GstGLWindowWinRTEGL *window_egl = GST_GL_WINDOW_WINRT_EGL (object);
+  GstGLWindowWinRTEGLPrivate *priv = window_egl->priv;
+
+  if (priv->resize_handler) {
+    delete priv->resize_handler;
+    priv->resize_handler = nullptr;
+  }
+
+  G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gst_gl_window_winrt_egl_finalize (GObject * object)
+{
+  GstGLWindowWinRTEGL *window_egl = GST_GL_WINDOW_WINRT_EGL (object);
+  GstGLWindowWinRTEGLPrivate *priv = window_egl->priv;
+
+  g_mutex_clear (&priv->event_lock);
+
+  G_OBJECT_CLASS (parent_class)->finalize (object);
 }
 
 static void
@@ -65,10 +559,31 @@ gst_gl_window_winrt_egl_set_window_handle (GstGLWindow * window,
     guintptr handle)
 {
   GstGLWindowWinRTEGL *window_egl = GST_GL_WINDOW_WINRT_EGL (window);
+  GstGLWindowWinRTEGLPrivate *priv = window_egl->priv;
 
   GST_INFO_OBJECT (window, "Setting WinRT EGL window handle: %p", handle);
 
   window_egl->window = (EGLNativeWindowType) handle;
+
+  if (priv->resize_handler)
+    delete priv->resize_handler;
+  priv->resize_handler = nullptr;
+
+  if (!handle) {
+    GST_WARNING_OBJECT (window, "NULL window handle");
+    return;
+  }
+
+  priv->resize_handler =
+      new GstGLWindowWinRTEGLResizeHandler
+      (reinterpret_cast<IInspectable*> (handle), window);
+
+  if (!priv->resize_handler->IsValid ()) {
+    GST_WARNING_OBJECT (window,
+        "Invalid window handle %" G_GUINTPTR_FORMAT, handle);
+    delete priv->resize_handler;
+    priv->resize_handler = nullptr;
+  }
 }
 
 static guintptr
@@ -95,7 +610,8 @@ gst_gl_window_winrt_egl_new (GstGLDisplay * display)
 
   GST_INFO_OBJECT (display, "Creating WinRT EGL window");
 
-  window_egl = g_object_new (GST_TYPE_GL_WINDOW_WINRT_EGL, NULL);
+  window_egl = (GstGLWindowWinRTEGL *)
+      g_object_new (GST_TYPE_GL_WINDOW_WINRT_EGL, NULL);
 
   return window_egl;
 }
@@ -106,3 +622,46 @@ gst_gl_window_winrt_egl_get_display (GstGLWindow * window)
   /* EGL_DEFAULT_DISPLAY */
   return 0;
 }
+
+static void
+gst_gl_window_winrt_egl_show (GstGLWindow * window)
+{
+  GstGLWindowWinRTEGL *window_egl = GST_GL_WINDOW_WINRT_EGL (window);
+  GstGLWindowWinRTEGLPrivate *priv = window_egl->priv;
+  guint width, height;
+  gboolean resize = FALSE;
+
+  if (!priv->resize_handler)
+    return;
+
+  g_mutex_lock (&priv->event_lock);
+  if (!priv->surface_width || !priv->surface_height) {
+    if (priv->resize_handler->GetWindowSize (&width, &height)) {
+      GST_INFO_OBJECT (window, "Client window size %dx%d", width, height);
+      priv->surface_width = width;
+      priv->surface_height = height;
+      resize = TRUE;
+    }
+  }
+  g_mutex_unlock (&priv->event_lock);
+
+  if (resize)
+    gst_gl_window_resize (window, width, height);
+}
+
+static void
+gst_gl_window_winrt_egl_on_resize (GstGLWindow * window,
+    guint width, guint height)
+{
+  GstGLWindowWinRTEGL *window_egl = GST_GL_WINDOW_WINRT_EGL (window);
+  GstGLWindowWinRTEGLPrivate *priv = window_egl->priv;
+
+  GST_DEBUG_OBJECT (window, "New client window size %dx%d", width, height);
+
+  g_mutex_lock (&priv->event_lock);
+  priv->surface_width = width;
+  priv->surface_height = height;
+  g_mutex_unlock (&priv->event_lock);
+
+  gst_gl_window_resize (window, width, height);
+}
\ No newline at end of file
index 08bff31..7c95528 100644 (file)
@@ -37,6 +37,7 @@ GType gst_gl_window_winrt_egl_get_type (void);
 
 typedef struct _GstGLWindowWinRTEGL        GstGLWindowWinRTEGL;
 typedef struct _GstGLWindowWinRTEGLClass   GstGLWindowWinRTEGLClass;
+typedef struct _GstGLWindowWinRTEGLPrivate GstGLWindowWinRTEGLPrivate;
 
 struct _GstGLWindowWinRTEGL {
   /*< private >*/
@@ -46,6 +47,8 @@ struct _GstGLWindowWinRTEGL {
    * ICoreWindow, ISwapChainPanel, IPropertySet */
   EGLNativeWindowType window;
 
+  GstGLWindowWinRTEGLPrivate *priv;
+
   gpointer _reserved[GST_PADDING];
 };