/* GStreamer
 * Copyright (C) 2019 Seungha Yang <seungha.yang@navercorp.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.
 *
 * NOTE: some of implementations are copied/modified from Chromium code
 *
 * Copyright 2015 The Chromium Authors. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *    * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *    * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *    * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "gstd3d11decoder.h"
#include "gstd3d11pluginutils.h"
#include <string.h>
#include <string>
#include <wrl.h>

#ifdef HAVE_WINMM
#include <mmsystem.h>
#endif

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

GST_DEBUG_CATEGORY_EXTERN (gst_d3d11_decoder_debug);
#define GST_CAT_DEFAULT gst_d3d11_decoder_debug

/* GUID might not be defined in MinGW header */
DEFINE_GUID (GST_GUID_D3D11_DECODER_PROFILE_H264_IDCT_FGT, 0x1b81be67, 0xa0c7,
    0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5);
DEFINE_GUID (GST_GUID_D3D11_DECODER_PROFILE_H264_VLD_NOFGT, 0x1b81be68, 0xa0c7,
    0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5);
DEFINE_GUID (GST_GUID_D3D11_DECODER_PROFILE_H264_VLD_FGT, 0x1b81be69, 0xa0c7,
    0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5);
DEFINE_GUID (GST_GUID_D3D11_DECODER_PROFILE_HEVC_VLD_MAIN,
    0x5b11d51b, 0x2f4c, 0x4452, 0xbc, 0xc3, 0x09, 0xf2, 0xa1, 0x16, 0x0c, 0xc0);
DEFINE_GUID (GST_GUID_D3D11_DECODER_PROFILE_HEVC_VLD_MAIN10,
    0x107af0e0, 0xef1a, 0x4d19, 0xab, 0xa8, 0x67, 0xa1, 0x63, 0x07, 0x3d, 0x13);
DEFINE_GUID (GST_GUID_D3D11_DECODER_PROFILE_VP8_VLD,
    0x90b899ea, 0x3a62, 0x4705, 0x88, 0xb3, 0x8d, 0xf0, 0x4b, 0x27, 0x44, 0xe7);
DEFINE_GUID (GST_GUID_D3D11_DECODER_PROFILE_VP9_VLD_PROFILE0,
    0x463707f8, 0xa1d0, 0x4585, 0x87, 0x6d, 0x83, 0xaa, 0x6d, 0x60, 0xb8, 0x9e);
DEFINE_GUID (GST_GUID_D3D11_DECODER_PROFILE_VP9_VLD_10BIT_PROFILE2,
    0xa4c749ef, 0x6ecf, 0x48aa, 0x84, 0x48, 0x50, 0xa7, 0xa1, 0x16, 0x5f, 0xf7);
DEFINE_GUID (GST_GUID_D3D11_DECODER_PROFILE_MPEG2_VLD, 0xee27417f, 0x5e28,
    0x4e65, 0xbe, 0xea, 0x1d, 0x26, 0xb5, 0x08, 0xad, 0xc9);
DEFINE_GUID (GST_GUID_D3D11_DECODER_PROFILE_MPEG2and1_VLD, 0x86695f12, 0x340e,
    0x4f04, 0x9f, 0xd3, 0x92, 0x53, 0xdd, 0x32, 0x74, 0x60);
DEFINE_GUID (GST_GUID_D3D11_DECODER_PROFILE_AV1_VLD_PROFILE0, 0xb8be4ccb,
    0xcf53, 0x46ba, 0x8d, 0x59, 0xd6, 0xb8, 0xa6, 0xda, 0x5d, 0x2a);

static const GUID *profile_h264_list[] = {
  &GST_GUID_D3D11_DECODER_PROFILE_H264_IDCT_FGT,
  &GST_GUID_D3D11_DECODER_PROFILE_H264_VLD_NOFGT,
  &GST_GUID_D3D11_DECODER_PROFILE_H264_VLD_FGT,
};

static const GUID *profile_hevc_list[] = {
  &GST_GUID_D3D11_DECODER_PROFILE_HEVC_VLD_MAIN,
};

static const GUID *profile_hevc_10_list[] = {
  &GST_GUID_D3D11_DECODER_PROFILE_HEVC_VLD_MAIN10,
};

static const GUID *profile_vp8_list[] = {
  &GST_GUID_D3D11_DECODER_PROFILE_VP8_VLD,
};

static const GUID *profile_vp9_list[] = {
  &GST_GUID_D3D11_DECODER_PROFILE_VP9_VLD_PROFILE0,
};

static const GUID *profile_vp9_10_list[] = {
  &GST_GUID_D3D11_DECODER_PROFILE_VP9_VLD_10BIT_PROFILE2,
};

static const GUID *profile_mpeg2_list[] = {
  &GST_GUID_D3D11_DECODER_PROFILE_MPEG2_VLD,
  &GST_GUID_D3D11_DECODER_PROFILE_MPEG2and1_VLD
};

static const GUID *profile_av1_list[] = {
  &GST_GUID_D3D11_DECODER_PROFILE_AV1_VLD_PROFILE0,
  /* TODO: add more profile */
};

DEFINE_GUID (IID_GST_D3D11_DECODER_VIEW_DATA, 0xe1fd3e17, 0x1e33,
    0x4198, 0x9e, 0x48, 0xdb, 0x01, 0x55, 0x2b, 0xf1, 0x78);

/* *INDENT-OFF* */
class IGstD3D11DecoderViewData: public IUnknown
{
public:
  static HRESULT
  CreateInstance (guint8 index, IGstD3D11DecoderViewData ** data)
  {
    IGstD3D11DecoderViewData *self;

    self = new IGstD3D11DecoderViewData (index);
    if (!self)
      return E_OUTOFMEMORY;

    *data = self;

    return S_OK;
  }

  STDMETHODIMP_ (ULONG)
  AddRef (void)
  {
    return InterlockedIncrement (&ref_count_);
  }

  STDMETHODIMP_ (ULONG)
  Release (void)
  {
    ULONG ref_count;

    ref_count = InterlockedDecrement (&ref_count_);

    if (ref_count == 0)
      delete this;

    return ref_count;
  }

  STDMETHODIMP
  QueryInterface (REFIID riid, void ** object)
  {
    if (riid == IID_IUnknown) {
      *object = static_cast<IUnknown *>
          (static_cast<IGstD3D11DecoderViewData *> (this));
    } else if (riid == IID_GST_D3D11_DECODER_VIEW_DATA) {
      *object = this;
    } else {
      *object = nullptr;
      return E_NOINTERFACE;
    }

    AddRef ();

    return S_OK;
  }

  guint8
  GetViewIndex (void)
  {
    return index_;
  }

private:
  IGstD3D11DecoderViewData (guint8 index) : index_(index), ref_count_(1)
  {
  }

  virtual ~IGstD3D11DecoderViewData (void)
  {
  }

private:
  guint8 index_;
  ULONG ref_count_;
};
/* *INDENT-ON* */

struct _GstD3D11Decoder
{
  GstObject parent;

  gboolean configured;
  gboolean opened;

  GstD3D11Device *device;

  ID3D11VideoDevice *video_device;
  ID3D11VideoContext *video_context;

  ID3D11VideoDecoder *decoder_handle;

  GstVideoInfo info;
  GstVideoInfo output_info;
  GstDxvaCodec codec;
  gint offset_x;
  gint offset_y;
  gint coded_width;
  gint coded_height;
  DXGI_FORMAT decoder_format;
  gboolean downstream_supports_d3d11;
  gboolean need_crop;
  gboolean use_crop_meta;

  GstVideoCodecState *input_state;
  GstVideoCodecState *output_state;

  SRWLOCK lock;
  /* performance frequency */
  LARGE_INTEGER frequency;
  gboolean flushing;

  GstBufferPool *internal_pool;
  /* Internal pool params */
  gint aligned_width;
  gint aligned_height;
  gboolean use_array_of_texture;
  guint dpb_size;
  guint downstream_min_buffers;
  gboolean wait_on_pool_full;

  /* Used for array-of-texture */
  guint8 next_view_id;

  /* for staging */
  ID3D11Texture2D *staging;
  guint stating_plane_offset[GST_VIDEO_MAX_PLANES];

  GUID decoder_profile;

  /* For device specific workaround */
  gboolean can_direct_rendering;

  /* For high precision clock */
  guint timer_resolution;
};

static void gst_d3d11_decoder_dispose (GObject * obj);
static void gst_d3d11_decoder_finalize (GObject * obj);
static gboolean gst_d3d11_decoder_can_direct_render (GstD3D11Decoder * decoder,
    GstVideoDecoder * videodec, GstBuffer * view_buffer,
    gint display_width, gint display_height);

#define parent_class gst_d3d11_decoder_parent_class
G_DEFINE_TYPE (GstD3D11Decoder, gst_d3d11_decoder, GST_TYPE_OBJECT);

static void
gst_d3d11_decoder_class_init (GstD3D11DecoderClass * klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->dispose = gst_d3d11_decoder_dispose;
  gobject_class->finalize = gst_d3d11_decoder_finalize;
}

static void
gst_d3d11_decoder_init (GstD3D11Decoder * self)
{
  QueryPerformanceFrequency (&self->frequency);
}

static void
gst_d3d11_decoder_clear_resource (GstD3D11Decoder * self)
{
  GstD3D11SRWLockGuard lk (&self->lock);
  if (self->internal_pool) {
    gst_buffer_pool_set_active (self->internal_pool, FALSE);
    gst_clear_object (&self->internal_pool);
  }

  GST_D3D11_CLEAR_COM (self->decoder_handle);
  GST_D3D11_CLEAR_COM (self->staging);
}

static void
gst_d3d11_decoder_reset (GstD3D11Decoder * self)
{
  gst_d3d11_decoder_clear_resource (self);

  self->dpb_size = 0;
  self->downstream_min_buffers = 0;

  self->configured = FALSE;
  self->opened = FALSE;
  self->flushing = FALSE;

  self->use_array_of_texture = FALSE;
  self->downstream_supports_d3d11 = FALSE;

  g_clear_pointer (&self->output_state, gst_video_codec_state_unref);
  g_clear_pointer (&self->input_state, gst_video_codec_state_unref);
}

