d3d11videosink: Add support for full screen mode
authorSeungha Yang <seungha.yang@navercorp.com>
Sun, 15 Dec 2019 10:29:10 +0000 (19:29 +0900)
committerSeungha Yang <seungha.yang@navercorp.com>
Fri, 20 Dec 2019 10:21:02 +0000 (19:21 +0900)
borderless top-most style full screen mode support.
Basically fullscreen toggle mode is disabled by default. To enable it
use "fullscreen-toggle-mode" property to allow fullscreen mode change
by user input and/or property.

sys/d3d11/gstd3d11videosink.c
sys/d3d11/gstd3d11videosink.h
sys/d3d11/gstd3d11videosinkbin.c
sys/d3d11/gstd3d11window.c
sys/d3d11/gstd3d11window.h

index e2eed1a..519605a 100644 (file)
@@ -33,12 +33,16 @@ enum
   PROP_0,
   PROP_ADAPTER,
   PROP_FORCE_ASPECT_RATIO,
-  PROP_ENABLE_NAVIGATION_EVENTS
+  PROP_ENABLE_NAVIGATION_EVENTS,
+  PROP_FULLSCREEN_TOGGLE_MODE,
+  PROP_FULLSCREEN,
 };
 
 #define DEFAULT_ADAPTER                   -1
 #define DEFAULT_FORCE_ASPECT_RATIO        TRUE
 #define DEFAULT_ENABLE_NAVIGATION_EVENTS  TRUE
+#define DEFAULT_FULLSCREEN_TOGGLE_MODE    GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_NONE
+#define DEFAULT_FULLSCREEN                FALSE
 
 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
     GST_PAD_SINK,
@@ -120,6 +124,19 @@ gst_d3d11_video_sink_class_init (GstD3D11VideoSinkClass * klass)
           DEFAULT_ENABLE_NAVIGATION_EVENTS,
           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
+  g_object_class_install_property (gobject_class, PROP_FULLSCREEN_TOGGLE_MODE,
+      g_param_spec_flags ("fullscreen-toggle-mode",
+          "Full screen toggle mode",
+          "Full screen toggle mode used to trigger fullscreen mode change",
+          GST_D3D11_WINDOW_TOGGLE_MODE_GET_TYPE, DEFAULT_FULLSCREEN_TOGGLE_MODE,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_FULLSCREEN,
+      g_param_spec_boolean ("fullscreen",
+          "fullscreen",
+          "Ignored when \"fullscreen-toggle-mode\" does not include \"property\"",
+          DEFAULT_FULLSCREEN, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
   element_class->set_context =
       GST_DEBUG_FUNCPTR (gst_d3d11_video_sink_set_context);
 
@@ -149,6 +166,8 @@ gst_d3d11_video_sink_init (GstD3D11VideoSink * self)
   self->adapter = DEFAULT_ADAPTER;
   self->force_aspect_ratio = DEFAULT_FORCE_ASPECT_RATIO;
   self->enable_navigation_events = DEFAULT_ENABLE_NAVIGATION_EVENTS;
+  self->fullscreen_toggle_mode = DEFAULT_FULLSCREEN_TOGGLE_MODE;
+  self->fullscreen = DEFAULT_FULLSCREEN;
 }
 
 static void
@@ -175,6 +194,19 @@ gst_d3d11_videosink_set_property (GObject * object, guint prop_id,
             "enable-navigation-events", self->enable_navigation_events, NULL);
       }
       break;
+    case PROP_FULLSCREEN_TOGGLE_MODE:
+      self->fullscreen_toggle_mode = g_value_get_flags (value);
+      if (self->window) {
+        g_object_set (self->window,
+            "fullscreen-toggle-mode", self->fullscreen_toggle_mode, NULL);
+      }
+      break;
+    case PROP_FULLSCREEN:
+      self->fullscreen = g_value_get_boolean (value);
+      if (self->window) {
+        g_object_set (self->window, "fullscreen", self->fullscreen, NULL);
+      }
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -198,6 +230,16 @@ gst_d3d11_videosink_get_property (GObject * object, guint prop_id,
     case PROP_ENABLE_NAVIGATION_EVENTS:
       g_value_set_boolean (value, self->enable_navigation_events);
       break;
+    case PROP_FULLSCREEN_TOGGLE_MODE:
+      g_value_set_flags (value, self->fullscreen_toggle_mode);
+      break;
+    case PROP_FULLSCREEN:
+      if (self->window) {
+        g_object_get_property (G_OBJECT (self->window), pspec->name, value);
+      } else {
+        g_value_set_boolean (value, self->fullscreen);
+      }
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -329,10 +371,10 @@ gst_d3d11_video_sink_set_caps (GstBaseSink * sink, GstCaps * caps)
       self->render_rect.h);
   self->pending_render_rect = FALSE;
 
-  if (!self->force_aspect_ratio) {
-    g_object_set (self->window,
-        "force-aspect-ratio", self->force_aspect_ratio, NULL);
-  }
+  g_object_set (self->window,
+      "force-aspect-ratio", self->force_aspect_ratio,
+      "fullscreen-toggle-mode", self->fullscreen_toggle_mode,
+      "fullscreen", self->fullscreen, NULL);
 
   GST_OBJECT_UNLOCK (self);
 
index 768bdf0..c443906 100644 (file)
@@ -57,6 +57,8 @@ struct _GstD3D11VideoSink
   gint adapter;
   gboolean force_aspect_ratio;
   gboolean enable_navigation_events;
+  GstD3D11WindowFullscreenToggleMode fullscreen_toggle_mode;
+  gboolean fullscreen;
 
   /* saved render rectangle until we have a window */
   GstVideoRectangle render_rect;
index 28209e9..a2e507e 100644 (file)
@@ -53,6 +53,8 @@ enum
   PROP_ADAPTER,
   PROP_FORCE_ASPECT_RATIO,
   PROP_ENABLE_NAVIGATION_EVENTS,
+  PROP_FULLSCREEN_TOGGLE_MODE,
+  PROP_FULLSCREEN,
 };
 
 /* basesink */
