| /* GStreamer | |
| * Copyright (C) 2023 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 "gstd3d12.h" | |
| #include "gstd3d12-private.h" | |
| #include "gstd3d11on12.h" | |
| #include <directx/d3dx12.h> | |
| #include <wrl.h> | |
| #include <vector> | |
| #include <string.h> | |
| #include <mutex> | |
| #include <condition_variable> | |
| #include <string> | |
| #include <locale> | |
| #include <codecvt> | |
| #include <algorithm> | |
| #include <d3d12sdklayers.h> | |
| #include <memory> | |
| #include <queue> | |
| #include <unordered_map> | |
| #include <thread> | |
| GST_DEBUG_CATEGORY_STATIC (gst_d3d12_device_debug); | |
| GST_DEBUG_CATEGORY_STATIC (gst_d3d12_sdk_debug); | |
| #define GST_CAT_DEFAULT gst_d3d12_device_debug | |
| enum | |
| { | |
| PROP_0, | |
| PROP_ADAPTER_INDEX, | |
| PROP_ADAPTER_LUID, | |
| PROP_DEVICE_ID, | |
| PROP_VENDOR_ID, | |
| PROP_HARDWARE, | |
| PROP_DESCRIPTION, | |
| }; | |
| /* *INDENT-OFF* */ | |
| using namespace Microsoft::WRL; | |
| struct _GstD3D12DevicePrivate | |
| { | |
| ~_GstD3D12DevicePrivate () | |
| { | |
| auto hr = device->GetDeviceRemovedReason (); | |
| /* If device were not removed, make sure no pending command in queue */ | |
| if (hr == S_OK) { | |
| if (direct_queue) | |
| gst_d3d12_command_queue_fence_wait (direct_queue, G_MAXUINT64, nullptr); | |
| if (copy_queue) | |
| gst_d3d12_command_queue_fence_wait (copy_queue, G_MAXUINT64, nullptr); | |
| } | |
| gst_clear_object (&direct_queue); | |
| gst_clear_object (©_queue); | |
| gst_clear_object (&direct_ca_pool); | |
| gst_clear_object (&direct_cl_pool); | |
| gst_clear_object (©_ca_pool); | |
| gst_clear_object (©_cl_pool); | |
| factory = nullptr; | |
| adapter = nullptr; | |
| d3d11on12 = nullptr; | |
| if (info_queue && device) { | |
| ComPtr <ID3D12DebugDevice> debug_dev; | |
| device.As (&debug_dev); | |
| if (debug_dev) { | |
| debug_dev->ReportLiveDeviceObjects (D3D12_RLDO_DETAIL | | |
| D3D12_RLDO_IGNORE_INTERNAL); | |
| UINT64 num_msg = info_queue->GetNumStoredMessages (); | |
| for (UINT64 i = 0; i < num_msg; i++) { | |
| HRESULT hr; | |
| SIZE_T msg_len; | |
| D3D12_MESSAGE *msg; | |
| hr = info_queue->GetMessage (i, nullptr, &msg_len); | |
| if (FAILED (hr) || msg_len == 0) | |
| continue; | |
| msg = (D3D12_MESSAGE *) g_malloc0 (msg_len); | |
| hr = info_queue->GetMessage (i, msg, &msg_len); | |
| if (FAILED (hr) || msg_len == 0) { | |
| g_free (msg); | |
| continue; | |
| } | |
| gst_debug_log (gst_d3d12_sdk_debug, GST_LEVEL_INFO, | |
| __FILE__, GST_FUNCTION, __LINE__, nullptr, | |
| "D3D12InfoQueue: %s", msg->pDescription); | |
| g_free (msg); | |
| } | |
| info_queue->ClearStoredMessages (); | |
| } | |
| } | |
| } | |
| ComPtr<ID3D12Device> device; | |
| ComPtr<IDXGIAdapter1> adapter; | |
| ComPtr<IDXGIFactory2> factory; | |
| std::unordered_map<GstVideoFormat, GstD3D12Format> format_table; | |
| std::recursive_mutex extern_lock; | |
| std::mutex lock; | |
| ComPtr<ID3D12InfoQueue> info_queue; | |
| ComPtr<IUnknown> d3d11on12; | |
| GstD3D12CommandQueue *direct_queue = nullptr; | |
| GstD3D12CommandQueue *copy_queue = nullptr; | |
| GstD3D12CommandListPool *direct_cl_pool = nullptr; | |
| GstD3D12CommandAllocatorPool *direct_ca_pool = nullptr; | |
| GstD3D12CommandListPool *copy_cl_pool = nullptr; | |
| GstD3D12CommandAllocatorPool *copy_ca_pool = nullptr; | |
| guint rtv_inc_size; | |
| guint adapter_index = 0; | |
| guint device_id = 0; | |
| guint vendor_id = 0; | |
| std::string description; | |
| gint64 adapter_luid = 0; | |
| }; | |
| enum GstD3D12DeviceConstructType | |
| { | |
| GST_D3D12_DEVICE_CONSTRUCT_FOR_INDEX, | |
| GST_D3D12_DEVICE_CONSTRUCT_FOR_LUID, | |
| }; | |
| struct GstD3D12DeviceConstructData | |
| { | |
| union | |
| { | |
| guint index; | |
| gint64 luid; | |
| } data; | |
| GstD3D12DeviceConstructType type; | |
| }; | |
| static GstD3D12Device * | |
| gst_d3d12_device_new_internal (const GstD3D12DeviceConstructData * data); | |
| struct DeviceCache | |
| { | |
| ~DeviceCache() | |
| { | |
| if (priv) | |
| delete priv; | |
| } | |
| GstD3D12Device *device = nullptr; | |
| GstD3D12DevicePrivate *priv = nullptr; | |
| guint64 token = 0; | |
| }; | |
| /* Because ID3D12Device instance is a singleton per adapter, | |
| * this DeviceCacheManager object will cache GstD3D12Device object and | |
| * will return the same GstD3D12Device object for create request | |
| * if instanted object exists already. | |
| * | |
| * Another role of this object dtor thread management. | |
| * GstD3D12CommandQueue object held by GstD3D12Device will run background | |
| * garbage collection thread and releasing garbage collection data | |
| * could result in releasing GstD3D12Device object, which can cause self-thread | |
| * joining. This manager will run one background thread to avoid it | |
| */ | |
| class DeviceCacheManager | |
| { | |
| public: | |
| DeviceCacheManager (const DeviceCacheManager &) = delete; | |
| DeviceCacheManager& operator= (const DeviceCacheManager &) = delete; | |
| static DeviceCacheManager * GetInstance() | |
| { | |
| static DeviceCacheManager *inst = nullptr; | |
| GST_D3D12_CALL_ONCE_BEGIN { | |
| inst = new DeviceCacheManager (); | |
| } GST_D3D12_CALL_ONCE_END; | |
| return inst; | |
| } | |
| void InitThread () | |
| { | |
| std::lock_guard <std::mutex> lk (lock_); | |
| if (!thread_) | |
| thread_ = new std::thread (&DeviceCacheManager::threadFunc, this); | |
| } | |
| void Sync () | |
| { | |
| guint64 to_wait = 0; | |
| { | |
| std::lock_guard <std::mutex> lk (lock_); | |
| if (!thread_) | |
| return; | |
| token_++; | |
| to_wait = token_; | |
| auto empty_item = std::make_shared <DeviceCache> (); | |
| empty_item->token = to_wait; | |
| std::lock_guard <std::mutex> olk (thread_lock_); | |
| to_remove_.push (std::move (empty_item)); | |
| thread_cond_.notify_one (); | |
| } | |
| std::unique_lock <std::mutex> olk (token_lock_); | |
| while (token_synced_ < to_wait) | |
| token_cond_.wait (olk); | |
| } | |
| GstD3D12Device * Create (const GstD3D12DeviceConstructData * data) | |
| { | |
| std::lock_guard <std::mutex> lk (lock_); | |
| auto it = std::find_if (list_.begin (), list_.end (), | |
| [&] (const auto & cache) { | |
| const auto priv = cache->priv; | |
| if (data->type == GST_D3D12_DEVICE_CONSTRUCT_FOR_INDEX) | |
| return priv->adapter_index == data->data.index; | |
| return priv->adapter_luid == data->data.luid; | |
| }); | |
| if (it != list_.end ()) | |
| return (GstD3D12Device *) gst_object_ref ((*it)->device); | |
| auto device = gst_d3d12_device_new_internal (data); | |
| if (!device) | |
| return nullptr; | |
| gst_object_ref_sink (device); | |
| auto item = std::make_shared <DeviceCache> (); | |
| item->device = device; | |
| item->priv = device->priv; | |
| g_object_weak_ref (G_OBJECT (device), DeviceCacheManager::NotifyCb, this); | |
| list_.push_back (item); | |
| return device; | |
| } | |
| static void NotifyCb (gpointer data, GObject * device) | |
| { | |
| auto self = (DeviceCacheManager *) data; | |
| self->remove ((GstD3D12Device *) device); | |
| } | |
| private: | |
| DeviceCacheManager () {} | |
| ~DeviceCacheManager () {} | |
| void threadFunc () | |
| { | |
| while (true) { | |
| std::unique_lock <std::mutex> lk (thread_lock_); | |
| while (to_remove_.empty ()) | |
| thread_cond_.wait (lk); | |
| while (!to_remove_.empty ()) { | |
| guint64 token; | |
| { | |
| auto item = to_remove_.front (); | |
| to_remove_.pop (); | |
| token = item->token; | |
| } | |
| std::lock_guard <std::mutex> olk (token_lock_); | |
| token_synced_ = token; | |
| token_cond_.notify_all (); | |
| } | |
| } | |
| } | |
| void remove (GstD3D12Device * device) | |
| { | |
| std::lock_guard <std::mutex> lk (lock_); | |
| auto it = std::find_if (list_.begin (), list_.end (), | |
| [&] (const auto & cache) { | |
| return cache->device == device; | |
| }); | |
| std::shared_ptr<DeviceCache> cached; | |
| if (it != list_.end ()) { | |
| cached = *it; | |
| list_.erase (it); | |
| } else { | |
| GST_WARNING ("Couldn't find device from cache"); | |
| } | |
| if (cached && thread_) { | |
| std::lock_guard <std::mutex> tlk (thread_lock_); | |
| token_++; | |
| cached->token = token_; | |
| to_remove_.push (std::move (cached)); | |
| thread_cond_.notify_one (); | |
| } | |
| } | |
| private: | |
| std::mutex lock_; | |
| std::vector<std::shared_ptr<DeviceCache>> list_; | |
| std::mutex thread_lock_; | |
| std::condition_variable thread_cond_; | |
| std::thread *thread_ = nullptr; | |
| std::queue<std::shared_ptr<DeviceCache>> to_remove_; | |
| std::mutex token_lock_; | |
| std::condition_variable token_cond_; | |
| guint64 token_ = 0; | |
| guint64 token_synced_ = 0; | |
| }; | |
| /* *INDENT-ON* */ | |
| void | |
| gst_d3d12_init_background_thread (void) | |
| { | |
| auto manager = DeviceCacheManager::GetInstance (); | |
| manager->InitThread (); | |
| } | |
| void | |
| gst_d3d12_sync_background_thread (void) | |
| { | |
| auto manager = DeviceCacheManager::GetInstance (); | |
| manager->Sync (); | |
| } | |
| static gboolean | |
| gst_d3d12_device_enable_debug (void) | |
| { | |
| static gboolean enabled = FALSE; | |
| GST_D3D12_CALL_ONCE_BEGIN { | |
| GST_DEBUG_CATEGORY_INIT (gst_d3d12_sdk_debug, | |
| "d3d12debuglayer", 0, "d3d12 SDK layer debug"); | |
| /* Enables debug layer only if it's requested, otherwise | |
| * already configured d3d12 devices (e.g., owned by application) | |
| * will be invalidated by ID3D12Debug::EnableDebugLayer() */ | |
| if (!g_getenv ("GST_ENABLE_D3D12_DEBUG")) | |
| return; | |
| HRESULT hr; | |
| ComPtr < ID3D12Debug > d3d12_debug; | |
| hr = D3D12GetDebugInterface (IID_PPV_ARGS (&d3d12_debug)); | |
| if (FAILED (hr)) | |
| return; | |
| d3d12_debug->EnableDebugLayer (); | |
| enabled = TRUE; | |
| GST_INFO ("D3D12 debug layer is enabled"); | |
| ComPtr < ID3D12Debug5 > d3d12_debug5; | |
| hr = d3d12_debug.As (&d3d12_debug5); | |
| if (SUCCEEDED (hr)) | |
| d3d12_debug5->SetEnableAutoName (TRUE); | |
| ComPtr < ID3D12Debug1 > d3d12_debug1; | |
| hr = d3d12_debug.As (&d3d12_debug1); | |
| if (FAILED (hr)) | |
| return; | |
| d3d12_debug1->SetEnableSynchronizedCommandQueueValidation (TRUE); | |
| GST_INFO ("Enabled synchronized command queue validation"); | |
| if (!g_getenv ("GST_ENABLE_D3D12_DEBUG_GPU_VALIDATION")) | |
| return; | |
| d3d12_debug1->SetEnableGPUBasedValidation (TRUE); | |
| GST_INFO ("Enabled GPU based validation"); | |
| } | |
| GST_D3D12_CALL_ONCE_END; | |
| return enabled; | |
| } | |
| #define gst_d3d12_device_parent_class parent_class | |
| G_DEFINE_TYPE (GstD3D12Device, gst_d3d12_device, GST_TYPE_OBJECT); | |
| static void gst_d3d12_device_finalize (GObject * object); | |
| static void gst_d3d12_device_get_property (GObject * object, guint prop_id, | |
| GValue * value, GParamSpec * pspec); | |
| static void gst_d3d12_device_setup_format_table (GstD3D12Device * self); | |
| static void | |
| gst_d3d12_device_class_init (GstD3D12DeviceClass * klass) | |
| { | |
| GObjectClass *gobject_class = G_OBJECT_CLASS (klass); | |
| GParamFlags readable_flags = | |
| (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); | |
| gobject_class->finalize = gst_d3d12_device_finalize; | |
| gobject_class->get_property = gst_d3d12_device_get_property; | |
| g_object_class_install_property (gobject_class, PROP_ADAPTER_INDEX, | |
| g_param_spec_uint ("adapter-index", "Adapter Index", | |
| "DXGI Adapter index for creating device", | |
| 0, G_MAXUINT32, 0, 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", | |
| 0, G_MAXINT64, 0, 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_DESCRIPTION, | |
| g_param_spec_string ("description", "Description", | |
| "Human readable device description", nullptr, readable_flags)); | |
| } | |
| static void | |
| gst_d3d12_device_init (GstD3D12Device * self) | |
| { | |
| self->priv = new GstD3D12DevicePrivate (); | |
| } | |
| static void | |
| gst_d3d12_device_finalize (GObject * object) | |
| { | |
| auto self = GST_D3D12_DEVICE (object); | |
| GST_DEBUG_OBJECT (self, "Finalize"); | |
| /* Don't delete private struct. DeviceCacheManager will destroy it */ | |
| G_OBJECT_CLASS (parent_class)->finalize (object); | |
| } | |
| static void | |
| gst_d3d12_device_get_property (GObject * object, guint prop_id, | |
| GValue * value, GParamSpec * pspec) | |
| { | |
| GstD3D12Device *self = GST_D3D12_DEVICE (object); | |
| GstD3D12DevicePrivate *priv = self->priv; | |
| switch (prop_id) { | |
| case PROP_ADAPTER_INDEX: | |
| g_value_set_uint (value, priv->adapter_index); | |
| break; | |
| case PROP_ADAPTER_LUID: | |
| g_value_set_int64 (value, priv->adapter_luid); | |
| 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_DESCRIPTION: | |
| g_value_set_string (value, priv->description.c_str ()); | |
| break; | |
| default: | |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); | |
| break; | |
| } | |
| } | |
| static gboolean | |
| check_format_support (GstD3D12Device * self, DXGI_FORMAT format, | |
| guint flags, D3D12_FEATURE_DATA_FORMAT_SUPPORT * support) | |
| { | |
| ID3D12Device *device = self->priv->device.Get (); | |
| HRESULT hr; | |
| support->Format = format; | |
| hr = device->CheckFeatureSupport (D3D12_FEATURE_FORMAT_SUPPORT, support, | |
| sizeof (D3D12_FEATURE_DATA_FORMAT_SUPPORT)); | |
| if (FAILED (hr)) { | |
| GST_INFO_OBJECT (self, | |
| "Failed to check feature support for DXGI format %d", format); | |
| return FALSE; | |
| } | |
| if (((guint) support->Support1 & flags) != flags) { | |
| GST_INFO_OBJECT (self, | |
| "DXGI format %d supports1 flag 0x%x, required 0x%x", format, | |
| support->Support1, flags); | |
| return FALSE; | |
| } | |
| return TRUE; | |
| } | |
| static void | |
| gst_d3d12_device_setup_format_table (GstD3D12Device * self) | |
| { | |
| auto priv = self->priv; | |
| for (guint i = 0; i < GST_D3D12_N_FORMATS; i++) { | |
| const auto iter = &g_gst_d3d12_default_format_map[i]; | |
| D3D12_FEATURE_DATA_FORMAT_SUPPORT support[GST_VIDEO_MAX_PLANES]; | |
| 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: | |
| if (!check_format_support (self, iter->dxgi_format, | |
| iter->format_support1[0], &support[0])) { | |
| 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: | |
| { | |
| if (!check_format_support (self, iter->dxgi_format, | |
| iter->format_support1[0], &support[0])) { | |
| bool supported = true; | |
| for (guint j = 0; j < GST_VIDEO_MAX_PLANES; j++) { | |
| if (iter->resource_format[j] == DXGI_FORMAT_UNKNOWN) | |
| break; | |
| if (!check_format_support (self, iter->resource_format[j], | |
| iter->format_support1[0], &support[j])) { | |
| supported = false; | |
| break; | |
| } | |
| } | |
| if (!supported) | |
| continue; | |
| native = false; | |
| } | |
| 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: | |
| { | |
| bool supported = true; | |
| native = false; | |
| for (guint j = 0; j < GST_VIDEO_MAX_PLANES; j++) { | |
| if (iter->resource_format[j] == DXGI_FORMAT_UNKNOWN) | |
| break; | |
| if (!check_format_support (self, iter->resource_format[j], | |
| iter->format_support1[0], &support[j])) { | |
| supported = false; | |
| break; | |
| } | |
| } | |
| if (!supported) | |
| continue; | |
| break; | |
| } | |
| default: | |
| g_assert_not_reached (); | |
| return; | |
| } | |
| auto format = *iter; | |
| if (!native) | |
| format.dxgi_format = DXGI_FORMAT_UNKNOWN; | |
| for (guint j = 0; j < GST_VIDEO_MAX_PLANES; j++) { | |
| format.format_support1[j] = support[j].Support1; | |
| format.format_support2[j] = support[j].Support2; | |
| } | |
| priv->format_table[format.format] = format; | |
| } | |
| } | |
| static HRESULT | |
| gst_d3d12_device_find_adapter (const GstD3D12DeviceConstructData * data, | |
| IDXGIFactory2 * factory, guint * index, IDXGIAdapter1 ** rst) | |
| { | |
| HRESULT hr; | |
| switch (data->type) { | |
| case GST_D3D12_DEVICE_CONSTRUCT_FOR_INDEX:{ | |
| ComPtr < IDXGIAdapter1 > adapter; | |
| hr = factory->EnumAdapters1 (data->data.index, &adapter); | |
| if (FAILED (hr)) | |
| return hr; | |
| *index = data->data.index; | |
| *rst = adapter.Detach (); | |
| return S_OK; | |
| } | |
| case GST_D3D12_DEVICE_CONSTRUCT_FOR_LUID: | |
| for (UINT i = 0;; i++) { | |
| ComPtr < IDXGIAdapter1 > adapter; | |
| DXGI_ADAPTER_DESC1 desc; | |
| hr = factory->EnumAdapters1 (i, &adapter); | |
| if (FAILED (hr)) | |
| return hr; | |
| hr = adapter->GetDesc1 (&desc); | |
| if (FAILED (hr)) | |
| return hr; | |
| if (gst_d3d12_luid_to_int64 (&desc.AdapterLuid) != data->data.luid) { | |
| continue; | |
| } | |
| *index = i; | |
| *rst = adapter.Detach (); | |
| return S_OK; | |
| } | |
| default: | |
| g_assert_not_reached (); | |
| break; | |
| } | |
| return E_FAIL; | |
| } | |
| static GstD3D12Device * | |
| gst_d3d12_device_new_internal (const GstD3D12DeviceConstructData * data) | |
| { | |
| ComPtr < IDXGIFactory2 > factory; | |
| ComPtr < IDXGIAdapter1 > adapter; | |
| ComPtr < ID3D12Device > device; | |
| HRESULT hr; | |
| UINT factory_flags = 0; | |
| guint index = 0; | |
| GST_DEBUG_CATEGORY_INIT (gst_d3d12_device_debug, | |
| "d3d12device", 0, "d3d12 device object"); | |
| gst_d3d12_device_enable_debug (); | |
| hr = CreateDXGIFactory2 (factory_flags, IID_PPV_ARGS (&factory)); | |
| if (FAILED (hr)) { | |
| GST_WARNING ("Could create dxgi factory, hr: 0x%x", (guint) hr); | |
| return nullptr; | |
| } | |
| hr = gst_d3d12_device_find_adapter (data, factory.Get (), &index, &adapter); | |
| if (FAILED (hr)) { | |
| GST_WARNING ("Could not find adapter, hr: 0x%x", (guint) hr); | |
| return nullptr; | |
| } | |
| DXGI_ADAPTER_DESC1 desc; | |
| hr = adapter->GetDesc1 (&desc); | |
| if (FAILED (hr)) { | |
| GST_WARNING ("Could not get adapter desc, hr: 0x%x", (guint) hr); | |
| return nullptr; | |
| } | |
| hr = D3D12CreateDevice (adapter.Get (), D3D_FEATURE_LEVEL_11_0, | |
| IID_PPV_ARGS (&device)); | |
| if (FAILED (hr)) { | |
| GST_WARNING ("Could not create device, hr: 0x%x", (guint) hr); | |
| return nullptr; | |
| } | |
| GstD3D12Device *self = (GstD3D12Device *) | |
| g_object_new (GST_TYPE_D3D12_DEVICE, nullptr); | |
| GstD3D12DevicePrivate *priv = self->priv; | |
| priv->factory = factory; | |
| priv->adapter = adapter; | |
| priv->device = device; | |
| priv->adapter_luid = gst_d3d12_luid_to_int64 (&desc.AdapterLuid); | |
| priv->vendor_id = desc.VendorId; | |
| priv->device_id = desc.DeviceId; | |
| priv->adapter_index = index; | |
| std::wstring_convert < std::codecvt_utf8 < wchar_t >, wchar_t >converter; | |
| priv->description = converter.to_bytes (desc.Description); | |
| GST_INFO_OBJECT (self, | |
| "adapter index %d: D3D12 device vendor-id: 0x%04x, device-id: 0x%04x, " | |
| "Flags: 0x%x, adapter-luid: %" G_GINT64_FORMAT ", %s", | |
| priv->adapter_index, desc.VendorId, desc.DeviceId, desc.Flags, | |
| priv->adapter_luid, priv->description.c_str ()); | |
| gst_d3d12_device_setup_format_table (self); | |
| if (gst_d3d12_device_enable_debug ()) { | |
| ComPtr < ID3D12InfoQueue > info_queue; | |
| device.As (&info_queue); | |
| priv->info_queue = info_queue; | |
| } | |
| D3D12_COMMAND_QUEUE_DESC queue_desc = { }; | |
| queue_desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; | |
| queue_desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; | |
| priv->direct_queue = gst_d3d12_command_queue_new (self, &queue_desc, 0); | |
| if (!priv->direct_queue) | |
| goto error; | |
| priv->direct_cl_pool = gst_d3d12_command_list_pool_new (self, | |
| D3D12_COMMAND_LIST_TYPE_DIRECT); | |
| if (!priv->direct_cl_pool) | |
| goto error; | |
| priv->direct_ca_pool = gst_d3d12_command_allocator_pool_new (self, | |
| D3D12_COMMAND_LIST_TYPE_DIRECT); | |
| if (!priv->direct_ca_pool) | |
| goto error; | |
| queue_desc.Type = D3D12_COMMAND_LIST_TYPE_COPY; | |
| priv->copy_queue = gst_d3d12_command_queue_new (self, &queue_desc, 0); | |
| if (!priv->copy_queue) | |
| goto error; | |
| priv->copy_cl_pool = gst_d3d12_command_list_pool_new (self, | |
| D3D12_COMMAND_LIST_TYPE_COPY); | |
| if (!priv->copy_cl_pool) | |
| goto error; | |
| priv->copy_ca_pool = gst_d3d12_command_allocator_pool_new (self, | |
| D3D12_COMMAND_LIST_TYPE_COPY); | |
| if (!priv->copy_ca_pool) | |
| goto error; | |
| priv->rtv_inc_size = | |
| device->GetDescriptorHandleIncrementSize (D3D12_DESCRIPTOR_HEAP_TYPE_RTV); | |
| return self; | |
| error: | |
| gst_object_unref (self); | |
| return nullptr; | |
| } | |
| GstD3D12Device * | |
| gst_d3d12_device_new (guint adapter_index) | |
| { | |
| auto manager = DeviceCacheManager::GetInstance (); | |
| GstD3D12DeviceConstructData data; | |
| data.data.index = adapter_index; | |
| data.type = GST_D3D12_DEVICE_CONSTRUCT_FOR_INDEX; | |
| return manager->Create (&data); | |
| } | |
| GstD3D12Device * | |
| gst_d3d12_device_new_for_adapter_luid (gint64 adapter_luid) | |
| { | |
| auto manager = DeviceCacheManager::GetInstance (); | |
| GstD3D12DeviceConstructData data; | |
| data.data.luid = adapter_luid; | |
| data.type = GST_D3D12_DEVICE_CONSTRUCT_FOR_LUID; | |
| return manager->Create (&data); | |
| } | |
| ID3D12Device * | |
| gst_d3d12_device_get_device_handle (GstD3D12Device * device) | |
| { | |
| g_return_val_if_fail (GST_IS_D3D12_DEVICE (device), nullptr); | |
| return device->priv->device.Get (); | |
| } | |
| IDXGIAdapter1 * | |
| gst_d3d12_device_get_adapter_handle (GstD3D12Device * device) | |
| { | |
| g_return_val_if_fail (GST_IS_D3D12_DEVICE (device), nullptr); | |
| return device->priv->adapter.Get (); | |
| } | |
| IDXGIFactory2 * | |
| gst_d3d12_device_get_factory_handle (GstD3D12Device * device) | |
| { | |
| g_return_val_if_fail (GST_IS_D3D12_DEVICE (device), nullptr); | |
| return device->priv->factory.Get (); | |
| } | |
| gboolean | |
| gst_d3d12_device_get_d3d11on12_device (GstD3D12Device * device, | |
| IUnknown ** d3d11on12) | |
| { | |
| g_return_val_if_fail (GST_IS_D3D12_DEVICE (device), FALSE); | |
| g_return_val_if_fail (d3d11on12, FALSE); | |
| auto priv = device->priv; | |
| std::lock_guard < std::mutex > lk (priv->lock); | |
| if (!priv->d3d11on12) { | |
| ComPtr < ID3D12CommandQueue > cq; | |
| gst_d3d12_command_queue_get_handle (priv->direct_queue, &cq); | |
| auto hr = GstD3D11On12CreateDevice (priv->device.Get (), cq.Get (), | |
| &priv->d3d11on12); | |
| if (!gst_d3d12_result (hr, device)) { | |
| GST_ERROR_OBJECT (device, "Couldn't create d3d11on12 device"); | |
| return FALSE; | |
| } | |
| } | |
| *d3d11on12 = priv->d3d11on12.Get (); | |
| (*d3d11on12)->AddRef (); | |
| return TRUE; | |
| } | |
| void | |
| gst_d3d12_device_lock (GstD3D12Device * device) | |
| { | |
| g_return_if_fail (GST_IS_D3D12_DEVICE (device)); | |
| auto priv = device->priv; | |
| priv->extern_lock.lock (); | |
| } | |
| void | |
| gst_d3d12_device_unlock (GstD3D12Device * device) | |
| { | |
| g_return_if_fail (GST_IS_D3D12_DEVICE (device)); | |
| auto priv = device->priv; | |
| priv->extern_lock.unlock (); | |
| } | |
| gboolean | |
| gst_d3d12_device_get_format (GstD3D12Device * device, | |
| GstVideoFormat format, GstD3D12Format * device_format) | |
| { | |
| g_return_val_if_fail (GST_IS_D3D12_DEVICE (device), FALSE); | |
| g_return_val_if_fail (device_format != nullptr, FALSE); | |
| auto priv = device->priv; | |
| const auto & target = priv->format_table.find (format); | |
| if (target == priv->format_table.end ()) | |
| return FALSE; | |
| if (device_format) | |
| *device_format = target->second; | |
| return TRUE; | |
| } | |
| GstD3D12CommandQueue * | |
| gst_d3d12_device_get_command_queue (GstD3D12Device * device, | |
| D3D12_COMMAND_LIST_TYPE queue_type) | |
| { | |
| g_return_val_if_fail (GST_IS_D3D12_DEVICE (device), nullptr); | |
| auto priv = device->priv; | |
| switch (queue_type) { | |
| case D3D12_COMMAND_LIST_TYPE_DIRECT: | |
| return priv->direct_queue; | |
| case D3D12_COMMAND_LIST_TYPE_COPY: | |
| return priv->copy_queue; | |
| default: | |
| break; | |
| } | |
| GST_ERROR_OBJECT (device, "Not supported queue type %d", queue_type); | |
| return nullptr; | |
| } | |
| gboolean | |
| gst_d3d12_device_execute_command_lists (GstD3D12Device * device, | |
| D3D12_COMMAND_LIST_TYPE queue_type, guint num_command_lists, | |
| ID3D12CommandList ** command_lists, guint64 * fence_value) | |
| { | |
| g_return_val_if_fail (GST_IS_D3D12_DEVICE (device), FALSE); | |
| auto priv = device->priv; | |
| GstD3D12CommandQueue *queue; | |
| switch (queue_type) { | |
| case D3D12_COMMAND_LIST_TYPE_DIRECT: | |
| queue = priv->direct_queue; | |
| break; | |
| case D3D12_COMMAND_LIST_TYPE_COPY: | |
| queue = priv->copy_queue; | |
| break; | |
| default: | |
| GST_ERROR_OBJECT (device, "Not supported queue type %d", queue_type); | |
| return FALSE; | |
| } | |
| auto hr = gst_d3d12_command_queue_execute_command_lists (queue, | |
| num_command_lists, command_lists, fence_value); | |
| return gst_d3d12_result (hr, device); | |
| } | |
| guint64 | |
| gst_d3d12_device_get_completed_value (GstD3D12Device * device, | |
| D3D12_COMMAND_LIST_TYPE queue_type) | |
| { | |
| g_return_val_if_fail (GST_IS_D3D12_DEVICE (device), G_MAXUINT64); | |
| auto priv = device->priv; | |
| GstD3D12CommandQueue *queue; | |
| switch (queue_type) { | |
| case D3D12_COMMAND_LIST_TYPE_DIRECT: | |
| queue = priv->direct_queue; | |
| break; | |
| case D3D12_COMMAND_LIST_TYPE_COPY: | |
| queue = priv->copy_queue; | |
| break; | |
| default: | |
| GST_ERROR_OBJECT (device, "Not supported queue type %d", queue_type); | |
| return G_MAXUINT64; | |
| } | |
| return gst_d3d12_command_queue_get_completed_value (queue); | |
| } | |
| gboolean | |
| gst_d3d12_device_set_fence_notify (GstD3D12Device * device, | |
| D3D12_COMMAND_LIST_TYPE queue_type, guint64 fence_value, | |
| GstD3D12FenceData * fence_data) | |
| { | |
| g_return_val_if_fail (GST_IS_D3D12_DEVICE (device), FALSE); | |
| g_return_val_if_fail (fence_data, FALSE); | |
| auto priv = device->priv; | |
| GstD3D12CommandQueue *queue; | |
| switch (queue_type) { | |
| case D3D12_COMMAND_LIST_TYPE_DIRECT: | |
| queue = priv->direct_queue; | |
| break; | |
| case D3D12_COMMAND_LIST_TYPE_COPY: | |
| queue = priv->copy_queue; | |
| break; | |
| default: | |
| GST_ERROR_OBJECT (device, "Not supported queue type %d", queue_type); | |
| return FALSE; | |
| } | |
| gst_d3d12_command_queue_set_notify (queue, fence_value, fence_data, | |
| (GDestroyNotify) gst_d3d12_fence_data_unref); | |
| return TRUE; | |
| } | |
| gboolean | |
| gst_d3d12_device_fence_wait (GstD3D12Device * device, | |
| D3D12_COMMAND_LIST_TYPE queue_type, guint64 fence_value, | |
| HANDLE event_handle) | |
| { | |
| g_return_val_if_fail (GST_IS_D3D12_DEVICE (device), FALSE); | |
| auto priv = device->priv; | |
| GstD3D12CommandQueue *queue; | |
| switch (queue_type) { | |
| case D3D12_COMMAND_LIST_TYPE_DIRECT: | |
| queue = priv->direct_queue; | |
| break; | |
| case D3D12_COMMAND_LIST_TYPE_COPY: | |
| queue = priv->copy_queue; | |
| break; | |
| default: | |
| GST_ERROR_OBJECT (device, "Not supported queue type %d", queue_type); | |
| return FALSE; | |
| } | |
| auto hr = gst_d3d12_command_queue_fence_wait (queue, | |
| fence_value, event_handle); | |
| return gst_d3d12_result (hr, device); | |
| } | |
| gboolean | |
| gst_d3d12_device_copy_texture_region (GstD3D12Device * device, | |
| guint num_args, const GstD3D12CopyTextureRegionArgs * args, | |
| D3D12_COMMAND_LIST_TYPE command_type, guint64 * fence_value) | |
| { | |
| g_return_val_if_fail (GST_IS_D3D12_DEVICE (device), FALSE); | |
| g_return_val_if_fail (num_args > 0, FALSE); | |
| g_return_val_if_fail (args, FALSE); | |
| HRESULT hr; | |
| auto priv = device->priv; | |
| GstD3D12CommandAllocatorPool *ca_pool; | |
| GstD3D12CommandAllocator *gst_ca = nullptr; | |
| GstD3D12CommandListPool *cl_pool; | |
| GstD3D12CommandList *gst_cl = nullptr; | |
| GstD3D12CommandQueue *queue = nullptr; | |
| guint64 fence_val = 0; | |
| switch (command_type) { | |
| case D3D12_COMMAND_LIST_TYPE_DIRECT: | |
| queue = priv->direct_queue; | |
| ca_pool = priv->direct_ca_pool; | |
| cl_pool = priv->direct_cl_pool; | |
| break; | |
| case D3D12_COMMAND_LIST_TYPE_COPY: | |
| queue = priv->copy_queue; | |
| ca_pool = priv->copy_ca_pool; | |
| cl_pool = priv->copy_cl_pool; | |
| break; | |
| default: | |
| GST_ERROR_OBJECT (device, "Not supported command list type %d", | |
| command_type); | |
| return FALSE; | |
| } | |
| gst_d3d12_command_allocator_pool_acquire (ca_pool, &gst_ca); | |
| if (!gst_ca) { | |
| GST_ERROR_OBJECT (device, "Couldn't acquire command allocator"); | |
| return FALSE; | |
| } | |
| ComPtr < ID3D12CommandAllocator > ca; | |
| gst_d3d12_command_allocator_get_handle (gst_ca, &ca); | |
| gst_d3d12_command_list_pool_acquire (cl_pool, ca.Get (), &gst_cl); | |
| if (!gst_cl) { | |
| GST_ERROR_OBJECT (device, "Couldn't acquire command list"); | |
| gst_clear_d3d12_command_allocator (&gst_ca); | |
| return FALSE; | |
| } | |
| ComPtr < ID3D12CommandList > cl_base; | |
| ComPtr < ID3D12GraphicsCommandList > cl; | |
| gst_d3d12_command_list_get_handle (gst_cl, &cl_base); | |
| cl_base.As (&cl); | |
| for (guint i = 0; i < num_args; i++) { | |
| const auto arg = args[i]; | |
| cl->CopyTextureRegion (&arg.dst, arg.dst_x, arg.dst_y, arg.dst_z, | |
| &arg.src, arg.src_box); | |
| } | |
| hr = cl->Close (); | |
| if (!gst_d3d12_result (hr, device)) { | |
| GST_ERROR_OBJECT (device, "Couldn't close command list"); | |
| gst_clear_d3d12_command_list (&gst_cl); | |
| gst_clear_d3d12_command_allocator (&gst_ca); | |
| return FALSE; | |
| } | |
| ID3D12CommandList *cmd_list[] = { cl.Get () }; | |
| hr = gst_d3d12_command_queue_execute_command_lists (queue, | |
| 1, cmd_list, &fence_val); | |
| auto ret = gst_d3d12_result (hr, device); | |
| /* We can release command list since command list pool will hold it */ | |
| gst_d3d12_command_list_unref (gst_cl); | |
| if (ret) { | |
| gst_d3d12_command_queue_set_notify (queue, fence_val, gst_ca, | |
| (GDestroyNotify) gst_d3d12_command_allocator_unref); | |
| } else { | |
| gst_d3d12_command_allocator_unref (gst_ca); | |
| } | |
| if (fence_value) | |
| *fence_value = fence_val; | |
| return ret; | |
| } | |
| static inline GstDebugLevel | |
| d3d12_message_severity_to_gst (D3D12_MESSAGE_SEVERITY level) | |
| { | |
| switch (level) { | |
| case D3D12_MESSAGE_SEVERITY_CORRUPTION: | |
| case D3D12_MESSAGE_SEVERITY_ERROR: | |
| return GST_LEVEL_ERROR; | |
| case D3D12_MESSAGE_SEVERITY_WARNING: | |
| return GST_LEVEL_WARNING; | |
| case D3D12_MESSAGE_SEVERITY_INFO: | |
| return GST_LEVEL_INFO; | |
| case D3D12_MESSAGE_SEVERITY_MESSAGE: | |
| return GST_LEVEL_DEBUG; | |
| default: | |
| break; | |
| } | |
| return GST_LEVEL_LOG; | |
| } | |
| void | |
| gst_d3d12_device_d3d12_debug (GstD3D12Device * device, const gchar * file, | |
| const gchar * function, gint line) | |
| { | |
| g_return_if_fail (GST_IS_D3D12_DEVICE (device)); | |
| if (!device->priv->info_queue) | |
| return; | |
| GstD3D12DevicePrivate *priv = device->priv; | |
| std::lock_guard < std::recursive_mutex > lk (priv->extern_lock); | |
| ID3D12InfoQueue *info_queue = priv->info_queue.Get (); | |
| UINT64 num_msg = info_queue->GetNumStoredMessages (); | |
| for (guint64 i = 0; i < num_msg; i++) { | |
| HRESULT hr; | |
| SIZE_T msg_len; | |
| D3D12_MESSAGE *msg; | |
| GstDebugLevel msg_level; | |
| GstDebugLevel selected_level; | |
| hr = info_queue->GetMessage (i, nullptr, &msg_len); | |
| if (FAILED (hr) || msg_len == 0) | |
| continue; | |
| msg = (D3D12_MESSAGE *) g_malloc0 (msg_len); | |
| hr = info_queue->GetMessage (i, msg, &msg_len); | |
| if (FAILED (hr) || msg_len == 0) { | |
| g_free (msg); | |
| continue; | |
| } | |
| msg_level = d3d12_message_severity_to_gst (msg->Severity); | |
| if (msg->Category == D3D12_MESSAGE_CATEGORY_STATE_CREATION && | |
| msg_level > GST_LEVEL_ERROR) { | |
| /* Do not warn for live object, since there would be live object | |
| * when ReportLiveDeviceObjects was called */ | |
| selected_level = GST_LEVEL_INFO; | |
| } else { | |
| selected_level = msg_level; | |
| } | |
| gst_debug_log (gst_d3d12_sdk_debug, selected_level, file, function, line, | |
| G_OBJECT (device), "D3D12InfoQueue: %s", msg->pDescription); | |
| g_free (msg); | |
| } | |
| info_queue->ClearStoredMessages (); | |
| } | |
| void | |
| gst_d3d12_device_clear_yuv_texture (GstD3D12Device * device, GstMemory * mem) | |
| { | |
| auto priv = device->priv; | |
| auto dmem = GST_D3D12_MEMORY_CAST (mem); | |
| ComPtr < ID3D12DescriptorHeap > heap; | |
| auto resource = gst_d3d12_memory_get_resource_handle (dmem); | |
| auto desc = resource->GetDesc (); | |
| if (desc.Format != DXGI_FORMAT_NV12 && desc.Format != DXGI_FORMAT_P010 && | |
| desc.Format != DXGI_FORMAT_P016) { | |
| return; | |
| } | |
| gst_d3d12_memory_get_render_target_view_heap (dmem, &heap); | |
| if (!heap) | |
| return; | |
| D3D12_RECT rect = { }; | |
| if (!gst_d3d12_memory_get_plane_rectangle (dmem, 1, &rect)) | |
| return; | |
| GstD3D12CommandAllocator *gst_ca = nullptr; | |
| gst_d3d12_command_allocator_pool_acquire (priv->direct_ca_pool, &gst_ca); | |
| if (!gst_ca) | |
| return; | |
| ComPtr < ID3D12CommandAllocator > ca; | |
| gst_d3d12_command_allocator_get_handle (gst_ca, &ca); | |
| GstD3D12CommandList *gst_cl = nullptr; | |
| gst_d3d12_command_list_pool_acquire (priv->direct_cl_pool, | |
| ca.Get (), &gst_cl); | |
| if (!gst_cl) { | |
| gst_d3d12_command_allocator_unref (gst_ca); | |
| return; | |
| } | |
| ComPtr < ID3D12CommandList > cl_base; | |
| ComPtr < ID3D12GraphicsCommandList > cl; | |
| gst_d3d12_command_list_get_handle (gst_cl, &cl_base); | |
| cl_base.As (&cl); | |
| auto rtv_handle = | |
| CD3DX12_CPU_DESCRIPTOR_HANDLE (heap->GetCPUDescriptorHandleForHeapStart | |
| (), | |
| priv->rtv_inc_size); | |
| const FLOAT clear_color[4] = { 0.5f, 0.5f, 0.5f, 1.0f }; | |
| cl->ClearRenderTargetView (rtv_handle, clear_color, 1, &rect); | |
| auto hr = cl->Close (); | |
| if (!gst_d3d12_result (hr, device)) { | |
| gst_clear_d3d12_command_list (&gst_cl); | |
| gst_clear_d3d12_command_allocator (&gst_ca); | |
| return; | |
| } | |
| ID3D12CommandList *cmd_list[] = { cl.Get () }; | |
| guint64 fence_val = 0; | |
| hr = gst_d3d12_command_queue_execute_command_lists (priv->direct_queue, | |
| 1, cmd_list, &fence_val); | |
| auto ret = gst_d3d12_result (hr, device); | |
| gst_d3d12_command_list_unref (gst_cl); | |
| if (ret) { | |
| gst_d3d12_command_queue_set_notify (priv->direct_queue, fence_val, | |
| gst_ca, (GDestroyNotify) gst_d3d12_command_allocator_unref); | |
| dmem->fence_value = fence_val; | |
| } else { | |
| gst_d3d12_command_allocator_unref (gst_ca); | |
| } | |
| } |