static void
gst_d3d11_decoder_dispose (GObject * obj)
{
  GstD3D11Decoder *self = GST_D3D11_DECODER (obj);

  gst_d3d11_decoder_reset (self);

  GST_D3D11_CLEAR_COM (self->video_device);
  GST_D3D11_CLEAR_COM (self->video_context);

  gst_clear_object (&self->device);

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

static void
gst_d3d11_decoder_finalize (GObject * obj)
{
#if HAVE_WINMM
  GstD3D11Decoder *self = GST_D3D11_DECODER (obj);

  /* Restore clock precision */
  if (self->timer_resolution)
    timeEndPeriod (self->timer_resolution);
#endif

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

GstD3D11Decoder *
gst_d3d11_decoder_new (GstD3D11Device * device, GstDxvaCodec codec)
{
  GstD3D11Decoder *self;
  ID3D11VideoDevice *video_device;
  ID3D11VideoContext *video_context;

  g_return_val_if_fail (GST_IS_D3D11_DEVICE (device), nullptr);
  g_return_val_if_fail (codec > GST_DXVA_CODEC_NONE, nullptr);
  g_return_val_if_fail (codec < GST_DXVA_CODEC_LAST, nullptr);

  video_device = gst_d3d11_device_get_video_device_handle (device);
  if (!video_device) {
    GST_WARNING_OBJECT (device, "ID3D11VideoDevice is not available");
    return nullptr;
  }

  video_context = gst_d3d11_device_get_video_context_handle (device);
  if (!video_context) {
    GST_WARNING_OBJECT (device, "ID3D11VideoContext is not available");
    return nullptr;
  }

  self = (GstD3D11Decoder *) g_object_new (GST_TYPE_D3D11_DECODER, nullptr);

  self->device = (GstD3D11Device *) gst_object_ref (device);
  self->codec = codec;
  self->video_device = video_device;
  video_device->AddRef ();

  self->video_context = video_context;
  video_context->AddRef ();

  gst_object_ref_sink (self);

  return self;
}

static gboolean
gst_d3d11_decoder_ensure_output_view (GstD3D11Decoder * self,
    GstBuffer * buffer)
{
  GstD3D11Memory *mem;
  ID3D11Resource *texture;
  ComPtr < IGstD3D11DecoderViewData > data;
  UINT size;
  HRESULT hr;

  mem = (GstD3D11Memory *) gst_buffer_peek_memory (buffer, 0);
  if (!gst_d3d11_memory_get_decoder_output_view (mem, self->video_device,
          self->decoder_handle, &self->decoder_profile)) {
    GST_ERROR_OBJECT (self, "Decoder output view is unavailable");
    return FALSE;
  }

  if (!self->use_array_of_texture)
    return TRUE;

  size = sizeof (IGstD3D11DecoderViewData *);

  texture = gst_d3d11_memory_get_resource_handle (mem);
  texture->GetPrivateData (IID_GST_D3D11_DECODER_VIEW_DATA,
      &size, data.GetAddressOf ());

  if (!data) {
    g_assert (self->next_view_id < 127);

    hr = IGstD3D11DecoderViewData::CreateInstance (self->next_view_id, &data);
    g_assert (SUCCEEDED (hr));

    texture->SetPrivateDataInterface (IID_GST_D3D11_DECODER_VIEW_DATA,
        data.Get ());

    self->next_view_id++;
    /* valid view range is [0, 126] */
    self->next_view_id %= 127;
  }

  return TRUE;
}

static gboolean
gst_d3d11_decoder_prepare_output_view_pool (GstD3D11Decoder * self)
{
  GstD3D11AllocationParams *alloc_params = NULL;
  GstBufferPool *pool = NULL;
  GstCaps *caps = NULL;
  GstVideoAlignment align;
  GstD3D11AllocationFlags alloc_flags = GST_D3D11_ALLOCATION_FLAG_DEFAULT;
  gint bind_flags = D3D11_BIND_DECODER;
  GstVideoInfo *info = &self->info;
  guint pool_size;

  GstD3D11SRWLockGuard lk (&self->lock);
  if (self->internal_pool) {
    gst_buffer_pool_set_active (self->internal_pool, FALSE);
    gst_clear_object (&self->internal_pool);
  }

  if (self->use_array_of_texture) {
    /* array of texture can have shader resource view */
    bind_flags |= D3D11_BIND_SHADER_RESOURCE;
  }

  alloc_params = gst_d3d11_allocation_params_new (self->device, info,
      alloc_flags, bind_flags, 0);

  if (!alloc_params) {
    GST_ERROR_OBJECT (self, "Failed to create allocation param");
    goto error;
  }

  pool_size = self->dpb_size + self->downstream_min_buffers;
  GST_DEBUG_OBJECT (self,
      "Configuring internal pool with size %d "
      "(dpb size: %d, downstream min buffers: %d)", pool_size, self->dpb_size,
      self->downstream_min_buffers);

  if (!self->use_array_of_texture) {
    gst_d3d11_allocation_params_set_array_size (alloc_params, pool_size);
  } else {
    self->next_view_id = 0;

    /* Valid view id range is [0, 126] */
    pool_size = 127;
  }

  gst_video_alignment_reset (&align);

  align.padding_right = self->aligned_width - GST_VIDEO_INFO_WIDTH (info);
  align.padding_bottom = self->aligned_height - GST_VIDEO_INFO_HEIGHT (info);
  if (!gst_d3d11_allocation_params_alignment (alloc_params, &align)) {
    GST_ERROR_OBJECT (self, "Cannot set alignment");
    goto error;
  }

  caps = gst_video_info_to_caps (info);
  if (!caps) {
    GST_ERROR_OBJECT (self, "Couldn't convert video info to caps");
    goto error;
  }

  pool = gst_d3d11_buffer_pool_new_with_options (self->device,
      caps, alloc_params, 0, pool_size);
  gst_clear_caps (&caps);
  g_clear_pointer (&alloc_params, gst_d3d11_allocation_params_free);

  if (!pool) {
    GST_ERROR_OBJECT (self, "Failed to create buffer pool");
    goto error;
  }

  if (!gst_buffer_pool_set_active (pool, TRUE)) {
    GST_ERROR_OBJECT (self, "Couldn't activate pool");
    goto error;
  }

  self->internal_pool = pool;

  return TRUE;

error:
  if (alloc_params)
    gst_d3d11_allocation_params_free (alloc_params);
  if (pool)
    gst_object_unref (pool);
  if (caps)
    gst_caps_unref (caps);

  return FALSE;
}

gboolean
gst_d3d11_decoder_get_supported_decoder_profile (GstD3D11Device * device,
    GstDxvaCodec codec, GstVideoFormat format, const GUID ** selected_profile)
{
  GUID *guid_list = nullptr;
  const GUID *profile = nullptr;
  guint available_profile_count;
  guint i, j;
  HRESULT hr;
  ID3D11VideoDevice *video_device;
  const GUID **profile_list = nullptr;
  guint profile_size = 0;
  gboolean ret = FALSE;

  g_return_val_if_fail (GST_IS_D3D11_DEVICE (device), FALSE);
  g_return_val_if_fail (selected_profile != nullptr, FALSE);

  video_device = gst_d3d11_device_get_video_device_handle (device);
  if (!video_device)
    return FALSE;

  switch (codec) {
    case GST_DXVA_CODEC_H264:
      if (format == GST_VIDEO_FORMAT_NV12) {
        profile_list = profile_h264_list;
        profile_size = G_N_ELEMENTS (profile_h264_list);
      }
      break;
    case GST_DXVA_CODEC_H265:
      if (format == GST_VIDEO_FORMAT_NV12) {
        profile_list = profile_hevc_list;
        profile_size = G_N_ELEMENTS (profile_hevc_list);
      } else if (format == GST_VIDEO_FORMAT_P010_10LE) {
        profile_list = profile_hevc_10_list;
        profile_size = G_N_ELEMENTS (profile_hevc_10_list);
      }
      break;
    case GST_DXVA_CODEC_VP8:
      if (format == GST_VIDEO_FORMAT_NV12) {
        profile_list = profile_vp8_list;
        profile_size = G_N_ELEMENTS (profile_vp8_list);
      }
      break;
    case GST_DXVA_CODEC_VP9:
      if (format == GST_VIDEO_FORMAT_NV12) {
        profile_list = profile_vp9_list;
        profile_size = G_N_ELEMENTS (profile_vp9_list);
      } else if (format == GST_VIDEO_FORMAT_P010_10LE) {
        profile_list = profile_vp9_10_list;
        profile_size = G_N_ELEMENTS (profile_vp9_10_list);
      }
      break;
    case GST_DXVA_CODEC_MPEG2:
      if (format == GST_VIDEO_FORMAT_NV12) {
        profile_list = profile_mpeg2_list;
        profile_size = G_N_ELEMENTS (profile_mpeg2_list);
      }
      break;
    case GST_DXVA_CODEC_AV1:
      profile_list = profile_av1_list;
      profile_size = G_N_ELEMENTS (profile_av1_list);
      break;
    default:
      break;
  }

  if (!profile_list) {
    GST_ERROR_OBJECT (device,
        "Not supported codec (%d) and format (%s) configuration", codec,
        gst_video_format_to_string (format));
    return FALSE;
  }

  available_profile_count = video_device->GetVideoDecoderProfileCount ();

  if (available_profile_count == 0) {
    GST_INFO_OBJECT (device, "No available decoder profile");
    return FALSE;
  }

  GST_DEBUG_OBJECT (device,
      "Have %u available decoder profiles", available_profile_count);
  guid_list = g_new0 (GUID, available_profile_count);

  for (i = 0; i < available_profile_count; i++) {
    hr = video_device->GetVideoDecoderProfile (i, &guid_list[i]);
    if (!gst_d3d11_result (hr, device)) {
      GST_WARNING_OBJECT (device, "Failed to get %d th decoder profile", i);
      goto out;
    }
  }

#ifndef GST_DISABLE_GST_DEBUG
  GST_LOG_OBJECT (device, "Supported decoder GUID");
  for (i = 0; i < available_profile_count; i++) {
    const GUID *guid = &guid_list[i];

    GST_LOG_OBJECT (device,
        "\t { %8.8x-%4.4x-%4.4x-%2.2x%2.2x-%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x }",
        (guint) guid->Data1, (guint) guid->Data2, (guint) guid->Data3,
        guid->Data4[0], guid->Data4[1], guid->Data4[2], guid->Data4[3],
        guid->Data4[4], guid->Data4[5], guid->Data4[6], guid->Data4[7]);
  }

  GST_LOG_OBJECT (device, "Requested decoder GUID");
  for (i = 0; i < profile_size; i++) {
    const GUID *guid = profile_list[i];

    GST_LOG_OBJECT (device,
        "\t { %8.8x-%4.4x-%4.4x-%2.2x%2.2x-%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x }",
        (guint) guid->Data1, (guint) guid->Data2, (guint) guid->Data3,
        guid->Data4[0], guid->Data4[1], guid->Data4[2], guid->Data4[3],
        guid->Data4[4], guid->Data4[5], guid->Data4[6], guid->Data4[7]);
  }
#endif

  for (i = 0; i < profile_size; i++) {
    for (j = 0; j < available_profile_count; j++) {
      if (IsEqualGUID (*profile_list[i], guid_list[j])) {
        profile = profile_list[i];
        break;
      }
    }
  }

  if (!profile) {
    GST_INFO_OBJECT (device, "No supported decoder profile for %s codec",
        gst_dxva_codec_to_string (codec));
    goto out;
  }

  *selected_profile = profile;

  GST_DEBUG_OBJECT (device,
      "Selected guid "
      "{ %8.8x-%4.4x-%4.4x-%2.2x%2.2x-%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x }",
      (guint) profile->Data1, (guint) profile->Data2, (guint) profile->Data3,
      profile->Data4[0], profile->Data4[1], profile->Data4[2],
      profile->Data4[3], profile->Data4[4], profile->Data4[5],
      profile->Data4[6], profile->Data4[7]);

  ret = TRUE;

out:
  g_free (guid_list);
  return ret;
}

GstFlowReturn
gst_d3d11_decoder_configure (GstD3D11Decoder * decoder,
    GstVideoCodecState * input_state, const GstVideoInfo * out_info,
    gint offset_x, gint offset_y, gint coded_width, gint coded_height,
    guint dpb_size)
{
  GstD3D11Format d3d11_format;

  g_return_val_if_fail (GST_IS_D3D11_DECODER (decoder), GST_FLOW_ERROR);
  g_return_val_if_fail (out_info != NULL, GST_FLOW_ERROR);
  g_return_val_if_fail (input_state != NULL, GST_FLOW_ERROR);
  g_return_val_if_fail (coded_width >= GST_VIDEO_INFO_WIDTH (out_info),
      GST_FLOW_ERROR);
  g_return_val_if_fail (coded_height >= GST_VIDEO_INFO_HEIGHT (out_info),
      GST_FLOW_ERROR);
  g_return_val_if_fail (dpb_size > 0, GST_FLOW_ERROR);

  gst_d3d11_decoder_reset (decoder);

  if (!gst_d3d11_device_get_format (decoder->device,
          GST_VIDEO_INFO_FORMAT (out_info), &d3d11_format) ||
      d3d11_format.dxgi_format == DXGI_FORMAT_UNKNOWN) {
    GST_ERROR_OBJECT (decoder, "Could not determine dxgi format from %s",
        gst_video_format_to_string (GST_VIDEO_INFO_FORMAT (out_info)));
    return GST_FLOW_ERROR;
  }

  /* Additional 2 frames to help zero-copying */
  dpb_size += 2;

  decoder->input_state = gst_video_codec_state_ref (input_state);
  decoder->info = decoder->output_info = *out_info;
  decoder->offset_x = offset_x;
  decoder->offset_y = offset_y;
  decoder->coded_width = coded_width;
  decoder->coded_height = coded_height;
  decoder->dpb_size = dpb_size;
  decoder->decoder_format = d3d11_format.dxgi_format;

  decoder->configured = TRUE;

  if (offset_x != 0 || offset_y != 0)
    decoder->need_crop = TRUE;
  else
    decoder->need_crop = FALSE;

  return GST_FLOW_OK;
}

static gboolean
gst_d3d11_decoder_ensure_staging_texture (GstD3D11Decoder * self)
{
  ID3D11Device *device_handle;
  D3D11_TEXTURE2D_DESC desc = { 0, };
  HRESULT hr;

  if (self->staging)
    return TRUE;

  device_handle = gst_d3d11_device_get_device_handle (self->device);

  /* create stage texture to copy out */
  desc.Width = self->aligned_width;
  desc.Height = self->aligned_height;
  desc.MipLevels = 1;
  desc.Format = self->decoder_format;
  desc.SampleDesc.Count = 1;
  desc.ArraySize = 1;
  desc.Usage = D3D11_USAGE_STAGING;
  desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;

  hr = device_handle->CreateTexture2D (&desc, nullptr, &self->staging);
  if (!gst_d3d11_result (hr, self->device)) {
    GST_ERROR_OBJECT (self, "Couldn't create staging texture");
    return FALSE;
  }

  /* decoder supports single plane (e.g,. YUV packed) or semi-planar */
  self->stating_plane_offset[0] = 0;
  self->stating_plane_offset[1] = desc.Height;

  return TRUE;
}

static void
gst_d3d11_decoder_enable_high_precision_timer (GstD3D11Decoder * self)
{
#if HAVE_WINMM
  GstD3D11DeviceVendor vendor;

  if (self->timer_resolution)
    return;

  vendor = gst_d3d11_get_device_vendor (self->device);
  /* Do this only for NVIDIA at the moment, other vendors doesn't seem to be
   * requiring retry for BeginFrame() */
  if (vendor == GST_D3D11_DEVICE_VENDOR_NVIDIA) {
    TIMECAPS time_caps;
    if (timeGetDevCaps (&time_caps, sizeof (TIMECAPS)) == TIMERR_NOERROR) {
      guint resolution;
      MMRESULT ret;

      resolution = MIN (MAX (time_caps.wPeriodMin, 1), time_caps.wPeriodMax);

      ret = timeBeginPeriod (resolution);
      if (ret == TIMERR_NOERROR) {
        self->timer_resolution = resolution;
        GST_INFO_OBJECT (self, "Updated timer resolution to %d", resolution);
      }
    }
  }
#endif
}

static gboolean
gst_d3d11_decoder_open (GstD3D11Decoder * self)
{
  HRESULT hr;
  BOOL can_support = FALSE;
  guint config_count;
  D3D11_VIDEO_DECODER_CONFIG *config_list = NULL;
  D3D11_VIDEO_DECODER_CONFIG *best_config = NULL;
  D3D11_VIDEO_DECODER_DESC decoder_desc = { 0, };
  const GUID *selected_profile = NULL;
  guint i;
  gint aligned_width, aligned_height;
  guint alignment;
  GstD3D11DeviceVendor vendor;
  ID3D11VideoDevice *video_device;
  GstVideoInfo *info = &self->info;
  GstD3D11DeviceLockGuard lk (self->device);

  if (self->opened)
    return TRUE;

  if (!self->configured) {
    GST_ERROR_OBJECT (self, "Should configure first");
    return FALSE;
  }

  video_device = self->video_device;

  if (!gst_d3d11_decoder_get_supported_decoder_profile (self->device,
          self->codec, GST_VIDEO_INFO_FORMAT (info), &selected_profile)) {
    goto error;
  }

  hr = video_device->CheckVideoDecoderFormat (selected_profile,
      self->decoder_format, &can_support);
  if (!gst_d3d11_result (hr, self->device) || !can_support) {
    GST_ERROR_OBJECT (self,
        "VideoDevice could not support dxgi format %d, hr: 0x%x",
        self->decoder_format, (guint) hr);
    goto error;
  }

  gst_d3d11_decoder_clear_resource (self);
  self->can_direct_rendering = TRUE;

  vendor = gst_d3d11_get_device_vendor (self->device);
  switch (vendor) {
    case GST_D3D11_DEVICE_VENDOR_XBOX:
      /* FIXME: Need to figure out Xbox device's behavior
       * https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/issues/1312
       */
      self->can_direct_rendering = FALSE;
      break;
    default:
      break;
  }

  /* NOTE: other dxva implementations (ffmpeg and vlc) do this
   * and they say the required alignment were mentioned by dxva spec.
   * See ff_dxva2_common_frame_params() in dxva.c of ffmpeg and
   * directx_va_Setup() in directx_va.c of vlc.
   * But... where it is? */
  switch (self->codec) {
    case GST_DXVA_CODEC_H265:
    case GST_DXVA_CODEC_AV1:
      /* See directx_va_Setup() impl. in vlc */
      if (vendor != GST_D3D11_DEVICE_VENDOR_XBOX)
        alignment = 128;
      else
        alignment = 16;
      break;
    case GST_DXVA_CODEC_MPEG2:
      /* XXX: ffmpeg does this */
      alignment = 32;
      break;
    default:
      alignment = 16;
      break;
  }

  aligned_width = GST_ROUND_UP_N (self->coded_width, alignment);
  aligned_height = GST_ROUND_UP_N (self->coded_height, alignment);
  if (aligned_width != self->coded_width ||
      aligned_height != self->coded_height) {
    GST_DEBUG_OBJECT (self,
        "coded resolution %dx%d is not aligned to %d, adjust to %dx%d",
        self->coded_width, self->coded_height, alignment, aligned_width,
        aligned_height);
  }

  self->aligned_width = aligned_width;
  self->aligned_height = aligned_height;

  decoder_desc.SampleWidth = aligned_width;
  decoder_desc.SampleHeight = aligned_height;
  decoder_desc.OutputFormat = self->decoder_format;
  decoder_desc.Guid = *selected_profile;

  hr = video_device->GetVideoDecoderConfigCount (&decoder_desc, &config_count);
  if (!gst_d3d11_result (hr, self->device) || config_count == 0) {
    GST_ERROR_OBJECT (self, "Could not get decoder config count, hr: 0x%x",
        (guint) hr);
    goto error;
  }

  GST_DEBUG_OBJECT (self, "Total %d config available", config_count);

  config_list = g_new0 (D3D11_VIDEO_DECODER_CONFIG, config_count);
  for (i = 0; i < config_count; i++) {
    hr = video_device->GetVideoDecoderConfig (&decoder_desc, i,
        &config_list[i]);
    if (!gst_d3d11_result (hr, self->device)) {
      GST_ERROR_OBJECT (self, "Could not get decoder %dth config, hr: 0x%x",
          i, (guint) hr);
      goto error;
    }

    /* FIXME: need support DXVA_Slice_H264_Long ?? */
    /* this config uses DXVA_Slice_H264_Short */
    switch (self->codec) {
      case GST_DXVA_CODEC_H264:
        if (config_list[i].ConfigBitstreamRaw == 2)
          best_config = &config_list[i];
        break;
      case GST_DXVA_CODEC_H265:
      case GST_DXVA_CODEC_VP9:
      case GST_DXVA_CODEC_VP8:
      case GST_DXVA_CODEC_MPEG2:
      case GST_DXVA_CODEC_AV1:
        if (config_list[i].ConfigBitstreamRaw == 1)
          best_config = &config_list[i];
        break;
      default:
        g_assert_not_reached ();
        goto error;
    }

    if (best_config)
      break;
  }

  if (best_config == NULL) {
    GST_ERROR_OBJECT (self, "Could not determine decoder config");
    goto error;
  }

  GST_DEBUG_OBJECT (self, "ConfigDecoderSpecific 0x%x",
      best_config->ConfigDecoderSpecific);

  /* bit 14 is equal to 1b means this config support array of texture and
   * it's recommended type as per DXVA spec */
  if ((best_config->ConfigDecoderSpecific & 0x4000) == 0x4000) {
    GST_DEBUG_OBJECT (self, "Config support array of texture");
    self->use_array_of_texture = TRUE;
  }

  hr = video_device->CreateVideoDecoder (&decoder_desc,
      best_config, &self->decoder_handle);
  if (!gst_d3d11_result (hr, self->device) || !self->decoder_handle) {
    GST_ERROR_OBJECT (self,
        "Could not create decoder object, hr: 0x%x", (guint) hr);
    goto error;
  }

  GST_DEBUG_OBJECT (self, "Decoder object %p created", self->decoder_handle);

  if (!self->downstream_supports_d3d11 &&
      !gst_d3d11_decoder_ensure_staging_texture (self)) {
    GST_ERROR_OBJECT (self, "Couldn't prepare staging texture");
    goto error;
  }

  self->decoder_profile = *selected_profile;

  /* Store pool related information here, then we will setup internal pool
   * later once the number of min buffer size required by downstream is known.
   * Actual buffer pool size will be "dpb_size + downstream_min_buffers"
   */
  self->downstream_min_buffers = 0;
  self->wait_on_pool_full = FALSE;

  self->opened = TRUE;

  gst_d3d11_decoder_enable_high_precision_timer (self);
  g_free (config_list);

  return TRUE;

error:
  g_free (config_list);
  gst_d3d11_decoder_reset (self);

  return FALSE;
}

static GstFlowReturn
gst_d3d11_decoder_begin_frame (GstD3D11Decoder * self,
    ID3D11VideoDecoderOutputView * output_view, guint content_key_size,
    gconstpointer content_key)
{
  ID3D11VideoContext *video_context;
  guint retry_count = 0;
  HRESULT hr;
  BOOL timer_ret;
  LARGE_INTEGER now;
  LONGLONG timeout;

  video_context = self->video_context;

  timer_ret = QueryPerformanceCounter (&now);
  g_assert (timer_ret);

  /* 20 sec timeout should be sufficient */
  timeout = now.QuadPart + 20 * self->frequency.QuadPart;

  do {
    if (self->flushing) {
      GST_DEBUG_OBJECT (self, "We are flushing");
      return GST_FLOW_FLUSHING;
    }

    GST_LOG_OBJECT (self, "Try begin frame, retry count %d", retry_count);
    hr = video_context->DecoderBeginFrame (self->decoder_handle,
        output_view, content_key_size, content_key);

    /* HACK: Do retry with 1ms sleep per failure, since DXVA/D3D11
     * doesn't provide API for "GPU-IS-READY-TO-DECODE" like signal.
     */
    if (hr == E_PENDING) {
      GST_LOG_OBJECT (self, "GPU is busy, try again. Retry count %d",
          retry_count);
      Sleep (1);
    } else {
      if (gst_d3d11_result (hr, self->device))
        GST_LOG_OBJECT (self, "Succeeded with retry count %d", retry_count);
      break;
    }

    retry_count++;
    timer_ret = QueryPerformanceCounter (&now);
    g_assert (timer_ret);
  } while (now.QuadPart < timeout);

  if (!gst_d3d11_result (hr, self->device)) {
    GST_ERROR_OBJECT (self, "Failed to begin frame, hr: 0x%x", (guint) hr);
    return GST_FLOW_ERROR;
  }

  return GST_FLOW_OK;
}

static gboolean
gst_d3d11_decoder_end_frame (GstD3D11Decoder * decoder)
{
  HRESULT hr;
  ID3D11VideoContext *video_context;

  video_context = decoder->video_context;
  hr = video_context->DecoderEndFrame (decoder->decoder_handle);

  if (!gst_d3d11_result (hr, decoder->device)) {
    GST_WARNING_OBJECT (decoder, "EndFrame failed, hr: 0x%x", (guint) hr);
    return FALSE;
  }

  return TRUE;
}

static gboolean
gst_d3d11_decoder_get_decoder_buffer (GstD3D11Decoder * decoder,
    D3D11_VIDEO_DECODER_BUFFER_TYPE type, guint * buffer_size,
    gpointer * buffer)
{
  UINT size;
  void *decoder_buffer;
  HRESULT hr;
  ID3D11VideoContext *video_context;

  video_context = decoder->video_context;
  hr = video_context->GetDecoderBuffer (decoder->decoder_handle,
      type, &size, &decoder_buffer);

  if (!gst_d3d11_result (hr, decoder->device)) {
    GST_WARNING_OBJECT (decoder, "Getting buffer type %d error, hr: 0x%x",
        type, (guint) hr);
    return FALSE;
  }

  *buffer_size = size;
  *buffer = decoder_buffer;

  return TRUE;
}

static gboolean
gst_d3d11_decoder_release_decoder_buffer (GstD3D11Decoder * decoder,
    D3D11_VIDEO_DECODER_BUFFER_TYPE type)
{
  HRESULT hr;
  ID3D11VideoContext *video_context;

  video_context = decoder->video_context;
  hr = video_context->ReleaseDecoderBuffer (decoder->decoder_handle, type);

  if (!gst_d3d11_result (hr, decoder->device)) {
    GST_WARNING_OBJECT (decoder, "ReleaseDecoderBuffer failed, hr: 0x%x",
        (guint) hr);
    return FALSE;
  }

  return TRUE;
}

static gboolean
gst_d3d11_decoder_submit_decoder_buffers (GstD3D11Decoder * decoder,
    guint buffer_count, const D3D11_VIDEO_DECODER_BUFFER_DESC * buffers)
{
  HRESULT hr;
  ID3D11VideoContext *video_context;

  video_context = decoder->video_context;
  hr = video_context->SubmitDecoderBuffers (decoder->decoder_handle,
      buffer_count, buffers);
  if (!gst_d3d11_result (hr, decoder->device)) {
    GST_WARNING_OBJECT (decoder, "SubmitDecoderBuffers failed, hr: 0x%x",
        (guint) hr);
    return FALSE;
  }

  return TRUE;
}

static ID3D11VideoDecoderOutputView *
gst_d3d11_decoder_get_output_view_from_picture (GstD3D11Decoder * self,
    GstCodecPicture * picture, guint8 * index)
{
  GstMemory *mem;
  GstD3D11Memory *dmem;
  ID3D11VideoDecoderOutputView *view;
  GstBuffer *buffer;

  if (index)
    *index = 0xff;

  buffer = (GstBuffer *) gst_codec_picture_get_user_data (picture);
  if (!buffer) {
    GST_DEBUG_OBJECT (self, "picture without attached user data");
    return nullptr;
  }

  mem = gst_buffer_peek_memory (buffer, 0);
  if (!gst_is_d3d11_memory (mem)) {
    GST_WARNING_OBJECT (self, "Not a d3d11 memory");
    return nullptr;
  }

  dmem = (GstD3D11Memory *) mem;
  view = gst_d3d11_memory_get_decoder_output_view (dmem, self->video_device,
      self->decoder_handle, &self->decoder_profile);

  if (!view) {
    GST_ERROR_OBJECT (self, "Decoder output view is unavailable");
    return nullptr;
  }

  if (index) {
    if (self->use_array_of_texture) {
      ID3D11Resource *texture;
      ComPtr < IGstD3D11DecoderViewData > data;
      UINT size;

      texture = gst_d3d11_memory_get_resource_handle (dmem);
      size = sizeof (IGstD3D11DecoderViewData *);

      texture->GetPrivateData (IID_GST_D3D11_DECODER_VIEW_DATA,
          &size, data.GetAddressOf ());

      if (!data) {
        GST_ERROR_OBJECT (self, "memory has no private data");
        return nullptr;
      }

      *index = data->GetViewIndex ();
    } else {
      *index = gst_d3d11_memory_get_subresource_index (dmem);
    }
  }

  return view;
}

guint8
gst_d3d11_decoder_get_picture_id (GstD3D11Decoder * decoder,
    GstCodecPicture * picture)
{
  guint8 id = 0xff;

  if (!picture)
    return 0xff;

  if (!gst_d3d11_decoder_get_output_view_from_picture (decoder, picture, &id))
    return 0xff;

  return id;
}

GstFlowReturn
gst_d3d11_decoder_start_picture (GstD3D11Decoder * decoder,
    GstCodecPicture * picture, guint8 * picture_id)
{
  if (!gst_d3d11_decoder_get_output_view_from_picture (decoder,
          picture, picture_id)) {
    return GST_FLOW_ERROR;
  }

  return GST_FLOW_OK;
}

GstFlowReturn
gst_d3d11_decoder_end_picture (GstD3D11Decoder * decoder,
    GstCodecPicture * picture, const GstDxvaDecodingArgs * args)
{
  ID3D11VideoDecoderOutputView *output_view;
  guint d3d11_buffer_size;
  gpointer d3d11_buffer;
  D3D11_VIDEO_DECODER_BUFFER_DESC buffer_desc[4];
  guint buffer_desc_size;
  GstFlowReturn ret = GST_FLOW_OK;

  g_return_val_if_fail (GST_IS_D3D11_DECODER (decoder), GST_FLOW_ERROR);
  g_return_val_if_fail (picture != nullptr, GST_FLOW_ERROR);
  g_return_val_if_fail (args != nullptr, GST_FLOW_ERROR);

  output_view = gst_d3d11_decoder_get_output_view_from_picture (decoder,
      picture, nullptr);
  if (!output_view) {
    GST_ERROR_OBJECT (decoder, "No output view attached");
    return GST_FLOW_ERROR;
  }

  memset (buffer_desc, 0, sizeof (buffer_desc));

  buffer_desc[0].BufferType = D3D11_VIDEO_DECODER_BUFFER_PICTURE_PARAMETERS;
  buffer_desc[0].DataSize = args->picture_params_size;

  buffer_desc[1].BufferType = D3D11_VIDEO_DECODER_BUFFER_SLICE_CONTROL;
  buffer_desc[1].DataSize = args->slice_control_size;

  buffer_desc[2].BufferType = D3D11_VIDEO_DECODER_BUFFER_BITSTREAM;
  buffer_desc[2].DataOffset = 0;
  buffer_desc[2].DataSize = args->bitstream_size;

  buffer_desc_size = 3;
  if (args->inverse_quantization_matrix &&
      args->inverse_quantization_matrix_size > 0) {
    buffer_desc[3].BufferType =
        D3D11_VIDEO_DECODER_BUFFER_INVERSE_QUANTIZATION_MATRIX;
    buffer_desc[3].DataSize = args->inverse_quantization_matrix_size;
    buffer_desc_size++;
  }

  GstD3D11DeviceLockGuard lk (decoder->device);
  ret = gst_d3d11_decoder_begin_frame (decoder, output_view, 0, nullptr);
  if (ret != GST_FLOW_OK)
    return ret;

  if (!gst_d3d11_decoder_get_decoder_buffer (decoder,
          D3D11_VIDEO_DECODER_BUFFER_PICTURE_PARAMETERS, &d3d11_buffer_size,
          &d3d11_buffer)) {
    GST_ERROR_OBJECT (decoder,
        "Failed to get decoder buffer for picture parameters");
    goto error;
  }

  if (d3d11_buffer_size < args->picture_params_size) {
    GST_ERROR_OBJECT (decoder,
        "Too small picture param buffer size %d", d3d11_buffer_size);

    gst_d3d11_decoder_release_decoder_buffer (decoder,
        D3D11_VIDEO_DECODER_BUFFER_PICTURE_PARAMETERS);
    goto error;
  }

  memcpy (d3d11_buffer, args->picture_params, args->picture_params_size);

  if (!gst_d3d11_decoder_release_decoder_buffer (decoder,
          D3D11_VIDEO_DECODER_BUFFER_PICTURE_PARAMETERS)) {
    GST_ERROR_OBJECT (decoder, "Failed to release picture param buffer");
    goto error;
  }

  if (!gst_d3d11_decoder_get_decoder_buffer (decoder,
          D3D11_VIDEO_DECODER_BUFFER_SLICE_CONTROL, &d3d11_buffer_size,
          &d3d11_buffer)) {
    GST_ERROR_OBJECT (decoder, "Failed to get slice control buffer");
    goto error;
  }

  if (d3d11_buffer_size < args->slice_control_size) {
    GST_ERROR_OBJECT (decoder,
        "Too small slice control buffer size %d", d3d11_buffer_size);

    gst_d3d11_decoder_release_decoder_buffer (decoder,
        D3D11_VIDEO_DECODER_BUFFER_SLICE_CONTROL);
    goto error;
  }

  memcpy (d3d11_buffer, args->slice_control, args->slice_control_size);

  if (!gst_d3d11_decoder_release_decoder_buffer (decoder,
          D3D11_VIDEO_DECODER_BUFFER_SLICE_CONTROL)) {
    GST_ERROR_OBJECT (decoder, "Failed to release slice control buffer");
    goto error;
  }

  if (!gst_d3d11_decoder_get_decoder_buffer (decoder,
          D3D11_VIDEO_DECODER_BUFFER_BITSTREAM, &d3d11_buffer_size,
          &d3d11_buffer)) {
    GST_ERROR_OBJECT (decoder, "Failed to get bitstream buffer");
    goto error;
  }

  if (d3d11_buffer_size < args->bitstream_size) {
    GST_ERROR_OBJECT (decoder, "Too small bitstream buffer size %d",
        d3d11_buffer_size);

    gst_d3d11_decoder_release_decoder_buffer (decoder,
        D3D11_VIDEO_DECODER_BUFFER_BITSTREAM);
    goto error;
  }

  memcpy (d3d11_buffer, args->bitstream, args->bitstream_size);

  if (!gst_d3d11_decoder_release_decoder_buffer (decoder,
          D3D11_VIDEO_DECODER_BUFFER_BITSTREAM)) {
    GST_ERROR_OBJECT (decoder, "Failed to release bitstream buffer");
    goto error;
  }

  if (args->inverse_quantization_matrix_size > 0) {
    if (!gst_d3d11_decoder_get_decoder_buffer (decoder,
            D3D11_VIDEO_DECODER_BUFFER_INVERSE_QUANTIZATION_MATRIX,
            &d3d11_buffer_size, &d3d11_buffer)) {
      GST_ERROR_OBJECT (decoder,
          "Failed to get inverse quantization matrix buffer");
      goto error;
    }

    if (d3d11_buffer_size < args->inverse_quantization_matrix_size) {
      GST_ERROR_OBJECT (decoder,
          "Too small inverse quantization matrix buffer buffer %d",
          d3d11_buffer_size);

      gst_d3d11_decoder_release_decoder_buffer (decoder,
          D3D11_VIDEO_DECODER_BUFFER_INVERSE_QUANTIZATION_MATRIX);
      goto error;
    }

    memcpy (d3d11_buffer, args->inverse_quantization_matrix,
        args->inverse_quantization_matrix_size);

    if (!gst_d3d11_decoder_release_decoder_buffer (decoder,
            D3D11_VIDEO_DECODER_BUFFER_INVERSE_QUANTIZATION_MATRIX)) {
      GST_ERROR_OBJECT (decoder,
          "Failed to release inverse quantization matrix buffer");
      goto error;
    }
  }

  if (!gst_d3d11_decoder_submit_decoder_buffers (decoder,
          buffer_desc_size, buffer_desc)) {
    GST_ERROR_OBJECT (decoder, "Failed to submit decoder buffers");
    goto error;
  }

  if (!gst_d3d11_decoder_end_frame (decoder))
    return GST_FLOW_ERROR;

  return GST_FLOW_OK;

error:
  gst_d3d11_decoder_end_frame (decoder);
  return GST_FLOW_ERROR;
}

GstFlowReturn
gst_d3d11_decoder_new_picture (GstD3D11Decoder * decoder,
    GstVideoDecoder * videodec, GstCodecPicture * picture)
{
  GstBuffer *buf = NULL;
  GstFlowReturn ret;

  g_return_val_if_fail (GST_IS_D3D11_DECODER (decoder), GST_FLOW_ERROR);

  if (!decoder->internal_pool) {
    /* Try negotiate again whatever the previous negotiation result was.
     * There could be updated field(s) in sinkpad caps after we negotiated with
     * downstream on new_sequence() call. For example, h264/h265 parse
     * will be able to update HDR10 related caps field after parsing
     * corresponding SEI messages which are usually placed after the essential
     * headers */
    gst_video_decoder_negotiate (videodec);

    if (!gst_d3d11_decoder_prepare_output_view_pool (decoder)) {
      GST_ERROR_OBJECT (videodec, "Failed to setup internal pool");
      return GST_FLOW_ERROR;
    }
  } else if (!gst_buffer_pool_set_active (decoder->internal_pool, TRUE)) {
    GST_ERROR_OBJECT (videodec, "Couldn't set active internal pool");
    return GST_FLOW_ERROR;
  }

  ret = gst_buffer_pool_acquire_buffer (decoder->internal_pool, &buf, NULL);
  if (ret != GST_FLOW_OK || !buf) {
    if (ret != GST_FLOW_FLUSHING) {
      GST_ERROR_OBJECT (videodec, "Couldn't get buffer from pool, ret %s",
          gst_flow_get_name (ret));
    } else {
      GST_DEBUG_OBJECT (videodec, "We are flusing");
    }

    return ret;
  }

  if (!gst_d3d11_decoder_ensure_output_view (decoder, buf)) {
    GST_ERROR_OBJECT (videodec, "Output view unavailable");
    gst_buffer_unref (buf);

    return GST_FLOW_ERROR;
  }

  gst_codec_picture_set_user_data (picture,
      buf, (GDestroyNotify) gst_buffer_unref);

  return GST_FLOW_OK;
}

GstFlowReturn
gst_d3d11_decoder_duplicate_picture (GstD3D11Decoder * decoder,
    GstCodecPicture * src, GstCodecPicture * dst)
{
  GstBuffer *buf = (GstBuffer *) gst_codec_picture_get_user_data (src);

  if (!buf)
    return GST_FLOW_ERROR;

  gst_codec_picture_set_user_data (dst, gst_buffer_ref (buf),
      (GDestroyNotify) gst_buffer_unref);

  return GST_FLOW_OK;
}

static void
gst_d3d11_decoder_crop_and_copy_texture (GstD3D11Decoder * self,
    ID3D11Texture2D * src_texture, guint src_subresource,
    ID3D11Texture2D * dst_texture, guint dst_subresource)
{
  GstD3D11Device *device = self->device;
  ID3D11DeviceContext *context =
      gst_d3d11_device_get_device_context_handle (device);
  D3D11_BOX src_box = { 0, };
  GstD3D11DeviceLockGuard lk (device);

  /* NOTE: this may be incorrect for non-4:2:0 formats, but we do support
   * only 4:2:0 8/10 bits streams at the moment */
  src_box.left = GST_ROUND_UP_2 (self->offset_x);
  src_box.top = GST_ROUND_UP_2 (self->offset_y);
  src_box.right = GST_ROUND_UP_2 (self->offset_x + self->info.width);
  src_box.bottom = GST_ROUND_UP_2 (self->offset_y + self->info.height);
  src_box.front = 0;
  src_box.back = 1;

  context->CopySubresourceRegion (dst_texture, dst_subresource,
      0, 0, 0, src_texture, src_subresource, &src_box);
}

static gboolean
gst_d3d11_decoder_crop_and_copy_buffer (GstD3D11Decoder * self,
    GstBuffer * src, GstBuffer * dst)
{
  GstD3D11Device *device = self->device;
  ID3D11DeviceContext *context =
      gst_d3d11_device_get_device_context_handle (device);
  GstD3D11Memory *src_dmem;
  GstMemory *dst_mem;
  ID3D11Texture2D *src_texture;
  guint src_subresource;
  GstMapInfo map;
  D3D11_MAPPED_SUBRESOURCE d3d11_map;
  HRESULT hr;
  GstVideoFrame frame;

  src_dmem = (GstD3D11Memory *) gst_buffer_peek_memory (src, 0);
  src_texture =
      (ID3D11Texture2D *) gst_d3d11_memory_get_resource_handle (src_dmem);
  src_subresource = gst_d3d11_memory_get_subresource_index (src_dmem);

  dst_mem = gst_buffer_peek_memory (dst, 0);
  if (gst_is_d3d11_memory (dst_mem)) {
    GstD3D11Memory *dst_dmem = GST_D3D11_MEMORY_CAST (dst_mem);
    if (dst_dmem->device == self->device &&
        gst_memory_map (dst_mem, &map,
            (GstMapFlags) (GST_MAP_WRITE | GST_MAP_D3D11))) {
      ID3D11Texture2D *dst_texture = (ID3D11Texture2D *) map.data;
      guint dst_subresource = gst_d3d11_memory_get_subresource_index (dst_dmem);

      GST_TRACE_OBJECT (self, "Copying to output D3D11 texture");
      gst_d3d11_decoder_crop_and_copy_texture (self, src_texture,
          src_subresource, dst_texture, dst_subresource);
      gst_memory_unmap (dst_mem, &map);

      return TRUE;
    }
  }

  /* fallback copy, copy to our staging texture and copy to output */
  if (!gst_d3d11_decoder_ensure_staging_texture (self))
    return FALSE;

  GstD3D11DeviceLockGuard lk (device);
  if (!gst_video_frame_map (&frame, &self->output_info, dst, GST_MAP_WRITE)) {
    GST_ERROR_OBJECT (self, "Failed to map output buffer");
    return FALSE;
  }

  gst_d3d11_decoder_crop_and_copy_texture (self, src_texture, src_subresource,
      self->staging, 0);

  hr = context->Map (self->staging, 0, D3D11_MAP_READ, 0, &d3d11_map);
  if (!gst_d3d11_result (hr, device)) {
    GST_ERROR_OBJECT (self, "Failed to map staging texture");
    gst_video_frame_unmap (&frame);
    return FALSE;
  }

  g_assert (GST_VIDEO_FRAME_N_PLANES (&frame) <= 2);
  for (guint i = 0; i < GST_VIDEO_FRAME_N_PLANES (&frame); i++) {
    guint8 *src_data = (guint8 *) d3d11_map.pData +
        self->stating_plane_offset[i] * d3d11_map.RowPitch;
    guint8 *dst_data = (guint8 *) GST_VIDEO_FRAME_PLANE_DATA (&frame, i);
    guint width_in_bytes = GST_VIDEO_FRAME_COMP_WIDTH (&frame, i) *
        GST_VIDEO_FRAME_COMP_PSTRIDE (&frame, i);
    gint stride = GST_VIDEO_FRAME_PLANE_STRIDE (&frame, i);
    gint height = GST_VIDEO_FRAME_COMP_HEIGHT (&frame, i);

    for (gint j = 0; j < height; j++) {
      memcpy (dst_data, src_data, width_in_bytes);
      dst_data += stride;
      src_data += d3d11_map.RowPitch;
    }
  }

  context->Unmap (self->staging, 0);
  gst_video_frame_unmap (&frame);

  return TRUE;
}

GstFlowReturn
gst_d3d11_decoder_output_picture (GstD3D11Decoder * decoder,
    GstVideoDecoder * videodec, GstVideoCodecFrame * frame,
    GstCodecPicture * picture, GstVideoBufferFlags buffer_flags,
    gint display_width, gint display_height)
{
  GstFlowReturn ret = GST_FLOW_OK;
  GstBuffer *view_buffer;
  bool attach_crop_meta = false;

  if (picture->discont_state) {
    g_clear_pointer (&decoder->input_state, gst_video_codec_state_unref);
    decoder->input_state = gst_video_codec_state_ref (picture->discont_state);
  }

  view_buffer = (GstBuffer *) gst_codec_picture_get_user_data (picture);
  if (!view_buffer) {
    GST_ERROR_OBJECT (decoder, "Could not get output view");
    ret = GST_FLOW_ERROR;
    goto error;
  }

  if (display_width != GST_VIDEO_INFO_WIDTH (&decoder->output_info) ||
      display_height != GST_VIDEO_INFO_HEIGHT (&decoder->output_info)) {
    GST_INFO_OBJECT (videodec, "Frame size changed, do renegotiate");

    gst_video_info_set_format (&decoder->output_info,
        GST_VIDEO_INFO_FORMAT (&decoder->info), display_width, display_height);
    GST_VIDEO_INFO_INTERLACE_MODE (&decoder->output_info) =
        GST_VIDEO_INFO_INTERLACE_MODE (&decoder->info);

    if (!gst_video_decoder_negotiate (videodec)) {
      GST_ERROR_OBJECT (videodec, "Failed to re-negotiate with new frame size");
      ret = GST_FLOW_NOT_NEGOTIATED;
      goto error;
    }
  } else if (picture->discont_state) {
    if (!gst_video_decoder_negotiate (videodec)) {
      GST_ERROR_OBJECT (videodec, "Could not re-negotiate with updated state");
      ret = GST_FLOW_NOT_NEGOTIATED;
      goto error;
    }
  }

  if (gst_d3d11_decoder_can_direct_render (decoder, videodec, view_buffer,
          display_width, display_height)) {
    GstMemory *mem;

    mem = gst_buffer_peek_memory (view_buffer, 0);
    GST_MINI_OBJECT_FLAG_SET (mem, GST_D3D11_MEMORY_TRANSFER_NEED_DOWNLOAD);

    if (decoder->need_crop)
      attach_crop_meta = true;

    frame->output_buffer = gst_buffer_ref (view_buffer);
  } else {
    frame->output_buffer = gst_video_decoder_allocate_output_buffer (videodec);
    if (!frame->output_buffer) {
      GST_ERROR_OBJECT (videodec, "Couldn't allocate output buffer");
      ret = GST_FLOW_ERROR;
      goto error;
    }

    if (!gst_d3d11_decoder_crop_and_copy_buffer (decoder, view_buffer,
            frame->output_buffer)) {
      ret = GST_FLOW_ERROR;
      goto error;
    }
  }

  GST_BUFFER_FLAG_SET (frame->output_buffer, buffer_flags);
  gst_codec_picture_unref (picture);

  if (attach_crop_meta) {
    frame->output_buffer = gst_buffer_make_writable (frame->output_buffer);

    auto crop_meta = gst_buffer_add_video_crop_meta (frame->output_buffer);
    crop_meta->x = decoder->offset_x;
    crop_meta->y = decoder->offset_y;
    crop_meta->width = decoder->info.width;
    crop_meta->height = decoder->info.height;

    GST_TRACE_OBJECT (decoder, "Attatching crop meta");
  }

  return gst_video_decoder_finish_frame (videodec, frame);

error:
  gst_codec_picture_unref (picture);
  gst_video_decoder_release_frame (videodec, frame);

  return ret;
}

gboolean
gst_d3d11_decoder_negotiate (GstD3D11Decoder * decoder,
    GstVideoDecoder * videodec)
{
  GstVideoInfo *info;
  GstCaps *peer_caps;
  GstVideoCodecState *state = NULL;
  gboolean alternate_interlaced;
  gboolean alternate_supported = FALSE;
  gboolean d3d11_supported = FALSE;
  /* No d3d11 element supports alternate now */
  gboolean d3d11_alternate_supported = FALSE;
  GstVideoCodecState *input_state;
  GstStructure *s;
  const gchar *str;

  g_return_val_if_fail (GST_IS_D3D11_DECODER (decoder), FALSE);
  g_return_val_if_fail (GST_IS_VIDEO_DECODER (videodec), FALSE);

  info = &decoder->output_info;
  input_state = decoder->input_state;

  if (!decoder->configured) {
    GST_WARNING_OBJECT (videodec, "Decoder is not configured");
    return FALSE;
  }

  alternate_interlaced =
      (GST_VIDEO_INFO_INTERLACE_MODE (info) ==
      GST_VIDEO_INTERLACE_MODE_ALTERNATE);

  peer_caps = gst_pad_get_allowed_caps (GST_VIDEO_DECODER_SRC_PAD (videodec));
  GST_DEBUG_OBJECT (videodec, "Allowed caps %" GST_PTR_FORMAT, peer_caps);

  if (!peer_caps || gst_caps_is_any (peer_caps)) {
    GST_DEBUG_OBJECT (videodec,
        "cannot determine output format, use system memory");
  } else {
    GstCapsFeatures *features;
    guint size = gst_caps_get_size (peer_caps);
    guint i;

    for (i = 0; i < size; i++) {
      features = gst_caps_get_features (peer_caps, i);

      if (!features)
        continue;

      if (gst_caps_features_contains (features,
              GST_CAPS_FEATURE_MEMORY_D3D11_MEMORY)) {
        d3d11_supported = TRUE;

        if (gst_caps_features_contains (features,
                GST_CAPS_FEATURE_FORMAT_INTERLACED)) {
          d3d11_alternate_supported = TRUE;
        }
      }

      if (gst_caps_features_contains (features,
              GST_CAPS_FEATURE_FORMAT_INTERLACED)) {
        alternate_supported = TRUE;
      }
    }
  }
  gst_clear_caps (&peer_caps);

  GST_DEBUG_OBJECT (videodec,
      "Downstream feature support, D3D11 memory: %d, interlaced format %d",
      d3d11_supported, alternate_supported);

  if (alternate_interlaced) {
    /* FIXME: D3D11 cannot support alternating interlaced stream yet */
    GST_FIXME_OBJECT (videodec,
        "Implement alternating interlaced stream for D3D11");

    if (d3d11_alternate_supported || (!d3d11_supported && alternate_supported)) {
      gint height = GST_VIDEO_INFO_HEIGHT (info);

      /* Set caps resolution with display size, that's how we designed
       * for alternating interlaced stream */
      height = 2 * height;
      state = gst_video_decoder_set_interlaced_output_state (videodec,
          GST_VIDEO_INFO_FORMAT (info), GST_VIDEO_INFO_INTERLACE_MODE (info),
          GST_VIDEO_INFO_WIDTH (info), height, input_state);
    } else {
      GST_WARNING_OBJECT (videodec,
          "Downstream doesn't support alternating interlaced stream");

      state = gst_video_decoder_set_output_state (videodec,
          GST_VIDEO_INFO_FORMAT (info), GST_VIDEO_INFO_WIDTH (info),
          GST_VIDEO_INFO_HEIGHT (info), input_state);

      /* XXX: adjust PAR, this would produce output similar to that of
       * "line doubling" (so called bob deinterlacing) processing.
       * apart from missing anchor line (top-field or bottom-field) information.
       * Potentially flickering could happen. So this might not be correct.
       * But it would be better than negotiation error of half-height squeezed
       * image */
      state->info.par_d *= 2;
      state->info.fps_n *= 2;
    }
  } else {
    state = gst_video_decoder_set_interlaced_output_state (videodec,
        GST_VIDEO_INFO_FORMAT (info), GST_VIDEO_INFO_INTERLACE_MODE (info),
        GST_VIDEO_INFO_WIDTH (info), GST_VIDEO_INFO_HEIGHT (info), input_state);
  }

  if (!state) {
    GST_ERROR_OBJECT (decoder, "Couldn't set output state");
    return FALSE;
  }

  state->caps = gst_video_info_to_caps (&state->info);

  s = gst_caps_get_structure (input_state->caps, 0);
  str = gst_structure_get_string (s, "mastering-display-info");
  if (str) {
    gst_caps_set_simple (state->caps,
        "mastering-display-info", G_TYPE_STRING, str, nullptr);
  }

  str = gst_structure_get_string (s, "content-light-level");
  if (str) {
    gst_caps_set_simple (state->caps,
        "content-light-level", G_TYPE_STRING, str, nullptr);
  }

  g_clear_pointer (&decoder->output_state, gst_video_codec_state_unref);
  decoder->output_state = state;

  if (d3d11_supported) {
    gst_caps_set_features (state->caps, 0,
        gst_caps_features_new (GST_CAPS_FEATURE_MEMORY_D3D11_MEMORY, NULL));
  }

  decoder->downstream_supports_d3d11 = d3d11_supported;

  return gst_d3d11_decoder_open (decoder);
}

gboolean
gst_d3d11_decoder_decide_allocation (GstD3D11Decoder * decoder,
    GstVideoDecoder * videodec, GstQuery * query)
{
  GstCaps *outcaps;
  GstBufferPool *pool = NULL;
  guint n, size, min = 0, max = 0;
  GstVideoInfo vinfo = { 0, };
  GstStructure *config;
  GstD3D11AllocationParams *d3d11_params;
  gboolean use_d3d11_pool;

  g_return_val_if_fail (GST_IS_D3D11_DECODER (decoder), FALSE);
  g_return_val_if_fail (GST_IS_VIDEO_DECODER (videodec), FALSE);
  g_return_val_if_fail (query != NULL, FALSE);

  if (!decoder->opened) {
    GST_ERROR_OBJECT (videodec, "Should open decoder first");
    return FALSE;
  }

  gst_query_parse_allocation (query, &outcaps, NULL);

  if (!outcaps) {
    GST_DEBUG_OBJECT (decoder, "No output caps");
    return FALSE;
  }

  use_d3d11_pool = decoder->downstream_supports_d3d11;
  if (use_d3d11_pool) {
    decoder->use_crop_meta =
        gst_query_find_allocation_meta (query, GST_VIDEO_CROP_META_API_TYPE,
        nullptr);
  } else {
    decoder->use_crop_meta = FALSE;
  }

  GST_DEBUG_OBJECT (videodec, "Use crop meta %d", decoder->use_crop_meta);

  gst_video_info_from_caps (&vinfo, outcaps);
  n = gst_query_get_n_allocation_pools (query);
  if (n > 0)
    gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max);

  /* create our own pool */
  if (pool && use_d3d11_pool) {
    if (!GST_IS_D3D11_BUFFER_POOL (pool)) {
      GST_DEBUG_OBJECT (videodec,
          "Downstream pool is not d3d11, will create new one");
      gst_clear_object (&pool);
    } else {
      GstD3D11BufferPool *dpool = GST_D3D11_BUFFER_POOL (pool);
      if (dpool->device != decoder->device) {
        GST_DEBUG_OBJECT (videodec, "Different device, will create new one");
        gst_clear_object (&pool);
      }
    }
  }

  if (!pool) {
    if (use_d3d11_pool)
      pool = gst_d3d11_buffer_pool_new (decoder->device);
    else
      pool = gst_video_buffer_pool_new ();

    size = (guint) vinfo.size;
  }

  config = gst_buffer_pool_get_config (pool);
  gst_buffer_pool_config_set_params (config, outcaps, size, min, max);
  gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META);

  if (use_d3d11_pool) {
    GstVideoAlignment align;
    gint width, height;

    gst_video_alignment_reset (&align);

    d3d11_params = gst_buffer_pool_config_get_d3d11_allocation_params (config);
    if (!d3d11_params)
      d3d11_params = gst_d3d11_allocation_params_new (decoder->device, &vinfo,
          GST_D3D11_ALLOCATION_FLAG_DEFAULT, 0, 0);

    width = GST_VIDEO_INFO_WIDTH (&vinfo);
    height = GST_VIDEO_INFO_HEIGHT (&vinfo);

    /* need alignment to copy decoder output texture to downstream texture */
    align.padding_right = GST_ROUND_UP_16 (width) - width;
    align.padding_bottom = GST_ROUND_UP_16 (height) - height;
    if (!gst_d3d11_allocation_params_alignment (d3d11_params, &align)) {
      GST_ERROR_OBJECT (videodec, "Cannot set alignment");
      return FALSE;
    }

    /* Needs render target bind flag so that it can be used for
     * output of shader pipeline if internal resizing is required.
     * Also, downstream can keep using video processor even if we copy
     * some decoded textures into downstream buffer */
    gst_d3d11_allocation_params_set_bind_flags (d3d11_params,
        D3D11_BIND_RENDER_TARGET);

    gst_buffer_pool_config_set_d3d11_allocation_params (config, d3d11_params);
    gst_d3d11_allocation_params_free (d3d11_params);

    /* Store min buffer size. We need to take account of the amount of buffers
     * which might be held by downstream in case of zero-copy playback */
    if (!decoder->internal_pool) {
      if (n > 0) {
        GST_DEBUG_OBJECT (videodec, "Downstream proposed pool");
        decoder->wait_on_pool_full = TRUE;
        /* XXX: hardcoded bound 16, to avoid too large pool size */
        decoder->downstream_min_buffers = MIN (min, 16);
      } else {
        GST_DEBUG_OBJECT (videodec, "Downstream didn't propose pool");
        decoder->wait_on_pool_full = FALSE;
        /* don't know how many buffers would be queued by downstream */
        decoder->downstream_min_buffers = 4;
      }
    } else {
      /* We configured our DPB pool already, let's check if our margin can
       * cover min size */
      decoder->wait_on_pool_full = FALSE;

      if (n > 0) {
        if (decoder->downstream_min_buffers >= min)
          decoder->wait_on_pool_full = TRUE;

        GST_DEBUG_OBJECT (videodec,
            "Pre-allocated margin %d can%s cover downstream min size %d",
            decoder->downstream_min_buffers,
            decoder->wait_on_pool_full ? "" : "not", min);
      } else {
        GST_DEBUG_OBJECT (videodec, "Downstream min size is unknown");
      }
    }

    GST_DEBUG_OBJECT (videodec, "Downstream min buffres: %d", min);

    /* We will not use downstream pool for decoding, and therefore preallocation
     * is unnecessary. So, Non-zero min buffer will be a waste of GPU memory */
    min = 0;
  }

  gst_buffer_pool_set_config (pool, config);
  /* d3d11 buffer pool will update buffer size based on allocated texture,
   * get size from config again */
  config = gst_buffer_pool_get_config (pool);
  gst_buffer_pool_config_get_params (config, nullptr, &size, nullptr, nullptr);
  gst_structure_free (config);

  if (n > 0)
    gst_query_set_nth_allocation_pool (query, 0, pool, size, min, max);
  else
    gst_query_add_allocation_pool (query, pool, size, min, max);
  gst_object_unref (pool);

  return TRUE;
}

