/* GStreamer
 * Copyright (C) 2019 Seungha Yang <seungha.yang@navercorp.com>
 * Copyright (C) 2020 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 "gstd3d11device.h"
#include "gstd3d11device-private.h"
#include "gstd3d11utils.h"
#include "gstd3d11format.h"
#include "gstd3d11-private.h"
#include "gstd3d11memory.h"
#include "gstd3d11compile.h"
#include "gstd3d11shadercache.h"
#include <gmodule.h>
#include <wrl.h>

#include <windows.h>
#include <versionhelpers.h>
#include <map>
#include <utility>
#include <atomic>
#include <mutex>
#include <string.h>
#include <unordered_map>

/**
 * SECTION:gstd3d11device
 * @title: GstD3D11Device
 * @short_description: Direct3D11 device abstraction
 *
 * #GstD3D11Device wraps ID3D11Device and ID3D11DeviceContext for GPU resources
 * to be able to be shared among various elements. Caller can get native
 * Direct3D11 handles via getter method.
 * Basically Direct3D11 API doesn't require dedicated thread like that of
 * OpenGL context, and ID3D11Device APIs are supposed to be thread-safe.
 * But concurrent call for ID3D11DeviceContext and DXGI API are not allowed.
 * To protect such object, callers need to make use of gst_d3d11_device_lock()
 * and gst_d3d11_device_unlock()
 */

/* *INDENT-OFF* */
using namespace Microsoft::WRL;
/* *INDENT-ON* */

#if HAVE_D3D11SDKLAYERS_H
#include <d3d11sdklayers.h>

/* mingw header does not define D3D11_RLDO_IGNORE_INTERNAL
 * D3D11_RLDO_SUMMARY = 0x1,
   D3D11_RLDO_DETAIL = 0x2,
 * D3D11_RLDO_IGNORE_INTERNAL = 0x4
 */
#define GST_D3D11_RLDO_FLAGS (0x2 | 0x4)
#endif

#if HAVE_DXGIDEBUG_H
#include <dxgidebug.h>
typedef HRESULT (WINAPI * DXGIGetDebugInterface_t) (REFIID riid,
    void **ppDebug);
static DXGIGetDebugInterface_t GstDXGIGetDebugInterface = nullptr;

#endif

#if (HAVE_D3D11SDKLAYERS_H || HAVE_DXGIDEBUG_H)
GST_DEBUG_CATEGORY_STATIC (gst_d3d11_debug_layer_debug);
#endif
GST_DEBUG_CATEGORY_STATIC (gst_d3d11_device_debug);
#define GST_CAT_DEFAULT gst_d3d11_device_debug

enum
{
  PROP_0,
  PROP_ADAPTER,
  PROP_DEVICE_ID,
  PROP_VENDOR_ID,
  PROP_HARDWARE,
  PROP_DESCRIPTION,
  PROP_CREATE_FLAGS,
  PROP_ADAPTER_LUID,
};

#define DEFAULT_ADAPTER 0
#define DEFAULT_CREATE_FLAGS 0

/* *INDENT-OFF* */
struct _GstD3D11DevicePrivate
{
  guint adapter = 0;
  guint device_id = 0;
  guint vendor_id = 0;
  gboolean hardware = 0;
  gchar *description = nullptr;
  guint create_flags = 0;
  gint64 adapter_luid = 0;

  ID3D11Device *device = nullptr;
  ID3D11Device5 *device5 = nullptr;
  ID3D11DeviceContext *device_context = nullptr;
  ID3D11DeviceContext4 *device_context4 = nullptr;

  ID3D11VideoDevice *video_device = nullptr;
  ID3D11VideoContext *video_context = nullptr;

  IDXGIFactory1 *factory = nullptr;
  std::unordered_map<GstVideoFormat, GstD3D11Format> format_table;

  std::recursive_mutex extern_lock;
  std::mutex resource_lock;

  LARGE_INTEGER frequency;

  D3D_FEATURE_LEVEL feature_level;
  std::map <gint64, ComPtr<ID3D11PixelShader>> ps_cache;
  std::map <gint64,
      std::pair<ComPtr<ID3D11VertexShader>, ComPtr<ID3D11InputLayout>>> vs_cache;
  std::map <D3D11_FILTER, ComPtr<ID3D11SamplerState>> sampler_cache;

  ID3D11RasterizerState *rs = nullptr;
  ID3D11RasterizerState *rs_msaa = nullptr;

#if HAVE_D3D11SDKLAYERS_H
  ID3D11Debug *d3d11_debug = nullptr;
  ID3D11InfoQueue *d3d11_info_queue = nullptr;
#endif

#if HAVE_DXGIDEBUG_H
  IDXGIDebug *dxgi_debug = nullptr;
  IDXGIInfoQueue *dxgi_info_queue = nullptr;
#endif
};
/* *INDENT-ON* */

static void
debug_init_once (void)
{
  GST_D3D11_CALL_ONCE_BEGIN {
    GST_DEBUG_CATEGORY_INIT (gst_d3d11_device_debug,
        "d3d11device", 0, "d3d11 device object");
#if defined(HAVE_D3D11SDKLAYERS_H) || defined(HAVE_DXGIDEBUG_H)
    GST_DEBUG_CATEGORY_INIT (gst_d3d11_debug_layer_debug,
        "d3d11debuglayer", 0, "native d3d11 and dxgi debug");
#endif
  } GST_D3D11_CALL_ONCE_END;
}

#define gst_d3d11_device_parent_class parent_class
G_DEFINE_TYPE_WITH_CODE (GstD3D11Device, gst_d3d11_device, GST_TYPE_OBJECT,
    debug_init_once ());

static void gst_d3d11_device_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec);
static void gst_d3d11_device_dispose (GObject * object);
static void gst_d3d11_device_finalize (GObject * object);

#if HAVE_D3D11SDKLAYERS_H
static gboolean
gst_d3d11_device_enable_d3d11_debug (void)
{
  static GModule *d3d11_debug_module = nullptr;
  /* If all below libraries are unavailable, d3d11 device would fail with
   * D3D11_CREATE_DEVICE_DEBUG flag */
  static const gchar *sdk_dll_names[] = {
    "d3d11sdklayers.dll",
    "d3d11_1sdklayers.dll",
    "d3d11_2sdklayers.dll",
    "d3d11_3sdklayers.dll",
  };

  GST_D3D11_CALL_ONCE_BEGIN {
    for (guint i = 0; i < G_N_ELEMENTS (sdk_dll_names); i++) {
      d3d11_debug_module = g_module_open (sdk_dll_names[i], G_MODULE_BIND_LAZY);
      if (d3d11_debug_module)
        return;
    }
  }
  GST_D3D11_CALL_ONCE_END;

  if (d3d11_debug_module)
    return TRUE;

  return FALSE;
}

static inline GstDebugLevel
d3d11_message_severity_to_gst (D3D11_MESSAGE_SEVERITY level)
{
  switch (level) {
    case D3D11_MESSAGE_SEVERITY_CORRUPTION:
    case D3D11_MESSAGE_SEVERITY_ERROR:
      return GST_LEVEL_ERROR;
    case D3D11_MESSAGE_SEVERITY_WARNING:
      return GST_LEVEL_WARNING;
    case D3D11_MESSAGE_SEVERITY_INFO:
      return GST_LEVEL_INFO;
    case D3D11_MESSAGE_SEVERITY_MESSAGE:
      return GST_LEVEL_DEBUG;
    default:
      break;
  }

  return GST_LEVEL_LOG;
}

