d3d11: Implement fence abstraction
authorSeungha Yang <seungha@centricular.com>
Fri, 22 Jul 2022 15:11:18 +0000 (00:11 +0900)
committerGStreamer Marge Bot <gitlab-merge-bot@gstreamer-foundation.org>
Sat, 23 Jul 2022 16:53:14 +0000 (16:53 +0000)
Depending on device feature level, d3d11 runtime can support
ID3D11Fence which is equivalent to ID3D12Fence.
Waiting using fence has performance-wise benefit over pulling
ID3D11Query status. If ID3D11Fence is not supported by device,
then ID3D11Query will be used instead.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/2790>

subprojects/gst-plugins-bad/gst-libs/gst/d3d11/gstd3d11_fwd.h
subprojects/gst-plugins-bad/gst-libs/gst/d3d11/gstd3d11device.cpp
subprojects/gst-plugins-bad/gst-libs/gst/d3d11/gstd3d11device.h

index e4601ae..1589fc8 100644 (file)
@@ -63,5 +63,8 @@ typedef struct _GstD3D11Converter GstD3D11Converter;
 typedef struct _GstD3D11ConverterClass GstD3D11ConverterClass;
 typedef struct _GstD3D11ConverterPrivate GstD3D11ConverterPrivate;
 
+typedef struct _GstD3D11Fence GstD3D11Fence;
+typedef struct _GstD3D11FencePrivate GstD3D11FencePrivate;
+
 G_END_DECLS
 
index a36f284..41d4f0e 100644 (file)
@@ -105,7 +105,9 @@ struct _GstD3D11DevicePrivate
   gint64 adapter_luid;
 
   ID3D11Device *device;
+  ID3D11Device5 *device5;
   ID3D11DeviceContext *device_context;
+  ID3D11DeviceContext4 *device_context4;
 
   ID3D11VideoDevice *video_device;
   ID3D11VideoContext *video_context;
@@ -116,6 +118,8 @@ struct _GstD3D11DevicePrivate
   GRecMutex extern_lock;
   GMutex resource_lock;
 
+  LARGE_INTEGER frequency;
+
 #if HAVE_D3D11SDKLAYERS_H
   ID3D11Debug *d3d11_debug;
   ID3D11InfoQueue *d3d11_info_queue;
@@ -703,6 +707,8 @@ gst_d3d11_device_dispose (GObject * object)
 
   GST_LOG_OBJECT (self, "dispose");
 
+  GST_D3D11_CLEAR_COM (priv->device5);
+  GST_D3D11_CLEAR_COM (priv->device_context4);
   GST_D3D11_CLEAR_COM (priv->video_device);
   GST_D3D11_CLEAR_COM (priv->video_context);
   GST_D3D11_CLEAR_COM (priv->device);
@@ -951,7 +957,9 @@ gst_d3d11_device_new_internal (const GstD3D11DeviceConstructData * data)
   ComPtr < IDXGIAdapter1 > adapter;
   ComPtr < IDXGIFactory1 > factory;
   ComPtr < ID3D11Device > device;
+  ComPtr < ID3D11Device5 > device5;
   ComPtr < ID3D11DeviceContext > device_context;
+  ComPtr < ID3D11DeviceContext4 > device_context4;
   HRESULT hr;
   UINT create_flags;
   guint adapter_index = 0;
@@ -1077,6 +1085,14 @@ gst_d3d11_device_new_internal (const GstD3D11DeviceConstructData * data)
 
   priv = self->priv;
 
+  hr = device.As (&device5);
+  if (SUCCEEDED (hr))
+    hr = device_context.As (&device_context4);
+  if (SUCCEEDED (hr)) {
+    priv->device5 = device5.Detach ();
+    priv->device_context4 = device_context4.Detach ();
+  }
+
   priv->adapter = adapter_index;
   priv->device = device.Detach ();
   priv->device_context = device_context.Detach ();