static void
gst_d3d11_decoder_set_flushing (GstD3D11Decoder * self, gboolean flushing)
{
  GstD3D11SRWLockGuard lk (&self->lock);
  if (self->internal_pool)
    gst_buffer_pool_set_flushing (self->internal_pool, flushing);
  self->flushing = flushing;
}

void
gst_d3d11_decoder_sink_event (GstD3D11Decoder * decoder, GstEvent * event)
{
  switch (GST_EVENT_TYPE (event)) {
    case GST_EVENT_FLUSH_START:
      gst_d3d11_decoder_set_flushing (decoder, TRUE);
      break;
    case GST_EVENT_FLUSH_STOP:
      gst_d3d11_decoder_set_flushing (decoder, FALSE);
      break;
    default:
      break;
  }
}

static gboolean
gst_d3d11_decoder_can_direct_render (GstD3D11Decoder * self,
    GstVideoDecoder * videodec, GstBuffer * view_buffer,
    gint display_width, gint display_height)
{
  GstMemory *mem;
  GstD3D11PoolAllocator *alloc;
  guint max_size = 0, outstanding_size = 0;

  /* We don't support direct render for reverse playback */
  if (videodec->input_segment.rate < 0)
    return FALSE;

  if (!self->can_direct_rendering || !self->downstream_supports_d3d11)
    return FALSE;

  /* different size, need copy */
  /* TODO: crop meta */
  if (display_width != GST_VIDEO_INFO_WIDTH (&self->info) ||
      display_height != GST_VIDEO_INFO_HEIGHT (&self->info))
    return FALSE;

  /* We need to crop but downstream does not support crop, need to copy */
  if (self->need_crop && !self->use_crop_meta)
    return FALSE;

  /* we can do direct render in this case, since there is no DPB pool size
   * limit */
  if (self->use_array_of_texture)
    return TRUE;

  /* Let's believe downstream info */
  if (self->wait_on_pool_full)
    return TRUE;

  /* Check if we are about to full */
  mem = gst_buffer_peek_memory (view_buffer, 0);

  /* something went wrong */
  if (!gst_is_d3d11_memory (mem)) {
    GST_ERROR_OBJECT (self, "Not a D3D11 memory");
    return FALSE;
  }

  alloc = GST_D3D11_POOL_ALLOCATOR (mem->allocator);
  if (!gst_d3d11_pool_allocator_get_pool_size (alloc, &max_size,
          &outstanding_size)) {
    GST_ERROR_OBJECT (self, "Couldn't query pool size");
    return FALSE;
  }

  /* 2 buffer margin */
  if (max_size <= outstanding_size + 1) {
    GST_DEBUG_OBJECT (self, "memory pool is about to full (%u/%u)",
        outstanding_size, max_size);
    return FALSE;
  }

  GST_LOG_OBJECT (self, "Can do direct rendering");

  return TRUE;
}