void
gst_d3d11_device_d3d11_debug (GstD3D11Device * device,
    const gchar * file, const gchar * function, gint line)
{
  GstD3D11DevicePrivate *priv = device->priv;
  D3D11_MESSAGE *msg;
  SIZE_T msg_len = 0;
  HRESULT hr;
  UINT64 num_msg, i;
  ID3D11InfoQueue *info_queue = priv->d3d11_info_queue;

  if (!info_queue)
    return;

  num_msg = info_queue->GetNumStoredMessages ();

  for (i = 0; i < num_msg; i++) {
    GstDebugLevel level;

    hr = info_queue->GetMessage (i, NULL, &msg_len);

    if (FAILED (hr) || msg_len == 0) {
      return;
    }

    msg = (D3D11_MESSAGE *) g_malloc0 (msg_len);
    hr = info_queue->GetMessage (i, msg, &msg_len);

    level = d3d11_message_severity_to_gst (msg->Severity);
    if (msg->Category == D3D11_MESSAGE_CATEGORY_STATE_CREATION &&
        level > GST_LEVEL_ERROR) {
      /* Do not warn for live object, since there would be live object
       * when ReportLiveDeviceObjects was called */
      level = GST_LEVEL_INFO;
    }

    gst_debug_log (gst_d3d11_debug_layer_debug, level, file, function, line,
        G_OBJECT (device), "D3D11InfoQueue: %s", msg->pDescription);
    g_free (msg);
  }

  info_queue->ClearStoredMessages ();

  return;
}
#else
void
gst_d3d11_device_d3d11_debug (GstD3D11Device * device,
    const gchar * file, const gchar * function, gint line)
{
  /* do nothing */
  return;
}
#endif

#if HAVE_DXGIDEBUG_H
static gboolean
gst_d3d11_device_enable_dxgi_debug (void)
{
#if (!GST_D3D11_WINAPI_ONLY_APP)
  static GModule *dxgi_debug_module = nullptr;

  GST_D3D11_CALL_ONCE_BEGIN {
    dxgi_debug_module = g_module_open ("dxgidebug.dll", G_MODULE_BIND_LAZY);

    if (dxgi_debug_module)
      g_module_symbol (dxgi_debug_module,
          "DXGIGetDebugInterface", (gpointer *) & GstDXGIGetDebugInterface);
  }
  GST_D3D11_CALL_ONCE_END;

  if (!GstDXGIGetDebugInterface)
    return FALSE;
#endif

  return TRUE;
}

static HRESULT
gst_d3d11_device_dxgi_get_device_interface (REFIID riid, void **debug)
{
#if (!GST_D3D11_WINAPI_ONLY_APP)
  if (GstDXGIGetDebugInterface) {
    return GstDXGIGetDebugInterface (riid, debug);
  } else {
    return E_NOINTERFACE;
  }
#else
  return DXGIGetDebugInterface1 (0, riid, debug);
#endif
}

static inline GstDebugLevel
dxgi_info_queue_message_severity_to_gst (DXGI_INFO_QUEUE_MESSAGE_SEVERITY level)
{
  switch (level) {
    case DXGI_INFO_QUEUE_MESSAGE_SEVERITY_CORRUPTION:
    case DXGI_INFO_QUEUE_MESSAGE_SEVERITY_ERROR:
      return GST_LEVEL_ERROR;
    case DXGI_INFO_QUEUE_MESSAGE_SEVERITY_WARNING:
      return GST_LEVEL_WARNING;
    case DXGI_INFO_QUEUE_MESSAGE_SEVERITY_INFO:
      return GST_LEVEL_INFO;
    case DXGI_INFO_QUEUE_MESSAGE_SEVERITY_MESSAGE:
      return GST_LEVEL_DEBUG;
    default:
      break;
  }

  return GST_LEVEL_LOG;
}

void
gst_d3d11_device_dxgi_debug (GstD3D11Device * device,
    const gchar * file, const gchar * function, gint line)
{
  GstD3D11DevicePrivate *priv = device->priv;
  DXGI_INFO_QUEUE_MESSAGE *msg;
  SIZE_T msg_len = 0;
  HRESULT hr;
  UINT64 num_msg, i;
  IDXGIInfoQueue *info_queue = priv->dxgi_info_queue;

  if (!info_queue)
    return;

  num_msg = info_queue->GetNumStoredMessages (DXGI_DEBUG_ALL);

  for (i = 0; i < num_msg; i++) {
    GstDebugLevel level;

    hr = info_queue->GetMessage (DXGI_DEBUG_ALL, i, NULL, &msg_len);

    if (FAILED (hr) || msg_len == 0) {
      return;
    }

    msg = (DXGI_INFO_QUEUE_MESSAGE *) g_malloc0 (msg_len);
    hr = info_queue->GetMessage (DXGI_DEBUG_ALL, i, msg, &msg_len);

    level = dxgi_info_queue_message_severity_to_gst (msg->Severity);
    gst_debug_log (gst_d3d11_debug_layer_debug, level, file, function, line,
        G_OBJECT (device), "DXGIInfoQueue: %s", msg->pDescription);
    g_free (msg);
  }

  info_queue->ClearStoredMessages (DXGI_DEBUG_ALL);

  return;
}
#else
void
gst_d3d11_device_dxgi_debug (GstD3D11Device * device,
    const gchar * file, const gchar * function, gint line)
{
  /* do nothing */
  return;
}
#endif