@@ -1099,6 +1115,9 @@ gst_d3d11_device_new_internal (const GstD3D11DeviceConstructData * data)
   gst_d3d11_device_setup_format_table (self);
   gst_d3d11_device_setup_debug_layer (self);
 
+  BOOL ret = QueryPerformanceFrequency (&priv->frequency);
+  g_assert (ret);
+
   return self;
 }
 
@@ -1382,3 +1401,258 @@ gst_d3d11_device_get_format (GstD3D11Device * device, GstVideoFormat format,
 
   return FALSE;
 }
+
+GST_DEFINE_MINI_OBJECT_TYPE (GstD3D11Fence, gst_d3d11_fence);
+
+struct _GstD3D11FencePrivate
+{
+  UINT64 fence_value;
+  ID3D11Fence *fence;
+  ID3D11Query *query;
+  HANDLE event_handle;
+  gboolean signalled;
+  gboolean synced;
+};
+
+static void
+_gst_d3d11_fence_free (GstD3D11Fence * fence)
+{
+  GstD3D11FencePrivate *priv = fence->priv;
+
+  GST_D3D11_CLEAR_COM (priv->fence);
+  GST_D3D11_CLEAR_COM (priv->query);
+  if (priv->event_handle)
+    CloseHandle (priv->event_handle);
+
+  gst_clear_object (&fence->device);
+
+  g_free (priv);
+  g_free (fence);
+}
+
+/**
+ * gst_d3d11_device_create_fence:
+ * @device: a #GstD3D11Device
+ *
+ * Creates fence object (i.e., ID3D11Fence) if available, otherwise
+ * ID3D11Query with D3D11_QUERY_EVENT is created.
+ *
+ * Returns: a #GstD3D11Fence object
+ *
+ * Since: 1.22
+ */
+GstD3D11Fence *
+gst_d3d11_device_create_fence (GstD3D11Device * device)
+{
+  GstD3D11DevicePrivate *priv;
+  ID3D11Fence *fence = nullptr;
+  HRESULT hr = S_OK;
+  GstD3D11Fence *self;
+
+  g_return_val_if_fail (GST_IS_D3D11_DEVICE (device), nullptr);
+
+  priv = device->priv;
+
+  if (priv->device5 && priv->device_context4) {
+    hr = priv->device5->CreateFence (0, D3D11_FENCE_FLAG_NONE,
+        IID_PPV_ARGS (&fence));
+
+    if (!gst_d3d11_result (hr, device))
+      GST_WARNING_OBJECT (device, "Failed to create fence object");
+  }
+
+  self = g_new0 (GstD3D11Fence, 1);
+  self->device = (GstD3D11Device *) gst_object_ref (device);
+  self->priv = g_new0 (GstD3D11FencePrivate, 1);
+  self->priv->fence = fence;
+  if (fence) {
+    self->priv->event_handle = CreateEventEx (nullptr, nullptr,
+        0, EVENT_ALL_ACCESS);
+  }
+
+  gst_mini_object_init (GST_MINI_OBJECT_CAST (self), 0,
+      GST_TYPE_D3D11_FENCE, nullptr, nullptr,
+      (GstMiniObjectFreeFunction) _gst_d3d11_fence_free);
+
+  return self;
+}
+
+/**
+ * gst_d3d11_fence_signal:
+ * @fence: a #GstD3D11Fence
+ *
+ * Sets sync point to fence for waiting.
+ * Must be called with gst_d3d11_device_lock() held
+ *
+ * Returns: %TRUE if successful
+ *
+ * Since: 1.22
+ */
+gboolean
+gst_d3d11_fence_signal (GstD3D11Fence * fence)
+{
+  HRESULT hr = S_OK;
+  GstD3D11Device *device;
+  GstD3D11DevicePrivate *device_priv;
+  GstD3D11FencePrivate *priv;
+
+  g_return_val_if_fail (GST_IS_D3D11_FENCE (fence), FALSE);
+
+  device = fence->device;
+  device_priv = device->priv;
+  priv = fence->priv;
+
+  priv->signalled = FALSE;
+  priv->synced = FALSE;
+
+  if (priv->fence) {
+    priv->fence_value++;
+
+    GST_LOG_OBJECT (device, "Signals with fence value %" G_GUINT64_FORMAT,
+        priv->fence_value);
+
+    hr = device_priv->device_context4->Signal (priv->fence, priv->fence_value);
+    if (!gst_d3d11_result (hr, device)) {
+      GST_ERROR_OBJECT (device, "Failed to signal fence value %"
+          G_GUINT64_FORMAT, fence->priv->fence_value);
+      return FALSE;
+    }
+  } else {
+    D3D11_QUERY_DESC desc;
+
+    GST_D3D11_CLEAR_COM (priv->query);
+
+    desc.Query = D3D11_QUERY_EVENT;
+    desc.MiscFlags = 0;
+
+    GST_LOG_OBJECT (device, "Creating query object");
+
+    hr = device_priv->device->CreateQuery (&desc, &priv->query);
+    if (!gst_d3d11_result (hr, device)) {
+      GST_ERROR_OBJECT (device, "Failed to create query object");
+      return FALSE;
+    }
+
+    device_priv->device_context->End (priv->query);
+  }
+
+  priv->signalled = TRUE;
+
+  return TRUE;
+}
+
+/**
+ * gst_d3d11_fence_wait:
+ * @fence: a #GstD3D11Fence
+ *
+ * Waits until previously issued GPU commands have been completed
+ * Must be called with gst_d3d11_device_lock() held
+ *
+ * Returns: %TRUE if successful
+ *
+ * Since: 1.22
+ */
+gboolean
+gst_d3d11_fence_wait (GstD3D11Fence * fence)
+{
+  HRESULT hr = S_OK;
+  GstD3D11Device *device;
+  GstD3D11DevicePrivate *device_priv;
+  GstD3D11FencePrivate *priv;
+  BOOL timer_ret;
+  LARGE_INTEGER current_time, now;
+
+  g_return_val_if_fail (GST_IS_D3D11_FENCE (fence), FALSE);
+
+  device = fence->device;
+  device_priv = device->priv;
+  priv = fence->priv;
+
+  if (!priv->signalled) {
+    GST_DEBUG_OBJECT (device, "Fence is not signalled, nothing to wait");
+    return TRUE;
+  }
+
+  if (priv->synced) {
+    GST_DEBUG_OBJECT (device, "Already synced");
+    return TRUE;
+  }
+
+  timer_ret = QueryPerformanceCounter (&current_time);
+  g_assert (timer_ret);
+
+  now = current_time;
+
+  if (priv->fence) {
+    GST_LOG_OBJECT (device, "Waiting fence value %" G_GUINT64_FORMAT,
+        priv->fence_value);
+
+    if (fence->priv->fence->GetCompletedValue () < fence->priv->fence_value) {
+      hr = fence->priv->fence->SetEventOnCompletion (fence->priv->fence_value,
+          fence->priv->event_handle);
+      if (!gst_d3d11_result (hr, device)) {
+        GST_WARNING_OBJECT (device, "Failed set event handle");
+        return FALSE;
+      }
+
+      /* 20 seconds should be sufficient time */
+      DWORD ret = WaitForSingleObject (priv->event_handle, 20000);
+      if (ret != WAIT_OBJECT_0) {
+        GST_WARNING_OBJECT (device,
+            "Failed to wait object, ret 0x%x", (guint) ret);
+        return FALSE;
+      }
+    }
+  } else {
+    LONGLONG timeout;
+    BOOL sync_done = FALSE;
+
+    g_assert (priv->query != nullptr);
+
+    /* 20 sec timeout */
+    timeout = now.QuadPart + 20 * device_priv->frequency.QuadPart;
+
+    GST_LOG_OBJECT (device, "Waiting event");
+
+    while (now.QuadPart < timeout && !sync_done) {
+      hr = device_priv->device_context->GetData (priv->query,
+          &sync_done, sizeof (BOOL), 0);
+      if (FAILED (hr)) {
+        GST_WARNING_OBJECT (device, "Failed to get event data");
+        return FALSE;
+      }
+
+      if (sync_done)
+        break;
+
+      g_thread_yield ();
+      timer_ret = QueryPerformanceCounter (&now);
+      g_assert (timer_ret);
+    }
+
+    if (!sync_done) {
+      GST_WARNING_OBJECT (device, "Timeout");
+      return FALSE;
+    }
+
+    GST_D3D11_CLEAR_COM (priv->query);
+  }
+
+#ifndef GST_DISABLE_GST_DEBUG
+  if (gst_debug_category_get_threshold (GST_CAT_DEFAULT) >= GST_LEVEL_LOG) {
+    GstClockTime elapsed;
+
+    QueryPerformanceCounter (&now);
+    elapsed = gst_util_uint64_scale (now.QuadPart - current_time.QuadPart,
+        GST_SECOND, device_priv->frequency.QuadPart);
+
+    GST_LOG_OBJECT (device, "Wait done, elapsed %" GST_TIME_FORMAT,
+        GST_TIME_ARGS (elapsed));
+  }
+#endif
+
+  priv->signalled = FALSE;
+  priv->synced = TRUE;
+
+  return TRUE;
+}
index 970d334..01b2728 100644 (file)
@@ -34,15 +34,19 @@ G_BEGIN_DECLS
 #define GST_IS_D3D11_DEVICE_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_D3D11_DEVICE))
 #define GST_D3D11_DEVICE_CAST(obj)        ((GstD3D11Device*)(obj))
 