/* Keep sync with chromium and keep in sorted order.
 * See supported_profile_helpers.cc in chromium */
static const guint legacy_amd_list[] = {
  0x130f, 0x6700, 0x6701, 0x6702, 0x6703, 0x6704, 0x6705, 0x6706, 0x6707,
  0x6708, 0x6709, 0x6718, 0x6719, 0x671c, 0x671d, 0x671f, 0x6720, 0x6721,
  0x6722, 0x6723, 0x6724, 0x6725, 0x6726, 0x6727, 0x6728, 0x6729, 0x6738,
  0x6739, 0x673e, 0x6740, 0x6741, 0x6742, 0x6743, 0x6744, 0x6745, 0x6746,
  0x6747, 0x6748, 0x6749, 0x674a, 0x6750, 0x6751, 0x6758, 0x6759, 0x675b,
  0x675d, 0x675f, 0x6760, 0x6761, 0x6762, 0x6763, 0x6764, 0x6765, 0x6766,
  0x6767, 0x6768, 0x6770, 0x6771, 0x6772, 0x6778, 0x6779, 0x677b, 0x6798,
  0x67b1, 0x6821, 0x683d, 0x6840, 0x6841, 0x6842, 0x6843, 0x6849, 0x6850,
  0x6858, 0x6859, 0x6880, 0x6888, 0x6889, 0x688a, 0x688c, 0x688d, 0x6898,
  0x6899, 0x689b, 0x689c, 0x689d, 0x689e, 0x68a0, 0x68a1, 0x68a8, 0x68a9,
  0x68b0, 0x68b8, 0x68b9, 0x68ba, 0x68be, 0x68bf, 0x68c0, 0x68c1, 0x68c7,
  0x68c8, 0x68c9, 0x68d8, 0x68d9, 0x68da, 0x68de, 0x68e0, 0x68e1, 0x68e4,
  0x68e5, 0x68e8, 0x68e9, 0x68f1, 0x68f2, 0x68f8, 0x68f9, 0x68fa, 0x68fe,
  0x9400, 0x9401, 0x9402, 0x9403, 0x9405, 0x940a, 0x940b, 0x940f, 0x9440,
  0x9441, 0x9442, 0x9443, 0x9444, 0x9446, 0x944a, 0x944b, 0x944c, 0x944e,
  0x9450, 0x9452, 0x9456, 0x945a, 0x945b, 0x945e, 0x9460, 0x9462, 0x946a,
  0x946b, 0x947a, 0x947b, 0x9480, 0x9487, 0x9488, 0x9489, 0x948a, 0x948f,
  0x9490, 0x9491, 0x9495, 0x9498, 0x949c, 0x949e, 0x949f, 0x94a0, 0x94a1,
  0x94a3, 0x94b1, 0x94b3, 0x94b4, 0x94b5, 0x94b9, 0x94c0, 0x94c1, 0x94c3,
  0x94c4, 0x94c5, 0x94c6, 0x94c7, 0x94c8, 0x94c9, 0x94cb, 0x94cc, 0x94cd,
  0x9500, 0x9501, 0x9504, 0x9505, 0x9506, 0x9507, 0x9508, 0x9509, 0x950f,
  0x9511, 0x9515, 0x9517, 0x9519, 0x9540, 0x9541, 0x9542, 0x954e, 0x954f,
  0x9552, 0x9553, 0x9555, 0x9557, 0x955f, 0x9580, 0x9581, 0x9583, 0x9586,
  0x9587, 0x9588, 0x9589, 0x958a, 0x958b, 0x958c, 0x958d, 0x958e, 0x958f,
  0x9590, 0x9591, 0x9593, 0x9595, 0x9596, 0x9597, 0x9598, 0x9599, 0x959b,
  0x95c0, 0x95c2, 0x95c4, 0x95c5, 0x95c6, 0x95c7, 0x95c9, 0x95cc, 0x95cd,
  0x95ce, 0x95cf, 0x9610, 0x9611, 0x9612, 0x9613, 0x9614, 0x9615, 0x9616,
  0x9640, 0x9641, 0x9642, 0x9643, 0x9644, 0x9645, 0x9647, 0x9648, 0x9649,
  0x964a, 0x964b, 0x964c, 0x964e, 0x964f, 0x9710, 0x9711, 0x9712, 0x9713,
  0x9714, 0x9715, 0x9802, 0x9803, 0x9804, 0x9805, 0x9806, 0x9807, 0x9808,
  0x9809, 0x980a, 0x9830, 0x983d, 0x9850, 0x9851, 0x9874, 0x9900, 0x9901,
  0x9903, 0x9904, 0x9905, 0x9906, 0x9907, 0x9908, 0x9909, 0x990a, 0x990b,
  0x990c, 0x990d, 0x990e, 0x990f, 0x9910, 0x9913, 0x9917, 0x9918, 0x9919,
  0x9990, 0x9991, 0x9992, 0x9993, 0x9994, 0x9995, 0x9996, 0x9997, 0x9998,
  0x9999, 0x999a, 0x999b, 0x999c, 0x999d, 0x99a0, 0x99a2, 0x99a4
};