static void
gst_d3d11_device_class_init (GstD3D11DeviceClass * klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  GParamFlags readable_flags =
      (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

  gobject_class->get_property = gst_d3d11_device_get_property;
  gobject_class->dispose = gst_d3d11_device_dispose;
  gobject_class->finalize = gst_d3d11_device_finalize;

  g_object_class_install_property (gobject_class, PROP_ADAPTER,
      g_param_spec_uint ("adapter", "Adapter",
          "DXGI Adapter index for creating device",
          0, G_MAXUINT32, DEFAULT_ADAPTER, readable_flags));

  g_object_class_install_property (gobject_class, PROP_DEVICE_ID,
      g_param_spec_uint ("device-id", "Device Id",
          "DXGI Device ID", 0, G_MAXUINT32, 0, readable_flags));

  g_object_class_install_property (gobject_class, PROP_VENDOR_ID,
      g_param_spec_uint ("vendor-id", "Vendor Id",
          "DXGI Vendor ID", 0, G_MAXUINT32, 0, readable_flags));

  g_object_class_install_property (gobject_class, PROP_HARDWARE,
      g_param_spec_boolean ("hardware", "Hardware",
          "Whether hardware device or not", TRUE, readable_flags));

  g_object_class_install_property (gobject_class, PROP_DESCRIPTION,
      g_param_spec_string ("description", "Description",
          "Human readable device description", NULL, readable_flags));

  g_object_class_install_property (gobject_class, PROP_ADAPTER_LUID,
      g_param_spec_int64 ("adapter-luid", "Adapter LUID",
          "DXGI Adapter LUID (Locally Unique Identifier) of created device",
          G_MININT64, G_MAXINT64, 0, readable_flags));

  gst_d3d11_memory_init_once ();
}

static void
gst_d3d11_device_init (GstD3D11Device * self)
{
  self->priv = new GstD3D11DevicePrivate ();
}

static gboolean
is_windows_8_or_greater (void)
{
  static gboolean ret = FALSE;

  GST_D3D11_CALL_ONCE_BEGIN {
#if (!GST_D3D11_WINAPI_ONLY_APP)
    if (IsWindows8OrGreater ())
      ret = TRUE;
#else
    ret = TRUE;
#endif
  } GST_D3D11_CALL_ONCE_END;

  return ret;
}

static guint
check_format_support (GstD3D11Device * self, DXGI_FORMAT format)
{
  GstD3D11DevicePrivate *priv = self->priv;
  ID3D11Device *handle = priv->device;
  HRESULT hr;
  UINT format_support;

  hr = handle->CheckFormatSupport (format, &format_support);
  if (FAILED (hr) || format_support == 0)
    return 0;

  return format_support;
}

static void
dump_format (GstD3D11Device * self, GstD3D11Format * format)
{
  gchar *format_support_str = g_flags_to_string (GST_TYPE_D3D11_FORMAT_SUPPORT,
      format->format_support[0]);

  GST_LOG_OBJECT (self, "%s -> %s (%d), "
      "resource format: %s (%d), %s (%d), %s (%d), %s (%d), flags (0x%x) %s",
      gst_video_format_to_string (format->format),
      gst_d3d11_dxgi_format_to_string (format->dxgi_format),
      format->dxgi_format,
      gst_d3d11_dxgi_format_to_string (format->resource_format[0]),
      format->resource_format[0],
      gst_d3d11_dxgi_format_to_string (format->resource_format[1]),
      format->resource_format[1],
      gst_d3d11_dxgi_format_to_string (format->resource_format[2]),
      format->resource_format[2],
      gst_d3d11_dxgi_format_to_string (format->resource_format[3]),
      format->resource_format[3], format->format_support[0],
      format_support_str);

  g_free (format_support_str);
}

static void
gst_d3d11_device_setup_format_table (GstD3D11Device * self)
{
  GstD3D11DevicePrivate *priv = self->priv;

  for (guint i = 0; i < G_N_ELEMENTS (_gst_d3d11_default_format_map); i++) {
    const GstD3D11Format *iter = &_gst_d3d11_default_format_map[i];
    GstD3D11Format format;
    guint support[GST_VIDEO_MAX_PLANES] = { 0, };
    gboolean native = TRUE;

    switch (iter->format) {
        /* RGB/GRAY */
      case GST_VIDEO_FORMAT_BGRA:
      case GST_VIDEO_FORMAT_BGRx:
      case GST_VIDEO_FORMAT_RGBA:
      case GST_VIDEO_FORMAT_RGBx:
      case GST_VIDEO_FORMAT_RGB10A2_LE:
      case GST_VIDEO_FORMAT_RGBA64_LE:
      case GST_VIDEO_FORMAT_GRAY8:
      case GST_VIDEO_FORMAT_GRAY16_LE:
        support[0] = check_format_support (self, iter->dxgi_format);
        if (!support[0]) {
          const gchar *format_name =
              gst_d3d11_dxgi_format_to_string (iter->dxgi_format);
          GST_INFO_OBJECT (self, "DXGI_FORMAT_%s (%d) for %s is not supported",
              format_name, (guint) iter->dxgi_format,
              gst_video_format_to_string (iter->format));
          continue;
        }
        break;
        /* YUV DXGI native formats */
      case GST_VIDEO_FORMAT_VUYA:
      case GST_VIDEO_FORMAT_Y410:
      case GST_VIDEO_FORMAT_NV12:
      case GST_VIDEO_FORMAT_P010_10LE:
      case GST_VIDEO_FORMAT_P012_LE:
      case GST_VIDEO_FORMAT_P016_LE:
      case GST_VIDEO_FORMAT_YUY2:
      case GST_VIDEO_FORMAT_Y210:
      case GST_VIDEO_FORMAT_Y212_LE:
      case GST_VIDEO_FORMAT_Y412_LE:
      case GST_VIDEO_FORMAT_BGRA64_LE:
      case GST_VIDEO_FORMAT_BGR10A2_LE:
      case GST_VIDEO_FORMAT_RBGA:
      {
        gboolean supported = TRUE;

        if (is_windows_8_or_greater ())
          support[0] = check_format_support (self, iter->dxgi_format);

        if (!support[0]) {
          GST_DEBUG_OBJECT (self,
              "DXGI_FORMAT_%s (%d) for %s is not supported, "
              "checking resource format",
              gst_d3d11_dxgi_format_to_string (iter->dxgi_format),
              (guint) iter->dxgi_format,
              gst_video_format_to_string (iter->format));

          native = FALSE;
          for (guint j = 0; j < GST_VIDEO_MAX_PLANES; j++) {
            if (iter->resource_format[j] == DXGI_FORMAT_UNKNOWN)
              break;

            support[j] = check_format_support (self, iter->resource_format[j]);
            if (support[j] == 0) {
              supported = FALSE;
              break;
            }
          }

          if (!supported) {
            GST_INFO_OBJECT (self, "%s is not supported",
                gst_video_format_to_string (iter->format));
            continue;
          }
        }
        break;
      }
        /* non-DXGI native formats */
      case GST_VIDEO_FORMAT_NV21:
      case GST_VIDEO_FORMAT_I420:
      case GST_VIDEO_FORMAT_YV12:
      case GST_VIDEO_FORMAT_I420_10LE:
      case GST_VIDEO_FORMAT_I420_12LE:
      case GST_VIDEO_FORMAT_Y42B:
      case GST_VIDEO_FORMAT_I422_10LE:
      case GST_VIDEO_FORMAT_I422_12LE:
      case GST_VIDEO_FORMAT_Y444:
      case GST_VIDEO_FORMAT_Y444_10LE:
      case GST_VIDEO_FORMAT_Y444_12LE:
      case GST_VIDEO_FORMAT_Y444_16LE:
      case GST_VIDEO_FORMAT_AYUV:
      case GST_VIDEO_FORMAT_AYUV64:
      case GST_VIDEO_FORMAT_UYVY:
      case GST_VIDEO_FORMAT_VYUY:
      case GST_VIDEO_FORMAT_YVYU:
      case GST_VIDEO_FORMAT_ARGB:
      case GST_VIDEO_FORMAT_xRGB:
      case GST_VIDEO_FORMAT_ABGR:
      case GST_VIDEO_FORMAT_xBGR:
      case GST_VIDEO_FORMAT_RGB:
      case GST_VIDEO_FORMAT_BGR:
      case GST_VIDEO_FORMAT_v210:
      case GST_VIDEO_FORMAT_v216:
      case GST_VIDEO_FORMAT_v308:
      case GST_VIDEO_FORMAT_IYU2:
      case GST_VIDEO_FORMAT_RGB16:
      case GST_VIDEO_FORMAT_BGR16:
      case GST_VIDEO_FORMAT_RGB15:
      case GST_VIDEO_FORMAT_BGR15:
      case GST_VIDEO_FORMAT_r210:
        /* RGB planar formats */
      case GST_VIDEO_FORMAT_RGBP:
      case GST_VIDEO_FORMAT_BGRP:
      case GST_VIDEO_FORMAT_GBR:
      case GST_VIDEO_FORMAT_GBR_10LE:
      case GST_VIDEO_FORMAT_GBR_12LE:
      case GST_VIDEO_FORMAT_GBR_16LE:
      case GST_VIDEO_FORMAT_GBRA:
      case GST_VIDEO_FORMAT_GBRA_10LE:
      case GST_VIDEO_FORMAT_GBRA_12LE:
      {
        gboolean supported = TRUE;

        native = FALSE;
        for (guint j = 0; j < GST_VIDEO_MAX_PLANES; j++) {
          if (iter->resource_format[j] == DXGI_FORMAT_UNKNOWN)
            break;

          support[j] = check_format_support (self, iter->resource_format[j]);
          if (support[j] == 0) {
            supported = FALSE;
            break;
          }
        }

        if (!supported) {
          GST_INFO_OBJECT (self, "%s is not supported",
              gst_video_format_to_string (iter->format));
          continue;
        }
        break;
      }
      default:
        g_assert_not_reached ();
        return;
    }

    format = *iter;

    if (!native)
      format.dxgi_format = DXGI_FORMAT_UNKNOWN;

    for (guint j = 0; j < GST_VIDEO_MAX_PLANES; j++)
      format.format_support[j] = support[j];

#ifndef GST_DISABLE_GST_DEBUG
    if (gst_debug_category_get_threshold (GST_CAT_DEFAULT) >= GST_LEVEL_LOG)
      dump_format (self, &format);
#endif

    priv->format_table[format.format] = format;
  }
}

static void
gst_d3d11_device_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec)
{
  GstD3D11Device *self = GST_D3D11_DEVICE (object);
  GstD3D11DevicePrivate *priv = self->priv;

  switch (prop_id) {
    case PROP_ADAPTER:
      g_value_set_uint (value, priv->adapter);
      break;
    case PROP_DEVICE_ID:
      g_value_set_uint (value, priv->device_id);
      break;
    case PROP_VENDOR_ID:
      g_value_set_uint (value, priv->vendor_id);
      break;
    case PROP_HARDWARE:
      g_value_set_boolean (value, priv->hardware);
      break;
    case PROP_DESCRIPTION:
      g_value_set_string (value, priv->description);
      break;
    case PROP_ADAPTER_LUID:
      g_value_set_int64 (value, priv->adapter_luid);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

void
gst_d3d11_device_log_live_objects (GstD3D11Device * device,
    const gchar * file, const gchar * function, gint line)
{
#if HAVE_D3D11SDKLAYERS_H
  if (device->priv->d3d11_debug) {
    device->priv->d3d11_debug->ReportLiveDeviceObjects ((D3D11_RLDO_FLAGS)
        GST_D3D11_RLDO_FLAGS);
  }

  if (device->priv->d3d11_info_queue)
    gst_d3d11_device_d3d11_debug (device, file, function, line);
#endif

#if HAVE_DXGIDEBUG_H
  if (device->priv->dxgi_debug) {
    device->priv->dxgi_debug->ReportLiveObjects (DXGI_DEBUG_ALL,
        (DXGI_DEBUG_RLO_FLAGS) GST_D3D11_RLDO_FLAGS);
  }

  if (device->priv->dxgi_info_queue)
    gst_d3d11_device_dxgi_debug (device, file, function, line);
#endif
}

static void
gst_d3d11_device_dispose (GObject * object)
{
  GstD3D11Device *self = GST_D3D11_DEVICE (object);
  GstD3D11DevicePrivate *priv = self->priv;

  GST_LOG_OBJECT (self, "dispose");

  priv->ps_cache.clear ();
  priv->vs_cache.clear ();
  priv->sampler_cache.clear ();

  GST_D3D11_CLEAR_COM (priv->rs);
  GST_D3D11_CLEAR_COM (priv->rs_msaa);
  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);
  GST_D3D11_CLEAR_COM (priv->device_context);
  GST_D3D11_CLEAR_COM (priv->factory);
  gst_d3d11_device_log_live_objects (self, __FILE__, GST_FUNCTION, __LINE__);

#if HAVE_D3D11SDKLAYERS_H
  GST_D3D11_CLEAR_COM (priv->d3d11_debug);
  GST_D3D11_CLEAR_COM (priv->d3d11_info_queue);
#endif

#if HAVE_DXGIDEBUG_H
  GST_D3D11_CLEAR_COM (priv->dxgi_debug);
  GST_D3D11_CLEAR_COM (priv->dxgi_info_queue);
#endif

  G_OBJECT_CLASS (parent_class)->dispose (object);
}

static void
gst_d3d11_device_finalize (GObject * object)
{
  GstD3D11Device *self = GST_D3D11_DEVICE (object);
  GstD3D11DevicePrivate *priv = self->priv;

  GST_LOG_OBJECT (self, "finalize");

  g_free (priv->description);

  delete priv;

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

typedef enum
{
  DEVICE_CONSTRUCT_FOR_ADAPTER_INDEX,
  DEVICE_CONSTRUCT_FOR_ADAPTER_LUID,
  DEVICE_CONSTRUCT_WRAPPED,
} GstD3D11DeviceConstructType;

typedef struct _GstD3D11DeviceConstructData
{
  union
  {
    guint adapter_index;
    gint64 adapter_luid;
    ID3D11Device *device;
  } data;
  GstD3D11DeviceConstructType type;
  UINT create_flags;
} GstD3D11DeviceConstructData;

static HRESULT
_gst_d3d11_device_get_adapter (const GstD3D11DeviceConstructData * data,
    IDXGIFactory1 * factory, guint * index, DXGI_ADAPTER_DESC * adapter_desc,
    IDXGIAdapter1 ** dxgi_adapter)
{
  HRESULT hr = S_OK;
  ComPtr < IDXGIAdapter1 > adapter1;
  DXGI_ADAPTER_DESC desc;

  switch (data->type) {
    case DEVICE_CONSTRUCT_FOR_ADAPTER_INDEX:
    {
      hr = factory->EnumAdapters1 (data->data.adapter_index, &adapter1);
      if (FAILED (hr))
        return hr;

      hr = adapter1->GetDesc (&desc);
      if (FAILED (hr))
        return hr;

      *index = data->data.adapter_index;
      *adapter_desc = desc;
      *dxgi_adapter = adapter1.Detach ();

      return S_OK;
    }
    case DEVICE_CONSTRUCT_FOR_ADAPTER_LUID:
    {
      for (guint i = 0;; i++) {
        gint64 luid;

        adapter1 = nullptr;

        hr = factory->EnumAdapters1 (i, &adapter1);
        if (FAILED (hr))
          return hr;

        hr = adapter1->GetDesc (&desc);
        if (FAILED (hr))
          continue;

        luid = gst_d3d11_luid_to_int64 (&desc.AdapterLuid);
        if (luid != data->data.adapter_luid)
          continue;

        *index = i;
        *adapter_desc = desc;
        *dxgi_adapter = adapter1.Detach ();

        return S_OK;
      }

      return E_FAIL;
    }
    case DEVICE_CONSTRUCT_WRAPPED:
    {
      ComPtr < IDXGIDevice > dxgi_device;
      ComPtr < IDXGIAdapter > adapter;
      ID3D11Device *device = data->data.device;
      guint luid;

      hr = device->QueryInterface (IID_PPV_ARGS (&dxgi_device));
      if (FAILED (hr))
        return hr;

      hr = dxgi_device->GetAdapter (&adapter);
      if (FAILED (hr))
        return hr;

      hr = adapter.As (&adapter1);
      if (FAILED (hr))
        return hr;

      hr = adapter1->GetDesc (&desc);
      if (FAILED (hr))
        return hr;

      luid = gst_d3d11_luid_to_int64 (&desc.AdapterLuid);

      for (guint i = 0;; i++) {
        DXGI_ADAPTER_DESC tmp_desc;
        ComPtr < IDXGIAdapter1 > tmp;

        hr = factory->EnumAdapters1 (i, &tmp);
        if (FAILED (hr))
          return hr;

        hr = tmp->GetDesc (&tmp_desc);
        if (FAILED (hr))
          continue;

        if (luid != gst_d3d11_luid_to_int64 (&tmp_desc.AdapterLuid))
          continue;

        *index = i;
        *adapter_desc = desc;
        *dxgi_adapter = adapter1.Detach ();

        return S_OK;
      }

      return E_FAIL;
    }
    default:
      g_assert_not_reached ();
      break;
  }

  return E_FAIL;
}

static void
gst_d3d11_device_setup_debug_layer (GstD3D11Device * self)
{
#if HAVE_DXGIDEBUG_H
  if (gst_debug_category_get_threshold (gst_d3d11_debug_layer_debug) >
      GST_LEVEL_ERROR) {
    GstD3D11DevicePrivate *priv = self->priv;

    if (gst_d3d11_device_enable_dxgi_debug ()) {
      IDXGIDebug *debug = nullptr;
      IDXGIInfoQueue *info_queue = nullptr;
      HRESULT hr;

      GST_CAT_INFO_OBJECT (gst_d3d11_debug_layer_debug, self,
          "dxgi debug library was loaded");
      hr = gst_d3d11_device_dxgi_get_device_interface (IID_PPV_ARGS (&debug));

      if (SUCCEEDED (hr)) {
        GST_CAT_INFO_OBJECT (gst_d3d11_debug_layer_debug, self,
            "IDXGIDebug interface available");
        priv->dxgi_debug = debug;

        hr = gst_d3d11_device_dxgi_get_device_interface (IID_PPV_ARGS
            (&info_queue));
        if (SUCCEEDED (hr)) {
          GST_CAT_INFO_OBJECT (gst_d3d11_debug_layer_debug, self,
              "IDXGIInfoQueue interface available");
          priv->dxgi_info_queue = info_queue;
        }
      }
    } else {
      GST_CAT_INFO_OBJECT (gst_d3d11_debug_layer_debug, self,
          "couldn't load dxgi debug library");
    }
  }
#endif

#if HAVE_D3D11SDKLAYERS_H
  if ((self->priv->create_flags & D3D11_CREATE_DEVICE_DEBUG) != 0) {
    GstD3D11DevicePrivate *priv = self->priv;
    ID3D11Debug *debug;
    ID3D11InfoQueue *info_queue;
    HRESULT hr;

    hr = priv->device->QueryInterface (IID_PPV_ARGS (&debug));

    if (SUCCEEDED (hr)) {
      GST_CAT_INFO_OBJECT (gst_d3d11_debug_layer_debug, self,
          "D3D11Debug interface available");
      priv->d3d11_debug = debug;

      hr = priv->device->QueryInterface (IID_PPV_ARGS (&info_queue));
      if (SUCCEEDED (hr)) {
        GST_CAT_INFO_OBJECT (gst_d3d11_debug_layer_debug, self,
            "ID3D11InfoQueue interface available");
        priv->d3d11_info_queue = info_queue;
      }
    }
  }
#endif
}

static GstD3D11Device *
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;
  DXGI_ADAPTER_DESC adapter_desc;
  static const D3D_FEATURE_LEVEL feature_levels[] = {
    D3D_FEATURE_LEVEL_11_1,
    D3D_FEATURE_LEVEL_11_0,
    D3D_FEATURE_LEVEL_10_1,
    D3D_FEATURE_LEVEL_10_0,
  };
  D3D_FEATURE_LEVEL selected_level;

  debug_init_once ();

  hr = CreateDXGIFactory1 (IID_PPV_ARGS (&factory));
  if (!gst_d3d11_result (hr, NULL)) {
    GST_WARNING ("cannot create dxgi factory, hr: 0x%x", (guint) hr);
    return nullptr;
  }

  create_flags = 0;
  if (data->type != DEVICE_CONSTRUCT_WRAPPED) {
    create_flags = data->create_flags;
#if HAVE_D3D11SDKLAYERS_H
    if (gst_debug_category_get_threshold (gst_d3d11_debug_layer_debug) >
        GST_LEVEL_ERROR) {
      /* DirectX SDK should be installed on system for this */
      if (gst_d3d11_device_enable_d3d11_debug ()) {
        GST_CAT_INFO (gst_d3d11_debug_layer_debug,
            "d3d11 debug library was loaded");
        create_flags |= D3D11_CREATE_DEVICE_DEBUG;
      } else {
        GST_CAT_INFO (gst_d3d11_debug_layer_debug,
            "couldn't load d3d11 debug library");
      }
    }
#endif
  }

  /* Ensure valid device handle */
  if (data->type == DEVICE_CONSTRUCT_WRAPPED) {
    ID3D11Device *external_device = data->data.device;

    hr = external_device->QueryInterface (IID_PPV_ARGS (&device));
    if (FAILED (hr)) {
      GST_WARNING ("Not a valid external ID3D11Device handle");
      return nullptr;
    }

    selected_level = device->GetFeatureLevel ();
    if (selected_level < D3D_FEATURE_LEVEL_10_0) {
      GST_ERROR ("Feature level 0x%x is not supported", (guint) selected_level);
      return nullptr;
    }

    device->GetImmediateContext (&device_context);
  }

  hr = _gst_d3d11_device_get_adapter (data, factory.Get (), &adapter_index,
      &adapter_desc, &adapter);
  if (FAILED (hr)) {
    GST_INFO ("Failed to get DXGI adapter");
    return nullptr;
  }

  if (data->type != DEVICE_CONSTRUCT_WRAPPED) {
    hr = D3D11CreateDevice (adapter.Get (), D3D_DRIVER_TYPE_UNKNOWN,
        NULL, create_flags, feature_levels, G_N_ELEMENTS (feature_levels),
        D3D11_SDK_VERSION, &device, &selected_level, &device_context);

    if (FAILED (hr)) {
      /* Retry if the system could not recognize D3D_FEATURE_LEVEL_11_1 */
      hr = D3D11CreateDevice (adapter.Get (), D3D_DRIVER_TYPE_UNKNOWN,
          NULL, create_flags, &feature_levels[1],
          G_N_ELEMENTS (feature_levels) - 1, D3D11_SDK_VERSION, &device,
          &selected_level, &device_context);
    }

    /* if D3D11_CREATE_DEVICE_DEBUG was enabled but couldn't create device,
     * try it without the flag again */
    if (FAILED (hr) && (create_flags & D3D11_CREATE_DEVICE_DEBUG) != 0) {
      create_flags &= ~D3D11_CREATE_DEVICE_DEBUG;

      hr = D3D11CreateDevice (adapter.Get (), D3D_DRIVER_TYPE_UNKNOWN,
          NULL, create_flags, feature_levels, G_N_ELEMENTS (feature_levels),
          D3D11_SDK_VERSION, &device, &selected_level, &device_context);

      if (FAILED (hr)) {
        /* Retry if the system could not recognize D3D_FEATURE_LEVEL_11_1 */
        hr = D3D11CreateDevice (adapter.Get (), D3D_DRIVER_TYPE_UNKNOWN,
            NULL, create_flags, &feature_levels[1],
            G_N_ELEMENTS (feature_levels) - 1, D3D11_SDK_VERSION, &device,
            &selected_level, &device_context);
      }
    }
  }

  if (FAILED (hr)) {
    switch (data->type) {
      case DEVICE_CONSTRUCT_FOR_ADAPTER_INDEX:
      {
        GST_INFO ("Failed to create d3d11 device for adapter index %d"
            " with flags 0x%x, hr: 0x%x", data->data.adapter_index,
            create_flags, (guint) hr);
        return nullptr;
      }
      case DEVICE_CONSTRUCT_FOR_ADAPTER_LUID:
      {
        GST_WARNING ("Failed to create d3d11 device for adapter luid %"
            G_GINT64_FORMAT " with flags 0x%x, hr: 0x%x",
            data->data.adapter_luid, create_flags, (guint) hr);
        return nullptr;
      }
      default:
        break;
    }

    return nullptr;
  }

  GstD3D11Device *self = nullptr;
  GstD3D11DevicePrivate *priv;

  self = (GstD3D11Device *) g_object_new (GST_TYPE_D3D11_DEVICE, nullptr);
  gst_object_ref_sink (self);

  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 ();
  priv->factory = factory.Detach ();

  priv->vendor_id = adapter_desc.VendorId;
  priv->device_id = adapter_desc.DeviceId;
  priv->description = g_utf16_to_utf8 ((gunichar2 *) adapter_desc.Description,
      -1, nullptr, nullptr, nullptr);
  priv->adapter_luid = gst_d3d11_luid_to_int64 (&adapter_desc.AdapterLuid);
  priv->feature_level = priv->device->GetFeatureLevel ();

  DXGI_ADAPTER_DESC1 desc1;
  hr = adapter->GetDesc1 (&desc1);

  /* DXGI_ADAPTER_FLAG_SOFTWARE is missing in dxgi.h of mingw */
  if (SUCCEEDED (hr) && (desc1.Flags & 0x2) != 0x2)
    priv->hardware = TRUE;

  priv->create_flags = create_flags;
  gst_d3d11_device_setup_format_table (self);
  gst_d3d11_device_setup_debug_layer (self);

  BOOL ret = QueryPerformanceFrequency (&priv->frequency);
  g_assert (ret);

  return self;
}

/**
 * gst_d3d11_device_new:
 * @adapter_index: the index of adapter for creating d3d11 device
 * @flags: a D3D11_CREATE_DEVICE_FLAG value used for creating d3d11 device
 *
 * Returns: (transfer full) (nullable): a new #GstD3D11Device for @adapter_index
 * or %NULL when failed to create D3D11 device with given adapter index.
 *
 * Since: 1.22
 */
GstD3D11Device *
gst_d3d11_device_new (guint adapter_index, guint flags)
{
  GstD3D11DeviceConstructData data;

  data.data.adapter_index = adapter_index;
  data.type = DEVICE_CONSTRUCT_FOR_ADAPTER_INDEX;
  data.create_flags = flags;

  return gst_d3d11_device_new_internal (&data);
}

/**
 * gst_d3d11_device_new_for_adapter_luid:
 * @adapter_luid: an int64 representation of the DXGI adapter LUID
 * @flags: a D3D11_CREATE_DEVICE_FLAG value used for creating d3d11 device
 *
 * Returns: (transfer full) (nullable): a new #GstD3D11Device for @adapter_luid
 * or %NULL when failed to create D3D11 device with given adapter luid.
 *
 * Since: 1.22
 */
GstD3D11Device *
gst_d3d11_device_new_for_adapter_luid (gint64 adapter_luid, guint flags)
{
  GstD3D11DeviceConstructData data;

  data.data.adapter_luid = adapter_luid;
  data.type = DEVICE_CONSTRUCT_FOR_ADAPTER_LUID;
  data.create_flags = flags;

  return gst_d3d11_device_new_internal (&data);
}

/**
 * gst_d3d11_device_new_wrapped:
 * @device: (transfer none): an existing ID3D11Device handle
 *
 * Returns: (transfer full) (nullable): a new #GstD3D11Device for @device
 * or %NULL if an error occurred
 *
 * Since: 1.22
 */
GstD3D11Device *
gst_d3d11_device_new_wrapped (ID3D11Device * device)
{
  GstD3D11DeviceConstructData data;

  g_return_val_if_fail (device != nullptr, nullptr);

  data.data.device = device;
  data.type = DEVICE_CONSTRUCT_WRAPPED;
  data.create_flags = 0;

  return gst_d3d11_device_new_internal (&data);
}

/**
 * gst_d3d11_device_get_device_handle:
 * @device: a #GstD3D11Device
 *
 * Used for various D3D11 APIs directly. Caller must not destroy returned device
 * object.
 *
 * Returns: (transfer none): the ID3D11Device handle
 *
 * Since: 1.22
 */
ID3D11Device *
gst_d3d11_device_get_device_handle (GstD3D11Device * device)
{
  g_return_val_if_fail (GST_IS_D3D11_DEVICE (device), NULL);

  return device->priv->device;
}

/**
 * gst_d3d11_device_get_device_context_handle:
 * @device: a #GstD3D11Device
 *
 * Used for various D3D11 APIs directly. Caller must not destroy returned device
 * object. Any ID3D11DeviceContext call needs to be protected by
 * gst_d3d11_device_lock() and gst_d3d11_device_unlock() method.
 *
 * Returns: (transfer none): the immeidate ID3D11DeviceContext handle
 *
 * Since: 1.22
 */
ID3D11DeviceContext *
gst_d3d11_device_get_device_context_handle (GstD3D11Device * device)
{
  g_return_val_if_fail (GST_IS_D3D11_DEVICE (device), NULL);

  return device->priv->device_context;
}

/**
 * gst_d3d11_device_get_dxgi_factory_handle:
 * @device: a #GstD3D11Device
 *
 * Used for various D3D11 APIs directly. Caller must not destroy returned device
 * object.
 *
 * Returns: (transfer none): the IDXGIFactory1 handle
 *
 * Since: 1.22
 */
IDXGIFactory1 *
gst_d3d11_device_get_dxgi_factory_handle (GstD3D11Device * device)
{
  g_return_val_if_fail (GST_IS_D3D11_DEVICE (device), NULL);

  return device->priv->factory;
}

/**
 * gst_d3d11_device_get_video_device_handle:
 * @device: a #GstD3D11Device
 *
 * Used for various D3D11 APIs directly. Caller must not destroy returned device
 * object.
 *
 * Returns: (nullable) (transfer none) : the ID3D11VideoDevice handle or %NULL
 * if ID3D11VideoDevice is unavailable.
 *
 * Since: 1.22
 */
ID3D11VideoDevice *
gst_d3d11_device_get_video_device_handle (GstD3D11Device * device)
{
  GstD3D11DevicePrivate *priv;

  g_return_val_if_fail (GST_IS_D3D11_DEVICE (device), NULL);

  priv = device->priv;
  std::lock_guard < std::mutex > lk (priv->resource_lock);
  if (!priv->video_device) {
    HRESULT hr;
    ID3D11VideoDevice *video_device = NULL;

    hr = priv->device->QueryInterface (IID_PPV_ARGS (&video_device));
    if (gst_d3d11_result (hr, device))
      priv->video_device = video_device;
  }

  return priv->video_device;
}

/**
 * gst_d3d11_device_get_video_context_handle:
 * @device: a #GstD3D11Device
 *
 * Used for various D3D11 APIs directly. Caller must not destroy returned device
 * object.
 *
 * Returns: (nullable) (transfer none): the ID3D11VideoContext handle or %NULL
 * if ID3D11VideoContext is unavailable.
 *
 * Since: 1.22
 */
ID3D11VideoContext *
gst_d3d11_device_get_video_context_handle (GstD3D11Device * device)
{
  GstD3D11DevicePrivate *priv;

  g_return_val_if_fail (GST_IS_D3D11_DEVICE (device), NULL);

  priv = device->priv;
  std::lock_guard < std::mutex > lk (priv->resource_lock);
  if (!priv->video_context) {
    HRESULT hr;
    ID3D11VideoContext *video_context = NULL;

    hr = priv->device_context->QueryInterface (IID_PPV_ARGS (&video_context));
    if (gst_d3d11_result (hr, device))
      priv->video_context = video_context;
  }

  return priv->video_context;
}

/**
 * gst_d3d11_device_lock:
 * @device: a #GstD3D11Device
 *
 * Take lock for @device. Any thread-unsafe API call needs to be
 * protected by this method. This call must be paired with
 * gst_d3d11_device_unlock()
 *
 * Since: 1.22
 */
void
gst_d3d11_device_lock (GstD3D11Device * device)
{
  GstD3D11DevicePrivate *priv;

  g_return_if_fail (GST_IS_D3D11_DEVICE (device));

  priv = device->priv;

  GST_TRACE_OBJECT (device, "device locking");
  priv->extern_lock.lock ();
  GST_TRACE_OBJECT (device, "device locked");
}

/**
 * gst_d3d11_device_unlock:
 * @device: a #GstD3D11Device
 *
 * Release lock for @device. This call must be paired with
 * gst_d3d11_device_lock()
 *
 * Since: 1.22
 */
void
gst_d3d11_device_unlock (GstD3D11Device * device)
{
  GstD3D11DevicePrivate *priv;

  g_return_if_fail (GST_IS_D3D11_DEVICE (device));

  priv = device->priv;

  priv->extern_lock.unlock ();
  GST_TRACE_OBJECT (device, "device unlocked");
}

/**
 * gst_d3d11_device_get_format:
 * @device: a #GstD3D11Device
 * @format: a #GstVideoFormat
 * @device_format: (out caller-allocates) (nullable): a #GstD3D11Format
 *
 * Converts @format to #GstD3D11Format if the @format is supported
 * by device
 *
 * Returns: %TRUE if @format is supported by @device
 *
 * Since: 1.22
 */
gboolean
gst_d3d11_device_get_format (GstD3D11Device * device, GstVideoFormat format,
    GstD3D11Format * device_format)
{
  GstD3D11DevicePrivate *priv;

  g_return_val_if_fail (GST_IS_D3D11_DEVICE (device), FALSE);

  priv = device->priv;

  const auto & target = priv->format_table.find (format);
  if (target == priv->format_table.end ()) {
    if (device_format)
      gst_d3d11_format_init (device_format);

    return FALSE;
  }

  if (device_format)
    *device_format = target->second;

  return TRUE;
}

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;
}