+#define GST_TYPE_D3D11_FENCE              (gst_d3d11_fence_get_type())
+#define GST_IS_D3D11_FENCE(obj)           (GST_IS_MINI_OBJECT_TYPE(obj, GST_TYPE_D3D11_FENCE))
+#define GST_D3D11_FENCE(obj)              ((GstD3D11Fence *)obj)
+#define GST_D3D11_FENCE_CAST(obj)         (GST_D3D11_FENCE(obj))
+
 #define GST_D3D11_DEVICE_HANDLE_CONTEXT_TYPE "gst.d3d11.device.handle"
 
 struct _GstD3D11Device
 {
   GstObject parent;
 
-  GstD3D11DevicePrivate *priv;
-
   /*< private >*/
+  GstD3D11DevicePrivate *priv;
   gpointer _gst_reserved[GST_PADDING];
 };
 
@@ -94,5 +98,43 @@ gboolean              gst_d3d11_device_get_format         (GstD3D11Device * devi
                                                            GstVideoFormat format,
                                                            GstD3D11Format * device_format);
 
+struct _GstD3D11Fence
+{
+  GstMiniObject parent;
+
+  GstD3D11Device *device;
+
+  /*< private >*/
+  GstD3D11FencePrivate *priv;
+  gpointer _gst_reserved[GST_PADDING];
+};
+
+GST_D3D11_API
+GType           gst_d3d11_fence_get_type      (void);
+
+GST_D3D11_API
+GstD3D11Fence * gst_d3d11_device_create_fence (GstD3D11Device * device);
+
+GST_D3D11_API
+gboolean        gst_d3d11_fence_signal        (GstD3D11Fence * fence);
+
+GST_D3D11_API
+gboolean        gst_d3d11_fence_wait          (GstD3D11Fence * fence);
+
+static inline void
+gst_d3d11_fence_unref (GstD3D11Fence * fence)
+{
+  gst_mini_object_unref (GST_MINI_OBJECT_CAST (fence));
+}
+
+static inline void
+gst_clear_d3d11_fence (GstD3D11Fence ** fence)
+{
+  if (fence && *fence) {
+    gst_d3d11_fence_unref (*fence);
+    *fence = NULL;
+  }
+}
+
 G_END_DECLS