static const guint legacy_intel_list[] = {
  0x102, 0x106, 0x116, 0x126, 0x152, 0x156, 0x166,
  0x402, 0x406, 0x416, 0x41e, 0xa06, 0xa16, 0xf31,
};

static gint
binary_search_compare (const guint * a, const guint * b)
{
  return *a - *b;
}

/* Certain AMD GPU drivers like R600, R700, Evergreen and Cayman and some second
 * generation Intel GPU drivers crash if we create a video device with a
 * resolution higher then 1920 x 1088. This function checks if the GPU is in
 * this list and if yes returns true. */
gboolean
gst_d3d11_decoder_util_is_legacy_device (GstD3D11Device * device)
{
  const guint amd_id[] = { 0x1002, 0x1022 };
  const guint intel_id = 0x8086;
  guint device_id = 0;
  guint vendor_id = 0;
  guint *match = NULL;

  g_return_val_if_fail (GST_IS_D3D11_DEVICE (device), FALSE);

  g_object_get (device, "device-id", &device_id, "vendor-id", &vendor_id, NULL);

  if (vendor_id == amd_id[0] || vendor_id == amd_id[1]) {
    match =
        (guint *) gst_util_array_binary_search ((gpointer) legacy_amd_list,
        G_N_ELEMENTS (legacy_amd_list), sizeof (guint),
        (GCompareDataFunc) binary_search_compare,
        GST_SEARCH_MODE_EXACT, &device_id, NULL);
  } else if (vendor_id == intel_id) {
    match =
        (guint *) gst_util_array_binary_search ((gpointer) legacy_intel_list,
        G_N_ELEMENTS (legacy_intel_list), sizeof (guint),
        (GCompareDataFunc) binary_search_compare,
        GST_SEARCH_MODE_EXACT, &device_id, NULL);
  }

  if (match) {
    GST_DEBUG_OBJECT (device, "it's legacy device");
    return TRUE;
  }

  return FALSE;
}