gint64
gst_d3d11_pixel_shader_token_new (void)
{
  /* *INDENT-OFF* */
  static std::atomic < gint64 > token_ { 0 };
  /* *INDENT-ON* */

  return token_.fetch_add (1);
}

gint64
gst_d3d11_vertex_shader_token_new (void)
{
  /* *INDENT-OFF* */
  static std::atomic < gint64 > token_ { 0 };
  /* *INDENT-ON* */

  return token_.fetch_add (1);
}

gint64
gst_d3d11_compute_shader_token_new (void)
{
  /* *INDENT-OFF* */
  static std::atomic < gint64 > token_ { 0 };
  /* *INDENT-ON* */

  return token_.fetch_add (1);
}

HRESULT
gst_d3d11_device_get_pixel_shader_uncached (GstD3D11Device * device,
    gint64 token, const void *bytecode, gsize bytecode_size,
    const gchar * source, gsize source_size, const gchar * entry_point,
    const D3D_SHADER_MACRO * defines, ID3D11PixelShader ** ps)
{
  GstD3D11DevicePrivate *priv = device->priv;
  HRESULT hr;
  ComPtr < ID3D11PixelShader > shader;
  ComPtr < ID3DBlob > blob;
  const void *data;
  gsize size;

  GST_LOG_OBJECT (device,
      "Creating pixel shader for token %" G_GINT64_FORMAT ", source:\n%s",
      token, source);

  if (bytecode && bytecode_size > 1) {
    data = bytecode;
    size = bytecode_size;
    GST_DEBUG_OBJECT (device,
        "Creating shader \"%s\" using precompiled bytecode", entry_point);
  } else {
    hr = gst_d3d11_shader_cache_get_pixel_shader_blob (token,
        source, source_size, entry_point, defines, &blob);
    if (!gst_d3d11_result (hr, device))
      return hr;

    data = blob->GetBufferPointer ();
    size = blob->GetBufferSize ();
  }

  hr = priv->device->CreatePixelShader (data, size, nullptr, &shader);
  if (!gst_d3d11_result (hr, device))
    return hr;

  GST_DEBUG_OBJECT (device,
      "Created pixel shader \"%s\" for token %" G_GINT64_FORMAT,
      entry_point, token);
  *ps = shader.Detach ();

  return S_OK;
}

