d3d12videosink: Add support for window handle update

A large refactoring commit for adding features and improve performance

* Reuse internal converter and overlay compositor:
  Converter can be reused as long as input and display formats are not
  changed. Also overlay compositor reconstruction is required only if
  display format is changed

* Don't wait for full GPU flush on resize or close:
  D3D12 swapchain requires GPU idle in order to resize backbuffer.
  Thus CPU side waiting is required for swapchain related commands
  to be finished. However, don't need to wait for full GPU flushing.

* Support multiple sink on a single external window
  Keep installed subclass window procedure even if there's no associated
  our internal HWND. This will make window procedure hooking less racy.
  Then parent HWND's message will be transferred to our internal HWNDs
  if needed.

* Adding support for window handle update
  Application can change target HWND even when videosink is playing or
  paused state. So, users can call gst_video_overlay_set_window_handle()
  against d3d12videosink anytime. The videosink will be able to update
  internal state and setup resource upon requested.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/7013>
This commit is contained in:
Seungha Yang 2024-06-10 23:40:55 +09:00 committed by GStreamer Marge Bot
parent c00c36e33b
commit 37e1847464
14 changed files with 3032 additions and 1602 deletions

View file

@ -24,6 +24,7 @@
#include <gst/d3d12/gstd3d12device-private.h>
#include <gst/d3d12/gstd3d12format-private.h>
#include <gst/d3d12/gstd3d12converter-private.h>
#include <gst/d3d12/gstd3d12commandqueue-private.h>
#include <gst/d3d12/gstd3d12compat.h>
/*

View file

@ -0,0 +1,32 @@
/* GStreamer
* Copyright (C) 2024 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
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <gst/gst.h>
#include <gst/d3d12/gstd3d12_fwd.h>
G_BEGIN_DECLS
GST_D3D12_API
HRESULT gst_d3d12_command_queue_idle_for_swapchain (GstD3D12CommandQueue * queue,
guint64 fence_value,
HANDLE event_handle);
G_END_DECLS

View file

@ -582,3 +582,55 @@ gst_d3d12_command_queue_drain (GstD3D12CommandQueue * queue)
return S_OK;
}
HRESULT
gst_d3d12_command_queue_idle_for_swapchain (GstD3D12CommandQueue * queue,
guint64 fence_value, HANDLE event_handle)
{
g_return_val_if_fail (GST_IS_D3D12_COMMAND_QUEUE (queue), E_INVALIDARG);
auto priv = queue->priv;
guint64 fence_to_wait = fence_value;
HRESULT hr;
{
std::lock_guard < std::mutex > lk (priv->execute_lock);
if (fence_value < priv->fence_val) {
fence_to_wait = fence_value + 1;
} else {
priv->fence_val++;
hr = priv->cq->Signal (priv->fence.Get (), priv->fence_val);
if (FAILED (hr)) {
GST_ERROR_OBJECT (queue, "Signal failed");
priv->fence_val--;
return hr;
}
fence_to_wait = priv->fence_val;
}
}
auto completed = priv->fence->GetCompletedValue ();
if (completed < fence_to_wait) {
bool close_handle = false;
if (!event_handle) {
event_handle = CreateEventEx (nullptr, nullptr, 0, EVENT_ALL_ACCESS);
close_handle = true;
}
hr = priv->fence->SetEventOnCompletion (fence_to_wait, event_handle);
if (FAILED (hr)) {
GST_ERROR_OBJECT (queue, "SetEventOnCompletion failed");
if (close_handle)
CloseHandle (event_handle);
return hr;
}
WaitForSingleObjectEx (event_handle, INFINITE, FALSE);
if (close_handle)
CloseHandle (event_handle);
}
return S_OK;
}

View file

@ -159,10 +159,12 @@ struct GstD3D12VideoSinkPrivate
GstD3D12VideoSinkPrivate ()
{
window = gst_d3d12_window_new ();
convert_config = gst_structure_new_empty ("convert-config");
}
~GstD3D12VideoSinkPrivate ()
{
gst_structure_free (convert_config);
gst_clear_caps (&caps);
gst_clear_object (&window);
if (pool) {
@ -181,8 +183,10 @@ struct GstD3D12VideoSinkPrivate
GstCaps *caps = nullptr;
gboolean update_window = FALSE;
GstBufferPool *pool = nullptr;
GstStructure *convert_config;
gboolean warn_closed_window = FALSE;
gboolean window_open_called = FALSE;
std::recursive_mutex lock;
/* properties */
@ -545,6 +549,7 @@ gst_d3d12_video_sink_dispose (GObject * object)
auto self = GST_D3D12_VIDEO_SINK (object);
auto priv = self->priv;
gst_d3d12_window_invalidate (priv->window);
g_signal_handlers_disconnect_by_data (priv->window, self);
G_OBJECT_CLASS (parent_class)->dispose (object);
@ -906,6 +911,23 @@ gst_d3d12_video_sink_set_info (GstVideoSink * sink, GstCaps * caps,
GST_DEBUG_OBJECT (self, "scaling to %dx%d",
GST_VIDEO_SINK_WIDTH (self), GST_VIDEO_SINK_HEIGHT (self));
if (priv->pool) {
gst_buffer_pool_set_active (priv->pool, FALSE);
gst_clear_object (&priv->pool);
}
priv->pool = gst_d3d12_buffer_pool_new (self->device);
auto config = gst_buffer_pool_get_config (priv->pool);
gst_buffer_pool_config_set_params (config, priv->caps, priv->info.size, 0, 0);
if (!gst_buffer_pool_set_config (priv->pool, config) ||
!gst_buffer_pool_set_active (priv->pool, TRUE)) {
GST_ELEMENT_ERROR (self, RESOURCE, FAILED, (nullptr),
("Couldn't setup buffer pool"));
gst_clear_object (&priv->pool);
return FALSE;
}
return TRUE;
}
@ -975,92 +997,126 @@ gst_d3d12_video_sink_on_fullscreen (GstD3D12Window * window,
g_object_notify (G_OBJECT (self), "fullscreen");
}
static gboolean
gst_d3d12_video_sink_foreach_meta (GstBuffer * buffer, GstMeta ** meta,
GstBuffer * uploaded)
{
if ((*meta)->info->api != GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE)
return TRUE;
auto cmeta = (GstVideoOverlayCompositionMeta *) (*meta);
if (!cmeta->overlay)
return TRUE;
if (gst_video_overlay_composition_n_rectangles (cmeta->overlay) == 0)
return TRUE;
gst_buffer_add_video_overlay_composition_meta (uploaded, cmeta->overlay);
return TRUE;
}
static GstFlowReturn
gst_d3d12_video_sink_update_window (GstD3D12VideoSink * self)
gst_d3d12_video_sink_set_buffer (GstD3D12VideoSink * self,
GstBuffer * buffer, gboolean is_prepare)
{
auto priv = self->priv;
auto overlay = GST_VIDEO_OVERLAY (self);
bool notify_window_handle = false;
guintptr window_handle = 0;
auto window_state = gst_d3d12_window_get_state (priv->window);
if (window_state == GST_D3D12_WINDOW_STATE_CLOSED) {
GST_WARNING_OBJECT (self, "Window was closed");
return GST_D3D12_WINDOW_FLOW_CLOSED;
}
GstFlowReturn ret = GST_FLOW_OK;
gboolean set_buffer = FALSE;
gboolean update_window = FALSE;
{
std::lock_guard < std::recursive_mutex > lk (priv->lock);
if (window_state == GST_D3D12_WINDOW_STATE_OPENED && !priv->update_window)
return GST_FLOW_OK;
GST_DEBUG_OBJECT (self, "Updating window with caps %" GST_PTR_FORMAT,
priv->caps);
if (window_state == GST_D3D12_WINDOW_STATE_INIT) {
if (!priv->window_handle)
gst_video_overlay_prepare_window_handle (overlay);
if (priv->window_handle) {
gst_video_overlay_got_window_handle (overlay, priv->window_handle);
window_handle = priv->window_handle;
if (is_prepare) {
if (priv->update_window) {
set_buffer = FALSE;
update_window = FALSE;
} else {
notify_window_handle = true;
set_buffer = TRUE;
update_window = FALSE;
}
} else {
window_handle = priv->window_handle;
if (priv->update_window) {
set_buffer = TRUE;
update_window = TRUE;
priv->update_window = FALSE;
} else {
set_buffer = FALSE;
update_window = FALSE;
}
}
priv->update_window = FALSE;
}
if (priv->pool) {
gst_buffer_pool_set_active (priv->pool, FALSE);
gst_clear_object (&priv->pool);
}
if (update_window) {
gst_structure_set (priv->convert_config,
GST_D3D12_CONVERTER_OPT_GAMMA_MODE,
GST_TYPE_VIDEO_GAMMA_MODE, priv->gamma_mode,
GST_D3D12_CONVERTER_OPT_PRIMARIES_MODE,
GST_TYPE_VIDEO_PRIMARIES_MODE, priv->primaries_mode,
GST_D3D12_CONVERTER_OPT_SAMPLER_FILTER,
GST_TYPE_D3D12_CONVERTER_SAMPLER_FILTER,
gst_d3d12_sampling_method_to_native (priv->sampling_method),
GST_D3D12_CONVERTER_OPT_DEST_ALPHA_MODE,
GST_TYPE_D3D12_CONVERTER_ALPHA_MODE,
GST_VIDEO_INFO_HAS_ALPHA (&priv->info) ?
GST_D3D12_CONVERTER_ALPHA_MODE_PREMULTIPLIED :
GST_D3D12_CONVERTER_ALPHA_MODE_UNSPECIFIED, nullptr);
auto config = gst_structure_new ("convert-config",
GST_D3D12_CONVERTER_OPT_GAMMA_MODE,
GST_TYPE_VIDEO_GAMMA_MODE, priv->gamma_mode,
GST_D3D12_CONVERTER_OPT_PRIMARIES_MODE,
GST_TYPE_VIDEO_PRIMARIES_MODE, priv->primaries_mode,
GST_D3D12_CONVERTER_OPT_SAMPLER_FILTER,
GST_TYPE_D3D12_CONVERTER_SAMPLER_FILTER,
gst_d3d12_sampling_method_to_native (priv->sampling_method), nullptr);
auto ret = gst_d3d12_window_prepare (priv->window, self->device,
window_handle, GST_VIDEO_SINK_WIDTH (self),
GST_VIDEO_SINK_HEIGHT (self), priv->caps, config, priv->display_format);
if (ret != GST_FLOW_OK) {
if (ret == GST_FLOW_FLUSHING) {
GST_WARNING_OBJECT (self, "We are flushing");
gst_d3d12_window_unprepare (priv->window);
ret = gst_d3d12_window_prepare (priv->window, self->device,
GST_VIDEO_SINK_WIDTH (self), GST_VIDEO_SINK_HEIGHT (self), priv->caps,
priv->convert_config, priv->display_format);
if (ret != GST_FLOW_OK)
return ret;
}
if (!set_buffer)
return GST_FLOW_OK;
GstBuffer *upload = nullptr;
auto mem = gst_buffer_peek_memory (buffer, 0);
if (!gst_is_d3d12_memory (mem)) {
gst_buffer_pool_acquire_buffer (priv->pool, &upload, nullptr);
if (!upload) {
GST_ERROR_OBJECT (self, "Couldn't allocate upload buffer");
return GST_FLOW_ERROR;
}
GST_ELEMENT_ERROR (self, RESOURCE, FAILED, (nullptr),
("Couldn't setup swapchain"));
return GST_FLOW_ERROR;
GstVideoFrame in_frame, out_frame;
if (!gst_video_frame_map (&in_frame, &priv->info, buffer, GST_MAP_READ)) {
GST_ERROR_OBJECT (self, "Couldn't map input frame");
gst_buffer_unref (upload);
return GST_FLOW_ERROR;
}
if (!gst_video_frame_map (&out_frame, &priv->info, upload, GST_MAP_WRITE)) {
GST_ERROR_OBJECT (self, "Couldn't map upload frame");
gst_video_frame_unmap (&in_frame);
gst_buffer_unref (upload);
return GST_FLOW_ERROR;
}
auto copy_ret = gst_video_frame_copy (&out_frame, &in_frame);
gst_video_frame_unmap (&out_frame);
gst_video_frame_unmap (&in_frame);
if (!copy_ret) {
GST_ERROR_OBJECT (self, "Couldn't copy frame");
gst_buffer_unref (upload);
return GST_FLOW_ERROR;
}
gst_buffer_foreach_meta (buffer,
(GstBufferForeachMetaFunc) gst_d3d12_video_sink_foreach_meta, upload);
buffer = upload;
}
if (notify_window_handle && !window_handle) {
window_handle = gst_d3d12_window_get_window_handle (priv->window);
gst_video_overlay_got_window_handle (overlay, window_handle);
}
ret = gst_d3d12_window_set_buffer (priv->window, buffer);
priv->pool = gst_d3d12_buffer_pool_new (self->device);
config = gst_buffer_pool_get_config (priv->pool);
if (upload)
gst_buffer_unref (upload);
gst_buffer_pool_config_set_params (config, priv->caps, priv->info.size, 0, 0);
if (!gst_buffer_pool_set_config (priv->pool, config) ||
!gst_buffer_pool_set_active (priv->pool, TRUE)) {
GST_ELEMENT_ERROR (self, RESOURCE, FAILED, (nullptr),
("Couldn't setup buffer pool"));
return GST_FLOW_ERROR;
}
return GST_FLOW_OK;
return ret;
}
static gboolean
@ -1078,6 +1134,7 @@ gst_d3d12_video_sink_start (GstBaseSink * sink)
}
priv->warn_closed_window = TRUE;
priv->window_open_called = FALSE;
return TRUE;
}
@ -1233,7 +1290,7 @@ gst_d3d12_video_sink_query (GstBaseSink * sink, GstQuery * query)
return GST_BASE_SINK_CLASS (parent_class)->query (sink, query);
}
static void
static GstFlowReturn
gst_d3d12_video_sink_check_device_update (GstD3D12VideoSink * self,
GstBuffer * buf)
{
@ -1241,11 +1298,11 @@ gst_d3d12_video_sink_check_device_update (GstD3D12VideoSink * self,
auto mem = gst_buffer_peek_memory (buf, 0);
if (!gst_is_d3d12_memory (mem))
return;
return GST_FLOW_OK;
auto dmem = GST_D3D12_MEMORY_CAST (mem);
if (gst_d3d12_device_is_equal (dmem->device, self->device))
return;
return GST_FLOW_OK;
GST_INFO_OBJECT (self, "Updating device %" GST_PTR_FORMAT " -> %"
GST_PTR_FORMAT, self->device, dmem->device);
@ -1258,25 +1315,90 @@ gst_d3d12_video_sink_check_device_update (GstD3D12VideoSink * self,
std::lock_guard < std::recursive_mutex > lk (priv->context_lock);
gst_clear_object (&self->device);
self->device = (GstD3D12Device *) gst_object_ref (dmem->device);
if (priv->pool) {
gst_buffer_pool_set_active (priv->pool, FALSE);
gst_clear_object (&priv->pool);
}
priv->pool = gst_d3d12_buffer_pool_new (self->device);
auto config = gst_buffer_pool_get_config (priv->pool);
gst_buffer_pool_config_set_params (config, priv->caps, priv->info.size, 0, 0);
if (!gst_buffer_pool_set_config (priv->pool, config) ||
!gst_buffer_pool_set_active (priv->pool, TRUE)) {
GST_ELEMENT_ERROR (self, RESOURCE, FAILED, (nullptr),
("Couldn't setup buffer pool"));
gst_clear_object (&priv->pool);
return GST_FLOW_ERROR;
}
return GST_FLOW_OK;
}
static gboolean
gst_d3d12_video_sink_foreach_meta (GstBuffer * buffer, GstMeta ** meta,
GstBuffer * uploaded)
static GstFlowReturn
gst_d3d12_video_sink_open_window (GstD3D12VideoSink * self)
{
if ((*meta)->info->api != GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE)
return TRUE;
auto overlay = GST_VIDEO_OVERLAY (self);
auto priv = self->priv;
guintptr window_handle = 0;
auto is_closed = gst_d3d12_window_is_closed (priv->window);
gboolean need_open = FALSE;
auto cmeta = (GstVideoOverlayCompositionMeta *) (*meta);
if (!cmeta->overlay)
return TRUE;
{
std::lock_guard < std::recursive_mutex > lk (priv->lock);
if (!priv->window_open_called) {
GST_DEBUG_OBJECT (self, "Open was not called, try open");
gst_video_overlay_prepare_window_handle (overlay);
need_open = TRUE;
} else if (priv->window_handle_updated) {
GST_DEBUG_OBJECT (self, "Set window handle was called, try open again");
need_open = TRUE;
} else if (is_closed) {
/* Request new window handle */
GST_LOG_OBJECT (self, "Window was closed, requesting new window handle");
gst_video_overlay_prepare_window_handle (overlay);
if (priv->window_handle_updated) {
GST_DEBUG_OBJECT (self, "Set window handle was called");
need_open = TRUE;
}
}
if (gst_video_overlay_composition_n_rectangles (cmeta->overlay) == 0)
return TRUE;
if (!need_open) {
if (!is_closed)
return GST_FLOW_OK;
gst_buffer_add_video_overlay_composition_meta (uploaded, cmeta->overlay);
GST_WARNING_OBJECT (self, "Window was closed");
return GST_D3D12_WINDOW_FLOW_CLOSED;
}
return TRUE;
window_handle = priv->window_handle;
priv->window_handle_updated = FALSE;
}
priv->window_open_called = TRUE;
priv->warn_closed_window = TRUE;
auto ret = gst_d3d12_window_open (priv->window, self->device,
GST_VIDEO_SINK_WIDTH (self), GST_VIDEO_SINK_HEIGHT (self),
(HWND) window_handle);
std::lock_guard < std::recursive_mutex > lk (priv->lock);
if (ret == GST_FLOW_OK) {
if (window_handle) {
GST_DEBUG_OBJECT (self, "Window created with HWND %p",
(void *) window_handle);
gst_video_overlay_got_window_handle (overlay, window_handle);
} else {
auto internal_hwnd = gst_d3d12_window_get_window_handle (priv->window);
GST_DEBUG_OBJECT (self, "Window created with internal HWND %p",
(void *) internal_hwnd);
gst_video_overlay_got_window_handle (overlay, internal_hwnd);
}
priv->update_window = TRUE;
}
return ret;
}
static GstFlowReturn
@ -1284,54 +1406,16 @@ gst_d3d12_video_sink_prepare (GstBaseSink * sink, GstBuffer * buffer)
{
auto self = GST_D3D12_VIDEO_SINK (sink);
auto priv = self->priv;
GstBuffer *upload = nullptr;
auto mem = gst_buffer_peek_memory (buffer, 0);
gst_d3d12_video_sink_check_device_update (self, buffer);
auto ret = gst_d3d12_video_sink_update_window (self);
auto ret = gst_d3d12_video_sink_check_device_update (self, buffer);
if (ret != GST_FLOW_OK)
goto out;
if (!gst_is_d3d12_memory (mem)) {
gst_buffer_pool_acquire_buffer (priv->pool, &upload, nullptr);
if (!upload) {
GST_ERROR_OBJECT (self, "Couldn't allocate upload buffer");
return GST_FLOW_ERROR;
}
ret = gst_d3d12_video_sink_open_window (self);
if (ret != GST_FLOW_OK)
goto out;
GstVideoFrame in_frame, out_frame;
if (!gst_video_frame_map (&in_frame, &priv->info, buffer, GST_MAP_READ)) {
GST_ERROR_OBJECT (self, "Couldn't map input frame");
gst_buffer_unref (upload);
return GST_FLOW_ERROR;
}
if (!gst_video_frame_map (&out_frame, &priv->info, upload, GST_MAP_WRITE)) {
GST_ERROR_OBJECT (self, "Couldn't map upload frame");
gst_video_frame_unmap (&in_frame);
gst_buffer_unref (upload);
return GST_FLOW_ERROR;
}
auto copy_ret = gst_video_frame_copy (&out_frame, &in_frame);
gst_video_frame_unmap (&out_frame);
gst_video_frame_unmap (&in_frame);
if (!copy_ret) {
GST_ERROR_OBJECT (self, "Couldn't copy frame");
gst_buffer_unref (upload);
return GST_FLOW_ERROR;
}
gst_buffer_foreach_meta (buffer,
(GstBufferForeachMetaFunc) gst_d3d12_video_sink_foreach_meta, upload);
buffer = upload;
}
ret = gst_d3d12_window_set_buffer (priv->window, buffer);
if (upload)
gst_buffer_unref (upload);
ret = gst_d3d12_video_sink_set_buffer (self, buffer, TRUE);
out:
if (ret == GST_D3D12_WINDOW_FLOW_CLOSED) {
@ -1359,9 +1443,13 @@ gst_d3d12_video_sink_show_frame (GstVideoSink * sink, GstBuffer * buf)
{
auto self = GST_D3D12_VIDEO_SINK (sink);
auto priv = self->priv;
auto sync = gst_base_sink_get_sync (GST_BASE_SINK_CAST (sink));
auto ret = gst_d3d12_video_sink_set_buffer (self, buf, FALSE);
if (ret != GST_FLOW_OK)
goto out;
auto ret = gst_d3d12_window_present (priv->window, sync);
ret = gst_d3d12_window_present (priv->window);
out:
if (ret == GST_D3D12_WINDOW_FLOW_CLOSED) {
if (priv->error_on_closed) {
GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
@ -1415,7 +1503,8 @@ gst_d3d12_video_sink_overlay_set_window_handle (GstVideoOverlay * overlay,
std::lock_guard < std::recursive_mutex > lk (priv->lock);
if (priv->window_handle != window_handle) {
priv->window_handle = window_handle;
priv->update_window = TRUE;
if (priv->window_handle)
priv->window_handle_updated = TRUE;
}
}

View file

@ -0,0 +1,78 @@
/* GStreamer
* Copyright (C) 2024 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
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <gst/d3d12/gstd3d12.h>
#include "gstd3d12pluginutils.h"
#include "gstd3d12overlaycompositor.h"
#include <mutex>
#include <vector>
#include <queue>
#include <memory>
#include <d3d11on12.h>
#include <d2d1_3.h>
#include <wrl.h>
struct SwapChainBuffer
{
SwapChainBuffer (GstBuffer * buffer, ID3D12Resource * backbuf_resource);
SwapChainBuffer () = delete;
~SwapChainBuffer();
Microsoft::WRL::ComPtr<ID2D1Bitmap1> d2d_target;
Microsoft::WRL::ComPtr<ID3D11Texture2D> wrapped_resource;
Microsoft::WRL::ComPtr<ID3D12Resource> resource;
GstBuffer *backbuf = nullptr;
bool is_first = true;
};
struct SwapChainResource
{
SwapChainResource (GstD3D12Device * device);
SwapChainResource () = delete;
~SwapChainResource();
void clear_resource ();
bool ensure_d3d11_target (SwapChainBuffer * swapbuf);
bool ensure_d2d_target (SwapChainBuffer * swapbuf);
Microsoft::WRL::ComPtr<IDXGISwapChain4> swapchain;
Microsoft::WRL::ComPtr<ID3D12GraphicsCommandList> cl;
Microsoft::WRL::ComPtr<ID3D11On12Device> device11on12;
Microsoft::WRL::ComPtr<ID3D11Device> device11;
Microsoft::WRL::ComPtr<ID3D11DeviceContext> context11;
Microsoft::WRL::ComPtr<ID2D1Factory3> factory2d;
Microsoft::WRL::ComPtr<ID2D1Device2> device2d;
Microsoft::WRL::ComPtr<ID2D1DeviceContext2> context2d;
std::vector<std::shared_ptr<SwapChainBuffer>> buffers;
GstBuffer *msaa_buf = nullptr;
GstBuffer *cached_buf = nullptr;
GstD3D12Converter *conv = nullptr;
GstD3D12OverlayCompositor *comp = nullptr;
GstD3D12Device *device = nullptr;
GstD3D12CommandAllocatorPool *ca_pool = nullptr;
HANDLE event_handle = nullptr;
UINT64 fence_val = 0;
std::queue<UINT64> prev_fence_val;
DXGI_FORMAT render_format = DXGI_FORMAT_UNKNOWN;
D3D12_RESOURCE_DESC buffer_desc;
};

View file

@ -0,0 +1,582 @@
/* GStreamer
* Copyright (C) 2024 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
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstd3d12window-swapchain.h"
#include <directx/d3dx12.h>
GST_DEBUG_CATEGORY_EXTERN (gst_d3d12_window_debug);
#define GST_CAT_DEFAULT gst_d3d12_window_debug
/* *INDENT-OFF* */
using namespace Microsoft::WRL;
#define BACK_BUFFER_COUNT 3
SwapChainBuffer::SwapChainBuffer (GstBuffer * buffer,
ID3D12Resource * backbuf_resource)
{
backbuf = buffer;
resource = backbuf_resource;
}
SwapChainBuffer::~SwapChainBuffer ()
{
d2d_target = nullptr;
wrapped_resource = nullptr;
resource = nullptr;
gst_clear_buffer (&backbuf);
}
SwapChainResource::SwapChainResource (GstD3D12Device * dev)
{
event_handle = CreateEventEx (nullptr, nullptr, 0, EVENT_ALL_ACCESS);
device = (GstD3D12Device *) gst_object_ref (dev);
auto device_handle = gst_d3d12_device_get_device_handle (device);
ca_pool = gst_d3d12_command_allocator_pool_new (device_handle,
D3D12_COMMAND_LIST_TYPE_DIRECT);
}
SwapChainResource::~SwapChainResource ()
{
GST_DEBUG_OBJECT (device, "Releasing swapchain resource");
context2d = nullptr;
device2d = nullptr;
factory2d = nullptr;
context11 = nullptr;
device11 = nullptr;
device11on12 = nullptr;
buffers.clear();
swapchain = nullptr;
cl = nullptr;
gst_clear_buffer (&msaa_buf);
gst_clear_buffer (&cached_buf);
gst_clear_object (&conv);
gst_clear_object (&comp);
gst_clear_object (&conv);
gst_clear_object (&ca_pool);
gst_clear_object (&device);
CloseHandle (event_handle);
}
void
SwapChainResource::clear_resource ()
{
if (!buffers.empty ()) {
auto cq = gst_d3d12_device_get_command_queue (device,
D3D12_COMMAND_LIST_TYPE_DIRECT);
gst_d3d12_command_queue_idle_for_swapchain (cq, fence_val, event_handle);
prev_fence_val = { };
}
if (context11)
gst_d3d12_device_11on12_lock (device);
buffers.clear ();
gst_clear_buffer (&msaa_buf);
if (context2d)
context2d->SetTarget (nullptr);
if (context11) {
context11->ClearState ();
context11->Flush ();
gst_d3d12_device_11on12_unlock (device);
}
}
static bool
ensure_d3d11 (SwapChainResource * resource)
{
if (resource->device11on12)
return true;
auto unknown = gst_d3d12_device_get_11on12_handle (resource->device);
if (!unknown)
return false;
unknown->QueryInterface (IID_PPV_ARGS (&resource->device11on12));
resource->device11on12.As (&resource->device11);
resource->device11->GetImmediateContext (&resource->context11);
return true;
}
static bool
ensure_d2d (SwapChainResource * resource)
{
if (resource->context2d)
return true;
if (!ensure_d3d11 (resource))
return false;
HRESULT hr;
if (!resource->factory2d) {
hr = D2D1CreateFactory (D2D1_FACTORY_TYPE_SINGLE_THREADED,
IID_PPV_ARGS (&resource->factory2d));
if (FAILED (hr))
return false;
}
GstD3D12Device11on12LockGuard lk (resource->device);
if (!resource->device2d) {
ComPtr<IDXGIDevice> device_dxgi;
hr = resource->device11.As (&device_dxgi);
if (FAILED (hr))
return false;
hr = resource->factory2d->CreateDevice (device_dxgi.Get (),
&resource->device2d);
if (FAILED (hr))
return false;
}
if (!resource->context2d) {
hr = resource->device2d->CreateDeviceContext (
D2D1_DEVICE_CONTEXT_OPTIONS_NONE, &resource->context2d);
if (FAILED (hr))
return false;
}
return true;
}
bool
SwapChainResource::ensure_d3d11_target (SwapChainBuffer * swapbuf)
{
if (swapbuf->wrapped_resource)
return true;
if (!ensure_d3d11 (this))
return false;
D3D11_RESOURCE_FLAGS d3d11_flags = { };
d3d11_flags.BindFlags = D3D11_BIND_RENDER_TARGET;
auto hr = device11on12->CreateWrappedResource (swapbuf->resource.Get (),
&d3d11_flags, D3D12_RESOURCE_STATE_RENDER_TARGET,
D3D12_RESOURCE_STATE_RENDER_TARGET,
IID_PPV_ARGS (&swapbuf->wrapped_resource));
if (FAILED (hr))
return false;
return true;
}
bool
SwapChainResource::ensure_d2d_target (SwapChainBuffer * swapbuf)
{
if (swapbuf->d2d_target)
return true;
if (!ensure_d2d (this))
return false;
if (!ensure_d3d11_target (swapbuf))
return false;
GstD3D12Device11on12LockGuard lk (device);
ComPtr<IDXGISurface> surface;
auto hr = swapbuf->wrapped_resource.As (&surface);
if (FAILED (hr))
return false;
D2D1_BITMAP_PROPERTIES1 props = D2D1::BitmapProperties1(
D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED));
hr = context2d->CreateBitmapFromDxgiSurface (surface.Get (), &props,
&swapbuf->d2d_target);
if (FAILED (hr))
return false;
return true;
}
SwapChain::SwapChain (GstD3D12Device * device)
{
resource_ = std::make_unique <SwapChainResource> (device);
}
struct AsyncReleaseData
{
std::unique_ptr<SwapChainResource> resource;
};
SwapChain::~SwapChain()
{
lock_.lock ();
if (!resource_->buffers.empty ()) {
auto cq = gst_d3d12_device_get_command_queue (resource_->device,
D3D12_COMMAND_LIST_TYPE_DIRECT);
gst_d3d12_command_queue_idle_for_swapchain (cq, resource_->fence_val,
resource_->event_handle);
}
resource_ = nullptr;
if (converter_config_)
gst_structure_free (converter_config_);
lock_.unlock ();
}
GstFlowReturn
SwapChain::setup_swapchain (GstD3D12Window * window, GstD3D12Device * device,
HWND hwnd, DXGI_FORMAT format, const GstVideoInfo * in_info,
const GstVideoInfo * out_info, GstStructure * conv_config,
bool & is_new_swapchain)
{
is_new_swapchain = false;
std::lock_guard <std::recursive_mutex> lk (lock_);
if (!gst_d3d12_device_is_equal (device, resource_->device)) {
gst_d3d12_device_fence_wait (resource_->device,
D3D12_COMMAND_LIST_TYPE_DIRECT, resource_->fence_val,
resource_->event_handle);
resource_ = std::make_unique <SwapChainResource> (device);
}
if (!resource_->swapchain) {
DXGI_SWAP_CHAIN_DESC1 desc = { };
desc.Format = format;
desc.SampleDesc.Count = 1;
desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
desc.BufferCount = BACK_BUFFER_COUNT;
desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
auto device = resource_->device;
auto factory = gst_d3d12_device_get_factory_handle (device);
auto cq = gst_d3d12_device_get_command_queue (device,
D3D12_COMMAND_LIST_TYPE_DIRECT);
auto cq_handle = gst_d3d12_command_queue_get_handle (cq);
ComPtr < IDXGISwapChain1 > swapchain;
auto hr = factory->CreateSwapChainForHwnd (cq_handle, hwnd, &desc, nullptr,
nullptr, &swapchain);
if (!gst_d3d12_result (hr, device))
return GST_FLOW_ERROR;
hr = swapchain.As (&resource_->swapchain);
if (!gst_d3d12_result (hr, device))
return GST_FLOW_ERROR;
is_new_swapchain = true;
} else {
resource_->clear_resource ();
if (render_format_ != format) {
gst_clear_object (&resource_->comp);
gst_clear_object (&resource_->conv);
} else {
if (GST_VIDEO_INFO_FORMAT (in_info) != in_format_) {
gst_clear_object (&resource_->conv);
} else if (converter_config_ &&
!gst_structure_is_equal (converter_config_, conv_config)) {
gst_clear_object (&resource_->conv);
}
}
}
if (converter_config_)
gst_structure_free (converter_config_);
converter_config_ = gst_structure_copy (conv_config);
if (!resource_->conv) {
resource_->conv = gst_d3d12_converter_new (resource_->device,
in_info, out_info, nullptr, nullptr, gst_structure_copy (conv_config));
if (!resource_->conv) {
GST_ERROR ("Couldn't create converter");
return GST_FLOW_ERROR;
}
} else {
g_object_set (resource_->conv, "src-x", (gint) 0, "src-y", (gint) 0,
"src-width", in_info->width, "src-height", in_info->height, nullptr);
}
if (!resource_->comp) {
resource_->comp = gst_d3d12_overlay_compositor_new (resource_->device,
out_info);
if (!resource_->comp) {
GST_ERROR ("Couldn't create overlay compositor");
return GST_FLOW_ERROR;
}
}
render_format_ = format;
crop_rect_ = CD3DX12_BOX (0, 0, in_info->width, in_info->height);
prev_crop_rect_ = crop_rect_;
return resize_buffer (window);
}
void
SwapChain::disable_alt_enter (HWND hwnd)
{
std::lock_guard <std::recursive_mutex> lk (lock_);
if (!resource_ || !resource_->swapchain)
return;
HRESULT hr = E_FAIL;
{
/* DXGI API is not thread safe, takes global lock */
static std::recursive_mutex factory_lock;
std::lock_guard <std::recursive_mutex> flk (factory_lock);
ComPtr < IDXGIFactory1 > parent_factory;
auto hr = resource_->swapchain->GetParent (IID_PPV_ARGS (&parent_factory));
if (SUCCEEDED (hr))
hr = parent_factory->MakeWindowAssociation (hwnd, DXGI_MWA_NO_ALT_ENTER);
}
if (SUCCEEDED (hr))
GST_DEBUG ("Alt-Enter is disabled for hwnd %p", hwnd);
}
GstFlowReturn
SwapChain::resize_buffer (GstD3D12Window * window)
{
std::lock_guard <std::recursive_mutex> lk (lock_);
if (!resource_->swapchain)
return GST_FLOW_OK;
auto device = resource_->device;
resource_->clear_resource ();
DXGI_SWAP_CHAIN_DESC desc = { };
resource_->swapchain->GetDesc (&desc);
auto hr = resource_->swapchain->ResizeBuffers (BACK_BUFFER_COUNT,
0, 0, render_format_, desc.Flags);
if (!gst_d3d12_result (hr, device))
return GST_FLOW_ERROR;
for (guint i = 0; i < BACK_BUFFER_COUNT; i++) {
ComPtr < ID3D12Resource > backbuf;
hr = resource_->swapchain->GetBuffer (i, IID_PPV_ARGS (&backbuf));
if (!gst_d3d12_result (hr, device)) {
GST_ERROR_OBJECT (device, "Couldn't get backbuffer");
return GST_FLOW_ERROR;
}
if (i == 0)
resource_->buffer_desc = GetDesc (backbuf);
auto mem = gst_d3d12_allocator_alloc_wrapped (nullptr, device,
backbuf.Get (), 0, nullptr, nullptr);
auto buf = gst_buffer_new ();
gst_buffer_append_memory (buf, mem);
auto swapbuf = std::make_shared < SwapChainBuffer > (buf, backbuf.Get ());
resource_->buffers.push_back (swapbuf);
}
auto buffer_desc = resource_->buffer_desc;
GstD3D12MSAAMode msaa_mode;
gst_d3d12_window_get_msaa (window, msaa_mode);
UINT sample_count = 1;
switch (msaa_mode) {
case GST_D3D12_MSAA_2X:
sample_count = 2;
break;
case GST_D3D12_MSAA_4X:
sample_count = 4;
break;
case GST_D3D12_MSAA_8X:
sample_count = 8;
break;
default:
break;
}
auto device_handle = gst_d3d12_device_get_device_handle (device);
D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS feature_data = { };
feature_data.Format = buffer_desc.Format;
feature_data.SampleCount = sample_count;
while (feature_data.SampleCount > 1) {
hr = device_handle->CheckFeatureSupport (
D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,
&feature_data, sizeof (feature_data));
if (SUCCEEDED (hr) && feature_data.NumQualityLevels > 0)
break;
feature_data.SampleCount /= 2;
}
if (feature_data.SampleCount > 1 && feature_data.NumQualityLevels > 0) {
GST_DEBUG_OBJECT (device, "Enable MSAA x%d with quality level %d",
feature_data.SampleCount, feature_data.NumQualityLevels - 1);
D3D12_HEAP_PROPERTIES heap_prop =
CD3DX12_HEAP_PROPERTIES (D3D12_HEAP_TYPE_DEFAULT);
D3D12_RESOURCE_DESC resource_desc =
CD3DX12_RESOURCE_DESC::Tex2D (buffer_desc.Format,
buffer_desc.Width, buffer_desc.Height,
1, 1, feature_data.SampleCount, feature_data.NumQualityLevels - 1,
D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET);
D3D12_CLEAR_VALUE clear_value = { };
clear_value.Format = buffer_desc.Format;
clear_value.Color[0] = 0.0f;
clear_value.Color[1] = 0.0f;
clear_value.Color[2] = 0.0f;
clear_value.Color[3] = 1.0f;
ComPtr < ID3D12Resource > msaa_texture;
hr = device_handle->CreateCommittedResource (&heap_prop,
D3D12_HEAP_FLAG_NONE,
&resource_desc, D3D12_RESOURCE_STATE_RENDER_TARGET, &clear_value,
IID_PPV_ARGS (&msaa_texture));
if (gst_d3d12_result (hr, device)) {
auto mem = gst_d3d12_allocator_alloc_wrapped (nullptr, device,
msaa_texture.Get (), 0, nullptr, nullptr);
resource_->msaa_buf = gst_buffer_new ();
gst_buffer_append_memory (resource_->msaa_buf, mem);
}
}
first_present_ = true;
backbuf_rendered_ = false;
GstFlowReturn ret = GST_FLOW_OK;
if (resource_->cached_buf) {
ret = set_buffer (window, resource_->cached_buf);
if (ret == GST_FLOW_OK)
ret = present ();
}
return ret;
}
GstFlowReturn
SwapChain::set_buffer (GstD3D12Window * window, GstBuffer * buffer)
{
std::lock_guard <std::recursive_mutex> lk (lock_);
if (!resource_->swapchain) {
if (!buffer) {
GST_DEBUG_OBJECT (window, "Swapchain is not configured");
return GST_FLOW_OK;
}
GST_ERROR_OBJECT (window, "Couldn't set buffer without swapchain");
return GST_FLOW_ERROR;
}
if (buffer)
gst_buffer_replace (&resource_->cached_buf, buffer);
if (!resource_->cached_buf)
return GST_FLOW_OK;
auto crop_rect = crop_rect_;
auto crop_meta = gst_buffer_get_video_crop_meta (resource_->cached_buf);
if (crop_meta) {
crop_rect = CD3DX12_BOX (crop_meta->x, crop_meta->y,
crop_meta->x + crop_meta->width, crop_meta->y + crop_meta->height);
}
if (crop_rect != prev_crop_rect_) {
g_object_set (resource_->conv, "src-x", (gint) crop_rect.left,
"src-y", (gint) crop_rect.top,
"src-width", (gint) (crop_rect.right - crop_rect.left),
"src-height", (gint) (crop_rect.bottom - crop_rect.top), nullptr);
prev_crop_rect_ = crop_rect;
}
before_rendering ();
auto ret = gst_d3d12_window_render (window, resource_.get (),
resource_->cached_buf, first_present_, output_rect_);
after_rendering ();
if (ret == GST_FLOW_OK)
backbuf_rendered_ = true;
return ret;
}
GstFlowReturn
SwapChain::present ()
{
std::lock_guard <std::recursive_mutex> lk (lock_);
if (!resource_->swapchain)
return GST_FLOW_ERROR;
if (!backbuf_rendered_)
return GST_FLOW_OK;
DXGI_PRESENT_PARAMETERS params = { };
if (!first_present_) {
params.DirtyRectsCount = 1;
params.pDirtyRects = &output_rect_;
}
auto hr = resource_->swapchain->Present1 (0, 0, &params);
switch (hr) {
case DXGI_ERROR_DEVICE_REMOVED:
case DXGI_ERROR_INVALID_CALL:
case E_OUTOFMEMORY:
gst_d3d12_result (hr, resource_->device);
return GST_FLOW_ERROR;
default:
/* Ignore other return code */
break;
}
first_present_ = false;
backbuf_rendered_ = false;
gst_d3d12_device_execute_command_lists (resource_->device,
D3D12_COMMAND_LIST_TYPE_DIRECT, 0, nullptr, &resource_->fence_val);
resource_->prev_fence_val.push (resource_->fence_val);
return GST_FLOW_OK;
}
void
SwapChain::before_rendering ()
{
UINT64 fence_val_to_wait = 0;
auto resource = resource_.get ();
while (resource->prev_fence_val.size () > BACK_BUFFER_COUNT + 1) {
fence_val_to_wait = resource->prev_fence_val.front ();
resource->prev_fence_val.pop ();
}
if (fence_val_to_wait) {
auto completed = gst_d3d12_device_get_completed_value (resource->device,
D3D12_COMMAND_LIST_TYPE_DIRECT);
if (completed < fence_val_to_wait) {
gst_d3d12_device_fence_wait (resource_->device,
D3D12_COMMAND_LIST_TYPE_DIRECT, fence_val_to_wait,
resource->event_handle);
}
}
}
void SwapChain::after_rendering ()
{
resource_->prev_fence_val.push (resource_->fence_val);
}
/* *INDENT-ON* */

View file

@ -0,0 +1,60 @@
/* GStreamer
* Copyright (C) 2024 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
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <gst/d3d12/gstd3d12.h>
#include "gstd3d12pluginutils.h"
#include "gstd3d12window-swapchain-resource.h"
#include "gstd3d12window.h"
#include <mutex>
#include <memory>
class SwapChain
{
public:
SwapChain (GstD3D12Device * device);
~SwapChain ();
GstFlowReturn setup_swapchain (GstD3D12Window * window,
GstD3D12Device * device, HWND hwnd, DXGI_FORMAT format,
const GstVideoInfo * in_info, const GstVideoInfo * out_info,
GstStructure * conv_config, bool & is_new_swapchain);
void disable_alt_enter (HWND hwnd);
GstFlowReturn resize_buffer (GstD3D12Window * window);
GstFlowReturn set_buffer (GstD3D12Window * window, GstBuffer * buffer);
GstFlowReturn present ();
private:
void before_rendering ();
void after_rendering ();
private:
std::unique_ptr<SwapChainResource> resource_;
DXGI_FORMAT render_format_ = DXGI_FORMAT_UNKNOWN;
std::recursive_mutex lock_;
GstVideoFormat in_format_ = GST_VIDEO_FORMAT_UNKNOWN;
GstStructure *converter_config_ = nullptr;
bool first_present_ = true;
bool backbuf_rendered_ = false;
RECT output_rect_ = { };
D3D12_BOX crop_rect_ = { };
D3D12_BOX prev_crop_rect_ = { };
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,151 @@
/* GStreamer
* Copyright (C) 2024 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
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include "gstd3d12window.h"
#include "gstd3d12window-swapchain.h"
#include <atomic>
#include <mutex>
#include <condition_variable>
#include <unordered_map>
#include <vector>
#include <memory>
#include <d3d11on12.h>
#include <d2d1_3.h>
#include <wrl.h>
struct FullscreenState
{
std::atomic<bool> fullscreen_on_alt_enter = { false };
std::atomic<bool> requested_fullscreen = { false };
std::atomic<bool> applied_fullscreen = { false };
LONG restore_style = 0;
WINDOWPLACEMENT restore_placement = { };
};
class SwapChainProxy
{
public:
SwapChainProxy (GstD3D12Window * window, SIZE_T id);
~SwapChainProxy ();
void set_window_handles (HWND parent_hwnd, HWND child_hwnd);
HWND get_window_handle ();
SIZE_T get_id ();
bool has_parent ();
void on_destroy ();
void set_fullscreen_on_alt_enter (bool enable);
void toggle_fullscreen (bool enable);
void update_render_rect ();
void handle_fullscreen_change (bool is_fullscreen);
void handle_syskey_down ();
void handle_update_render_rect ();
void handle_key_event (UINT msg, WPARAM wparam, LPARAM lparam);
void handle_mouse_event (UINT msg, WPARAM wparam, LPARAM lparam);
void handle_swapchain_created ();
GstFlowReturn setup_swapchain (GstD3D12Device * device, DXGI_FORMAT format,
const GstVideoInfo * in_info, const GstVideoInfo * out_info,
GstStructure * conv_config);
GstFlowReturn resize_buffer ();
GstFlowReturn set_buffer (GstBuffer * buffer);
GstFlowReturn present ();
private:
std::shared_ptr<SwapChain> get_swapchain ();
private:
GstD3D12Window *window_ = nullptr;
SIZE_T id_ = 0;
HWND hwnd_ = nullptr;
HWND parent_hwnd_ = nullptr;
GThread *window_thread_ = nullptr;
FullscreenState fstate_;
std::shared_ptr<SwapChain> swapchain_;
std::recursive_mutex lock_;
};
class HwndServer
{
public:
HwndServer(const HwndServer &) = delete;
HwndServer& operator=(const HwndServer &) = delete;
static HwndServer * get_instance()
{
static HwndServer *inst = nullptr;
GST_D3D12_CALL_ONCE_BEGIN {
inst = new HwndServer ();
} GST_D3D12_CALL_ONCE_END;
return inst;
}
void register_window (GstD3D12Window * window);
void unregister_window (GstD3D12Window * window);
void unlock_window (GstD3D12Window * window);
void unlock_stop_window (GstD3D12Window * window);
GstFlowReturn create_child_hwnd (GstD3D12Window * window,
HWND parent_hwnd, SIZE_T & proxy_id);
void create_child_hwnd_finish (GstD3D12Window * window,
HWND parent_hwnd, SIZE_T proxy_id);
SIZE_T create_internal_window (GstD3D12Window * window);
void release_proxy (GstD3D12Window * window, SIZE_T proxy_id);
void forward_parent_message (HWND parent,
UINT msg, WPARAM wparam, LPARAM lparam);
void on_parent_destroy (HWND parent_hwnd);
void on_proxy_destroy (GstD3D12Window * window,
SIZE_T proxy_id);
std::shared_ptr<SwapChainProxy> get_proxy (GstD3D12Window * window,
SIZE_T proxy_id);
private:
enum CreateState
{
None,
Waiting,
Opened,
Closed,
};
struct State
{
std::mutex create_lock;
std::condition_variable create_cond;
std::atomic<SIZE_T> id = { 0 };
bool flushing = false;
CreateState create_state = CreateState::None;
std::shared_ptr<SwapChainProxy> proxy;
};
HwndServer () {}
~HwndServer () {}
std::recursive_mutex lock_;
std::unordered_map<GstD3D12Window *, std::shared_ptr<State>> state_;
std::unordered_map<HWND, std::vector<HWND>> parent_hwnd_map_;
};

File diff suppressed because it is too large Load diff

View file

@ -23,6 +23,8 @@
#include <gst/video/video.h>
#include <gst/d3d12/gstd3d12.h>
#include "gstd3d12pluginutils.h"
#include "gstd3d12window-swapchain-resource.h"
#include <string>
G_BEGIN_DECLS
@ -32,13 +34,6 @@ G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE (GstD3D12Window, gst_d3d12_window,
GST, D3D12_WINDOW, GstObject)
enum GstD3D12WindowState
{
GST_D3D12_WINDOW_STATE_INIT,
GST_D3D12_WINDOW_STATE_OPENED,
GST_D3D12_WINDOW_STATE_CLOSED,
};
enum GstD3D12WindowOverlayMode
{
GST_D3D12_WINDOW_OVERLAY_NONE = 0,
@ -54,13 +49,20 @@ GType gst_d3d12_window_overlay_mode_get_type (void);
GstD3D12Window * gst_d3d12_window_new (void);
void gst_d3d12_window_invalidate (GstD3D12Window * window);
guintptr gst_d3d12_window_get_window_handle (GstD3D12Window * window);
GstD3D12WindowState gst_d3d12_window_get_state (GstD3D12Window * window);
gboolean gst_d3d12_window_is_closed (GstD3D12Window * window);
GstFlowReturn gst_d3d12_window_open (GstD3D12Window * window,
GstD3D12Device * device,
guint display_width,
guint display_height,
HWND parent_hwnd);
GstFlowReturn gst_d3d12_window_prepare (GstD3D12Window * window,
GstD3D12Device * device,
guintptr window_handle,
guint display_width,
guint display_height,
GstCaps * caps,
@ -76,18 +78,28 @@ void gst_d3d12_window_unlock_stop (GstD3D12Window * window);
GstFlowReturn gst_d3d12_window_set_buffer (GstD3D12Window * window,
GstBuffer * buffer);
GstFlowReturn gst_d3d12_window_present (GstD3D12Window * window,
gboolean sync);
GstFlowReturn gst_d3d12_window_render (GstD3D12Window * window,
SwapChainResource * resource,
GstBuffer * buffer,
bool is_first,
RECT & output_rect);
GstFlowReturn gst_d3d12_window_present (GstD3D12Window * window);
void gst_d3d12_window_set_render_rect (GstD3D12Window * window,
const GstVideoRectangle * rect);
void gst_d3d12_window_get_render_rect (GstD3D12Window * window,
GstVideoRectangle * rect);
void gst_d3d12_window_set_force_aspect_ratio (GstD3D12Window * window,
gboolean force_aspect_ratio);
void gst_d3d12_window_set_enable_navigation_events (GstD3D12Window * window,
gboolean enable);
gboolean gst_d3d12_window_get_navigation_events_enabled (GstD3D12Window * window);
void gst_d3d12_window_set_orientation (GstD3D12Window * window,
gboolean immediate,
GstVideoOrientationMethod orientation,
@ -111,8 +123,35 @@ void gst_d3d12_window_set_fullscreen (GstD3D12Window * window,
void gst_d3d12_window_set_msaa (GstD3D12Window * window,
GstD3D12MSAAMode msaa);
void gst_d3d12_window_get_msaa (GstD3D12Window * window,
GstD3D12MSAAMode & msaa);
void gst_d3d12_window_set_overlay_mode (GstD3D12Window * window,
GstD3D12WindowOverlayMode mode);
void gst_d3d12_window_on_key_event (GstD3D12Window * window,
const gchar * event,
const gchar * name);
void gst_d3d12_window_on_mouse_event (GstD3D12Window * window,
const gchar * event,
gint button,
double xpos,
double ypos,
guint modifier);
void gst_d3d12_window_get_create_params (GstD3D12Window * window,
std::wstring & title,
GstVideoRectangle * rect,
int & display_width,
int & display_height,
GstVideoOrientationMethod & orientation);
void gst_d3d12_window_get_mouse_pos_info (GstD3D12Window * window,
GstVideoRectangle * out_rect,
int & input_width,
int & input_height,
GstVideoOrientationMethod & orientation);
G_END_DECLS

View file

@ -28,6 +28,8 @@ d3d12_sources = [
'gstd3d12videosink.cpp',
'gstd3d12vp8dec.cpp',
'gstd3d12vp9dec.cpp',
'gstd3d12window-swapchain.cpp',
'gstd3d12window-win32.cpp',
'gstd3d12window.cpp',
'plugin.cpp',
]

View file

@ -0,0 +1,351 @@
/* GStreamer
* Copyright (C) 2024 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
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gst/gst.h>
#include <gst/video/video.h>
#include <windows.h>
#include <string.h>
#include "../key-handler.h"
static GMainLoop *loop = nullptr;
static HWND hwnd_0 = nullptr;
static HWND hwnd_1 = nullptr;
struct AppData
{
GstElement *pipeline = nullptr;
GstVideoOverlay *overlay_0 = nullptr;
GstVideoOverlay *overlay_1 = nullptr;
guint mode = 0;
};
#define APP_DATA_PROP_NAME L"EXAMPLE-APP-DATA"
static LRESULT CALLBACK
window_proc (HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
{
switch (message) {
case WM_NCCREATE:
{
LPCREATESTRUCTW lpcs = (LPCREATESTRUCTW) lparam;
auto data = (AppData *) lpcs->lpCreateParams;
SetPropW (hwnd, APP_DATA_PROP_NAME, data);
break;
}
case WM_DESTROY:
gst_println ("Destroy window %d", hwnd == hwnd_0 ? 0 : 1);
if (loop)
g_main_loop_quit (loop);
break;
case WM_SIZE:
{
auto data = (AppData *) GetPropW (hwnd, APP_DATA_PROP_NAME);
if (!data || !data->overlay_0 || !data->overlay_1)
break;
if ((data->mode == 1 && hwnd == hwnd_0) ||
(data->mode == 2 && hwnd == hwnd_1)) {
RECT rect;
GetClientRect (hwnd, &rect);
gint width = (rect.right - rect.left) / 2;
gint height = (rect.bottom - rect.top);
auto overlay_0 = data->overlay_0;
auto overlay_1 = data->overlay_1;
gst_video_overlay_set_render_rectangle (overlay_0,
0, 0, width, height);
gst_video_overlay_set_render_rectangle (overlay_1,
width, 0, width, height);
}
break;
}
default:
break;
}
return DefWindowProcW (hwnd, message, wparam, lparam);
}
static void
keyboard_cb (gchar input, gboolean is_ascii, AppData * data)
{
if (!is_ascii)
return;
switch (input) {
case 'q':
case 'Q':
gst_element_send_event (data->pipeline, gst_event_new_eos ());
break;
case ' ':
data->mode++;
data->mode %= 4;
switch (data->mode) {
case 0:
{
auto overlay_0 = data->overlay_0;
auto overlay_1 = data->overlay_1;
gst_video_overlay_set_window_handle (overlay_0,
(guintptr) hwnd_0);
gst_video_overlay_set_render_rectangle (overlay_0, 0, 0, -1, -1);
gst_video_overlay_set_window_handle (overlay_1,
(guintptr) hwnd_1);
gst_video_overlay_set_render_rectangle (overlay_1, 0, 0, -1, -1);
break;
}
case 1:
{
RECT rect;
GetClientRect (hwnd_0, &rect);
gint width = (rect.right - rect.left) / 2;
gint height = (rect.bottom - rect.top);
auto overlay_0 = data->overlay_0;
auto overlay_1 = data->overlay_1;
gst_video_overlay_set_window_handle (overlay_0,
(guintptr) hwnd_0);
gst_video_overlay_set_render_rectangle (overlay_0,
0, 0, width, height);
gst_video_overlay_set_window_handle (overlay_1,
(guintptr) hwnd_0);
gst_video_overlay_set_render_rectangle (overlay_1,
width, 0, width, height);
break;
}
case 2:
{
RECT rect;
GetClientRect (hwnd_1, &rect);
gint width = (rect.right - rect.left) / 2;
gint height = (rect.bottom - rect.top);
auto overlay_0 = data->overlay_0;
auto overlay_1 = data->overlay_1;
gst_video_overlay_set_window_handle (overlay_0,
(guintptr) hwnd_1);
gst_video_overlay_set_render_rectangle (overlay_0,
0, 0, width, height);
gst_video_overlay_set_window_handle (overlay_1,
(guintptr) hwnd_1);
gst_video_overlay_set_render_rectangle (overlay_1,
width, 0, width, height);
break;
}
case 3:
{
auto overlay_0 = data->overlay_0;
auto overlay_1 = data->overlay_1;
gst_video_overlay_set_window_handle (overlay_0,
(guintptr) hwnd_1);
gst_video_overlay_set_render_rectangle (overlay_0, 0, 0, -1, -1);
gst_video_overlay_set_window_handle (overlay_1,
(guintptr) hwnd_0);
gst_video_overlay_set_render_rectangle (overlay_1, 0, 0, -1, -1);
break;
}
default:
break;
}
break;
default:
break;
}
}
static gboolean
bus_msg (GstBus * bus, GstMessage * msg, AppData * data)
{
switch (GST_MESSAGE_TYPE (msg)) {
case GST_MESSAGE_ERROR:
{
GError *err;
gchar *dbg;
gst_message_parse_error (msg, &err, &dbg);
gst_printerrln ("ERROR %s", err->message);
if (dbg != nullptr)
gst_printerrln ("ERROR debug information: %s", dbg);
g_clear_error (&err);
g_free (dbg);
g_main_loop_quit (loop);
break;
}
case GST_MESSAGE_EOS:
{
gst_println ("Got EOS");
g_main_loop_quit (loop);
break;
}
default:
break;
}
return TRUE;
}
static gboolean
msg_cb (GIOChannel * source, GIOCondition condition, gpointer data)
{
MSG msg;
if (!PeekMessageW (&msg, nullptr, 0, 0, PM_REMOVE))
return G_SOURCE_CONTINUE;
TranslateMessage (&msg);
DispatchMessage (&msg);
return G_SOURCE_CONTINUE;
}
static void
print_keyboard_help (void)
{
static struct
{
const gchar *key_desc;
const gchar *key_help;
} key_controls[] = {
{
"q", "Quit"}, {
"space", "Toggle render window"}
};
guint i, chars_to_pad, desc_len, max_desc_len = 0;
gst_print ("\n%s\n", "Keyboard controls:");
for (i = 0; i < G_N_ELEMENTS (key_controls); ++i) {
desc_len = g_utf8_strlen (key_controls[i].key_desc, -1);
max_desc_len = MAX (max_desc_len, desc_len);
}
++max_desc_len;
for (i = 0; i < G_N_ELEMENTS (key_controls); ++i) {
chars_to_pad = max_desc_len - g_utf8_strlen (key_controls[i].key_desc, -1);
gst_print ("\t%s", key_controls[i].key_desc);
gst_print ("%-*s: ", chars_to_pad, "");
gst_print ("%s\n", key_controls[i].key_help);
}
gst_print ("\n");
}
gint
main (gint argc, gchar ** argv)
{
WNDCLASSEXW wc = { };
HINSTANCE hinstance = GetModuleHandle (nullptr);
GIOChannel *msg_io_channel = nullptr;
RECT wr = { 0, 0, 320, 240 };
AppData app_data = { };
gst_init (nullptr, nullptr);
print_keyboard_help ();
loop = g_main_loop_new (nullptr, FALSE);
/* prepare window */
wc.cbSize = sizeof (WNDCLASSEXW);
wc.lpfnWndProc = (WNDPROC) window_proc;
wc.hInstance = hinstance;
wc.hIcon = LoadIcon (nullptr, IDI_WINLOGO);
wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
wc.hCursor = LoadCursor (nullptr, IDC_ARROW);
wc.hbrBackground = (HBRUSH) GetStockObject (BLACK_BRUSH);
wc.lpszClassName = L"GstD3D12VideoSinkExample";
RegisterClassExW (&wc);
AdjustWindowRect (&wr, WS_OVERLAPPEDWINDOW, FALSE);
hwnd_0 = CreateWindowExW (0, wc.lpszClassName, L"Window-0",
WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT,
wr.right - wr.left, wr.bottom - wr.top, (HWND) nullptr, (HMENU) nullptr,
hinstance, &app_data);
hwnd_1 = CreateWindowExW (0, wc.lpszClassName, L"Window-1",
WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT,
wr.right - wr.left, wr.bottom - wr.top, (HWND) nullptr, (HMENU) nullptr,
hinstance, &app_data);
msg_io_channel = g_io_channel_win32_new_messages (0);
g_io_add_watch (msg_io_channel, G_IO_IN, msg_cb, nullptr);
/* prepare the pipeline */
app_data.pipeline = gst_parse_launch ("d3d12testsrc pattern=ball ! queue ! "
"d3d12videosink name=sink0 d3d12testsrc ! queue ! "
"d3d12videosink name=sink1", nullptr);
if (!app_data.pipeline) {
gst_printerrln ("Couldn't create pipeline");
return 0;
}
auto sink_0 = gst_bin_get_by_name (GST_BIN (app_data.pipeline), "sink0");
auto sink_1 = gst_bin_get_by_name (GST_BIN (app_data.pipeline), "sink1");
app_data.overlay_0 = GST_VIDEO_OVERLAY (sink_0);
app_data.overlay_1 = GST_VIDEO_OVERLAY (sink_1);
gst_video_overlay_set_window_handle (app_data.overlay_0, (guintptr) hwnd_0);
gst_video_overlay_set_window_handle (app_data.overlay_1, (guintptr) hwnd_1);
gst_object_unref (sink_0);
gst_object_unref (sink_1);
gst_bus_add_watch (GST_ELEMENT_BUS (app_data.pipeline), (GstBusFunc) bus_msg,
&app_data);
set_key_handler ((KeyInputCallback) keyboard_cb, &app_data);
gst_element_set_state (app_data.pipeline, GST_STATE_PLAYING);
g_main_loop_run (loop);
gst_element_set_state (app_data.pipeline, GST_STATE_NULL);
gst_bus_remove_watch (GST_ELEMENT_BUS (app_data.pipeline));
gst_object_unref (app_data.pipeline);
unset_key_handler ();
if (hwnd_0)
DestroyWindow (hwnd_0);
if (hwnd_1)
DestroyWindow (hwnd_1);
if (msg_io_channel)
g_io_channel_unref (msg_io_channel);
g_main_loop_unref (loop);
gst_deinit ();
return 0;
}

View file

@ -12,7 +12,16 @@ executable('d3d12enc-dynamic-reconfigure',
include_directories : [configinc],
dependencies: [gst_dep, gstbase_dep, gstvideo_dep],
c_args : gst_plugins_bad_args,
install: false)
install: false
)
executable('d3d12videosink-switch',
['d3d12videosink-switch.cpp', '../key-handler.c'],
include_directories : [configinc],
dependencies: [gst_dep, gstbase_dep, gstvideo_dep],
c_args : gst_plugins_bad_args,
install: false
)
if gstd3d12_dep.found()
if have_d2d_h and have_dwrite_h and have_d3d12video_h and dwrite_dep.found()