gboolean
gst_d3d11_decoder_supports_format (GstD3D11Device * device,
    const GUID * decoder_profile, DXGI_FORMAT format)
{
  HRESULT hr;
  BOOL can_support = FALSE;
  ID3D11VideoDevice *video_device;

  g_return_val_if_fail (GST_IS_D3D11_DEVICE (device), FALSE);
  g_return_val_if_fail (decoder_profile != NULL, FALSE);
  g_return_val_if_fail (format != DXGI_FORMAT_UNKNOWN, FALSE);

  video_device = gst_d3d11_device_get_video_device_handle (device);
  if (!video_device)
    return FALSE;

  hr = video_device->CheckVideoDecoderFormat (decoder_profile, format,
      &can_support);
  if (!gst_d3d11_result (hr, device) || !can_support) {
    GST_DEBUG_OBJECT (device,
        "VideoDevice could not support dxgi format %d, hr: 0x%x",
        format, (guint) hr);

    return FALSE;
  }

  return TRUE;
}

/* Don't call this method with legacy device */
gboolean
gst_d3d11_decoder_supports_resolution (GstD3D11Device * device,
    const GUID * decoder_profile, DXGI_FORMAT format, guint width, guint height)
{
  D3D11_VIDEO_DECODER_DESC desc;
  HRESULT hr;
  UINT config_count;
  ID3D11VideoDevice *video_device;

  g_return_val_if_fail (GST_IS_D3D11_DEVICE (device), FALSE);
  g_return_val_if_fail (decoder_profile != NULL, FALSE);
  g_return_val_if_fail (format != DXGI_FORMAT_UNKNOWN, FALSE);

  video_device = gst_d3d11_device_get_video_device_handle (device);
  if (!video_device)
    return FALSE;

  desc.SampleWidth = width;
  desc.SampleHeight = height;
  desc.OutputFormat = format;
  desc.Guid = *decoder_profile;

  hr = video_device->GetVideoDecoderConfigCount (&desc, &config_count);
  if (!gst_d3d11_result (hr, device) || config_count == 0) {
    GST_DEBUG_OBJECT (device, "Could not get decoder config count, hr: 0x%x",
        (guint) hr);
    return FALSE;
  }

  return TRUE;
}

