diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/d3d12/gstd3d12device-private.h b/subprojects/gst-plugins-bad/gst-libs/gst/d3d12/gstd3d12device-private.h index 5257446c83..8b021aa2f1 100644 --- a/subprojects/gst-plugins-bad/gst-libs/gst/d3d12/gstd3d12device-private.h +++ b/subprojects/gst-plugins-bad/gst-libs/gst/d3d12/gstd3d12device-private.h @@ -61,5 +61,8 @@ void gst_d3d12_device_11on12_lock (GstD3D12Device * device); GST_D3D12_API void gst_d3d12_device_11on12_unlock (GstD3D12Device * device); +GST_D3D12_API +void gst_d3d12_device_check_device_removed (GstD3D12Device * device); + G_END_DECLS diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/d3d12/gstd3d12device.cpp b/subprojects/gst-plugins-bad/gst-libs/gst/d3d12/gstd3d12device.cpp index e7bfdba731..e1fccb935f 100644 --- a/subprojects/gst-plugins-bad/gst-libs/gst/d3d12/gstd3d12device.cpp +++ b/subprojects/gst-plugins-bad/gst-libs/gst/d3d12/gstd3d12device.cpp @@ -41,6 +41,7 @@ #include #include #include +#include GST_DEBUG_CATEGORY_STATIC (gst_d3d12_sdk_debug); @@ -91,13 +92,21 @@ enum PROP_VENDOR_ID, PROP_HARDWARE, PROP_DESCRIPTION, + PROP_DEVICE_REMOVED_REASON, }; +static GParamSpec *pspec_removed_reason = nullptr; + /* *INDENT-OFF* */ using namespace Microsoft::WRL; struct DeviceInner { + DeviceInner () + { + dev_removed_event = CreateEventEx (nullptr, nullptr, 0, EVENT_ALL_ACCESS); + } + ~DeviceInner () { Drain (); @@ -114,7 +123,13 @@ struct DeviceInner factory = nullptr; adapter = nullptr; - ReportLiveObjects (); + if (removed_reason == S_OK) + ReportLiveObjects (); + + if (dev_removed_monitor_handle) + UnregisterWait (dev_removed_monitor_handle); + + CloseHandle (dev_removed_event); } void Drain () @@ -169,6 +184,24 @@ struct DeviceInner info_queue->ClearStoredMessages (); } + void AddClient (GstD3D12Device * client) + { + std::lock_guard lk (lock); + clients.push_back (client); + } + + void RemoveClient (GstD3D12Device * client) + { + std::lock_guard lk (lock); + auto it = clients.begin (); + for (auto it = clients.begin (); it != clients.end(); it++) { + if (*it == client) { + clients.erase (it); + return; + } + } + } + ComPtr device; ComPtr adapter; ComPtr factory; @@ -196,6 +229,13 @@ struct DeviceInner guint vendor_id = 0; std::string description; gint64 adapter_luid = 0; + + HANDLE dev_removed_monitor_handle = nullptr; + HANDLE dev_removed_event; + ComPtr dev_removed_fence; + std::atomic removed_reason = { S_OK }; + + std::vector clients; }; typedef std::shared_ptr DeviceInnerPtr; @@ -241,7 +281,7 @@ public: GstD3D12Device * GetDevice (const GstD3D12DeviceConstructData * data) { - std::lock_guard lk (lock_); + std::lock_guard lk (lock_); auto it = std::find_if (list_.begin (), list_.end (), [&] (const auto & device) { if (data->type == GST_D3D12_DEVICE_CONSTRUCT_FOR_INDEX) @@ -261,6 +301,8 @@ public: GST_DEBUG_OBJECT (device, "Reusing created device"); + device->priv->inner->AddClient (device); + return device; } @@ -275,12 +317,14 @@ public: list_.push_back (device->priv->inner); + device->priv->inner->AddClient (device); + return device; } void ReleaseDevice (gint64 luid) { - std::lock_guard lk (lock_); + std::lock_guard lk (lock_); for (const auto & it : list_) { if (it->adapter_luid == luid) { if (it.use_count () == 1) { @@ -292,6 +336,52 @@ public: } } + void OnDeviceRemoved (gint64 luid) + { + std::lock_guard lk (lock_); + DeviceInnerPtr ptr; + + { + auto it = std::find_if (list_.begin (), list_.end (), + [&] (const auto & device) { + return device->adapter_luid == luid; + }); + + if (it == list_.end ()) + return; + + ptr = *it; + list_.erase (it); + } + + UnregisterWait (ptr->dev_removed_monitor_handle); + ptr->dev_removed_monitor_handle = nullptr; + + ptr->removed_reason = ptr->device->GetDeviceRemovedReason (); + if (SUCCEEDED (ptr->removed_reason)) + ptr->removed_reason = DXGI_ERROR_DEVICE_REMOVED; + + auto error_text = g_win32_error_message ((guint) ptr->removed_reason); + GST_ERROR ("Adapter LUID: %" G_GINT64_FORMAT + ", DeviceRemovedReason: 0x%x, %s", ptr->adapter_luid, + (guint) ptr->removed_reason, GST_STR_NULL (error_text)); + g_free (error_text); + + std::vector clients; + { + std::lock_guard client_lk (ptr->lock); + for (auto it : ptr->clients) { + gst_object_ref (it); + clients.push_back (it); + } + } + + for (auto it : clients) { + g_object_notify_by_pspec (G_OBJECT (it), pspec_removed_reason); + gst_object_unref (it); + } + } + private: DeviceCacheManager () {} ~DeviceCacheManager () {} @@ -312,12 +402,20 @@ private: } private: - std::mutex lock_; + std::recursive_mutex lock_; std::vector list_; std::unordered_map name_map_; }; /* *INDENT-ON* */ +static VOID NTAPI +on_device_removed (PVOID context, BOOLEAN unused) +{ + DeviceInner *inner = (DeviceInner *) context; + auto manager = DeviceCacheManager::GetInstance (); + manager->OnDeviceRemoved (inner->adapter_luid); +} + static gboolean gst_d3d12_device_enable_debug (void) { @@ -373,6 +471,7 @@ gst_d3d12_device_enable_debug (void) #define gst_d3d12_device_parent_class parent_class G_DEFINE_TYPE (GstD3D12Device, gst_d3d12_device, GST_TYPE_OBJECT); +static void gst_d3d12_device_dispose (GObject * 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); @@ -385,6 +484,7 @@ gst_d3d12_device_class_init (GstD3D12DeviceClass * klass) GParamFlags readable_flags = (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + gobject_class->dispose = gst_d3d12_device_dispose; gobject_class->finalize = gst_d3d12_device_finalize; gobject_class->get_property = gst_d3d12_device_get_property; @@ -409,6 +509,13 @@ gst_d3d12_device_class_init (GstD3D12DeviceClass * klass) g_object_class_install_property (gobject_class, PROP_DESCRIPTION, g_param_spec_string ("description", "Description", "Human readable device description", nullptr, readable_flags)); + + pspec_removed_reason = + g_param_spec_int ("device-removed-reason", "Device Removed Reason", + "HRESULT code returned from ID3D12Device::GetDeviceRemovedReason", + G_MININT32, G_MAXINT32, 0, readable_flags); + g_object_class_install_property (gobject_class, PROP_DEVICE_REMOVED_REASON, + pspec_removed_reason); } static void @@ -417,6 +524,19 @@ gst_d3d12_device_init (GstD3D12Device * self) self->priv = new GstD3D12DevicePrivate (); } +static void +gst_d3d12_device_dispose (GObject * object) +{ + auto self = GST_D3D12_DEVICE (object); + + GST_DEBUG_OBJECT (self, "Dispose"); + + if (self->priv->inner) + self->priv->inner->RemoveClient (self); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + static void gst_d3d12_device_finalize (GObject * object) { @@ -459,6 +579,9 @@ gst_d3d12_device_get_property (GObject * object, guint prop_id, case PROP_DESCRIPTION: g_value_set_string (value, priv->description.c_str ()); break; + case PROP_DEVICE_REMOVED_REASON: + g_value_set_int (value, priv->removed_reason); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -795,6 +918,26 @@ gst_d3d12_device_new_internal (const GstD3D12DeviceConstructData * data) GST_OBJECT_FLAG_SET (priv->copy_cl_pool, GST_OBJECT_FLAG_MAY_BE_LEAKED); GST_OBJECT_FLAG_SET (priv->copy_ca_pool, GST_OBJECT_FLAG_MAY_BE_LEAKED); + hr = device->CreateFence (0, + D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS (&priv->dev_removed_fence)); + if (FAILED (hr)) { + GST_ERROR_OBJECT (self, "Couldn't create device removed monitor fence"); + gst_object_unref (self); + return nullptr; + } + + hr = priv->dev_removed_fence->SetEventOnCompletion (G_MAXUINT64, + priv->dev_removed_event); + if (FAILED (hr)) { + GST_ERROR_OBJECT (self, "SetEventOnCompletion failed"); + gst_object_unref (self); + return nullptr; + } + + RegisterWaitForSingleObject (&priv->dev_removed_monitor_handle, + priv->dev_removed_event, on_device_removed, priv.get (), INFINITE, + WT_EXECUTEONLYONCE); + return self; error: @@ -1445,3 +1588,16 @@ gst_d3d12_device_11on12_unlock (GstD3D12Device * device) auto priv = device->priv->inner; priv->device11on12_lock.unlock (); } + +void +gst_d3d12_device_check_device_removed (GstD3D12Device * device) +{ + g_return_if_fail (GST_IS_D3D12_DEVICE (device)); + + auto priv = device->priv->inner; + auto hr = priv->device->GetDeviceRemovedReason (); + if (FAILED (hr)) { + auto manager = DeviceCacheManager::GetInstance (); + manager->OnDeviceRemoved (priv->adapter_luid); + } +} diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/d3d12/gstd3d12utils.cpp b/subprojects/gst-plugins-bad/gst-libs/gst/d3d12/gstd3d12utils.cpp index 3ad88aac4a..50663cdb64 100644 --- a/subprojects/gst-plugins-bad/gst-libs/gst/d3d12/gstd3d12utils.cpp +++ b/subprojects/gst-plugins-bad/gst-libs/gst/d3d12/gstd3d12utils.cpp @@ -661,14 +661,11 @@ _gst_d3d12_result (HRESULT hr, GstD3D12Device * device, GstDebugCategory * cat, const gchar * file, const gchar * function, gint line, GstDebugLevel level) { #ifndef GST_DISABLE_GST_DEBUG - gboolean ret = TRUE; - if (device) gst_d3d12_device_d3d12_debug (device, file, function, line); if (FAILED (hr)) { gchar *error_text = nullptr; - error_text = g_win32_error_message ((guint) hr); /* g_win32_error_message() doesn't cover all HERESULT return code, * so it could be empty string, or nullptr if there was an error @@ -677,12 +674,14 @@ _gst_d3d12_result (HRESULT hr, GstD3D12Device * device, GstDebugCategory * cat, nullptr, "D3D12 call failed: 0x%x, %s", (guint) hr, GST_STR_NULL (error_text)); g_free (error_text); - - ret = FALSE; } - - return ret; -#else - return SUCCEEDED (hr); #endif + + if (SUCCEEDED (hr)) + return TRUE; + + if (device) + gst_d3d12_device_check_device_removed (device); + + return FALSE; } diff --git a/subprojects/gst-plugins-bad/tests/check/libs/d3d12device.cpp b/subprojects/gst-plugins-bad/tests/check/libs/d3d12device.cpp new file mode 100644 index 0000000000..6b35e7bec6 --- /dev/null +++ b/subprojects/gst-plugins-bad/tests/check/libs/d3d12device.cpp @@ -0,0 +1,185 @@ +/* GStreamer + * Copyright (C) 2024 Seungha Yang + * + * 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 +#include +#include +#include +#include +#include + +/* *INDENT-OFF* */ +using namespace Microsoft::WRL; +/* *INDENT-ON* */ + +GST_START_TEST (test_device_equal) +{ + auto device = gst_d3d12_device_new (0); + fail_unless (GST_IS_D3D12_DEVICE (device)); + + auto other_device = gst_d3d12_device_new (0); + fail_unless (GST_IS_D3D12_DEVICE (other_device)); + fail_unless (gst_d3d12_device_is_equal (device, other_device)); + + auto handle = gst_d3d12_device_get_device_handle (device); + auto other_handle = gst_d3d12_device_get_device_handle (other_device); + fail_unless_equals_pointer (handle, other_handle); + + gst_object_unref (device); + gst_object_unref (other_device); +} + +GST_END_TEST; + +struct DeviceRemovedData +{ + std::mutex lock; + std::condition_variable cond; + guint removed_count = 0; +}; + +static void +on_device_removed (GstD3D12Device * device, GParamSpec * pspec, + DeviceRemovedData * data) +{ + HRESULT hr = S_OK; + g_object_get (device, "device-removed-reason", &hr, nullptr); + fail_unless (FAILED (hr)); + + std::lock_guard lk (data->lock); + data->removed_count++; + data->cond.notify_all (); +} + +GST_START_TEST (test_device_removed) +{ + auto device = gst_d3d12_device_new (0); + fail_unless (GST_IS_D3D12_DEVICE (device)); + + ComPtr device5; + auto handle = gst_d3d12_device_get_device_handle (device); + fail_unless (handle != nullptr); + + handle->QueryInterface (IID_PPV_ARGS (&device5)); + if (!device5) { + gst_object_unref (device); + return; + } + + auto other_device = gst_d3d12_device_new (0); + + DeviceRemovedData data; + + g_signal_connect (device, "notify::device-removed-reason", + G_CALLBACK (on_device_removed), &data); + g_signal_connect (other_device, "notify::device-removed-reason", + G_CALLBACK (on_device_removed), &data); + + /* Emulate device removed case */ + device5->RemoveDevice (); + device5 = nullptr; + + /* Callback will be called from other thread */ + { + std::unique_lock lk (data.lock); + while (data.removed_count != 2) + data.cond.wait (lk); + } + + /* This will fail since we are holding removed device */ + auto null_device = gst_d3d12_device_new (0); + fail_if (null_device); + + gst_object_unref (device); + gst_object_unref (other_device); + + /* After releasing all devices, create device should be successful */ + device = gst_d3d12_device_new (0); + fail_unless (GST_IS_D3D12_DEVICE (device)); + + gst_object_unref (device); +} + +GST_END_TEST; + +static gboolean +check_d3d12_available (void) +{ + auto device = gst_d3d12_device_new (0); + if (!device) + return FALSE; + + gst_object_unref (device); + + return TRUE; +} + +/* ID3D12Device5::RemoveDevice requires Windows10 build 20348 or newer */ +static gboolean +check_remove_device_supported (void) +{ + OSVERSIONINFOEXW osverinfo = { }; + typedef NTSTATUS (WINAPI fRtlGetVersion) (PRTL_OSVERSIONINFOEXW); + gboolean ret = FALSE; + + memset (&osverinfo, 0, sizeof (OSVERSIONINFOEXW)); + osverinfo.dwOSVersionInfoSize = sizeof (OSVERSIONINFOEXW); + + auto hmodule = LoadLibraryW (L"ntdll.dll"); + if (!hmodule) + return FALSE; + + auto RtlGetVersion = (fRtlGetVersion *) GetProcAddress (hmodule, "RtlGetVersion"); + if (RtlGetVersion) { + RtlGetVersion (&osverinfo); + + if (osverinfo.dwMajorVersion > 10 || + (osverinfo.dwMajorVersion == 10 && osverinfo.dwBuildNumber >= 20348)) { + ret = TRUE; + } + } + + FreeLibrary (hmodule); + + return ret; +} + +static Suite * +d3d12device_suite (void) +{ + Suite *s = suite_create ("d3d12device"); + TCase *tc_basic = tcase_create ("general"); + + suite_add_tcase (s, tc_basic); + + if (!check_d3d12_available ()) + return s; + + tcase_add_test (tc_basic, test_device_equal); + if (check_remove_device_supported ()) + tcase_add_test (tc_basic, test_device_removed); + + return s; +} + +GST_CHECK_MAIN (d3d12device); diff --git a/subprojects/gst-plugins-bad/tests/check/meson.build b/subprojects/gst-plugins-bad/tests/check/meson.build index fda83b43ab..15ce7fdb9d 100644 --- a/subprojects/gst-plugins-bad/tests/check/meson.build +++ b/subprojects/gst-plugins-bad/tests/check/meson.build @@ -112,6 +112,7 @@ base_tests = [ [['libs/d3d11device.cpp'], not gstd3d11_dep.found(), [gstd3d11_dep, gstvideo_dep]], [['libs/d3d11memory.c'], not gstd3d11_dep.found(), [gstd3d11_dep]], [['libs/cudamemory.c'], not gstcuda_dep.found(), [gstcuda_dep, gstcuda_stub_dep]], + [['libs/d3d12device.cpp'], not gstd3d12_dep.found(), [gstd3d12_dep]], ] # Make sure our headers are C++ clean