d3d12videosink: Add support for fullscreen mode

Adding "fullscreen-on-alt-enter" and "fullscreen" properties so that
use can control fullscreen mode switch

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/5891>
This commit is contained in:
Seungha Yang 2024-01-05 21:22:00 +09:00 committed by GStreamer Marge Bot
parent 00bbac6541
commit de331217aa
3 changed files with 260 additions and 12 deletions

View file

@ -35,12 +35,16 @@ enum
PROP_FORCE_ASPECT_RATIO,
PROP_ENABLE_NAVIGATION_EVENTS,
PROP_ROTATE_METHOD,
PROP_FULLSCREEN_ON_ALT_ENTER,
PROP_FULLSCREEN,
};
#define DEFAULT_ADAPTER -1
#define DEFAULT_FORCE_ASPECT_RATIO TRUE
#define DEFAULT_ENABLE_NAVIGATION_EVENTS TRUE
#define DEFAULT_ROTATE_METHOD GST_VIDEO_ORIENTATION_IDENTITY
#define DEFAULT_FULLSCREEN_ON_ALT_ENTER FALSE
#define DEFAULT_FULLSCREEN FALSE
static GstStaticPadTemplate sink_template =
GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
@ -96,6 +100,8 @@ struct GstD3D12VideoSinkPrivate
GstVideoOrientationMethod orientation = DEFAULT_ROTATE_METHOD;
GstVideoOrientationMethod orientation_from_tag = DEFAULT_ROTATE_METHOD;
GstVideoOrientationMethod orientation_selected = DEFAULT_ROTATE_METHOD;
gboolean fullscreen_on_alt_enter = DEFAULT_FULLSCREEN_ON_ALT_ENTER;
gboolean fullscreen = DEFAULT_FULLSCREEN;
};
/* *INDENT-ON* */
@ -138,6 +144,8 @@ static void gst_d3d12_video_sink_key_event (GstD3D12Window * window,
static void gst_d3d12_video_sink_mouse_event (GstD3D12Window * window,
const gchar * event, gint button, gdouble x, gdouble y,
GstD3D12VideoSink * self);
static void gst_d3d12_video_sink_on_fullscreen (GstD3D12Window * window,
gboolean is_fullscreen, GstD3D12VideoSink * self);
static void
gst_d3d12_video_sink_video_overlay_init (GstVideoOverlayInterface * iface);
@ -193,6 +201,19 @@ gst_d3d12_video_sink_class_init (GstD3D12VideoSinkClass * klass)
GST_TYPE_VIDEO_ORIENTATION_METHOD, GST_VIDEO_ORIENTATION_IDENTITY,
(GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
g_object_class_install_property (object_class,
PROP_FULLSCREEN_ON_ALT_ENTER,
g_param_spec_boolean ("fullscreen-on-alt-enter",
"Fullscreen on Alt Enter",
"Enable fullscreen toggle on alt+enter key input",
DEFAULT_FULLSCREEN_ON_ALT_ENTER,
(GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
g_object_class_install_property (object_class, PROP_FULLSCREEN,
g_param_spec_boolean ("fullscreen", "Fullscreen",
"Fullscreen mode", DEFAULT_FULLSCREEN,
(GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
element_class->set_context =
GST_DEBUG_FUNCPTR (gst_d3d12_video_sink_set_context);
@ -228,6 +249,8 @@ gst_d3d12_video_sink_init (GstD3D12VideoSink * self)
G_CALLBACK (gst_d3d12_video_sink_key_event), self);
g_signal_connect (priv->window, "mouse-event",
G_CALLBACK (gst_d3d12_video_sink_mouse_event), self);
g_signal_connect (priv->window, "fullscreen",
G_CALLBACK (gst_d3d12_video_sink_on_fullscreen), self);
}
static void
@ -267,6 +290,15 @@ gst_d3d12_videosink_set_property (GObject * object, guint prop_id,
gst_d3d12_video_sink_set_orientation (self,
(GstVideoOrientationMethod) g_value_get_enum (value), FALSE);
break;
case PROP_FULLSCREEN_ON_ALT_ENTER:
priv->fullscreen_on_alt_enter = g_value_get_boolean (value);
gst_d3d12_window_enable_fullscreen_on_alt_enter (priv->window,
priv->fullscreen_on_alt_enter);
break;
case PROP_FULLSCREEN:
priv->fullscreen = g_value_get_boolean (value);
gst_d3d12_window_set_fullscreen (priv->window, priv->fullscreen);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@ -294,6 +326,12 @@ gst_d3d12_videosink_get_property (GObject * object, guint prop_id,
case PROP_ROTATE_METHOD:
g_value_set_enum (value, priv->orientation);
break;
case PROP_FULLSCREEN_ON_ALT_ENTER:
g_value_set_boolean (value, priv->fullscreen_on_alt_enter);
break;
case PROP_FULLSCREEN:
g_value_set_boolean (value, priv->fullscreen);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@ -442,6 +480,25 @@ gst_d3d12_video_sink_mouse_event (GstD3D12Window * window, const gchar * event,
gst_navigation_send_event_simple (GST_NAVIGATION (self), mouse_event);
}
static void
gst_d3d12_video_sink_on_fullscreen (GstD3D12Window * window,
gboolean is_fullscreen, GstD3D12VideoSink * self)
{
auto priv = self->priv;
gboolean notify = FALSE;
{
std::lock_guard < std::recursive_mutex > lk (priv->lock);
if (priv->fullscreen != is_fullscreen) {
priv->fullscreen = is_fullscreen;
notify = TRUE;
}
}
if (notify)
g_object_notify (G_OBJECT (self), "fullscreen");
}
static GstFlowReturn
gst_d3d12_video_sink_update_window (GstD3D12VideoSink * self)
{

View file

@ -44,6 +44,7 @@ GST_DEBUG_CATEGORY_STATIC (gst_d3d12_window_debug);
#define WS_GST_D3D12 (WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW)
#define EXTERNAL_PROC_PROP_NAME L"gst-d3d12-hwnd-external-proc"
#define D3D12_WINDOW_PROP_NAME L"gst-d3d12-hwnd-obj"
#define WM_GST_D3D12_FULLSCREEN (WM_USER + 1)
#define WM_GST_D3D12_CONSTRUCT_INTERNAL_WINDOW (WM_USER + 2)
#define WM_GST_D3D12_DESTROY_INTERNAL_WINDOW (WM_USER + 3)
#define WM_GST_D3D12_UPDATE_RENDER_RECT (WM_USER + 4)
@ -54,18 +55,12 @@ enum
{
SIGNAL_KEY_EVENT,
SIGNAL_MOUSE_EVENT,
SIGNAL_FULLSCREEN,
SIGNAL_LAST
};
static guint d3d12_window_signals[SIGNAL_LAST] = { 0, };
enum HwndState
{
HWND_STATE_INIT,
HWND_STATE_OPENED,
HWND_STATE_CLOSED,
};
/* *INDENT-OFF* */
struct SwapBuffer
{
@ -177,6 +172,13 @@ struct GstD3D12WindowPrivate
GstVideoOrientationMethod orientation = GST_VIDEO_ORIENTATION_IDENTITY;
/* fullscreen related variables */
gboolean fullscreen_on_alt_enter = TRUE;
gboolean requested_fullscreen = FALSE;
gboolean applied_fullscreen = FALSE;
LONG restore_style;
WINDOWPLACEMENT restore_placement;
GstD3D12FenceDataPool *fence_data_pool;
/* User specified rect */
@ -249,6 +251,11 @@ gst_d3d12_window_class_init (GstD3D12WindowClass * klass)
G_SIGNAL_RUN_LAST, 0, nullptr, nullptr, nullptr,
G_TYPE_NONE, 4, G_TYPE_STRING, G_TYPE_INT, G_TYPE_DOUBLE, G_TYPE_DOUBLE);
d3d12_window_signals[SIGNAL_FULLSCREEN] =
g_signal_new ("fullscreen", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, 0, nullptr, nullptr, nullptr,
G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
GST_DEBUG_CATEGORY_INIT (gst_d3d12_window_debug,
"d3d12window", 0, "d3d12window");
}
@ -417,6 +424,83 @@ gst_d3d12_window_on_mouse_event (GstD3D12Window * self, UINT msg, WPARAM wparam,
event, button, final_x, final_y);
}
static void
gst_d3d12_window_toggle_fullscreen_mode (GstD3D12Window * self,
gboolean emit_signal)
{
auto priv = self->priv;
HWND hwnd = nullptr;
gboolean is_fullscreen;
ComPtr < IDXGISwapChain > swapchain;
{
std::lock_guard < std::mutex > hlk (priv->hwnd_lock);
hwnd = priv->external_hwnd ? priv->external_hwnd : priv->hwnd;
if (!hwnd)
return;
if (priv->requested_fullscreen == priv->applied_fullscreen)
return;
{
std::lock_guard < std::recursive_mutex > lk (priv->lock);
if (priv->ctx)
swapchain = priv->ctx->swapchain;
}
if (!swapchain)
return;
GST_DEBUG_OBJECT (self, "Change mode to %s",
priv->requested_fullscreen ? "fullscreen" : "windowed");
priv->applied_fullscreen = priv->requested_fullscreen;
is_fullscreen = priv->applied_fullscreen;
}
if (!is_fullscreen) {
SetWindowLongW (hwnd, GWL_STYLE, priv->restore_style);
SetWindowPlacement (hwnd, &priv->restore_placement);
} else {
ComPtr < IDXGIOutput > output;
DXGI_OUTPUT_DESC output_desc;
/* remember current placement to restore window later */
GetWindowPlacement (hwnd, &priv->restore_placement);
/* show window before change style */
ShowWindow (hwnd, SW_SHOW);
priv->restore_style = GetWindowLong (hwnd, GWL_STYLE);
/* Make the window borderless so that the client area can fill the screen */
SetWindowLongA (hwnd, GWL_STYLE,
priv->restore_style &
~(WS_CAPTION | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_SYSMENU |
WS_THICKFRAME | WS_MAXIMIZE));
swapchain->GetContainingOutput (&output);
output->GetDesc (&output_desc);
SetWindowPos (hwnd, HWND_TOP,
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");
if (emit_signal) {
g_signal_emit (self, d3d12_window_signals[SIGNAL_FULLSCREEN],
0, is_fullscreen);
}
}
static GstD3D12Window *
gst_d3d12_window_from_hwnd (HWND hwnd)
{
@ -464,6 +548,42 @@ gst_d3d12_window_proc (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
}
return 0;
}
case WM_GST_D3D12_FULLSCREEN:
{
auto self = gst_d3d12_window_from_hwnd (hwnd);
if (self) {
gst_d3d12_window_toggle_fullscreen_mode (self, FALSE);
gst_object_unref (self);
}
return 0;
}
case WM_SYSKEYDOWN:
{
WORD state = GetKeyState (VK_RETURN);
BYTE high = HIBYTE (state);
if (high & 0x1) {
auto self = gst_d3d12_window_from_hwnd (hwnd);
if (self) {
auto priv = self->priv;
bool do_toggle = false;
{
std::lock_guard < std::mutex > lk (priv->hwnd_lock);
if (priv->fullscreen_on_alt_enter) {
priv->requested_fullscreen = !priv->applied_fullscreen;
do_toggle = true;
}
}
if (do_toggle)
gst_d3d12_window_toggle_fullscreen_mode (self, TRUE);
gst_object_unref (self);
}
}
break;
}
case WM_SIZE:
{
auto self = gst_d3d12_window_from_hwnd (hwnd);
@ -611,7 +731,7 @@ gst_d3d12_window_create_hwnd (GstD3D12Window * self)
priv->hwnd = CreateWindowExW (0, L"GstD3D12Hwnd", title.c_str (),
style, x, y, w, h, (HWND) nullptr, (HMENU) nullptr, inst, self);
priv->applied_fullscreen = FALSE;
priv->internal_hwnd_thread = g_thread_self ();
}
@ -697,6 +817,26 @@ sub_class_proc (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
gst_object_unref (self);
return 0;
}
case WM_SYSKEYDOWN:
{
WORD state = GetKeyState (VK_RETURN);
BYTE high = HIBYTE (state);
if (high & 0x1) {
bool do_toggle = false;
{
std::lock_guard < std::mutex > lk (priv->hwnd_lock);
if (priv->fullscreen_on_alt_enter) {
priv->requested_fullscreen = !priv->applied_fullscreen;
do_toggle = true;
}
}
if (do_toggle)
gst_d3d12_window_toggle_fullscreen_mode (self, TRUE);
}
break;
}
case WM_SIZE:
if (priv->render_rect.w > 0 || priv->render_rect.h > 0) {
MoveWindow (priv->hwnd,
@ -834,7 +974,7 @@ gst_d3d12_window_prepare_hwnd (GstD3D12Window * self, guintptr window_handle)
priv->hwnd_cond.wait (lk);
}
if (priv->state != HWND_STATE_OPENED) {
if (priv->state != GST_D3D12_WINDOW_STATE_OPENED) {
if (priv->flushing) {
GST_DEBUG_OBJECT (self, "We are flushing");
return GST_FLOW_FLUSHING;
@ -889,10 +1029,12 @@ gst_d3d12_window_unprepare (GstD3D12Window * window)
g_main_loop_quit (priv->loop);
g_clear_pointer (&priv->main_loop_thread, g_thread_join);
std::lock_guard < std::mutex > lk (priv->hwnd_lock);
priv->hwnd = nullptr;
priv->external_hwnd = nullptr;
priv->internal_hwnd_thread = nullptr;
priv->state = GST_D3D12_WINDOW_STATE_INIT;
priv->applied_fullscreen = FALSE;
}
void
@ -927,7 +1069,8 @@ gst_d3d12_window_on_resize (GstD3D12Window * self)
if (!priv->ctx)
return GST_FLOW_OK;
priv->ctx->WaitGpu ();
if (priv->ctx->fence_val != 0)
priv->ctx->WaitGpu ();
priv->ctx->swap_buffers.clear ();
DXGI_SWAP_CHAIN_DESC desc = { };
@ -1010,7 +1153,7 @@ gst_d3d12_window_prepare (GstD3D12Window * window, GstD3D12Device * device,
return ret;
}
std::lock_guard < std::recursive_mutex > lk (priv->lock);
std::unique_lock < std::recursive_mutex > lk (priv->lock);
HRESULT hr;
if (window->device != device) {
@ -1046,6 +1189,19 @@ gst_d3d12_window_prepare (GstD3D12Window * window, GstD3D12Device * device,
return GST_FLOW_ERROR;
}
ComPtr < IDXGIFactory1 > parent_factory;
hr = swapchain->GetParent (IID_PPV_ARGS (&parent_factory));
if (!gst_d3d12_result (hr, window->device)) {
GST_WARNING_OBJECT (window, "Couldn't get parent factory");
} else {
hr = parent_factory->MakeWindowAssociation (priv->hwnd,
DXGI_MWA_NO_ALT_ENTER);
if (!gst_d3d12_result (hr, window->device)) {
GST_WARNING_OBJECT (window, "MakeWindowAssociation failed, hr: 0x%x",
(guint) hr);
}
}
hr = swapchain.As (&ctx->swapchain);
if (!gst_d3d12_result (hr, window->device)) {
GST_ERROR_OBJECT (window, "IDXGISwapChain4 interface is unavailable");
@ -1089,7 +1245,17 @@ gst_d3d12_window_prepare (GstD3D12Window * window, GstD3D12Device * device,
return GST_FLOW_ERROR;
}
return gst_d3d12_window_on_resize (window);
ret = gst_d3d12_window_on_resize (window);
if (ret != GST_FLOW_OK)
return ret;
lk.unlock ();
std::lock_guard < std::mutex > hlk (priv->hwnd_lock);
if (priv->requested_fullscreen != priv->applied_fullscreen)
PostMessageW (priv->hwnd, WM_GST_D3D12_FULLSCREEN, 0, 0);
return GST_FLOW_OK;
}
GstFlowReturn
@ -1434,3 +1600,22 @@ gst_d3d12_window_get_state (GstD3D12Window * window)
return priv->state;
}
void
gst_d3d12_window_enable_fullscreen_on_alt_enter (GstD3D12Window * window,
gboolean enable)
{
auto priv = window->priv;
std::lock_guard < std::mutex > lk (priv->hwnd_lock);
priv->fullscreen_on_alt_enter = enable;
}
void
gst_d3d12_window_set_fullscreen (GstD3D12Window * window, gboolean enable)
{
auto priv = window->priv;
std::lock_guard < std::mutex > lk (priv->hwnd_lock);
priv->requested_fullscreen = enable;
if (priv->hwnd && priv->applied_fullscreen != priv->requested_fullscreen)
PostMessageW (priv->hwnd, WM_GST_D3D12_FULLSCREEN, 0, 0);
}

View file

@ -77,5 +77,11 @@ void gst_d3d12_window_set_orientation (GstD3D12Window * window,
void gst_d3d12_window_set_title (GstD3D12Window * window,
const gchar * title);
void gst_d3d12_window_enable_fullscreen_on_alt_enter (GstD3D12Window * window,
gboolean enable);
void gst_d3d12_window_set_fullscreen (GstD3D12Window * window,
gboolean enable);
G_END_DECLS