enum
{
  PROP_DECODER_ADAPTER_LUID = 1,
  PROP_DECODER_DEVICE_ID,
  PROP_DECODER_VENDOR_ID,
};

struct _GstD3D11DecoderClassData
{
  GstD3D11DecoderSubClassData subclass_data;
  GstCaps *sink_caps;
  GstCaps *src_caps;
  gchar *description;
};

/**
 * gst_d3d11_decoder_class_data_new:
 * @device: (transfer none): a #GstD3D11Device
 * @sink_caps: (transfer full): a #GstCaps
 * @src_caps: (transfer full): a #GstCaps
 * @max_resolution: maximum supported resolution
 *
 * Create new #GstD3D11DecoderClassData
 *
 * Returns: (transfer full): the new #GstD3D11DecoderClassData
 */
GstD3D11DecoderClassData *
gst_d3d11_decoder_class_data_new (GstD3D11Device * device, GstDxvaCodec codec,
    GstCaps * sink_caps, GstCaps * src_caps, guint max_resolution)
{
  GstD3D11DecoderClassData *ret;
  guint min_width = 1;
  guint min_height = 1;

  g_return_val_if_fail (GST_IS_D3D11_DEVICE (device), NULL);
  g_return_val_if_fail (sink_caps != NULL, NULL);
  g_return_val_if_fail (src_caps != NULL, NULL);

  /* FIXME: D3D11/DXVA does not have an API for querying minimum resolution
   * capability. Might need to find a nice way for testing minimum resolution.
   *
   * Below hardcoded values were checked on RTX 2080/3060 GPUs via NVDEC API
   * (VP8 decoding is not supported by those GPUs via D3D11/DXVA) */
  if (gst_d3d11_get_device_vendor (device) == GST_D3D11_DEVICE_VENDOR_NVIDIA) {
    switch (codec) {
      case GST_DXVA_CODEC_MPEG2:
      case GST_DXVA_CODEC_H264:
      case GST_DXVA_CODEC_VP8:
        min_width = 48;
        min_height = 16;
        break;
      case GST_DXVA_CODEC_H265:
        min_width = min_height = 144;
        break;
      case GST_DXVA_CODEC_VP9:
      case GST_DXVA_CODEC_AV1:
        min_width = min_height = 128;
        break;
      default:
        g_assert_not_reached ();
        return nullptr;
    }
  }

  ret = g_new0 (GstD3D11DecoderClassData, 1);

  gst_caps_set_simple (sink_caps,
      "width", GST_TYPE_INT_RANGE, min_width, max_resolution,
      "height", GST_TYPE_INT_RANGE, min_height, max_resolution, nullptr);
  gst_caps_set_simple (src_caps,
      "width", GST_TYPE_INT_RANGE, min_width, max_resolution,
      "height", GST_TYPE_INT_RANGE, min_height, max_resolution, nullptr);

  /* class data will be leaked if the element never gets instantiated */
  GST_MINI_OBJECT_FLAG_SET (sink_caps, GST_MINI_OBJECT_FLAG_MAY_BE_LEAKED);
  GST_MINI_OBJECT_FLAG_SET (src_caps, GST_MINI_OBJECT_FLAG_MAY_BE_LEAKED);

  ret->subclass_data.codec = codec;
  g_object_get (device, "adapter-luid", &ret->subclass_data.adapter_luid,
      "device-id", &ret->subclass_data.device_id,
      "vendor-id", &ret->subclass_data.vendor_id,
      "description", &ret->description, nullptr);
  ret->sink_caps = sink_caps;
  ret->src_caps = src_caps;

  return ret;
}