HRESULT
gst_d3d11_device_get_pixel_shader (GstD3D11Device * device, gint64 token,
    const void *bytecode, gsize bytecode_size, const gchar * source,
    gsize source_size, const gchar * entry_point,
    const D3D_SHADER_MACRO * defines, ID3D11PixelShader ** ps)
{
  GstD3D11DevicePrivate *priv = device->priv;
  HRESULT hr;
  ComPtr < ID3D11PixelShader > shader;

  GST_DEBUG_OBJECT (device, "Getting pixel shader \"%s\" for token %"
      G_GINT64_FORMAT, entry_point, token);

  std::lock_guard < std::mutex > lk (priv->resource_lock);
  auto cached = priv->ps_cache.find (token);
  if (cached != priv->ps_cache.end ()) {
    GST_DEBUG_OBJECT (device,
        "Found cached pixel shader \"%s\" for token %" G_GINT64_FORMAT,
        entry_point, token);
    *ps = cached->second.Get ();
    (*ps)->AddRef ();
    return S_OK;
  }

  hr = gst_d3d11_device_get_pixel_shader_uncached (device, token, bytecode,
      bytecode_size, source, source_size, entry_point, defines, &shader);
  if (!gst_d3d11_result (hr, device))
    return hr;

  priv->ps_cache[token] = shader;
  *ps = shader.Detach ();

  return S_OK;
}