@@ -76,6 +78,8 @@ enum
 #define DEFAULT_ADAPTER                   -1
 #define DEFAULT_FORCE_ASPECT_RATIO        TRUE
 #define DEFAULT_ENABLE_NAVIGATION_EVENTS  TRUE
+#define DEFAULT_FULLSCREEN_TOGGLE_MODE    GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_NONE
+#define DEFAULT_FULLSCREEN                FALSE
 
 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
     GST_PAD_SINK,
@@ -200,6 +204,17 @@ gst_d3d11_video_sink_bin_class_init (GstD3D11VideoSinkBinClass * klass)
           "When enabled, navigation events are sent upstream",
           DEFAULT_ENABLE_NAVIGATION_EVENTS,
           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_FULLSCREEN_TOGGLE_MODE,
+      g_param_spec_flags ("fullscreen-toggle-mode",
+          "Full screen toggle mode",
+          "Full screen toggle mode used to trigger fullscreen mode change",
+          GST_D3D11_WINDOW_TOGGLE_MODE_GET_TYPE, DEFAULT_FULLSCREEN_TOGGLE_MODE,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_FULLSCREEN,
+      g_param_spec_boolean ("fullscreen",
+          "fullscreen",
+          "Ignored when \"fullscreen-toggle-mode\" does not include \"property\"",
+          DEFAULT_FULLSCREEN, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
   gst_element_class_set_static_metadata (element_class,
       "Direct3D11 video sink bin", "Sink/Video",
index 1c43b0e..d620474 100644 (file)
@@ -41,10 +41,14 @@ enum
   PROP_D3D11_DEVICE,
   PROP_FORCE_ASPECT_RATIO,
   PROP_ENABLE_NAVIGATION_EVENTS,
+  PROP_FULLSCREEN_TOGGLE_MODE,
+  PROP_FULLSCREEN,
 };
 
 #define DEFAULT_ENABLE_NAVIGATION_EVENTS  TRUE
 #define DEFAULT_FORCE_ASPECT_RATIO        TRUE
+#define DEFAULT_FULLSCREEN_TOGGLE_MODE    GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_NONE
+#define DEFAULT_FULLSCREEN                FALSE
 
 enum
 {
@@ -56,9 +60,34 @@ enum
 
 static guint d3d11_window_signals[SIGNAL_LAST] = { 0, };
 
+GType
+gst_d3d11_window_fullscreen_toggle_mode_type (void)
+{
+  static volatile gsize mode_type = 0;
+
+  if (g_once_init_enter (&mode_type)) {
+    static const GFlagsValue mode_types[] = {
+      {GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_NONE,
+          "GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_NONE", "none"},
+      {GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_ALT_ENTER,
+          "GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_ALT_ENTER", "alt-enter"},
+      {GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_PROPERTY,
+          "GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_PROPERTY", "property"},
+      {0, NULL, NULL},
+    };
+    GType tmp = g_flags_register_static ("GstD3D11WindowFullscreenToggleMode",
+        mode_types);
+    g_once_init_leave (&mode_type, tmp);
+  }
+
+  return (GType) mode_type;
+}
+
 #define EXTERNAL_PROC_PROP_NAME "d3d11_window_external_proc"
 #define D3D11_WINDOW_PROP_NAME "gst_d3d11_window_object"
 
+#define WM_GST_D3D11_FULLSCREEN (WM_USER + 1)
+
 static LRESULT CALLBACK window_proc (HWND hWnd, UINT uMsg, WPARAM wParam,
     LPARAM lParam);
 static LRESULT FAR PASCAL sub_class_proc (HWND hWnd, UINT uMsg, WPARAM wParam,
@@ -83,6 +112,7 @@ static void gst_d3d11_window_close_internal_window (GstD3D11Window * self);
 static void release_external_win_id (GstD3D11Window * self);
 static GstFlowReturn gst_d3d111_window_present (GstD3D11Window * self,
     GstBuffer * buffer);
+static void gst_d3d11_window_change_fullscreen_mode (GstD3D11Window * self);
 
 static void
 gst_d3d11_window_class_init (GstD3D11WindowClass * klass)
@@ -115,6 +145,19 @@ gst_d3d11_window_class_init (GstD3D11WindowClass * klass)
           DEFAULT_ENABLE_NAVIGATION_EVENTS,
           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
+  g_object_class_install_property (gobject_class, PROP_FULLSCREEN_TOGGLE_MODE,
+      g_param_spec_flags ("fullscreen-toggle-mode",
+          "Full screen toggle mode",
+          "Full screen toggle mode used to trigger fullscreen mode change",
+          GST_D3D11_WINDOW_TOGGLE_MODE_GET_TYPE, DEFAULT_FULLSCREEN_TOGGLE_MODE,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_FULLSCREEN,
+      g_param_spec_boolean ("fullscreen",
+          "fullscreen",
+          "Ignored when \"fullscreen-toggle-mode\" does not include \"property\"",
+          DEFAULT_FULLSCREEN, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
   d3d11_window_signals[SIGNAL_KEY_EVENT] =
       g_signal_new ("key-event", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
@@ -143,6 +186,8 @@ gst_d3d11_window_init (GstD3D11Window * self)
 
   self->force_aspect_ratio = DEFAULT_FORCE_ASPECT_RATIO;
   self->enable_navigation_events = DEFAULT_ENABLE_NAVIGATION_EVENTS;
+  self->fullscreen_toggle_mode = GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_NONE;
+  self->fullscreen = DEFAULT_FULLSCREEN;
 
   self->aspect_ratio_n = 1;
   self->aspect_ratio_d = 1;
@@ -175,6 +220,18 @@ gst_d3d11_window_set_property (GObject * object, guint prop_id,
     case PROP_ENABLE_NAVIGATION_EVENTS:
       self->enable_navigation_events = g_value_get_boolean (value);
       break;
+    case PROP_FULLSCREEN_TOGGLE_MODE:
+      self->fullscreen_toggle_mode = g_value_get_flags (value);
+      break;
+    case PROP_FULLSCREEN:
+    {
+      self->requested_fullscreen = g_value_get_boolean (value);
+      if (self->swap_chain) {
+        g_atomic_int_add (&self->pending_fullscreen_count, 1);
+        PostMessage (self->internal_win_id, WM_GST_D3D11_FULLSCREEN, 0, 0);
+      }
+      break;
+    }
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -196,6 +253,12 @@ gst_d3d11_window_get_property (GObject * object, guint prop_id,
     case PROP_FORCE_ASPECT_RATIO:
       g_value_set_boolean (value, self->force_aspect_ratio);
       break;
+    case PROP_FULLSCREEN_TOGGLE_MODE:
+      g_value_set_flags (value, self->fullscreen_toggle_mode);
+      break;
+    case PROP_FULLSCREEN:
+      g_value_set_boolean (value, self->fullscreen);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -599,6 +662,71 @@ done:
   gst_d3d11_device_unlock (window->device);
 }
 
+/* always called from window thread */
+static void
+gst_d3d11_window_change_fullscreen_mode (GstD3D11Window * self)
+{
+  HWND hwnd = self->external_win_id ? self->external_win_id :
+      self->internal_win_id;
+
+  if (!self->swap_chain)
+    return;
+
+  if (self->requested_fullscreen == self->fullscreen)
+    return;
+
+  GST_DEBUG_OBJECT (self, "Change mode to %s",
+      self->requested_fullscreen ? "fullscreen" : "windowed");
+
+  self->fullscreen = !self->fullscreen;
+
+  if (!self->fullscreen) {
+    /* Restore the window's attributes and size */
+    SetWindowLong (hwnd, GWL_STYLE, self->restore_style);
+
+    SetWindowPos (hwnd, HWND_NOTOPMOST,
+        self->restore_rect.left,
+        self->restore_rect.top,
+        self->restore_rect.right - self->restore_rect.left,
+        self->restore_rect.bottom - self->restore_rect.top,
+        SWP_FRAMECHANGED | SWP_NOACTIVATE);
+
+    ShowWindow (hwnd, SW_NORMAL);
+  } else {
+    IDXGIOutput *output;
+    DXGI_OUTPUT_DESC output_desc;
+
+    /* show window before change style */
+    ShowWindow (hwnd, SW_SHOW);
+
+    /* Save the old window rect so we can restore it when exiting
+     * fullscreen mode */
+    GetWindowRect (hwnd, &self->restore_rect);
+    self->restore_style = GetWindowLong (hwnd, GWL_STYLE);
+
+    /* Make the window borderless so that the client area can fill the screen */
+    SetWindowLong (hwnd, GWL_STYLE,
+        self->restore_style &
+        ~(WS_CAPTION | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_SYSMENU |
+            WS_THICKFRAME));
+
+    IDXGISwapChain_GetContainingOutput (self->swap_chain, &output);
+    IDXGIOutput_GetDesc (output, &output_desc);
+    IDXGIOutput_Release (output);
+
+    SetWindowPos (hwnd, HWND_TOPMOST,
+        output_desc.DesktopCoordinates.left,
+        output_desc.DesktopCoordinates.top,
+        output_desc.DesktopCoordinates.right,
+        output_desc.DesktopCoordinates.bottom,
+        SWP_FRAMECHANGED | SWP_NOACTIVATE);
+
+    ShowWindow (hwnd, SW_MAXIMIZE);
+  }
+
+  GST_DEBUG_OBJECT (self, "Fullscreen mode change done");
+}
+
 static void
 gst_d3d11_window_on_keyboard_event (GstD3D11Window * self,
     HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
@@ -700,6 +828,28 @@ gst_d3d11_window_handle_window_proc (GstD3D11Window * self,
     case WM_MOUSEMOVE:
       gst_d3d11_window_on_mouse_event (self, hWnd, uMsg, wParam, lParam);
       break;
+    case WM_SYSKEYDOWN:
+      if ((self->fullscreen_toggle_mode &
+              GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_ALT_ENTER)
+          == GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_ALT_ENTER) {
+        WORD state = GetKeyState (VK_RETURN);
+        BYTE high = HIBYTE (state);
+
+        if (high & 0x1) {
+          self->requested_fullscreen = !self->fullscreen;
+          gst_d3d11_window_change_fullscreen_mode (self);
+        }
+      }
+      break;
+    case WM_GST_D3D11_FULLSCREEN:
+      if (g_atomic_int_get (&self->pending_fullscreen_count)) {
+        g_atomic_int_dec_and_test (&self->pending_fullscreen_count);
+        if ((self->fullscreen_toggle_mode &
+                GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_PROPERTY)
+            == GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_PROPERTY)
+          gst_d3d11_window_change_fullscreen_mode (self);
+      }
+      break;
     default:
       break;
   }
@@ -748,7 +898,7 @@ sub_class_proc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
   if (uMsg == WM_SIZE) {
     MoveWindow (self->internal_win_id, 0, 0, LOWORD (lParam), HIWORD (lParam),
         FALSE);
-  } else if (uMsg == WM_CLOSE) {
+  } else if (uMsg == WM_CLOSE || uMsg == WM_DESTROY) {
     g_mutex_lock (&self->lock);
     GST_WARNING_OBJECT (self, "external window is closing");
     release_external_win_id (self);
@@ -971,7 +1121,7 @@ gst_d3d11_window_prepare (GstD3D11Window * window, guint width, guint height,
 {
   DXGI_SWAP_CHAIN_DESC desc = { 0, };
   GstCaps *render_caps;
-  UINT swapchain_flags = 0;
+  UINT swapchain_flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
   DXGI_SWAP_EFFECT swap_effect = DXGI_SWAP_EFFECT_DISCARD;
 #if (DXGI_HEADER_VERSION >= 5)
   gboolean have_cll = FALSE;
@@ -1230,6 +1380,11 @@ gst_d3d11_window_prepare (GstD3D11Window * window, guint width, guint height,
     return FALSE;
   }
 
+  if (window->requested_fullscreen != window->fullscreen) {
+    g_atomic_int_add (&window->pending_fullscreen_count, 1);
+    PostMessage (window->internal_win_id, WM_GST_D3D11_FULLSCREEN, 0, 0);
+  }
+
   GST_DEBUG_OBJECT (window, "New swap chain 0x%p created", window->swap_chain);
 
   return TRUE;
index bcde80d..732ed99 100644 (file)
@@ -35,6 +35,7 @@ G_BEGIN_DECLS
 #define GST_IS_D3D11_WINDOW(obj)          (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_D3D11_WINDOW))
 #define GST_IS_D3D11_WINDOW_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_D3D11_WINDOW))
 #define GST_D3D11_WINDOW_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS((obj), GST_TYPE_D3D11_WINDOW, GstD3D11WindowClass))
+#define GST_D3D11_WINDOW_TOGGLE_MODE_GET_TYPE (gst_d3d11_window_fullscreen_toggle_mode_type())
 
 typedef struct _GstD3D11Window        GstD3D11Window;
 typedef struct _GstD3D11WindowClass   GstD3D11WindowClass;
@@ -48,6 +49,15 @@ typedef enum
   GST_D3D11_WINDOW_OVERLAY_STATE_CLOSED,
 } GstD3D11WindowOverlayState;
 
+typedef enum
+{
+  GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_NONE = 0,
+  GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_ALT_ENTER = (1 << 1),
+  GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_PROPERTY = (1 << 2),
+} GstD3D11WindowFullscreenToggleMode;
+
+GType gst_d3d11_window_fullscreen_toggle_mode_type (void);
+
 struct _GstD3D11Window
 {
   GstObject parent;
@@ -103,11 +113,22 @@ struct _GstD3D11Window
 
   gboolean pending_resize;
 
+  /* properties */
   gboolean force_aspect_ratio;
   gboolean enable_navigation_events;
+  GstD3D11WindowFullscreenToggleMode fullscreen_toggle_mode;
+  gboolean requested_fullscreen;
+  gboolean fullscreen;
+
+  /* atomic */
+  volatile gint pending_fullscreen_count;
 
   GstBuffer *cached_buffer;
   gboolean allow_tearing;
+
+  /* fullscreen related */
+  RECT restore_rect;
+  LONG restore_style;
 };
 
 struct _GstD3D11WindowClass