void
gst_d3d11_decoder_class_data_fill_subclass_data (GstD3D11DecoderClassData *
    data, GstD3D11DecoderSubClassData * subclass_data)
{
  g_return_if_fail (data != nullptr);
  g_return_if_fail (subclass_data != nullptr);

  *subclass_data = data->subclass_data;
}

static void
gst_d3d11_decoder_class_data_free (GstD3D11DecoderClassData * data)
{
  if (!data)
    return;

  gst_clear_caps (&data->sink_caps);
  gst_clear_caps (&data->src_caps);
  g_free (data->description);
  g_free (data);
}

typedef struct _GstD3D11DecoderDocCaps
{
  GstDxvaCodec codec;
  const gchar *sink_caps;
  const gchar *src_caps;
} GstD3D11DecoderDocCaps;

/* *INDENT-OFF* */
static const GstD3D11DecoderDocCaps doc_caps_list[] = {
  {
    GST_DXVA_CODEC_MPEG2,
    "video/mpeg, width = (int) [ 1, 1920 ], height = (int) [ 1, 1920 ], "
    "mpegversion = (int) 2, systemstream = (boolean) false, "
    "profile = (string) { main, simple }",

    "video/x-raw(memory:D3D11Memory), format = (string) NV12, "
    "width = (int) [ 1, 1920 ], height = (int) [ 1, 1920 ]; "
    "video/x-raw, format = (string) NV12, "
    "width = (int) [ 1, 1920 ], height = (int) [ 1, 1920 ]"},
  {
    GST_DXVA_CODEC_H264,
    "video/x-h264, width = (int) [ 1, 4096 ], height = (int) [ 1, 4096 ], "
    "stream-format = (string) { avc, avc3, byte-stream }, "
    "alignment = (string) au, "
    "profile = (string) { high, progressive-high, constrained-high, main, "
    "constrained-baseline, baseline }",

    "video/x-raw(memory:D3D11Memory), format = (string) NV12, "
    "width = (int) [ 1, 4096 ], height = (int) [ 1, 4096 ]; "
    "video/x-raw, format = (string) NV12, "
    "width = (int) [ 1, 4096 ], height = (int) [ 1, 4096 ]"
  },
  {
    GST_DXVA_CODEC_H265,
    "video/x-h265, width = (int) [ 1, 16384 ], height = (int) [ 1, 16384 ], "
    "stream-format = (string) { hev1, hvc1, byte-stream }, "
    "alignment = (string) au, profile = (string) { main, main-10 }",

    "video/x-raw(memory:D3D11Memory), format = (string) { NV12, P010_10LE }, "
    "width = (int) [ 1, 16384 ], height = (int) [ 1, 16384 ]; "
    "video/x-raw, format = (string) { NV12, P010_10LE }, "
    "width = (int) [ 1, 16384 ], height = (int) [ 1, 16384 ]"
  },
  {
    GST_DXVA_CODEC_VP8,
    "video/x-vp8, width = (int) [ 1, 4096 ], height = (int) [ 1, 4096 ]",

    "video/x-raw(memory:D3D11Memory), format = (string) NV12, "
    "width = (int) [ 1, 4096 ], height = (int) [ 1, 4096 ]; "
    "video/x-raw, format = (string) NV12, "
    "width = (int) [ 1, 4096 ], height = (int) [ 1, 4096 ]"
  },
  {
    GST_DXVA_CODEC_VP9,
    "video/x-vp9, width = (int) [ 1, 16384 ], height = (int) [ 1, 16384 ], "
    "alignment = (string) frame, profile = (string) { 0, 2 }",

    "video/x-raw(memory:D3D11Memory), format = (string) { NV12, P010_10LE }, "
    "width = (int) [ 1, 16384 ], height = (int) [ 1, 16384 ]; "
    "video/x-raw, format = (string) { NV12, P010_10LE }, "
    "width = (int) [ 1, 16384 ], height = (int) [ 1, 16384 ]"
  },
  {
    GST_DXVA_CODEC_AV1,
    "video/x-av1, width = (int) [ 1, 16384 ], height = (int) [ 1, 16384 ], "
    "alignment = (string) frame, profile = (string) main",

    "video/x-raw(memory:D3D11Memory), format = (string) { NV12, P010_10LE }, "
    "width = (int) [ 1, 16384 ], height = (int) [ 1, 16384 ]; "
    "video/x-raw, format = (string) { NV12, P010_10LE }, "
    "width = (int) [ 1, 16384 ], height = (int) [ 1, 16384 ]"
  },
};
/* *INDENT-ON* */

void
gst_d3d11_decoder_proxy_class_init (GstElementClass * klass,
    GstD3D11DecoderClassData * data, const gchar * author)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  GstD3D11DecoderSubClassData *cdata = &data->subclass_data;
  std::string long_name;
  std::string description;
  const gchar *codec_name;
  GParamFlags param_flags = (GParamFlags) (GST_PARAM_DOC_SHOW_DEFAULT |
      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
  GstPadTemplate *pad_templ;
  GstCaps *doc_caps;
  const GstD3D11DecoderDocCaps *d3d11_doc_caps = nullptr;

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

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

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

  codec_name = gst_dxva_codec_to_string (cdata->codec);
  long_name = "Direct3D11/DXVA " + std::string (codec_name) + " " +
      std::string (data->description) + " Decoder";
  description = "Direct3D11/DXVA based " + std::string (codec_name) +
      " video decoder";

  gst_element_class_set_metadata (klass, long_name.c_str (),
      "Codec/Decoder/Video/Hardware", description.c_str (), author);

  for (guint i = 0; i < G_N_ELEMENTS (doc_caps_list); i++) {
    if (doc_caps_list[i].codec == cdata->codec) {
      d3d11_doc_caps = &doc_caps_list[i];
      break;
    }
  }

  g_assert (d3d11_doc_caps);

  pad_templ = gst_pad_template_new ("sink",
      GST_PAD_SINK, GST_PAD_ALWAYS, data->sink_caps);
  doc_caps = gst_caps_from_string (d3d11_doc_caps->sink_caps);
  gst_pad_template_set_documentation_caps (pad_templ, doc_caps);
  gst_caps_unref (doc_caps);
  gst_element_class_add_pad_template (klass, pad_templ);

  pad_templ = gst_pad_template_new ("src",
      GST_PAD_SRC, GST_PAD_ALWAYS, data->src_caps);
  doc_caps = gst_caps_from_string (d3d11_doc_caps->src_caps);
  gst_pad_template_set_documentation_caps (pad_templ, doc_caps);
  gst_caps_unref (doc_caps);
  gst_element_class_add_pad_template (klass, pad_templ);

  gst_d3d11_decoder_class_data_free (data);
}

void
gst_d3d11_decoder_proxy_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec,
    GstD3D11DecoderSubClassData * subclass_data)
{
  switch (prop_id) {
    case PROP_DECODER_ADAPTER_LUID:
      g_value_set_int64 (value, subclass_data->adapter_luid);
      break;
    case PROP_DECODER_DEVICE_ID:
      g_value_set_uint (value, subclass_data->device_id);
      break;
    case PROP_DECODER_VENDOR_ID:
      g_value_set_uint (value, subclass_data->vendor_id);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

gboolean
gst_d3d11_decoder_proxy_open (GstVideoDecoder * videodec,
    GstD3D11DecoderSubClassData * subclass_data, GstD3D11Device ** device,
    GstD3D11Decoder ** decoder)
{
  GstElement *elem = GST_ELEMENT (videodec);

  if (!gst_d3d11_ensure_element_data_for_adapter_luid (elem,
          subclass_data->adapter_luid, device)) {
    GST_ERROR_OBJECT (elem, "Cannot create d3d11device");
    return FALSE;
  }

  *decoder = gst_d3d11_decoder_new (*device, subclass_data->codec);

  if (*decoder == nullptr) {
    GST_ERROR_OBJECT (elem, "Cannot create d3d11 decoder");
    gst_clear_object (device);
    return FALSE;
  }

  return TRUE;
}