HRESULT
gst_d3d11_device_get_vertex_shader (GstD3D11Device * device, gint64 token,
    const void *bytecode, gsize bytecode_size, const gchar * source,
    gsize source_size, const gchar * entry_point,
    const D3D11_INPUT_ELEMENT_DESC * input_desc, guint desc_len,
    ID3D11VertexShader ** vs, ID3D11InputLayout ** layout)
{
  GstD3D11DevicePrivate *priv = device->priv;
  HRESULT hr;
  ComPtr < ID3D11VertexShader > shader;
  ComPtr < ID3D11InputLayout > input_layout;
  ComPtr < ID3DBlob > blob;
  const void *data;
  gsize size;

  GST_DEBUG_OBJECT (device, "Getting vertext shader \"%s\" for token %"
      G_GINT64_FORMAT, entry_point, token);

  std::lock_guard < std::mutex > lk (priv->resource_lock);
  auto cached = priv->vs_cache.find (token);
  if (cached != priv->vs_cache.end ()) {
    GST_DEBUG_OBJECT (device,
        "Found cached vertex shader \"%s\" for token %" G_GINT64_FORMAT,
        entry_point, token);
    *vs = cached->second.first.Get ();
    *layout = cached->second.second.Get ();
    (*vs)->AddRef ();
    (*layout)->AddRef ();
    return S_OK;
  }

  GST_LOG_OBJECT (device,
      "Creating vertex shader for token %" G_GINT64_FORMAT ", shader: \n%s",
      token, source);

  if (bytecode && bytecode_size > 1) {
    data = bytecode;
    size = bytecode_size;
    GST_DEBUG_OBJECT (device,
        "Creating shader \"%s\" using precompiled bytecode", entry_point);
  } else {
    hr = gst_d3d11_shader_cache_get_vertex_shader_blob (token,
        source, source_size, entry_point, &blob);
    if (!gst_d3d11_result (hr, device))
      return hr;

    data = blob->GetBufferPointer ();
    size = blob->GetBufferSize ();
  }

  hr = priv->device->CreateVertexShader (data, size, nullptr, &shader);
  if (!gst_d3d11_result (hr, device))
    return hr;

  hr = priv->device->CreateInputLayout (input_desc, desc_len, data,
      size, &input_layout);
  if (!gst_d3d11_result (hr, device))
    return hr;

  GST_DEBUG_OBJECT (device, "Created vertex shader \"%s\" for token %"
      G_GINT64_FORMAT, entry_point, token);
  priv->vs_cache[token] = std::make_pair (shader, input_layout);

  *vs = shader.Detach ();
  *layout = input_layout.Detach ();

  return S_OK;
}

