Seungha Yang a1c8fc6163 d3d12device: Keep device object permanently
Because ID3D12Device objects are singletons per adapter,
GstD3D12Device was following the API design, that is, keep track
of global GstD3D12Device objects and reuses it.
That means ID3D12Device object can be released at the time
when GstD3D12Device is destroyed.

But exetrnal APIs such as NVENC does not seem to be happy
with the released ID3D12Device, that could be a driver bug though.

Let's hold already opened ID3D12Device permanently without releasing
it for now.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/6395>
2024-03-19 12:03:55 +00:00

452 lines
12 KiB
C++

/* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@centricular.com>
*
* This library is free software; you cln 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 "gstd3d12.h"
#include "gstd3d12-private.h"
#include <wrl.h>
#include <queue>
#include <mutex>
#include <memory>
#include <condition_variable>
GST_DEBUG_CATEGORY_STATIC (gst_d3d12_command_queue_debug);
#define GST_CAT_DEFAULT gst_d3d12_command_queue_debug
/* *INDENT-OFF* */
using namespace Microsoft::WRL;
struct GCData
{
GCData (gpointer user_data, GDestroyNotify destroy_notify,
guint64 fence_value) : data (user_data),
notify(destroy_notify), fence_val (fence_value) {}
~GCData ()
{
if (notify)
notify (data);
}
gpointer data = nullptr;
GDestroyNotify notify = nullptr;
guint64 fence_val = 0;
};
typedef std::shared_ptr<GCData> GCDataPtr;
struct gc_cmp {
bool operator()(const GCDataPtr &a, const GCDataPtr &b)
{
return a->fence_val > b->fence_val;
}
};
struct GstD3D12CommandQueuePrivate
{
GstD3D12CommandQueuePrivate ()
{
event_handle = CreateEventEx (nullptr, nullptr, 0, EVENT_ALL_ACCESS);
}
~GstD3D12CommandQueuePrivate ()
{
{
std::lock_guard <std::mutex> lk (lock);
shutdown = true;
cond.notify_one ();
}
g_clear_pointer (&gc_thread, g_thread_join);
auto completed = fence->GetCompletedValue ();
if (fence_val > completed) {
auto hr = fence->SetEventOnCompletion (completed, event_handle);
if (SUCCEEDED (hr))
WaitForSingleObjectEx (event_handle, INFINITE, FALSE);
}
CloseHandle (event_handle);
}
D3D12_COMMAND_QUEUE_DESC desc;
ComPtr<ID3D12Device> device;
ComPtr<ID3D12CommandQueue> cq;
ComPtr<ID3D12Fence> fence;
HANDLE event_handle;
guint64 fence_val = 0;
GThread *gc_thread = nullptr;
std::priority_queue<GCDataPtr, std::vector<GCDataPtr>, gc_cmp> gc_list;
std::mutex execute_lock;
std::mutex lock;
std::condition_variable cond;
bool shutdown = false;
size_t queue_size = 0;
};
/* *INDENT-ON* */
struct _GstD3D12CommandQueue
{
GstObject parent;
GstD3D12CommandQueuePrivate *priv;
};
static void gst_d3d12_command_queue_finalize (GObject * object);
#define gst_d3d12_command_queue_parent_class parent_class
G_DEFINE_TYPE (GstD3D12CommandQueue, gst_d3d12_command_queue, GST_TYPE_OBJECT);
static void
gst_d3d12_command_queue_class_init (GstD3D12CommandQueueClass * klass)
{
auto object_class = G_OBJECT_CLASS (klass);
object_class->finalize = gst_d3d12_command_queue_finalize;
}
static void
gst_d3d12_command_queue_init (GstD3D12CommandQueue * self)
{
self->priv = new GstD3D12CommandQueuePrivate ();
}
static void
gst_d3d12_command_queue_finalize (GObject * object)
{
auto self = GST_D3D12_COMMAND_QUEUE (object);
delete self->priv;
G_OBJECT_CLASS (parent_class)->finalize (object);
}
GstD3D12CommandQueue *
gst_d3d12_command_queue_new (ID3D12Device * device,
const D3D12_COMMAND_QUEUE_DESC * desc, guint queue_size)
{
g_return_val_if_fail (device, nullptr);
g_return_val_if_fail (desc, nullptr);
GST_D3D12_CALL_ONCE_BEGIN {
GST_DEBUG_CATEGORY_INIT (gst_d3d12_command_queue_debug,
"d3d12commandqueue", 0, "d3d12commandqueue");
} GST_D3D12_CALL_ONCE_END;
ComPtr < ID3D12CommandQueue > cq;
auto hr = device->CreateCommandQueue (desc, IID_PPV_ARGS (&cq));
if (FAILED (hr)) {
GST_ERROR ("Couldn't create command queue, hr: 0x%x", (guint) hr);
return nullptr;
}
ComPtr < ID3D12Fence > fence;
hr = device->CreateFence (0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS (&fence));
if (FAILED (hr)) {
GST_ERROR ("Couldn't create fence, hr: 0x%x", (guint) hr);
return nullptr;
}
auto self = (GstD3D12CommandQueue *)
g_object_new (GST_TYPE_D3D12_COMMAND_QUEUE, nullptr);
gst_object_ref_sink (self);
auto priv = self->priv;
priv->device = device;
priv->cq = cq;
priv->fence = fence;
priv->queue_size = queue_size;
return self;
}
gboolean
gst_d3d12_command_queue_get_handle (GstD3D12CommandQueue * queue,
ID3D12CommandQueue ** handle)
{
g_return_val_if_fail (GST_IS_D3D12_COMMAND_QUEUE (queue), FALSE);
g_return_val_if_fail (handle, FALSE);
auto priv = queue->priv;
*handle = priv->cq.Get ();
(*handle)->AddRef ();
return TRUE;
}
gboolean
gst_d3d12_command_queue_get_fence (GstD3D12CommandQueue * queue,
ID3D12Fence ** handle)
{
g_return_val_if_fail (GST_IS_D3D12_COMMAND_QUEUE (queue), FALSE);
g_return_val_if_fail (handle, FALSE);
auto priv = queue->priv;
*handle = priv->fence.Get ();
(*handle)->AddRef ();
return TRUE;
}
HRESULT
gst_d3d12_command_queue_execute_command_lists (GstD3D12CommandQueue * queue,
guint num_command_lists, ID3D12CommandList ** command_lists,
guint64 * fence_value)
{
g_return_val_if_fail (GST_IS_D3D12_COMMAND_QUEUE (queue), E_INVALIDARG);
auto priv = queue->priv;
HRESULT hr;
std::lock_guard < std::mutex > lk (priv->execute_lock);
priv->fence_val++;
if (num_command_lists)
priv->cq->ExecuteCommandLists (num_command_lists, command_lists);
hr = priv->cq->Signal (priv->fence.Get (), priv->fence_val);
if (FAILED (hr)) {
GST_ERROR_OBJECT (queue, "Signal failed");
priv->fence_val--;
} else if (fence_value) {
*fence_value = priv->fence_val;
}
if (priv->queue_size > 0) {
auto completed = priv->fence->GetCompletedValue ();
if (completed + priv->queue_size < priv->fence_val) {
hr = priv->fence->SetEventOnCompletion (priv->fence_val -
priv->queue_size, priv->event_handle);
if (FAILED (hr)) {
GST_ERROR_OBJECT (queue, "SetEventOnCompletion failed");
return hr;
}
WaitForSingleObjectEx (priv->event_handle, INFINITE, FALSE);
}
}
return hr;
}
HRESULT
gst_d3d12_command_queue_execute_wait (GstD3D12CommandQueue * queue,
ID3D12Fence * fence, guint64 fence_value)
{
g_return_val_if_fail (GST_IS_D3D12_COMMAND_QUEUE (queue), E_INVALIDARG);
g_return_val_if_fail (fence, E_INVALIDARG);
auto priv = queue->priv;
return priv->cq->Wait (fence, fence_value);
}
guint64
gst_d3d12_command_queue_get_completed_value (GstD3D12CommandQueue * queue)
{
g_return_val_if_fail (GST_IS_D3D12_COMMAND_QUEUE (queue), G_MAXUINT64);
return queue->priv->fence->GetCompletedValue ();
}
HRESULT
gst_d3d12_command_queue_fence_wait (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;
if (fence_value == G_MAXUINT64) {
std::lock_guard < std::mutex > lk (priv->execute_lock);
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;
}
static gpointer
gst_d3d12_command_queue_gc_thread (GstD3D12CommandQueue * self)
{
auto priv = self->priv;
GST_INFO_OBJECT (self, "Entering GC thread");
HANDLE event_handle = CreateEventEx (nullptr, nullptr, 0, EVENT_ALL_ACCESS);
while (true) {
GCDataPtr gc_data;
{
std::unique_lock < std::mutex > lk (priv->lock);
while (!priv->shutdown && priv->gc_list.empty ())
priv->cond.wait (lk);
if (priv->shutdown)
break;
auto completed = priv->fence->GetCompletedValue ();
while (!priv->gc_list.empty ()) {
auto top = priv->gc_list.top ();
if (top->fence_val > completed) {
gc_data = top;
priv->gc_list.pop ();
break;
}
GST_LOG_OBJECT (self, "Releasing fence data, completed %"
G_GUINT64_FORMAT ", fence value %" G_GUINT64_FORMAT,
completed, top->fence_val);
priv->gc_list.pop ();
}
}
if (gc_data) {
GST_LOG_OBJECT (self, "Waiting for fence data %" G_GUINT64_FORMAT,
gc_data->fence_val);
auto hr =
priv->fence->SetEventOnCompletion (gc_data->fence_val, event_handle);
if (FAILED (hr)) {
GST_ERROR_OBJECT (self, "SetEventOnCompletion failed");
} else {
WaitForSingleObjectEx (event_handle, INFINITE, FALSE);
GST_LOG_OBJECT (self, "Waiting done, %" G_GUINT64_FORMAT,
gc_data->fence_val);
}
}
}
GST_INFO_OBJECT (self, "Leaving GC thread");
CloseHandle (event_handle);
return nullptr;
}
void
gst_d3d12_command_queue_set_notify (GstD3D12CommandQueue * queue,
guint64 fence_value, gpointer fence_data, GDestroyNotify notify)
{
g_return_if_fail (GST_IS_D3D12_COMMAND_QUEUE (queue));
auto priv = queue->priv;
auto completed = priv->fence->GetCompletedValue ();
if (completed >= fence_value) {
GST_DEBUG_OBJECT (queue, "Already completed fence value %"
G_GUINT64_FORMAT, fence_value);
if (notify)
notify (fence_data);
return;
}
std::lock_guard < std::mutex > elk (priv->execute_lock);
auto gc_data = std::make_shared < GCData > (fence_data, notify, fence_value);
if (!priv->gc_thread) {
priv->gc_thread = g_thread_new ("GstD3D12Gc",
(GThreadFunc) gst_d3d12_command_queue_gc_thread, queue);
}
GST_LOG_OBJECT (queue, "Pushing GC data %" G_GUINT64_FORMAT, fence_value);
std::lock_guard < std::mutex > lk (priv->lock);
priv->gc_list.push (std::move (gc_data));
priv->cond.notify_one ();
}
HRESULT
gst_d3d12_command_queue_drain (GstD3D12CommandQueue * queue)
{
g_return_val_if_fail (GST_IS_D3D12_COMMAND_QUEUE (queue), E_INVALIDARG);
auto priv = queue->priv;
std::priority_queue < GCDataPtr, std::vector < GCDataPtr >, gc_cmp > gc_list;
{
std::lock_guard < std::mutex > elk (priv->execute_lock);
priv->fence_val++;
auto hr = priv->cq->Signal (priv->fence.Get (), priv->fence_val);
if (FAILED (hr)) {
GST_ERROR_OBJECT (queue, "Signal failed");
priv->fence_val--;
return hr;
}
auto completed = priv->fence->GetCompletedValue ();
if (completed < priv->fence_val) {
auto event_handle = CreateEventEx (nullptr, nullptr, 0, EVENT_ALL_ACCESS);
hr = priv->fence->SetEventOnCompletion (priv->fence_val, event_handle);
if (FAILED (hr)) {
GST_ERROR_OBJECT (queue, "SetEventOnCompletion failed");
CloseHandle (event_handle);
return hr;
}
WaitForSingleObjectEx (event_handle, INFINITE, FALSE);
CloseHandle (event_handle);
}
{
std::lock_guard < std::mutex > lk (priv->lock);
gc_list = priv->gc_list;
priv->gc_list = { };
}
}
return S_OK;
}