HRESULT
gst_d3d11_device_get_sampler (GstD3D11Device * device, D3D11_FILTER filter,
    ID3D11SamplerState ** sampler)
{
  GstD3D11DevicePrivate *priv = device->priv;
  ComPtr < ID3D11SamplerState > state;
  D3D11_SAMPLER_DESC desc;
  HRESULT hr;

  std::lock_guard < std::mutex > lk (priv->resource_lock);
  auto cached = priv->sampler_cache.find (filter);
  if (cached != priv->sampler_cache.end ()) {
    *sampler = cached->second.Get ();
    (*sampler)->AddRef ();
    return S_OK;
  }

  memset (&desc, 0, sizeof (D3D11_SAMPLER_DESC));

  desc.Filter = filter;
  desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
  desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
  desc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
  desc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
  desc.MaxLOD = D3D11_FLOAT32_MAX;

  if (filter == D3D11_FILTER_ANISOTROPIC) {
    if (priv->feature_level > D3D_FEATURE_LEVEL_9_1)
      desc.MaxAnisotropy = 16;
    else
      desc.MaxAnisotropy = 2;
  }

  hr = priv->device->CreateSamplerState (&desc, &state);
  if (!gst_d3d11_result (hr, device))
    return hr;

  priv->sampler_cache[filter] = state;
  *sampler = state.Detach ();

  return S_OK;
}

HRESULT
gst_d3d11_device_get_rasterizer (GstD3D11Device * device,
    ID3D11RasterizerState ** rasterizer)
{
  GstD3D11DevicePrivate *priv = device->priv;
  D3D11_RASTERIZER_DESC desc;
  HRESULT hr;

  std::lock_guard < std::mutex > lk (priv->resource_lock);
  if (priv->rs) {
    *rasterizer = priv->rs;
    priv->rs->AddRef ();
    return S_OK;
  }

  memset (&desc, 0, sizeof (D3D11_RASTERIZER_DESC));
  desc.FillMode = D3D11_FILL_SOLID;
  desc.CullMode = D3D11_CULL_NONE;
  desc.DepthClipEnable = TRUE;

  hr = priv->device->CreateRasterizerState (&desc, rasterizer);
  if (!gst_d3d11_result (hr, device))
    return hr;

  priv->rs = *rasterizer;
  priv->rs->AddRef ();

  return S_OK;
}

HRESULT
gst_d3d11_device_get_rasterizer_msaa (GstD3D11Device * device,
    ID3D11RasterizerState ** rasterizer)
{
  GstD3D11DevicePrivate *priv = device->priv;
  D3D11_RASTERIZER_DESC desc;
  HRESULT hr;

  std::lock_guard < std::mutex > lk (priv->resource_lock);
  if (priv->rs_msaa) {
    *rasterizer = priv->rs_msaa;
    priv->rs_msaa->AddRef ();
    return S_OK;
  }

  memset (&desc, 0, sizeof (D3D11_RASTERIZER_DESC));
  desc.FillMode = D3D11_FILL_SOLID;
  desc.CullMode = D3D11_CULL_NONE;
  desc.DepthClipEnable = TRUE;
  desc.MultisampleEnable = TRUE;
  desc.AntialiasedLineEnable = TRUE;

  hr = priv->device->CreateRasterizerState (&desc, rasterizer);
  if (!gst_d3d11_result (hr, device))
    return hr;

  priv->rs_msaa = *rasterizer;
  priv->rs_msaa->AddRef ();

  return S_OK;
}
