diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12ipc.cpp b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12ipc.cpp new file mode 100644 index 0000000000..18fbdc15d3 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12ipc.cpp @@ -0,0 +1,397 @@ +/* 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 "gstd3d12ipc.h" +#include +#include +#include +#include +#include + +#define GST_D3D12_IPC_MAGIC_NUMBER 0xD3D1210C + +bool +gst_d3d12_ipc_pkt_identify (std::vector < guint8 > &buf, + GstD3D12IpcPacketHeader & header) +{ + g_return_val_if_fail (buf.size () >= GST_D3D12_IPC_PKT_HEADER_SIZE, false); + + memcpy (&header, &buf[0], GST_D3D12_IPC_PKT_HEADER_SIZE); + + if (header.magic != GST_D3D12_IPC_MAGIC_NUMBER) + return false; + + buf.resize (GST_D3D12_IPC_PKT_HEADER_SIZE + header.payload_size); + + return true; +} + +bool +gst_d3d12_ipc_pkt_build_config (std::vector < guint8 > &buf, + DWORD pid, gint64 adapter_luid, const HANDLE fence_handle, GstCaps * caps) +{ + GstD3D12IpcPacketHeader header; + guint8 *ptr; + gchar *caps_str = nullptr; + guint caps_size = 0; + + g_return_val_if_fail (GST_IS_CAPS (caps), false); + + caps_str = gst_caps_serialize (caps, GST_SERIALIZE_FLAG_NONE); + if (!caps_str) + return false; + + caps_size = strlen (caps_str) + 1; + + header.type = GstD3D12IpcPktType::CONFIG; + header.magic = GST_D3D12_IPC_MAGIC_NUMBER; + header.payload_size = sizeof (DWORD) + sizeof (gint64) + sizeof (HANDLE) + + caps_size; + + buf.resize (GST_D3D12_IPC_PKT_HEADER_SIZE + header.payload_size); + + ptr = &buf[0]; + + memcpy (ptr, &header, GST_D3D12_IPC_PKT_HEADER_SIZE); + ptr += GST_D3D12_IPC_PKT_HEADER_SIZE; + + memcpy (ptr, &pid, sizeof (DWORD)); + ptr += sizeof (DWORD); + + memcpy (ptr, &adapter_luid, sizeof (gint64)); + ptr += sizeof (gint64); + + memcpy (ptr, &fence_handle, sizeof (HANDLE)); + ptr += sizeof (HANDLE); + + strcpy ((char *) ptr, caps_str); + g_free (caps_str); + + return true; +} + +bool +gst_d3d12_ipc_pkt_parse_config (std::vector < guint8 > &buf, + DWORD & pid, gint64 & adapter_luid, HANDLE & fence_handle, GstCaps ** caps) +{ + GstD3D12IpcPacketHeader header; + const guint8 *ptr; + std::string str; + + g_return_val_if_fail (buf.size () > + GST_D3D12_IPC_PKT_HEADER_SIZE + sizeof (gint64) + sizeof (HANDLE), false); + g_return_val_if_fail (caps, false); + + ptr = &buf[0]; + memcpy (&header, ptr, GST_D3D12_IPC_PKT_HEADER_SIZE); + + if (header.type != GstD3D12IpcPktType::CONFIG || + header.magic != GST_D3D12_IPC_MAGIC_NUMBER || + header.payload_size <= sizeof (gint64)) { + return false; + } + + ptr += GST_D3D12_IPC_PKT_HEADER_SIZE; + + memcpy (&pid, ptr, sizeof (DWORD)); + ptr += sizeof (DWORD); + + memcpy (&adapter_luid, ptr, sizeof (gint64)); + ptr += sizeof (gint64); + + memcpy (&fence_handle, ptr, sizeof (HANDLE)); + ptr += sizeof (HANDLE); + + *caps = gst_caps_from_string ((const gchar *) ptr); + if (*caps == nullptr) + return false; + + return true; +} + +void +gst_d3d12_ipc_pkt_build_need_data (std::vector < guint8 > &buf) +{ + GstD3D12IpcPacketHeader header; + + header.type = GstD3D12IpcPktType::NEED_DATA; + header.magic = GST_D3D12_IPC_MAGIC_NUMBER; + header.payload_size = 0; + + buf.resize (GST_D3D12_IPC_PKT_HEADER_SIZE); + + memcpy (&buf[0], &header, GST_D3D12_IPC_PKT_HEADER_SIZE); +} + +/* *INDENT-OFF* */ +bool +gst_d3d12_ipc_pkt_build_have_data (std::vector < guint8 > &buf, + GstClockTime pts, const GstD3D12IpcMemLayout & layout, + const HANDLE handle, guint64 fence_value, GstCaps * caps) +{ + GstD3D12IpcPacketHeader header; + guint8 *ptr; + gchar *caps_str = nullptr; + guint caps_size = 1; + + if (caps) { + caps_str = gst_caps_serialize (caps, GST_SERIALIZE_FLAG_NONE); + if (!caps_str) + return false; + + caps_size += strlen (caps_str) + 1; + } + + header.type = GstD3D12IpcPktType::HAVE_DATA; + header.magic = GST_D3D12_IPC_MAGIC_NUMBER; + header.payload_size = sizeof (GstClockTime) + sizeof (GstD3D12IpcMemLayout) + + sizeof (HANDLE) + sizeof (guint64) + caps_size; + + buf.resize (GST_D3D12_IPC_PKT_HEADER_SIZE + header.payload_size); + + ptr = &buf[0]; + memcpy (ptr, &header, GST_D3D12_IPC_PKT_HEADER_SIZE); + ptr += GST_D3D12_IPC_PKT_HEADER_SIZE; + + memcpy (ptr, &pts, sizeof (GstClockTime)); + ptr += sizeof (GstClockTime); + + memcpy (ptr, &layout, sizeof (GstD3D12IpcMemLayout)); + ptr += sizeof (GstD3D12IpcMemLayout); + + memcpy (ptr, &handle, sizeof (HANDLE)); + ptr += sizeof (HANDLE); + + memcpy (ptr, &fence_value, sizeof (guint64)); + ptr += sizeof (guint64); + + if (caps) { + *ptr = 1; + ptr++; + + strcpy ((char *) ptr, caps_str); + } else { + *ptr = 0; + } + + g_free (caps_str); + + return true; +} +/* *INDENT-ON* */ + +bool +gst_d3d12_ipc_pkt_parse_have_data (const std::vector < guint8 > &buf, + GstClockTime & pts, GstD3D12IpcMemLayout & layout, + HANDLE & handle, guint64 & fence_value, GstCaps ** caps) +{ + GstD3D12IpcPacketHeader header; + const guint8 *ptr; + std::string str; + + g_return_val_if_fail (buf.size () > + GST_D3D12_IPC_PKT_HEADER_SIZE + sizeof (GstClockTime) + + sizeof (GstD3D12IpcMemLayout) + sizeof (HANDLE) + sizeof (guint64), + false); + g_return_val_if_fail (caps, false); + + ptr = &buf[0]; + memcpy (&header, ptr, GST_D3D12_IPC_PKT_HEADER_SIZE); + + if (header.type != GstD3D12IpcPktType::HAVE_DATA || + header.magic != GST_D3D12_IPC_MAGIC_NUMBER || + header.payload_size <= sizeof (GstClockTime) + + sizeof (GstD3D12IpcMemLayout) + sizeof (HANDLE) + sizeof (guint64)) { + return false; + } + ptr += GST_D3D12_IPC_PKT_HEADER_SIZE; + + memcpy (&pts, ptr, sizeof (GstClockTime)); + ptr += sizeof (GstClockTime); + + memcpy (&layout, ptr, sizeof (GstD3D12IpcMemLayout)); + ptr += sizeof (GstD3D12IpcMemLayout); + + memcpy (&handle, ptr, sizeof (HANDLE)); + ptr += sizeof (HANDLE); + + memcpy (&fence_value, ptr, sizeof (guint64)); + ptr += sizeof (guint64); + + if (*ptr) { + ptr++; + + *caps = gst_caps_from_string ((const gchar *) ptr); + if (*caps == nullptr) + return false; + } + + return true; +} + +void +gst_d3d12_ipc_pkt_build_read_done (std::vector < guint8 > &buf) +{ + GstD3D12IpcPacketHeader header; + + header.type = GstD3D12IpcPktType::READ_DONE; + header.magic = GST_D3D12_IPC_MAGIC_NUMBER; + header.payload_size = 0; + + buf.resize (GST_D3D12_IPC_PKT_HEADER_SIZE); + + memcpy (&buf[0], &header, GST_D3D12_IPC_PKT_HEADER_SIZE); +} + +void +gst_d3d12_ipc_pkt_build_release_data (std::vector < guint8 > &buf, + const HANDLE handle) +{ + GstD3D12IpcPacketHeader header; + guint8 *ptr; + + header.type = GstD3D12IpcPktType::RELEASE_DATA; + header.magic = GST_D3D12_IPC_MAGIC_NUMBER; + header.payload_size = sizeof (HANDLE); + + buf.resize (GST_D3D12_IPC_PKT_HEADER_SIZE + header.payload_size); + + ptr = &buf[0]; + memcpy (ptr, &header, GST_D3D12_IPC_PKT_HEADER_SIZE); + ptr += GST_D3D12_IPC_PKT_HEADER_SIZE; + + memcpy (ptr, &handle, sizeof (HANDLE)); +} + +bool +gst_d3d12_ipc_pkt_parse_release_data (std::vector < guint8 > &buf, + HANDLE & handle) +{ + GstD3D12IpcPacketHeader header; + const guint8 *ptr; + + g_return_val_if_fail (buf.size () >= + GST_D3D12_IPC_PKT_HEADER_SIZE + sizeof (HANDLE), false); + + ptr = &buf[0]; + memcpy (&header, ptr, GST_D3D12_IPC_PKT_HEADER_SIZE); + + if (header.type != GstD3D12IpcPktType::RELEASE_DATA || + header.magic != GST_D3D12_IPC_MAGIC_NUMBER || + header.payload_size != sizeof (HANDLE)) { + return false; + } + ptr += GST_D3D12_IPC_PKT_HEADER_SIZE; + + memcpy (&handle, ptr, sizeof (HANDLE)); + + return true; +} + +void +gst_d3d12_ipc_pkt_build_eos (std::vector < guint8 > &buf) +{ + GstD3D12IpcPacketHeader header; + + header.type = GstD3D12IpcPktType::EOS; + header.magic = GST_D3D12_IPC_MAGIC_NUMBER; + header.payload_size = 0; + + buf.resize (GST_D3D12_IPC_PKT_HEADER_SIZE); + + memcpy (&buf[0], &header, GST_D3D12_IPC_PKT_HEADER_SIZE); +} + +void +gst_d3d12_ipc_pkt_build_fin (std::vector < guint8 > &buf) +{ + GstD3D12IpcPacketHeader header; + + header.type = GstD3D12IpcPktType::FIN; + header.magic = GST_D3D12_IPC_MAGIC_NUMBER; + header.payload_size = 0; + + buf.resize (GST_D3D12_IPC_PKT_HEADER_SIZE); + + memcpy (&buf[0], &header, GST_D3D12_IPC_PKT_HEADER_SIZE); +} + +bool +gst_d3d12_ipc_clock_is_system (GstClock * clock) +{ + GstClockType clock_type = GST_CLOCK_TYPE_MONOTONIC; + GstClock *mclock; + + if (G_OBJECT_TYPE (clock) != GST_TYPE_SYSTEM_CLOCK) + return false; + + g_object_get (clock, "clock-type", &clock_type, nullptr); + if (clock_type != GST_CLOCK_TYPE_MONOTONIC) + return false; + + mclock = gst_clock_get_master (clock); + if (!mclock) + return true; + + gst_object_unref (mclock); + return false; +} + +std::string +gst_d3d12_ipc_wstring_to_string (const std::wstring & str) +{ + std::wstring_convert < std::codecvt_utf8 < wchar_t >>conv; + return conv.to_bytes (str); +} + +std::wstring +gst_d3d12_ipc_string_to_wstring (const std::string & str) +{ + std::wstring_convert < std::codecvt_utf8 < wchar_t >>conv; + return conv.from_bytes (str); +} + +/* *INDENT-OFF* */ +static inline void rtrim(std::string &s) { + s.erase (std::find_if (s.rbegin(), s.rend(), + [](unsigned char ch) { + return !std::isspace (ch); + }).base (), s.end ()); +} +/* *INDENT-ON* */ + +std::string +gst_d3d12_ipc_win32_error_to_string (guint err) +{ + wchar_t buffer[1024]; + + if (!FormatMessageW (FORMAT_MESSAGE_IGNORE_INSERTS | + FORMAT_MESSAGE_FROM_SYSTEM, nullptr, err, 0, buffer, 1024, nullptr)) { + return std::string (""); + } + + std::string ret = gst_d3d12_ipc_wstring_to_string (buffer); + rtrim (ret); + + return ret; +} diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12ipc.h b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12ipc.h new file mode 100644 index 0000000000..cd7c809a99 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12ipc.h @@ -0,0 +1,149 @@ +/* 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. + */ + +#pragma once + +#include +#include +#include "gstd3d12.h" +#include +#include +#include + +/* + * Communication Sequence + * + * +--------+ +--------+ + * | client | | server | + * +--------+ +--------+ + * | | + * | | + * |<---------- CONFIG ------------+ + * | | + * +--------- NEED-DATA ---------->| + * | +-------+ + * | | Export + * | | D3D12 memory + * | |<------+ + * |<-------- HAVE-DATA -----------+ + * +--------+ | + * Import | | + * D3D12 memory | | + * +------->+ | + * +--------- READ-DONE ---------->| + * +--------+ | + * Release | | + * D3D12 memory | | + * +------->| | + * +-------- RELEASE-DATA -------->| + * | | + * +--------- NEED-DATA ---------->| + * | | + * |<----------- EOS --------------+ + * +--------+ | + * Cleanup all | | + * shared resources | | + * +------->| | + * +------------ FIN ------------->| + */ + +enum class GstD3D12IpcPktType : guint8 +{ + UNKNOWN, + CONFIG, + NEED_DATA, + HAVE_DATA, + READ_DONE, + RELEASE_DATA, + EOS, + FIN, +}; + +#pragma pack(push, 1) +struct GstD3D12IpcPacketHeader +{ + GstD3D12IpcPktType type; + guint32 payload_size; + guint32 magic; +}; + +struct GstD3D12IpcMemLayout +{ + guint32 pitch; + guint32 offset[4]; +}; +#pragma pack(pop) + +constexpr guint GST_D3D12_IPC_PKT_HEADER_SIZE = sizeof (GstD3D12IpcPacketHeader); + +#define GST_D3D12_IPC_FORMATS \ + "{ RGBA64_LE, RGB10A2_LE, BGRA, RGBA, BGRx, RGBx, VUYA, NV12, NV21, " \ + "P010_10LE, P012_LE, P016_LE }" + +bool gst_d3d12_ipc_pkt_identify (std::vector & buf, + GstD3D12IpcPacketHeader & header); + +bool gst_d3d12_ipc_pkt_build_config (std::vector & buf, + DWORD pid, + gint64 adapter_luid, + const HANDLE fence_handle, + GstCaps * caps); + +bool gst_d3d12_ipc_pkt_parse_config (std::vector & buf, + DWORD & pid, + gint64 & adapter_luid, + HANDLE & fence_handle, + GstCaps ** caps); + +void gst_d3d12_ipc_pkt_build_need_data (std::vector & buf); + +bool gst_d3d12_ipc_pkt_build_have_data (std::vector & buf, + GstClockTime pts, + const GstD3D12IpcMemLayout & layout, + const HANDLE handle, + guint64 fence_value, + GstCaps * caps); + +bool gst_d3d12_ipc_pkt_parse_have_data (const std::vector & buf, + GstClockTime & pts, + GstD3D12IpcMemLayout & layout, + HANDLE & handle, + guint64 & fence_value, + GstCaps ** caps); + +void gst_d3d12_ipc_pkt_build_read_done (std::vector & buf); + +void gst_d3d12_ipc_pkt_build_release_data (std::vector & buf, + const HANDLE handle); + +bool gst_d3d12_ipc_pkt_parse_release_data (std::vector & buf, + HANDLE & handle); + +void gst_d3d12_ipc_pkt_build_eos (std::vector & buf); + +void gst_d3d12_ipc_pkt_build_fin (std::vector & buf); + +bool gst_d3d12_ipc_clock_is_system (GstClock * clock); + +std::string gst_d3d12_ipc_wstring_to_string (const std::wstring & str); + +std::wstring gst_d3d12_ipc_string_to_wstring (const std::string & str); + +std::string gst_d3d12_ipc_win32_error_to_string (guint err); + diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12ipcclient.cpp b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12ipcclient.cpp new file mode 100644 index 0000000000..5af8fd0cfe --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12ipcclient.cpp @@ -0,0 +1,1158 @@ +/* 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 "gstd3d12ipcclient.h" +#include "gstd3d12pluginutils.h" +#include +#include +#include +#include +#include +#include +#include + +/* *INDENT-OFF* */ +using namespace Microsoft::WRL; +static std::mutex gc_pool_lock; +/* *INDENT-ON* */ + +GST_DEBUG_CATEGORY_STATIC (gst_d3d12_ipc_client_debug); +#define GST_CAT_DEFAULT gst_d3d12_ipc_client_debug + +static GThreadPool *gc_thread_pool = nullptr; + +void +gst_d3d12_ipc_client_deinit (void) +{ + std::lock_guard < std::mutex > lk (gc_pool_lock); + if (gc_thread_pool) { + g_thread_pool_free (gc_thread_pool, FALSE, TRUE); + gc_thread_pool = nullptr; + } +} + +/** + * GstD3D12IpcIOMode: + * + * Texture import mode + * + * Since: 1.26 + */ +GType +gst_d3d12_ipc_io_mode_get_type (void) +{ + static GType type = 0; + static const GEnumValue io_modes[] = { + /** + * GstD3D12IpcIOMode::copy: + * + * Copy remote texture to newly allocated texture + * + * Since: 1.26 + */ + {GST_D3D12_IPC_IO_COPY, "Copy remote texture", "copy"}, + + /** + * GstD3D12IpcIOMode::import: + * + * Import remote texture to without any allocation/copy + * + * Since: 1.26 + */ + {GST_D3D12_IPC_IO_IMPORT, "Import remote texture", "import"}, + {0, nullptr, nullptr} + }; + + GST_D3D12_CALL_ONCE_BEGIN { + type = g_enum_register_static ("GstD3D12IpcIOMode", io_modes); + } GST_D3D12_CALL_ONCE_END; + + return type; +} + +/* *INDENT-OFF* */ +struct GstD3D12IpcClientConn : public OVERLAPPED +{ + GstD3D12IpcClientConn (GstD3D12IpcClient * client, HANDLE pipe_handle) + : client (client), pipe (pipe_handle) + { + OVERLAPPED *parent = static_cast (this); + parent->Internal = 0; + parent->InternalHigh = 0; + parent->Offset = 0; + parent->OffsetHigh = 0; + + client_msg.resize (GST_D3D12_IPC_PKT_HEADER_SIZE); + server_msg.resize (GST_D3D12_IPC_PKT_HEADER_SIZE); + } + + ~GstD3D12IpcClientConn () + { + if (pipe != INVALID_HANDLE_VALUE) { + CancelIo (pipe); + CloseHandle (pipe); + } + } + + GstD3D12IpcClient *client; + + HANDLE pipe = INVALID_HANDLE_VALUE; + + GstD3D12IpcPktType type; + std::vector client_msg; + std::vector server_msg; +}; + +struct GstD3D12IpcImportData +{ + ~GstD3D12IpcImportData () + { + GST_LOG_OBJECT (client, "Release handle \"%p\"", server_handle); + gst_object_unref (client); + } + + GstD3D12IpcClient *client; + ComPtr texture; + GstD3D12IpcMemLayout layout; + HANDLE server_handle = nullptr; +}; + +struct GstD3D12IpcReleaseData +{ + GstD3D12IpcClient *self; + std::shared_ptr imported; +}; + +struct GstD3D12IpcClientPrivate +{ + GstD3D12IpcClientPrivate () + { + wakeup_event = CreateEvent (nullptr, FALSE, FALSE, nullptr); + cancellable = CreateEvent (nullptr, TRUE, FALSE, nullptr); + + shutdown = false; + io_pending = true; + } + + ~GstD3D12IpcClientPrivate () + { + gst_clear_caps (&caps); + if (pool) { + gst_buffer_pool_set_active (pool, FALSE); + gst_object_unref (pool); + } + + gst_clear_object (&device); + + CloseHandle (wakeup_event); + CloseHandle (cancellable); + if (server_process) + CloseHandle (server_process); + } + + std::string address; + GstD3D12IpcIOMode io_mode = GST_D3D12_IPC_IO_COPY; + GstClockTime timeout; + HANDLE wakeup_event; + HANDLE cancellable; + HANDLE server_process = nullptr; + std::mutex lock; + std::condition_variable cond; + GstD3D12Device *device = nullptr; + GstCaps *caps = nullptr; + GstBufferPool *pool = nullptr; + GstVideoInfo info; + bool server_eos = false; + bool flushing = false; + bool aborted = false; + bool sent_fin = false; + std::atomic shutdown; + std::atomic io_pending; + GThread *loop_thread = nullptr; + std::queue samples; + std::shared_ptr conn; + std::queue unused_data; + std::vector> imported; + ComPtr server_fence; +}; +/* *INDENT-ON* */ + +struct _GstD3D12IpcClient +{ + GstObject parent; + + GstD3D12IpcClientPrivate *priv; +}; + +static void gst_d3d12_ipc_client_dispose (GObject * object); +static void gst_d3d12_ipc_client_finalize (GObject * object); +static void gst_d3d12_ipc_client_continue (GstD3D12IpcClient * self); +static void gst_d3d12_ipc_client_send_msg (GstD3D12IpcClient * self); + +#define gst_d3d12_ipc_client_parent_class parent_class +G_DEFINE_TYPE (GstD3D12IpcClient, gst_d3d12_ipc_client, GST_TYPE_OBJECT); + +static void +gst_d3d12_ipc_client_class_init (GstD3D12IpcClientClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gst_d3d12_ipc_client_dispose; + object_class->finalize = gst_d3d12_ipc_client_finalize; + + GST_DEBUG_CATEGORY_INIT (gst_d3d12_ipc_client_debug, "d3d12ipcclient", + 0, "d3d12ipcclient"); +} + +static void +gst_d3d12_ipc_client_init (GstD3D12IpcClient * self) +{ + self->priv = new GstD3D12IpcClientPrivate (); +} + +static void +gst_d3d12_ipc_client_dispose (GObject * object) +{ + auto self = GST_D3D12_IPC_CLIENT (object); + auto priv = self->priv; + + GST_DEBUG_OBJECT (self, "dispose"); + + SetEvent (priv->cancellable); + + g_clear_pointer (&priv->loop_thread, g_thread_join); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_d3d12_ipc_client_finalize (GObject * object) +{ + auto self = GST_D3D12_IPC_CLIENT (object); + + GST_DEBUG_OBJECT (self, "finalize"); + + delete self->priv; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_d3d12_ipc_client_abort (GstD3D12IpcClient * self) +{ + auto priv = self->priv; + std::lock_guard < std::mutex > lk (priv->lock); + priv->aborted = true; + priv->cond.notify_all (); +} + +static bool +gst_d3d12_client_update_caps (GstD3D12IpcClient * self, GstCaps * caps) +{ + auto priv = self->priv; + + if (!caps) + return true; + + gst_clear_caps (&priv->caps); + priv->caps = caps; + + if (priv->pool) { + gst_buffer_pool_set_active (priv->pool, FALSE); + gst_clear_object (&priv->pool); + } + + if (!gst_video_info_from_caps (&priv->info, caps)) { + GST_ERROR_OBJECT (self, "Invalid caps"); + return false; + } + + if (priv->io_mode == GST_D3D12_IPC_IO_COPY) { + priv->pool = gst_d3d12_buffer_pool_new (priv->device); + auto config = gst_buffer_pool_get_config (priv->pool); + + gst_buffer_pool_config_add_option (config, + GST_BUFFER_POOL_OPTION_VIDEO_META); + + gst_buffer_pool_config_set_params (config, priv->caps, + GST_VIDEO_INFO_SIZE (&priv->info), 0, 0); + + auto params = gst_d3d12_allocation_params_new (priv->device, &priv->info, + GST_D3D12_ALLOCATION_FLAG_DEFAULT, + D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS | + D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, D3D12_HEAP_FLAG_NONE); + + gst_buffer_pool_config_set_d3d12_allocation_params (config, params); + gst_d3d12_allocation_params_free (params); + + if (!gst_buffer_pool_set_config (priv->pool, config)) { + GST_ERROR_OBJECT (self, "Couldn't set pool config"); + gst_clear_object (&priv->pool); + return false; + } + + if (!gst_buffer_pool_set_active (priv->pool, TRUE)) { + GST_ERROR_OBJECT (self, "Couldn't active pool"); + gst_clear_object (&priv->pool); + return false; + } + } + + return true; +} + +static bool +gst_d3d12_ipc_client_config_data (GstD3D12IpcClient * self) +{ + auto priv = self->priv; + gint64 prev_luid, luid; + GstCaps *caps = nullptr; + auto conn = priv->conn; + DWORD server_pid; + HANDLE server_fence_handle; + std::lock_guard < std::mutex > lk (priv->lock); + + g_object_get (priv->device, "adapter-luid", &prev_luid, nullptr); + + if (!gst_d3d12_ipc_pkt_parse_config (conn->server_msg, + server_pid, luid, server_fence_handle, &caps)) { + GST_ERROR_OBJECT (self, "Couldn't parse CONFIG-DATA"); + return false; + } + + if (priv->server_process) { + GST_WARNING_OBJECT (self, "Have server process handle already"); + CloseHandle (priv->server_process); + } + + priv->server_process = OpenProcess (PROCESS_DUP_HANDLE, FALSE, server_pid); + if (!priv->server_process) { + guint last_err = GetLastError (); + auto err = gst_d3d12_ipc_win32_error_to_string (last_err); + GST_ERROR_OBJECT (self, "Couldn't open server process, 0x%x (%s)", + last_err, err.c_str ()); + return false; + } + + if (prev_luid != luid) { + auto device = gst_d3d12_device_new_for_adapter_luid (luid); + if (!device) { + GST_ERROR_OBJECT (self, "Couldn't create device"); + return false; + } + + gst_object_unref (priv->device); + priv->device = device; + } + + if (!gst_d3d12_client_update_caps (self, caps)) + return false; + + HANDLE client_fence_handle; + if (!DuplicateHandle (priv->server_process, server_fence_handle, + GetCurrentProcess (), &client_fence_handle, 0, FALSE, + DUPLICATE_SAME_ACCESS)) { + guint last_err = GetLastError (); + auto err = gst_d3d12_ipc_win32_error_to_string (last_err); + GST_ERROR_OBJECT (self, "Couldn't duplicate handle, 0x%x (%s)", + last_err, err.c_str ()); + return false; + } + + priv->server_fence = nullptr; + auto device = gst_d3d12_device_get_device_handle (priv->device); + auto hr = device->OpenSharedHandle (client_fence_handle, + IID_PPV_ARGS (&priv->server_fence)); + CloseHandle (client_fence_handle); + if (!gst_d3d12_result (hr, priv->device)) { + GST_ERROR_OBJECT (self, "Couldn't open server fence"); + return false; + } + + priv->cond.notify_all (); + + return true; +} + +static void +gst_d3d12_ipc_client_release_imported_data (GstD3D12IpcReleaseData * data) +{ + auto self = data->self; + auto priv = self->priv; + HANDLE server_handle = data->imported->server_handle; + + GST_LOG_OBJECT (self, "Releasing data \"%p\"", server_handle); + + data->imported = nullptr; + + priv->lock.lock (); + priv->unused_data.push (server_handle); + priv->lock.unlock (); + + SetEvent (priv->wakeup_event); + + gst_object_unref (data->self); + + delete data; +} + +static bool +gst_d3d12_ipc_client_have_data (GstD3D12IpcClient * self) +{ + auto priv = self->priv; + GstBuffer *buffer = nullptr; + GstSample *sample; + GstClockTime pts; + GstCaps *caps = nullptr; + GstD3D12IpcMemLayout layout; + std::shared_ptr < GstD3D12IpcImportData > import_data; + std::unique_lock < std::mutex > lk (priv->lock); + HANDLE server_handle = nullptr; + HANDLE client_handle = nullptr; + auto conn = priv->conn; + ComPtr < ID3D12Resource > texture; + HRESULT hr; + guint64 fence_val = 0; + + if (!gst_d3d12_ipc_pkt_parse_have_data (conn->server_msg, + pts, layout, server_handle, fence_val, &caps)) { + GST_ERROR_OBJECT (self, "Couldn't parse HAVE-DATA packet"); + return false; + } + + if (!gst_d3d12_client_update_caps (self, caps)) + return false; + + if (!DuplicateHandle (priv->server_process, server_handle, + GetCurrentProcess (), &client_handle, 0, FALSE, + DUPLICATE_SAME_ACCESS)) { + guint last_err = GetLastError (); + auto err = gst_d3d12_ipc_win32_error_to_string (last_err); + GST_ERROR_OBJECT (self, "Couldn't duplicate handle, 0x%x (%s)", + last_err, err.c_str ()); + return false; + } + + GST_LOG_OBJECT (self, "Importing server handle %p", server_handle); + + auto device = gst_d3d12_device_get_device_handle (priv->device); + hr = device->OpenSharedHandle (client_handle, IID_PPV_ARGS (&texture)); + CloseHandle (client_handle); + + if (!gst_d3d12_result (hr, priv->device)) { + GST_ERROR_OBJECT (self, "Couldn't open resource"); + return false; + } + + import_data = std::make_shared < GstD3D12IpcImportData > (); + import_data->client = (GstD3D12IpcClient *) gst_object_ref (self); + import_data->texture = texture; + import_data->layout = layout; + import_data->server_handle = server_handle; + + if (priv->io_mode == GST_D3D12_IPC_IO_COPY) { + gst_buffer_pool_acquire_buffer (priv->pool, &buffer, nullptr); + if (!buffer) { + GST_ERROR_OBJECT (self, "Couldn't acquire buffer"); + return false; + } + + auto dmem = (GstD3D12Memory *) gst_buffer_peek_memory (buffer, 0); + auto num_planes = gst_d3d12_memory_get_plane_count (dmem); + auto resource = gst_d3d12_memory_get_resource_handle (dmem); + std::vector < GstD3D12CopyTextureRegionArgs > copy_args; + D3D12_BOX src_box[4]; + + for (guint i = 0; i < num_planes; i++) { + GstD3D12CopyTextureRegionArgs args = { }; + D3D12_RECT dst_rect; + + gst_d3d12_memory_get_plane_rectangle (dmem, i, &dst_rect); + + args.src = CD3DX12_TEXTURE_COPY_LOCATION (texture.Get (), i); + args.dst = CD3DX12_TEXTURE_COPY_LOCATION (resource, i); + + src_box[i].front = 0; + src_box[i].back = 1; + src_box[i].left = 0; + src_box[i].top = 0; + src_box[i].right = dst_rect.right; + src_box[i].bottom = dst_rect.bottom; + + args.src_box = &src_box[i]; + copy_args.push_back (args); + } + + auto queue = gst_d3d12_device_get_command_queue (priv->device, + D3D12_COMMAND_LIST_TYPE_DIRECT); + auto completed = priv->server_fence->GetCompletedValue (); + if (completed < fence_val) { + gst_d3d12_command_queue_execute_wait (queue, priv->server_fence.Get (), + fence_val); + } + + guint64 copy_fence_val; + gst_d3d12_device_copy_texture_region (priv->device, copy_args.size (), + copy_args.data (), D3D12_COMMAND_LIST_TYPE_DIRECT, ©_fence_val); + + auto data = new GstD3D12IpcReleaseData (); + data->self = (GstD3D12IpcClient *) gst_object_ref (self); + data->imported = import_data; + + gst_d3d12_command_queue_set_notify (queue, copy_fence_val, data, + (GDestroyNotify) gst_d3d12_ipc_client_release_imported_data); + + gst_d3d12_buffer_after_write (buffer, copy_fence_val); + } else { + gint stride[GST_VIDEO_MAX_PLANES]; + gsize offset[GST_VIDEO_MAX_PLANES]; + + for (guint i = 0; i < GST_VIDEO_MAX_PLANES; i++) { + stride[i] = import_data->layout.pitch; + offset[i] = import_data->layout.offset[i]; + } + + auto data = new GstD3D12IpcReleaseData (); + data->self = (GstD3D12IpcClient *) gst_object_ref (self); + data->imported = import_data; + + auto mem = gst_d3d12_allocator_alloc_wrapped (nullptr, priv->device, + texture.Get (), 0, data, + (GDestroyNotify) gst_d3d12_ipc_client_release_imported_data); + + gst_d3d12_memory_set_external_fence (GST_D3D12_MEMORY_CAST (mem), + priv->server_fence.Get (), fence_val); + + GST_MINI_OBJECT_FLAG_SET (mem, GST_MEMORY_FLAG_READONLY); + + buffer = gst_buffer_new (); + gst_buffer_append_memory (buffer, mem); + + gst_buffer_add_video_meta_full (buffer, GST_VIDEO_FRAME_FLAG_NONE, + GST_VIDEO_INFO_FORMAT (&priv->info), GST_VIDEO_INFO_WIDTH (&priv->info), + GST_VIDEO_INFO_HEIGHT (&priv->info), + GST_VIDEO_INFO_N_PLANES (&priv->info), offset, stride); + + priv->imported.push_back (import_data); + } + + GST_BUFFER_PTS (buffer) = pts; + GST_BUFFER_DTS (buffer) = GST_CLOCK_TIME_NONE; + GST_BUFFER_DURATION (buffer) = GST_CLOCK_TIME_NONE; + + sample = gst_sample_new (buffer, priv->caps, nullptr, nullptr); + gst_buffer_unref (buffer); + + /* Drops too old samples */ + std::queue < GstSample * >drop_queue; + while (priv->samples.size () > 2) { + drop_queue.push (priv->samples.front ()); + priv->samples.pop (); + } + + priv->samples.push (sample); + priv->cond.notify_all (); + lk.unlock (); + + import_data = nullptr; + while (!drop_queue.empty ()) { + auto old = drop_queue.front (); + gst_sample_unref (old); + drop_queue.pop (); + } + + return true; +} + +static void +gst_d3d12_ipc_client_wait_msg_finish (GstD3D12IpcClient * client) +{ + auto priv = client->priv; + GstD3D12IpcPacketHeader header; + auto conn = priv->conn; + + if (!gst_d3d12_ipc_pkt_identify (conn->server_msg, header)) { + GST_ERROR_OBJECT (client, "Broken header"); + gst_d3d12_ipc_client_abort (client); + return; + } + + switch (header.type) { + case GstD3D12IpcPktType::CONFIG: + GST_LOG_OBJECT (client, "Got CONFIG"); + if (!gst_d3d12_ipc_client_config_data (client)) { + gst_d3d12_ipc_client_abort (client); + return; + } + + gst_d3d12_ipc_client_continue (client); + break; + case GstD3D12IpcPktType::HAVE_DATA: + GST_LOG_OBJECT (client, "Got HAVE-DATA"); + gst_d3d12_device_lock (priv->device); + if (!gst_d3d12_ipc_client_have_data (client)) { + gst_d3d12_device_unlock (priv->device); + gst_d3d12_ipc_client_abort (client); + return; + } + + gst_d3d12_device_unlock (priv->device); + + GST_LOG_OBJECT (client, "Sending READ-DONE"); + gst_d3d12_ipc_pkt_build_read_done (conn->client_msg); + conn->type = GstD3D12IpcPktType::READ_DONE; + gst_d3d12_ipc_client_send_msg (client); + break; + case GstD3D12IpcPktType::EOS: + GST_DEBUG_OBJECT (client, "Got EOS"); + priv->server_eos = true; + priv->lock.lock (); + priv->cond.notify_all (); + priv->lock.unlock (); + gst_d3d12_ipc_client_continue (client); + break; + default: + GST_WARNING_OBJECT (client, "Unexpected packet type"); + gst_d3d12_ipc_client_abort (client); + break; + } +} + +static void WINAPI +gst_d3d12_ipc_client_payload_finish (DWORD error_code, DWORD size, + OVERLAPPED * overlap) +{ + auto conn = static_cast < GstD3D12IpcClientConn * >(overlap); + auto self = conn->client; + + if (error_code != ERROR_SUCCESS) { + auto err = gst_d3d12_ipc_win32_error_to_string (error_code); + GST_WARNING_OBJECT (self, "ReadFileEx callback failed with 0x%x (%s)", + (guint) error_code, err.c_str ()); + gst_d3d12_ipc_client_abort (self); + } + + gst_d3d12_ipc_client_wait_msg_finish (self); +} + +static void WINAPI +gst_d3d12_ipc_client_win32_wait_header_finish (DWORD error_code, DWORD size, + OVERLAPPED * overlap) +{ + auto conn = static_cast < GstD3D12IpcClientConn * >(overlap); + auto self = conn->client; + GstD3D12IpcPacketHeader header; + + if (error_code != ERROR_SUCCESS) { + auto err = gst_d3d12_ipc_win32_error_to_string (error_code); + GST_WARNING_OBJECT (self, "ReadFileEx callback failed with 0x%x (%s)", + (guint) error_code, err.c_str ()); + gst_d3d12_ipc_client_abort (self); + return; + } + + if (!gst_d3d12_ipc_pkt_identify (conn->server_msg, header)) { + GST_ERROR_OBJECT (self, "Broken header"); + gst_d3d12_ipc_client_abort (self); + return; + } + + if (header.payload_size == 0) { + gst_d3d12_ipc_client_wait_msg_finish (self); + return; + } + + GST_LOG_OBJECT (self, "Reading payload"); + + if (!ReadFileEx (conn->pipe, &conn->server_msg[0] + + GST_D3D12_IPC_PKT_HEADER_SIZE, header.payload_size, conn, + gst_d3d12_ipc_client_payload_finish)) { + guint last_err = GetLastError (); + auto err = gst_d3d12_ipc_win32_error_to_string (last_err); + GST_WARNING_OBJECT (self, "ReadFileEx failed with 0x%x (%s)", + last_err, err.c_str ()); + gst_d3d12_ipc_client_abort (self); + } +} + +static void +gst_d3d12_ipc_client_wait_msg (GstD3D12IpcClient * self) +{ + auto priv = self->priv; + auto conn = priv->conn; + priv->io_pending = true; + + if (!ReadFileEx (conn->pipe, &conn->server_msg[0], + GST_D3D12_IPC_PKT_HEADER_SIZE, conn.get (), + gst_d3d12_ipc_client_win32_wait_header_finish)) { + guint last_err = GetLastError (); + auto err = gst_d3d12_ipc_win32_error_to_string (last_err); + GST_WARNING_OBJECT (self, "ReadFileEx failed with 0x%x (%s)", + last_err, err.c_str ()); + gst_d3d12_ipc_client_abort (self); + } +} + +static void WINAPI +gst_cuda_ipc_client_win32_send_msg_finish (DWORD error_code, DWORD size, + OVERLAPPED * overlap) +{ + auto conn = static_cast < GstD3D12IpcClientConn * >(overlap); + auto self = conn->client; + + if (error_code != ERROR_SUCCESS) { + auto err = gst_d3d12_ipc_win32_error_to_string (error_code); + GST_WARNING_OBJECT (self, "WriteFileEx callback failed with 0x%x (%s)", + (guint) error_code, err.c_str ()); + gst_d3d12_ipc_client_abort (self); + return; + } + + switch (conn->type) { + case GstD3D12IpcPktType::NEED_DATA: + GST_LOG_OBJECT (self, "Sent NEED-DATA"); + gst_d3d12_ipc_client_wait_msg (self); + break; + case GstD3D12IpcPktType::READ_DONE: + GST_LOG_OBJECT (self, "Sent READ-DONE"); + gst_d3d12_ipc_client_continue (self); + break; + case GstD3D12IpcPktType::RELEASE_DATA: + GST_LOG_OBJECT (self, "Sent RELEASE-DATA"); + gst_d3d12_ipc_client_continue (self); + break; + case GstD3D12IpcPktType::FIN: + GST_DEBUG_OBJECT (self, "Sent FIN"); + gst_d3d12_ipc_client_abort (self); + break; + default: + GST_ERROR_OBJECT (self, "Unexpected msg type"); + gst_d3d12_ipc_client_abort (self); + break; + } +} + +static void +gst_d3d12_ipc_client_send_msg (GstD3D12IpcClient * self) +{ + auto priv = self->priv; + auto conn = priv->conn; + + priv->io_pending = true; + + if (!WriteFileEx (conn->pipe, &conn->client_msg[0], + conn->client_msg.size (), conn.get (), + gst_cuda_ipc_client_win32_send_msg_finish)) { + guint last_err = GetLastError (); + auto err = gst_d3d12_ipc_win32_error_to_string (last_err); + GST_WARNING_OBJECT (self, "WriteFileEx failed with 0x%x (%s)", + last_err, err.c_str ()); + gst_d3d12_ipc_client_abort (self); + } +} + +static void +gst_d3d12_ipc_client_run_gc (GstD3D12IpcClient * self) +{ + auto priv = self->priv; + + for (auto it = priv->imported.begin (); it != priv->imported.end ();) { + auto data = it->lock (); + if (!data) { + it = priv->imported.erase (it); + } else { + it++; + } + } +} + +static void +gst_d3d12_ipc_client_continue (GstD3D12IpcClient * self) +{ + auto priv = self->priv; + std::unique_lock < std::mutex > lk (priv->lock); + auto conn = priv->conn; + + if (!conn) { + GST_WARNING_OBJECT (self, "No connection was made"); + priv->aborted = true; + priv->cond.notify_all (); + return; + } + + if (priv->aborted) { + priv->cond.notify_all (); + GST_DEBUG_OBJECT (self, "Operation was aborted"); + return; + } + + if (!priv->unused_data.empty ()) { + HANDLE server_handle = priv->unused_data.front (); + priv->unused_data.pop (); + + GST_LOG_OBJECT (self, "Sending RELEASE-DATA %p", server_handle); + + gst_d3d12_ipc_pkt_build_release_data (conn->client_msg, server_handle); + conn->type = GstD3D12IpcPktType::RELEASE_DATA; + lk.unlock (); + + gst_d3d12_ipc_client_send_msg (self); + return; + } + + if (priv->shutdown) { + auto drop_queue = priv->samples; + while (!priv->samples.empty ()) + priv->samples.pop (); + lk.unlock (); + + while (!drop_queue.empty ()) { + auto sample = drop_queue.front (); + gst_sample_unref (sample); + drop_queue.pop (); + } + lk.lock (); + } + + if (priv->server_eos || priv->shutdown) { + gst_d3d12_ipc_client_run_gc (self); + + GST_DEBUG_OBJECT (self, "Remaining imported memory %" G_GSIZE_FORMAT, + priv->imported.size ()); + + if (priv->imported.empty ()) { + GST_DEBUG_OBJECT (self, "Drained"); + if (priv->sent_fin) { + priv->aborted = true; + priv->cond.notify_all (); + } else { + lk.unlock (); + + priv->sent_fin = true; + gst_d3d12_ipc_pkt_build_fin (conn->client_msg); + conn->type = GstD3D12IpcPktType::FIN; + + GST_DEBUG_OBJECT (self, "Sending FIN"); + gst_d3d12_ipc_client_send_msg (self); + return; + } + } else { + priv->io_pending = false; + } + return; + } + + lk.unlock (); + + gst_d3d12_ipc_pkt_build_need_data (conn->client_msg); + conn->type = GstD3D12IpcPktType::NEED_DATA; + + GST_LOG_OBJECT (self, "Sending NEED-DATA"); + gst_d3d12_ipc_client_send_msg (self); +} + +static gpointer +gst_d3d12_ipc_client_loop_thread_func (GstD3D12IpcClient * self) +{ + auto priv = self->priv; + DWORD mode = PIPE_READMODE_MESSAGE; + guint wait_ret; + HANDLE pipe = INVALID_HANDLE_VALUE; + GstClockTime start_time = gst_util_get_timestamp (); + HANDLE waitables[] = { priv->cancellable, priv->wakeup_event }; + std::wstring address = gst_d3d12_ipc_string_to_wstring (priv->address); + +#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8) + CREATEFILE2_EXTENDED_PARAMETERS params; + memset (¶ms, 0, sizeof (CREATEFILE2_EXTENDED_PARAMETERS)); + params.dwSize = sizeof (CREATEFILE2_EXTENDED_PARAMETERS); + params.dwFileAttributes = 0; + params.dwFileFlags = FILE_FLAG_OVERLAPPED; + params.dwSecurityQosFlags = SECURITY_IMPERSONATION; +#endif + + GST_DEBUG_OBJECT (self, "Starting loop thread"); + + std::unique_lock < std::mutex > lk (priv->lock); + do { + GstClockTime diff; + + if (priv->flushing) { + GST_DEBUG_OBJECT (self, "We are flushing"); + priv->aborted = true; + priv->cond.notify_all (); + goto out; + } +#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8) + pipe = CreateFile2 (address.c_str (), GENERIC_READ | GENERIC_WRITE, 0, + OPEN_EXISTING, ¶ms); +#else + pipe = CreateFileW (address.c_str (), + GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, nullptr); +#endif + + if (pipe != INVALID_HANDLE_VALUE) + break; + + if (priv->timeout > 0) { + diff = gst_util_get_timestamp () - start_time; + if (diff > priv->timeout) { + GST_WARNING_OBJECT (self, "Timeout"); + priv->aborted = true; + priv->cond.notify_all (); + goto out; + } + } + + /* Retry per 100ms */ + GST_DEBUG_OBJECT (self, "Sleep for next retry"); + priv->cond.wait_for (lk, std::chrono::milliseconds (100)); + } while (true); + + if (!SetNamedPipeHandleState (pipe, &mode, nullptr, nullptr)) { + guint last_err = GetLastError (); + auto err = gst_d3d12_ipc_win32_error_to_string (last_err); + GST_WARNING_OBJECT (self, "SetNamedPipeHandleState failed with 0x%x (%s)", + last_err, err.c_str ()); + + CloseHandle (pipe); + priv->aborted = true; + priv->cond.notify_all (); + goto out; + } + + priv->conn = std::make_shared < GstD3D12IpcClientConn > (self, pipe); + priv->cond.notify_all (); + lk.unlock (); + + gst_d3d12_ipc_client_wait_msg (self); + + do { + /* Enters alertable thread state and wait for I/O completion event + * or cancellable event */ + wait_ret = WaitForMultipleObjectsEx (G_N_ELEMENTS (waitables), waitables, + FALSE, INFINITE, TRUE); + if (wait_ret == WAIT_OBJECT_0) { + GST_DEBUG ("Operation cancelled"); + goto out; + } + + switch (wait_ret) { + case WAIT_IO_COMPLETION: + break; + case WAIT_OBJECT_0 + 1: + if (!priv->io_pending) + gst_d3d12_ipc_client_continue (self); + break; + default: + GST_WARNING ("Unexpected wait return 0x%x", wait_ret); + gst_d3d12_ipc_client_abort (self); + goto out; + } + } while (true); + +out: + while (!priv->samples.empty ()) { + auto sample = priv->samples.front (); + gst_sample_unref (sample); + priv->samples.pop (); + } + + priv->conn = nullptr; + + GST_DEBUG_OBJECT (self, "Exit loop thread"); + + return nullptr; +} + +GstFlowReturn +gst_d3d12_ipc_client_run (GstD3D12IpcClient * client) +{ + g_return_val_if_fail (GST_IS_D3D12_IPC_CLIENT (client), GST_FLOW_ERROR); + + auto priv = client->priv; + std::unique_lock < std::mutex > lk (priv->lock); + if (!priv->loop_thread) { + priv->loop_thread = g_thread_new ("d3d12-ipc-client", + (GThreadFunc) gst_d3d12_ipc_client_loop_thread_func, client); + + while (!priv->caps && !priv->aborted && !priv->flushing) + priv->cond.wait (lk); + } + + if (priv->flushing) { + GST_DEBUG_OBJECT (client, "We are flushing"); + return GST_FLOW_FLUSHING; + } else if (priv->aborted || !priv->caps) { + GST_DEBUG_OBJECT (client, "Aborted"); + return GST_FLOW_ERROR; + } + + return GST_FLOW_OK; +} + +GstCaps * +gst_d3d12_ipc_client_get_caps (GstD3D12IpcClient * client) +{ + GstCaps *caps = nullptr; + + g_return_val_if_fail (GST_IS_D3D12_IPC_CLIENT (client), nullptr); + + auto priv = client->priv; + + if (gst_d3d12_ipc_client_run (client) != GST_FLOW_OK) + return nullptr; + + std::lock_guard < std::mutex > lk (priv->lock); + if (priv->caps) + caps = gst_caps_ref (priv->caps); + + return caps; +} + +static void +gst_d3d12_ipc_client_stop_async (GstD3D12IpcClient * client, gpointer user_data) +{ + auto priv = client->priv; + + GST_DEBUG_OBJECT (client, "Stopping"); + std::unique_lock < std::mutex > lk (priv->lock); + while (!priv->aborted) + priv->cond.wait (lk); + lk.unlock (); + + SetEvent (priv->cancellable); + g_clear_pointer (&priv->loop_thread, g_thread_join); + + GST_DEBUG_OBJECT (client, "Stopped"); + + gst_object_unref (client); +} + +static void +gst_d3d12_ipc_client_push_stop_async (GstD3D12IpcClient * client) +{ + std::lock_guard < std::mutex > lk (gc_pool_lock); + if (!gc_thread_pool) { + gc_thread_pool = g_thread_pool_new ((GFunc) gst_d3d12_ipc_client_stop_async, + nullptr, -1, FALSE, nullptr); + } + + g_thread_pool_push (gc_thread_pool, gst_object_ref (client), nullptr); +} + +void +gst_d3d12_ipc_client_stop (GstD3D12IpcClient * client) +{ + g_return_if_fail (GST_IS_D3D12_IPC_CLIENT (client)); + + auto priv = client->priv; + + GST_DEBUG_OBJECT (client, "Stopping"); + priv->shutdown = true; + SetEvent (priv->wakeup_event); + + if (priv->io_mode == GST_D3D12_IPC_IO_COPY) { + std::unique_lock < std::mutex > lk (priv->lock); + while (!priv->aborted) + priv->cond.wait (lk); + lk.unlock (); + + GST_DEBUG_OBJECT (client, "Terminating"); + + SetEvent (priv->cancellable); + + g_clear_pointer (&priv->loop_thread, g_thread_join); + + GST_DEBUG_OBJECT (client, "Stopped"); + } else { + /* We don't know when imported memory gets released */ + gst_d3d12_ipc_client_push_stop_async (client); + } +} + +void +gst_d3d12_ipc_client_set_flushing (GstD3D12IpcClient * client, bool flushing) +{ + g_return_if_fail (GST_IS_D3D12_IPC_CLIENT (client)); + + auto priv = client->priv; + + std::lock_guard < std::mutex > lk (priv->lock); + priv->flushing = flushing; + priv->cond.notify_all (); +} + +GstFlowReturn +gst_d3d12_ipc_client_get_sample (GstD3D12IpcClient * client, + GstSample ** sample) +{ + g_return_val_if_fail (GST_IS_D3D12_IPC_CLIENT (client), GST_FLOW_ERROR); + g_return_val_if_fail (sample, GST_FLOW_ERROR); + + auto priv = client->priv; + + GST_LOG_OBJECT (client, "Waiting for sample"); + std::unique_lock < std::mutex > lk (priv->lock); + while (!priv->flushing && !priv->aborted && !priv->server_eos && + priv->samples.empty ()) { + priv->cond.wait (lk); + } + + if (!priv->samples.empty ()) { + *sample = priv->samples.front (); + priv->samples.pop (); + + GST_LOG_OBJECT (client, "Have sample"); + return GST_FLOW_OK; + } + + if (priv->flushing) { + GST_DEBUG_OBJECT (client, "Flushing"); + return GST_FLOW_FLUSHING; + } + + GST_DEBUG_OBJECT (client, "EOS"); + + return GST_FLOW_EOS; +} + +GstD3D12IpcClient * +gst_d3d12_ipc_client_new (const std::string & address, GstD3D12Device * device, + GstD3D12IpcIOMode io_mode, guint timeout) +{ + g_return_val_if_fail (GST_IS_D3D12_DEVICE (device), nullptr); + + auto self = (GstD3D12IpcClient *) + g_object_new (GST_TYPE_D3D12_IPC_CLIENT, nullptr); + gst_object_ref_sink (self); + + auto priv = self->priv; + priv->address = address; + priv->timeout = timeout * GST_SECOND; + priv->io_mode = io_mode; + priv->device = (GstD3D12Device *) gst_object_ref (device); + + return self; +} diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12ipcclient.h b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12ipcclient.h new file mode 100644 index 0000000000..b5c36a22a6 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12ipcclient.h @@ -0,0 +1,59 @@ +/* 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. + */ + +#pragma once + +#include +#include "gstd3d12ipc.h" + +G_BEGIN_DECLS + +enum GstD3D12IpcIOMode +{ + GST_D3D12_IPC_IO_COPY, + GST_D3D12_IPC_IO_IMPORT, +}; + +#define GST_TYPE_D3D12_IPC_IO_MODE (gst_d3d12_ipc_io_mode_get_type ()) +GType gst_d3d12_ipc_io_mode_get_type (void); + +#define GST_TYPE_D3D12_IPC_CLIENT (gst_d3d12_ipc_client_get_type()) +G_DECLARE_FINAL_TYPE (GstD3D12IpcClient, gst_d3d12_ipc_client, + GST, D3D12_IPC_CLIENT, GstObject); + +void gst_d3d12_ipc_client_deinit (void); + +GstD3D12IpcClient * gst_d3d12_ipc_client_new (const std::string & address, + GstD3D12Device * device, + GstD3D12IpcIOMode io_mode, + guint timeout); + +GstFlowReturn gst_d3d12_ipc_client_get_sample (GstD3D12IpcClient * client, + GstSample ** sample); + +void gst_d3d12_ipc_client_set_flushing (GstD3D12IpcClient * client, + bool flushing); + +GstCaps * gst_d3d12_ipc_client_get_caps (GstD3D12IpcClient * client); + +GstFlowReturn gst_d3d12_ipc_client_run (GstD3D12IpcClient * client); + +void gst_d3d12_ipc_client_stop (GstD3D12IpcClient * client); + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12ipcserver.cpp b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12ipcserver.cpp new file mode 100644 index 0000000000..19ed470b9f --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12ipcserver.cpp @@ -0,0 +1,870 @@ +/* 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 "gstd3d12ipcserver.h" +#include +#include +#include +#include +#include +#include + +GST_DEBUG_CATEGORY_STATIC (gst_d3d12_ipc_server_debug); +#define GST_CAT_DEFAULT gst_d3d12_ipc_server_debug + +/* *INDENT-OFF* */ +using namespace Microsoft::WRL; + +struct GstD3D12IpcServerData +{ + ~GstD3D12IpcServerData () + { + if (sample) + gst_sample_unref (sample); + } + + GstSample *sample = nullptr; + HANDLE handle = nullptr; + guint64 fence_val = 0; + GstD3D12IpcMemLayout layout; + GstClockTime pts; + guint64 seq_num; +}; + +struct GstD3D12IpcServerConn : public OVERLAPPED +{ + GstD3D12IpcServerConn (HANDLE pipe_handle) : pipe (pipe_handle) + { + OVERLAPPED *parent = static_cast (this); + parent->Internal = 0; + parent->InternalHigh = 0; + parent->Offset = 0; + parent->OffsetHigh = 0; + + client_msg.resize (GST_D3D12_IPC_PKT_HEADER_SIZE); + server_msg.resize (GST_D3D12_IPC_PKT_HEADER_SIZE); + } + + ~GstD3D12IpcServerConn() + { + if (pipe != INVALID_HANDLE_VALUE) { + CancelIo (pipe); + DisconnectNamedPipe (pipe); + CloseHandle (pipe); + } + + gst_clear_caps (&caps); + } + + GstD3D12IpcServer *server; + + HANDLE pipe; + + GstD3D12IpcPktType type; + std::vector client_msg; + std::vector server_msg; + std::shared_ptr data; + std::vector> peer_handles; + GstCaps *caps = nullptr; + guint64 seq_num = 0; + guint id; + bool eos = false; + bool pending_have_data = false; + bool configured = false; +}; + +struct GstD3D12IpcServerPrivate +{ + GstD3D12IpcServerPrivate () + { + cancellable = CreateEvent (nullptr, TRUE, FALSE, nullptr); + wakeup_event = CreateEvent (nullptr, FALSE, FALSE, nullptr); + + shutdown = false; + aborted = false; + } + + ~GstD3D12IpcServerPrivate () + { + CloseHandle (cancellable); + CloseHandle (wakeup_event); + if (fence_handle) + CloseHandle (fence_handle); + gst_clear_object (&device); + } + + GstD3D12Device *device = nullptr; + gint64 adapter_luid = 0; + std::mutex lock; + guint64 seq_num = 0; + guint next_conn_id = 0; + std::unordered_map> conn_map; + GThread *loop_thread = nullptr; + std::atomicshutdown; + std::atomicaborted; + std::shared_ptr data; + std::string address; + HANDLE cancellable; + HANDLE wakeup_event; + DWORD pid; + HANDLE fence_handle = nullptr; +}; +/* *INDENT-ON* */ + +struct _GstD3D12IpcServer +{ + GstObject parent; + + GstD3D12IpcServerPrivate *priv; +}; + +static void gst_d3d12_ipc_server_dispose (GObject * object); +static void gst_d3d12_ipc_server_finalize (GObject * object); +static void gst_d3d12_ipc_server_on_idle (GstD3D12IpcServer * self); +static void gst_d3d12_ipc_server_send_msg (GstD3D12IpcServer * self, + GstD3D12IpcServerConn * conn); +static void gst_d3d12_ipc_server_wait_msg (GstD3D12IpcServer * self, + GstD3D12IpcServerConn * conn); + +#define gst_d3d12_ipc_server_parent_class parent_class +G_DEFINE_TYPE (GstD3D12IpcServer, gst_d3d12_ipc_server, GST_TYPE_OBJECT); + +static void +gst_d3d12_ipc_server_class_init (GstD3D12IpcServerClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gst_d3d12_ipc_server_finalize; + object_class->dispose = gst_d3d12_ipc_server_dispose; + + GST_DEBUG_CATEGORY_INIT (gst_d3d12_ipc_server_debug, "d3d12ipcserver", + 0, "d3d12ipcserver"); +} + +static void +gst_d3d12_ipc_server_init (GstD3D12IpcServer * self) +{ + self->priv = new GstD3D12IpcServerPrivate (); + self->priv->pid = GetCurrentProcessId (); +} + +static void +gst_d3d12_ipc_server_dispose (GObject * object) +{ + auto self = GST_D3D12_IPC_SERVER (object); + auto priv = self->priv; + + GST_DEBUG_OBJECT (self, "dispose"); + + SetEvent (priv->cancellable); + + g_clear_pointer (&priv->loop_thread, g_thread_join); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_d3d12_ipc_server_finalize (GObject * object) +{ + auto self = GST_D3D12_IPC_SERVER (object); + + GST_DEBUG_OBJECT (self, "finalize"); + + delete self->priv; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static HANDLE +gst_cuda_ipc_server_win32_create_pipe (GstD3D12IpcServer * self, + OVERLAPPED * overlap, bool &io_pending) +{ + auto priv = self->priv; + HANDLE pipe = CreateNamedPipeA (priv->address.c_str (), + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, + PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, + PIPE_UNLIMITED_INSTANCES, 1024, 1024, 5000, nullptr); + + if (pipe == INVALID_HANDLE_VALUE) { + guint last_err = GetLastError (); + auto err = gst_d3d12_ipc_win32_error_to_string (last_err); + GST_ERROR_OBJECT (self, "CreateNamedPipeA failed with 0x%x (%s)", + last_err, err.c_str ()); + return INVALID_HANDLE_VALUE; + } + + if (ConnectNamedPipe (pipe, overlap)) { + guint last_err = GetLastError (); + auto err = gst_d3d12_ipc_win32_error_to_string (last_err); + GST_ERROR_OBJECT (self, "ConnectNamedPipe failed with 0x%x (%s)", + last_err, err.c_str ()); + return INVALID_HANDLE_VALUE; + } + + io_pending = false; + guint last_err = GetLastError (); + + switch (last_err) { + case ERROR_IO_PENDING: + io_pending = true; + break; + case ERROR_PIPE_CONNECTED: + SetEvent (overlap->hEvent); + break; + default: + { + auto err = gst_d3d12_ipc_win32_error_to_string (last_err); + GST_ERROR_OBJECT (self, "ConnectNamedPipe failed with 0x%x (%s)", + last_err, err.c_str ()); + CloseHandle (pipe); + return INVALID_HANDLE_VALUE; + } + } + + return pipe; +} + +static void +gst_d3d12_ipc_server_close_connection (GstD3D12IpcServer * self, + GstD3D12IpcServerConn * conn) +{ + auto priv = self->priv; + + GST_DEBUG_OBJECT (self, "Closing conn-id %u", conn->id); + + priv->conn_map.erase (conn->id); + + if (priv->shutdown && priv->conn_map.empty ()) { + GST_DEBUG_OBJECT (self, "All connection were closed"); + SetEvent (priv->cancellable); + } +} + +static void +gst_d3d12_ipc_server_have_data (GstD3D12IpcServer * self, + GstD3D12IpcServerConn * conn) +{ + GstCaps *caps; + + if (!conn->data) { + GST_ERROR_OBJECT (self, "Have no data to send, conn-id: %u", conn->id); + gst_d3d12_ipc_server_close_connection (self, conn); + return; + } + + conn->pending_have_data = false; + conn->seq_num = conn->data->seq_num + 1; + + caps = gst_sample_get_caps (conn->data->sample); + if (!conn->caps || !gst_caps_is_equal (conn->caps, caps)) { + GST_DEBUG_OBJECT (self, "Sending caps %" GST_PTR_FORMAT " to conn-id %u", + caps, conn->id); + gst_caps_replace (&conn->caps, caps); + } else { + caps = nullptr; + } + + GST_LOG_OBJECT (self, "Sending HAVE-DATA with handle \"%p\", conn-id :%u", + conn->data->handle, conn->id); + + if (!gst_d3d12_ipc_pkt_build_have_data (conn->server_msg, conn->data->pts, + conn->data->layout, conn->data->handle, conn->data->fence_val, + caps)) { + GST_ERROR_OBJECT (self, "Couldn't build HAVE-DATA pkt, conn-id: %u", + conn->id); + gst_d3d12_ipc_server_close_connection (self, conn); + return; + } + + conn->type = GstD3D12IpcPktType::HAVE_DATA; + gst_d3d12_ipc_server_send_msg (self, conn); +} + +static bool +gst_d3d12_ipc_server_on_release_data (GstD3D12IpcServer * self, + GstD3D12IpcServerConn * conn) +{ + bool found = false; + HANDLE handle = nullptr; + + if (!gst_d3d12_ipc_pkt_parse_release_data (conn->client_msg, handle)) { + GST_ERROR_OBJECT (self, "Couldn't parse RELEASE-DATA, conn-id: %u", + conn->id); + return false; + } + + GST_LOG_OBJECT (self, "RELEASE-DATA \"%p\", conn-id: %u", handle, conn->id); + + for (auto it = conn->peer_handles.begin (); it != conn->peer_handles.end (); + it++) { + auto other = (*it)->handle; + if (handle == other) { + found = true; + conn->peer_handles.erase (it); + break; + } + } + + if (!found) { + GST_WARNING_OBJECT (self, + "Unexpected name to remove, conn-id: %u", conn->id); + return false; + } + + GST_LOG_OBJECT (self, "Client is holding %" G_GSIZE_FORMAT " handles", + conn->peer_handles.size ()); + + return true; +} + +static void +gst_d3d12_ipc_server_wait_msg_finish (GstD3D12IpcServer * server, + GstD3D12IpcServerConn * conn) +{ + GstD3D12IpcPacketHeader header; + + if (!gst_d3d12_ipc_pkt_identify (conn->client_msg, header)) { + GST_ERROR_OBJECT (server, "Broken header, conn-id: %u", conn->id); + gst_d3d12_ipc_server_close_connection (server, conn); + return; + } + + switch (header.type) { + case GstD3D12IpcPktType::NEED_DATA: + GST_LOG_OBJECT (server, "NEED-DATA, conn-id: %u", conn->id); + if (!conn->data) { + GST_LOG_OBJECT (server, "Wait for available data, conn-id: %u", + conn->id); + conn->pending_have_data = true; + gst_d3d12_ipc_server_on_idle (server); + return; + } + gst_d3d12_ipc_server_have_data (server, conn); + break; + case GstD3D12IpcPktType::READ_DONE: + GST_LOG_OBJECT (server, "READ-DONE, conn-id: %u", conn->id); + + if (!conn->data) { + GST_ERROR_OBJECT (server, "Unexpected READ-DATA, conn-id: %u", + conn->id); + gst_d3d12_ipc_server_close_connection (server, conn); + return; + } + + conn->peer_handles.push_back (conn->data); + conn->data = nullptr; + gst_d3d12_ipc_server_wait_msg (server, conn); + break; + case GstD3D12IpcPktType::RELEASE_DATA: + GST_LOG_OBJECT (server, "RELEASE-DATA, conn-id: %u", conn->id); + if (!gst_d3d12_ipc_server_on_release_data (server, conn)) + gst_d3d12_ipc_server_close_connection (server, conn); + else + gst_d3d12_ipc_server_wait_msg (server, conn); + break; + case GstD3D12IpcPktType::FIN: + GST_DEBUG_OBJECT (server, "FIN, conn-id %u", conn->id); + gst_d3d12_ipc_server_close_connection (server, conn); + break; + default: + GST_ERROR_OBJECT (server, "Unexpected packet, conn-id: %u", conn->id); + gst_d3d12_ipc_server_close_connection (server, conn); + break; + } +} + +static void WINAPI +gst_d3d12_ipc_server_payload_finish (DWORD error_code, DWORD size, + OVERLAPPED * overlap) +{ + GstD3D12IpcServerConn *conn = + static_cast < GstD3D12IpcServerConn * >(overlap); + auto self = conn->server; + + if (error_code != ERROR_SUCCESS) { + auto err = gst_d3d12_ipc_win32_error_to_string (error_code); + GST_WARNING_OBJECT (self, "ReadFileEx callback failed with 0x%x (%s)", + (guint) error_code, err.c_str ()); + gst_d3d12_ipc_server_close_connection (self, conn); + return; + } + + gst_d3d12_ipc_server_wait_msg_finish (self, conn); +} + +static void WINAPI +gst_d3d12_ipc_server_wait_msg_header_finish (DWORD error_code, DWORD size, + OVERLAPPED * overlap) +{ + GstD3D12IpcServerConn *conn = + static_cast < GstD3D12IpcServerConn * >(overlap); + auto self = conn->server; + GstD3D12IpcPacketHeader header; + + if (error_code != ERROR_SUCCESS) { + auto err = gst_d3d12_ipc_win32_error_to_string (error_code); + GST_WARNING_OBJECT (self, "ReadFileEx callback failed with 0x%x (%s)", + (guint) error_code, err.c_str ()); + gst_d3d12_ipc_server_close_connection (self, conn); + return; + } + + if (!gst_d3d12_ipc_pkt_identify (conn->client_msg, header)) { + GST_ERROR_OBJECT (self, "Broken header"); + gst_d3d12_ipc_server_close_connection (self, conn); + return; + } + + if (header.payload_size == 0) { + gst_d3d12_ipc_server_wait_msg_finish (conn->server, conn); + return; + } + + GST_LOG_OBJECT (self, "Reading payload"); + + if (!ReadFileEx (conn->pipe, &conn->client_msg[0] + + GST_D3D12_IPC_PKT_HEADER_SIZE, header.payload_size, conn, + gst_d3d12_ipc_server_payload_finish)) { + guint last_err = GetLastError (); + auto err = gst_d3d12_ipc_win32_error_to_string (last_err); + GST_WARNING_OBJECT (self, "ReadFileEx failed with 0x%x (%s)", + last_err, err.c_str ()); + gst_d3d12_ipc_server_close_connection (self, conn); + } +} + +static void +gst_d3d12_ipc_server_wait_msg (GstD3D12IpcServer * self, + GstD3D12IpcServerConn * conn) +{ + if (!ReadFileEx (conn->pipe, &conn->client_msg[0], + GST_D3D12_IPC_PKT_HEADER_SIZE, conn, + gst_d3d12_ipc_server_wait_msg_header_finish)) { + guint last_err = GetLastError (); + auto err = gst_d3d12_ipc_win32_error_to_string (last_err); + GST_WARNING_OBJECT (self, "ReadFileEx failed with 0x%x (%s)", + last_err, err.c_str ()); + gst_d3d12_ipc_server_close_connection (self, conn); + } +} + +static void +gst_d3d12_ipc_server_eos (GstD3D12IpcServer * self, + GstD3D12IpcServerConn * conn) +{ + gst_d3d12_ipc_pkt_build_eos (conn->server_msg); + conn->eos = true; + conn->type = GstD3D12IpcPktType::EOS; + + gst_d3d12_ipc_server_send_msg (self, conn); +} + +static void +gst_d3d12_ipc_server_config_data (GstD3D12IpcServer * self, + GstD3D12IpcServerConn * conn) +{ + auto priv = self->priv; + GstCaps *caps = gst_sample_get_caps (conn->data->sample); + + gst_caps_replace (&conn->caps, caps); + + gst_d3d12_ipc_pkt_build_config (conn->server_msg, + priv->pid, priv->adapter_luid, priv->fence_handle, conn->caps); + conn->type = GstD3D12IpcPktType::CONFIG; + + GST_LOG_OBJECT (self, "Sending CONFIG, conn-id %u", conn->id); + gst_d3d12_ipc_server_send_msg (self, conn); +} + +static void +gst_d3d12_ipc_server_on_idle (GstD3D12IpcServer * self) +{ + auto priv = self->priv; + + GST_LOG_OBJECT (self, "idle"); + + if (priv->shutdown) { + GST_DEBUG_OBJECT (self, "We are stopping"); + + if (priv->conn_map.empty ()) { + GST_DEBUG_OBJECT (self, "All connections were closed"); + SetEvent (priv->cancellable); + return; + } + + std::vector < std::shared_ptr < GstD3D12IpcServerConn >> to_send_eos; + /* *INDENT-OFF* */ + for (auto it : priv->conn_map) { + auto conn = it.second; + if (conn->eos || !conn->pending_have_data) + continue; + + to_send_eos.push_back (conn); + } + + for (auto it : to_send_eos) { + GST_DEBUG_OBJECT (self, "Sending EOS to conn-id: %u", it->id); + gst_d3d12_ipc_server_eos (self, it.get ()); + } + + GST_DEBUG_OBJECT (self, "Have %" G_GSIZE_FORMAT " alive connections", + priv->conn_map.size()); + + size_t num_closed = 0; + for (auto it : priv->conn_map) { + auto conn = it.second; + GST_DEBUG_OBJECT (self, "conn-id %u" + " peer handle size %" G_GSIZE_FORMAT, conn->id, + conn->peer_handles.size ()); + + /* Cannot erase conn since it's still referenced. + * Manually close connection */ + if (conn->peer_handles.empty ()) { + if (conn->pipe != INVALID_HANDLE_VALUE) { + CancelIo (conn->pipe); + DisconnectNamedPipe (conn->pipe); + CloseHandle (conn->pipe); + conn->pipe = INVALID_HANDLE_VALUE; + } + + num_closed++; + } + } + /* *INDENT-ON* */ + + if (priv->conn_map.size () == num_closed) { + GST_DEBUG_OBJECT (self, "All connections were closed"); + SetEvent (priv->cancellable); + } + + return; + } + + if (priv->conn_map.empty ()) { + GST_LOG_OBJECT (self, "Have no connection"); + return; + } + + std::unique_lock < std::mutex > lk (priv->lock); + if (!priv->data) + return; + + /* *INDENT-OFF* */ + std::vector < std::shared_ptr < GstD3D12IpcServerConn >> to_config_data; + std::vector < std::shared_ptr < GstD3D12IpcServerConn >> to_send_have_data; + for (auto it : priv->conn_map) { + auto conn = it.second; + if (!conn->configured) { + conn->configured = true; + conn->data = priv->data; + to_config_data.push_back (conn); + } else if (conn->pending_have_data && conn->seq_num <= priv->data->seq_num) { + conn->data = priv->data; + to_send_have_data.push_back (conn); + } + } + lk.unlock (); + + for (auto it: to_config_data) + gst_d3d12_ipc_server_config_data (self, it.get ()); + + for (auto it: to_send_have_data) + gst_d3d12_ipc_server_have_data (self, it.get ()); + /* *INDENT-ON* */ +} + +static void WINAPI +gst_d3d12_ipc_server_send_msg_finish (DWORD error_code, DWORD size, + OVERLAPPED * overlap) +{ + GstD3D12IpcServerConn *conn = + static_cast < GstD3D12IpcServerConn * >(overlap); + auto self = conn->server; + + if (error_code != ERROR_SUCCESS) { + auto err = gst_d3d12_ipc_win32_error_to_string (error_code); + GST_WARNING_OBJECT (self, "ReadFileEx callback failed with 0x%x (%s)", + (guint) error_code, err.c_str ()); + gst_d3d12_ipc_server_close_connection (self, conn); + return; + } + + GST_LOG_OBJECT (self, "Sent message"); + + switch (conn->type) { + case GstD3D12IpcPktType::CONFIG: + GST_DEBUG_OBJECT (self, "Sent CONFIG-DATA, conn-id %u", conn->id); + gst_d3d12_ipc_server_wait_msg (self, conn); + break; + case GstD3D12IpcPktType::HAVE_DATA: + GST_LOG_OBJECT (self, "Sent HAVE-DATA, conn-id %u", conn->id); + gst_d3d12_ipc_server_wait_msg (self, conn); + break; + case GstD3D12IpcPktType::EOS: + GST_DEBUG_OBJECT (self, "Sent EOS, conn-id %u", conn->id); + gst_d3d12_ipc_server_wait_msg (self, conn); + break; + default: + GST_ERROR_OBJECT (self, "Unexpected msg type"); + gst_d3d12_ipc_server_close_connection (self, conn); + break; + } +} + +static void +gst_d3d12_ipc_server_send_msg (GstD3D12IpcServer * self, + GstD3D12IpcServerConn * conn) +{ + GST_LOG_OBJECT (self, "Sending message"); + + if (!WriteFileEx (conn->pipe, &conn->server_msg[0], + conn->server_msg.size (), conn, + gst_d3d12_ipc_server_send_msg_finish)) { + guint last_err = GetLastError (); + auto err = gst_d3d12_ipc_win32_error_to_string (last_err); + GST_WARNING_OBJECT (self, "WriteFileEx failed with 0x%x (%s)", + last_err, err.c_str ()); + gst_d3d12_ipc_server_close_connection (self, conn); + } +} + +static void +gst_d3d12_ipc_server_on_incoming_connection (GstD3D12IpcServer * self, + std::shared_ptr < GstD3D12IpcServerConn > conn) +{ + auto priv = self->priv; + + priv->lock.lock (); + conn->server = self; + conn->id = priv->next_conn_id; + conn->data = priv->data; + priv->next_conn_id++; + priv->lock.unlock (); + + GST_DEBUG_OBJECT (self, "New connection, conn-id: %u", conn->id); + + /* *INDENT-OFF* */ + priv->conn_map.insert ({conn->id, conn}); + /* *INDENT-ON* */ + + if (conn->data) { + conn->configured = true; + gst_d3d12_ipc_server_config_data (self, conn.get ()); + } else { + GST_DEBUG_OBJECT (self, "Have no config data yet, waiting for data"); + } +} + +static gpointer +gst_d3d12_ipc_server_loop_thread_func (GstD3D12IpcServer * self) +{ + auto priv = self->priv; + bool io_pending = false; + guint wait_ret; + HANDLE pipe; + OVERLAPPED overlap; + HANDLE waitables[3]; + + GST_DEBUG_OBJECT (self, "Entering loop"); + + memset (&overlap, 0, sizeof (OVERLAPPED)); + + overlap.hEvent = CreateEvent (nullptr, TRUE, TRUE, nullptr); + pipe = gst_cuda_ipc_server_win32_create_pipe (self, &overlap, io_pending); + if (pipe == INVALID_HANDLE_VALUE) { + CloseHandle (overlap.hEvent); + priv->aborted = true; + goto out; + } + + waitables[0] = overlap.hEvent; + waitables[1] = priv->wakeup_event; + waitables[2] = priv->cancellable; + + do { + wait_ret = WaitForMultipleObjectsEx (G_N_ELEMENTS (waitables), waitables, + FALSE, INFINITE, TRUE); + + if (wait_ret == WAIT_OBJECT_0 + 2) { + GST_DEBUG_OBJECT (self, "Operation cancelled"); + goto out; + } + + switch (wait_ret) { + case WAIT_OBJECT_0: + { + DWORD n_bytes; + + if (io_pending + && !GetOverlappedResult (pipe, &overlap, &n_bytes, FALSE)) { + guint last_err = GetLastError (); + auto err = gst_d3d12_ipc_win32_error_to_string (last_err); + GST_WARNING_OBJECT (self, "GetOverlappedResult failed with 0x%x (%s)", + last_err, err.c_str ()); + CloseHandle (pipe); + pipe = INVALID_HANDLE_VALUE; + break; + } + + auto conn = std::make_shared < GstD3D12IpcServerConn > (pipe); + conn->server = self; + pipe = INVALID_HANDLE_VALUE; + gst_d3d12_ipc_server_on_incoming_connection (self, conn); + + pipe = gst_cuda_ipc_server_win32_create_pipe (self, + &overlap, io_pending); + break; + } + case WAIT_IO_COMPLETION: + break; + case WAIT_OBJECT_0 + 1: + gst_d3d12_ipc_server_on_idle (self); + break; + default: + { + guint last_err = GetLastError (); + auto err = gst_d3d12_ipc_win32_error_to_string (last_err); + GST_ERROR_OBJECT (self, + "WaitForMultipleObjectsEx return 0x%x, last error 0x%x (%s)", + wait_ret, last_err, err.c_str ()); + priv->aborted = true; + goto out; + } + } + } while (true); + +out: + if (pipe != INVALID_HANDLE_VALUE) { + CancelIo (pipe); + DisconnectNamedPipe (pipe); + CloseHandle (pipe); + } + + CloseHandle (overlap.hEvent); + + priv->conn_map.clear (); + + GST_DEBUG_OBJECT (self, "Exit loop thread"); + + return nullptr; +} + +GstFlowReturn +gst_d3d12_ipc_server_send_data (GstD3D12IpcServer * server, GstSample * sample, + const GstD3D12IpcMemLayout & layout, HANDLE handle, GstClockTime pts) +{ + GstD3D12IpcServerPrivate *priv; + + g_return_val_if_fail (GST_IS_D3D12_IPC_SERVER (server), GST_FLOW_ERROR); + g_return_val_if_fail (GST_IS_SAMPLE (sample), GST_FLOW_ERROR); + + priv = server->priv; + + GST_LOG_OBJECT (server, "Sending data"); + + std::unique_lock < std::mutex > lk (priv->lock); + if (priv->aborted) { + GST_DEBUG_OBJECT (server, "Was aborted"); + return GST_FLOW_ERROR; + } + + auto buffer = gst_sample_get_buffer (sample); + auto dmem = (GstD3D12Memory *) gst_buffer_peek_memory (buffer, 0); + + auto data = std::make_shared < GstD3D12IpcServerData > (); + data->sample = gst_sample_ref (sample); + data->handle = handle; + data->layout = layout; + data->pts = pts; + data->fence_val = dmem->fence_value; + data->seq_num = priv->seq_num; + + priv->seq_num++; + priv->data = data; + lk.unlock (); + + SetEvent (priv->wakeup_event); + + return GST_FLOW_OK; +} + +void +gst_d3d12_ipc_server_stop (GstD3D12IpcServer * server) +{ + GstD3D12IpcServerPrivate *priv; + + g_return_if_fail (GST_IS_D3D12_IPC_SERVER (server)); + + priv = server->priv; + + GST_DEBUG_OBJECT (server, "Stopping"); + priv->shutdown = true; + SetEvent (priv->wakeup_event); + + g_clear_pointer (&priv->loop_thread, g_thread_join); + + GST_DEBUG_OBJECT (server, "Stopped"); +} + +GstD3D12IpcServer * +gst_d3d12_ipc_server_new (const std::string & address, gint64 adapter_luid, + ID3D12Fence * fence) +{ + ComPtr < ID3D12Device > device; + + auto hr = fence->GetDevice (IID_PPV_ARGS (&device)); + if (FAILED (hr)) { + GST_ERROR ("Couldn't get device handle"); + return nullptr; + } + + HANDLE fence_handle; + hr = device->CreateSharedHandle (fence, nullptr, GENERIC_ALL, nullptr, + &fence_handle); + if (FAILED (hr)) { + GST_ERROR ("Couldn't open shared handle"); + return nullptr; + } + + auto self = (GstD3D12IpcServer *) + g_object_new (GST_TYPE_D3D12_IPC_SERVER, nullptr); + gst_object_ref_sink (self); + + auto priv = self->priv; + priv->address = address; + priv->adapter_luid = adapter_luid; + priv->fence_handle = fence_handle; + + priv->loop_thread = g_thread_new ("d3d12-ipc-server", + (GThreadFunc) gst_d3d12_ipc_server_loop_thread_func, self); + + return self; +} + +gint64 +gst_d3d12_ipc_server_get_adapter_luid (GstD3D12IpcServer * server) +{ + g_return_val_if_fail (GST_IS_D3D12_IPC_SERVER (server), 0); + + return server->priv->adapter_luid; +} diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12ipcserver.h b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12ipcserver.h new file mode 100644 index 0000000000..2b60161417 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12ipcserver.h @@ -0,0 +1,45 @@ +/* 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. + */ + +#pragma once + +#include +#include "gstd3d12ipc.h" + +G_BEGIN_DECLS + +#define GST_TYPE_D3D12_IPC_SERVER (gst_d3d12_ipc_server_get_type()) +G_DECLARE_FINAL_TYPE (GstD3D12IpcServer, gst_d3d12_ipc_server, + GST, D3D12_IPC_SERVER, GstObject); + +GstD3D12IpcServer * gst_d3d12_ipc_server_new (const std::string & address, + gint64 adapter_luid, + ID3D12Fence * fence); + +GstFlowReturn gst_d3d12_ipc_server_send_data (GstD3D12IpcServer * server, + GstSample * sample, + const GstD3D12IpcMemLayout & layout, + HANDLE handle, + GstClockTime pts); + +void gst_d3d12_ipc_server_stop (GstD3D12IpcServer * server); + +gint64 gst_d3d12_ipc_server_get_adapter_luid (GstD3D12IpcServer * server); + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12ipcsink.cpp b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12ipcsink.cpp new file mode 100644 index 0000000000..7ec8820e66 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12ipcsink.cpp @@ -0,0 +1,706 @@ +/* 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. + */ + +/** + * SECTION:element-d3d12ipcsink + * @title: d3d12ipcsink + * @short_description: Direct3D12 Inter Process Communication (IPC) sink + * + * d3d12ipcsink exports Direct3D12 texture for connected d3d12ipcsrc elements + * to be able to import it + * + * ## Example launch line + * ``` + * gst-launch-1.0 videotestsrc ! d3d12upload ! d3d12ipcsink + * ``` + * + * Since: 1.26 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstd3d12ipcsink.h" +#include "gstd3d12ipcserver.h" +#include "gstd3d12pluginutils.h" +#include +#include +#include +#include + +/* *INDENT-OFF* */ +using namespace Microsoft::WRL; +/* *INDENT-ON* */ + +GST_DEBUG_CATEGORY_STATIC (gst_d3d12_ipc_sink_debug); +#define GST_CAT_DEFAULT gst_d3d12_ipc_sink_debug + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES + (GST_CAPS_FEATURE_MEMORY_D3D12_MEMORY, GST_D3D12_IPC_FORMATS) ";" + GST_VIDEO_CAPS_MAKE (GST_D3D12_IPC_FORMATS))); + +enum +{ + PROP_0, + PROP_ADAPTER, + PROP_PIPE_NAME, +}; + +#define DEFAULT_ADAPTER -1 +#define DEFAULT_PIPE_NAME "\\\\.\\pipe\\gst.d3d12.ipc" + +/* *INDENT-OFF* */ +struct GstD3D12IpcSinkPrivate +{ + GstD3D12Device *device = nullptr; + + GstBufferPool *fallback_pool = nullptr; + GstVideoInfo info; + + GstD3D12IpcServer *server = nullptr; + GstCaps *caps = nullptr; + GstSample *prepared_sample = nullptr; + HANDLE prepared_handle = nullptr; + GstD3D12IpcMemLayout layout; + + std::mutex lock; + + /* properties */ + gint adapter = DEFAULT_ADAPTER; + std::string pipe_name = DEFAULT_PIPE_NAME; +}; +/* *INDENT-ON* */ + +struct _GstD3D12IpcSink +{ + GstBaseSink parent; + + GstD3D12IpcSinkPrivate *priv; +}; + +static void gst_d3d12_ipc_sink_finalize (GObject * object); +static void gst_d3d12_ipc_sink_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec); +static void gst_win32_video_sink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static GstClock *gst_d3d12_ipc_sink_provide_clock (GstElement * elem); +static void gst_d3d12_ipc_sink_set_context (GstElement * elem, + GstContext * context); + +static gboolean gst_d3d12_ipc_sink_start (GstBaseSink * sink); +static gboolean gst_d3d12_ipc_sink_stop (GstBaseSink * sink); +static gboolean gst_d3d12_ipc_sink_set_caps (GstBaseSink * sink, + GstCaps * caps); +static void gst_d3d12_ipc_sink_get_time (GstBaseSink * sink, GstBuffer * buf, + GstClockTime * start, GstClockTime * end); +static gboolean gst_d3d12_ipc_sink_propose_allocation (GstBaseSink * sink, + GstQuery * query); +static gboolean gst_d3d12_ipc_sink_query (GstBaseSink * sink, GstQuery * query); +static GstFlowReturn gst_d3d12_ipc_sink_prepare (GstBaseSink * sink, + GstBuffer * buf); +static GstFlowReturn gst_d3d12_ipc_sink_render (GstBaseSink * sink, + GstBuffer * buf); + +#define gst_d3d12_ipc_sink_parent_class parent_class +G_DEFINE_TYPE (GstD3D12IpcSink, gst_d3d12_ipc_sink, GST_TYPE_BASE_SINK); + +static void +gst_d3d12_ipc_sink_class_init (GstD3D12IpcSinkClass * klass) +{ + auto object_class = G_OBJECT_CLASS (klass); + auto element_class = GST_ELEMENT_CLASS (klass); + auto sink_class = GST_BASE_SINK_CLASS (klass); + + object_class->finalize = gst_d3d12_ipc_sink_finalize; + object_class->set_property = gst_d3d12_ipc_sink_set_property; + object_class->get_property = gst_win32_video_sink_get_property; + + g_object_class_install_property (object_class, PROP_ADAPTER, + g_param_spec_int ("adapter", "Adapter", + "DXGI adapter index (-1 for default)", + -1, G_MAXINT32, DEFAULT_ADAPTER, + (GParamFlags) (G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY | + G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property (object_class, PROP_PIPE_NAME, + g_param_spec_string ("pipe-name", "Pipe Name", + "The name of Win32 named pipe to communicate with clients. " + "Validation of the pipe name is caller's responsibility", + DEFAULT_PIPE_NAME, (GParamFlags) (G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS | GST_PARAM_MUTABLE_READY))); + + gst_element_class_set_static_metadata (element_class, + "Direct3D12 IPC Sink", "Sink/Video", + "Sends Direct3D12 shared handle to peer d3d12ipcsrc elements", + "Seungha Yang "); + + gst_element_class_add_static_pad_template (element_class, &sink_template); + + element_class->provide_clock = + GST_DEBUG_FUNCPTR (gst_d3d12_ipc_sink_provide_clock); + element_class->set_context = + GST_DEBUG_FUNCPTR (gst_d3d12_ipc_sink_set_context); + + sink_class->start = GST_DEBUG_FUNCPTR (gst_d3d12_ipc_sink_start); + sink_class->stop = GST_DEBUG_FUNCPTR (gst_d3d12_ipc_sink_stop); + sink_class->set_caps = GST_DEBUG_FUNCPTR (gst_d3d12_ipc_sink_set_caps); + sink_class->propose_allocation = + GST_DEBUG_FUNCPTR (gst_d3d12_ipc_sink_propose_allocation); + sink_class->query = GST_DEBUG_FUNCPTR (gst_d3d12_ipc_sink_query); + sink_class->get_times = GST_DEBUG_FUNCPTR (gst_d3d12_ipc_sink_get_time); + sink_class->prepare = GST_DEBUG_FUNCPTR (gst_d3d12_ipc_sink_prepare); + sink_class->render = GST_DEBUG_FUNCPTR (gst_d3d12_ipc_sink_render); + + GST_DEBUG_CATEGORY_INIT (gst_d3d12_ipc_sink_debug, "d3d12ipcsink", + 0, "d3d12ipcsink"); +} + +static void +gst_d3d12_ipc_sink_init (GstD3D12IpcSink * self) +{ + self->priv = new GstD3D12IpcSinkPrivate (); + + GST_OBJECT_FLAG_SET (self, GST_ELEMENT_FLAG_PROVIDE_CLOCK); + GST_OBJECT_FLAG_SET (self, GST_ELEMENT_FLAG_REQUIRE_CLOCK); +} + +static void +gst_d3d12_ipc_sink_finalize (GObject * object) +{ + auto self = GST_D3D12_IPC_SINK (object); + + delete self->priv; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_d3d12_ipc_sink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + auto self = GST_D3D12_IPC_SINK (object); + auto priv = self->priv; + std::lock_guard < std::mutex > lk (priv->lock); + + switch (prop_id) { + case PROP_ADAPTER: + priv->adapter = g_value_get_int (value); + break; + case PROP_PIPE_NAME: + { + const gchar *pipe_name = g_value_get_string (value); + priv->pipe_name.clear (); + + if (pipe_name) + priv->pipe_name = pipe_name; + else + priv->pipe_name = DEFAULT_PIPE_NAME; + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_win32_video_sink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + auto self = GST_D3D12_IPC_SINK (object); + auto priv = self->priv; + std::lock_guard < std::mutex > lk (priv->lock); + + switch (prop_id) { + case PROP_ADAPTER: + g_value_set_int (value, priv->adapter); + break; + case PROP_PIPE_NAME: + g_value_set_string (value, priv->pipe_name.c_str ()); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GstClock * +gst_d3d12_ipc_sink_provide_clock (GstElement * elem) +{ + return gst_system_clock_obtain (); +} + +static void +gst_d3d12_ipc_sink_set_context (GstElement * elem, GstContext * context) +{ + auto self = GST_D3D12_IPC_SINK (elem); + auto priv = self->priv; + + gst_d3d12_handle_set_context (elem, context, priv->adapter, &priv->device); + + GST_ELEMENT_CLASS (parent_class)->set_context (elem, context); +} + +static gboolean +gst_d3d12_ipc_sink_start (GstBaseSink * sink) +{ + auto self = GST_D3D12_IPC_SINK (sink); + auto priv = self->priv; + + GST_DEBUG_OBJECT (self, "Start"); + + if (!gst_d3d12_ensure_element_data (GST_ELEMENT_CAST (self), priv->adapter, + &priv->device)) { + GST_ERROR_OBJECT (sink, "Cannot create d3d12device"); + return FALSE; + } + + return TRUE; +} + +static gboolean +gst_d3d12_ipc_sink_stop (GstBaseSink * sink) +{ + auto self = GST_D3D12_IPC_SINK (sink); + auto priv = self->priv; + + GST_DEBUG_OBJECT (self, "Stop"); + + if (priv->server) + gst_d3d12_ipc_server_stop (priv->server); + gst_clear_object (&priv->server); + + GST_DEBUG_OBJECT (self, "Server cleared"); + + if (priv->fallback_pool) { + gst_buffer_pool_set_active (priv->fallback_pool, FALSE); + gst_clear_object (&priv->fallback_pool); + } + + gst_clear_sample (&priv->prepared_sample); + gst_clear_object (&priv->device); + + return TRUE; +} + +static void +gst_d3d12_ipc_sink_get_time (GstBaseSink * sink, GstBuffer * buf, + GstClockTime * start, GstClockTime * end) +{ + auto self = GST_D3D12_IPC_SINK (sink); + auto priv = self->priv; + GstClockTime timestamp; + + timestamp = GST_BUFFER_PTS (buf); + if (!GST_CLOCK_TIME_IS_VALID (timestamp)) + timestamp = GST_BUFFER_DTS (buf); + + if (GST_CLOCK_TIME_IS_VALID (timestamp)) { + *start = timestamp; + if (GST_BUFFER_DURATION_IS_VALID (buf)) { + *end = timestamp + GST_BUFFER_DURATION (buf); + } else if (priv->info.fps_n > 0) { + *end = timestamp + + gst_util_uint64_scale_int (GST_SECOND, priv->info.fps_d, + priv->info.fps_n); + } else if (sink->segment.rate < 0) { + *end = timestamp; + } + } +} + +static GstBufferPool * +gst_d3d12_ipc_sink_create_pool (GstD3D12IpcSink * self, + const GstVideoInfo * info, GstCaps * caps) +{ + auto priv = self->priv; + + auto pool = gst_d3d12_buffer_pool_new (priv->device); + auto config = gst_buffer_pool_get_config (pool); + + gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META); + gst_buffer_pool_config_set_params (config, priv->caps, + GST_VIDEO_INFO_SIZE (info), 0, 0); + + auto params = gst_d3d12_allocation_params_new (priv->device, &priv->info, + GST_D3D12_ALLOCATION_FLAG_DEFAULT, + D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS | + D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, D3D12_HEAP_FLAG_SHARED); + + gst_buffer_pool_config_set_d3d12_allocation_params (config, params); + gst_d3d12_allocation_params_free (params); + + if (!gst_buffer_pool_set_config (pool, config)) { + GST_ERROR_OBJECT (self, "Couldn't set pool config"); + gst_object_unref (pool); + return nullptr; + } + + return pool; +} + +static gboolean +gst_d3d12_ipc_sink_set_caps (GstBaseSink * sink, GstCaps * caps) +{ + auto self = GST_D3D12_IPC_SINK (sink); + auto priv = self->priv; + GstCaps *new_caps; + GstStructure *s; + const gchar *str; + + GST_DEBUG_OBJECT (self, "New caps %" GST_PTR_FORMAT, caps); + + if (!gst_video_info_from_caps (&priv->info, caps)) { + GST_ERROR_OBJECT (self, "Invalid caps %" GST_PTR_FORMAT, caps); + return FALSE; + } + + if (priv->fallback_pool) { + gst_buffer_pool_set_active (priv->fallback_pool, FALSE); + gst_clear_object (&priv->fallback_pool); + } + + s = gst_caps_get_structure (caps, 0); + + /* Takes values we know it's always deserializable */ + new_caps = gst_caps_new_empty_simple ("video/x-raw"); + gst_caps_set_simple (new_caps, "format", G_TYPE_STRING, + gst_video_format_to_string (GST_VIDEO_INFO_FORMAT (&priv->info)), + "width", G_TYPE_INT, priv->info.width, + "height", G_TYPE_INT, priv->info.height, + "framerate", GST_TYPE_FRACTION, priv->info.fps_n, priv->info.fps_d, + "pixel-aspect-ratio", GST_TYPE_FRACTION, priv->info.par_n, + priv->info.par_d, nullptr); + + str = gst_structure_get_string (s, "colorimetry"); + if (str) + gst_caps_set_simple (new_caps, "colorimetry", G_TYPE_STRING, str, nullptr); + + str = gst_structure_get_string (s, "mastering-display-info"); + if (str) { + gst_caps_set_simple (new_caps, "mastering-display-info", G_TYPE_STRING, + str, nullptr); + } + + str = gst_structure_get_string (s, "content-light-level"); + if (str) { + gst_caps_set_simple (new_caps, "content-light-level", G_TYPE_STRING, + str, nullptr); + } + + gst_caps_set_features_simple (new_caps, + gst_caps_features_new (GST_CAPS_FEATURE_MEMORY_D3D12_MEMORY, nullptr)); + + gst_clear_caps (&priv->caps); + priv->caps = new_caps; + + return TRUE; +} + +static gboolean +gst_d3d12_ipc_sink_propose_allocation (GstBaseSink * sink, GstQuery * query) +{ + auto self = GST_D3D12_IPC_SINK (sink); + GstCaps *caps; + GstBufferPool *pool = nullptr; + GstVideoInfo info; + guint size; + gboolean need_pool; + + gst_query_parse_allocation (query, &caps, &need_pool); + if (!caps) { + GST_WARNING_OBJECT (sink, "No caps specified"); + return FALSE; + } + + if (!gst_video_info_from_caps (&info, caps)) { + GST_WARNING_OBJECT (sink, "Invalid caps %" GST_PTR_FORMAT, caps); + return FALSE; + } + + /* the normal size of a frame */ + size = info.size; + if (need_pool) { + auto features = gst_caps_get_features (caps, 0); + if (features + && gst_caps_features_contains (features, + GST_CAPS_FEATURE_MEMORY_D3D12_MEMORY)) { + GST_DEBUG_OBJECT (self, "upstream support d3d12 memory"); + pool = gst_d3d12_ipc_sink_create_pool (self, &info, caps); + if (!pool) { + GST_ERROR_OBJECT (self, "Couldn't create pool"); + return FALSE; + } + + auto config = gst_buffer_pool_get_config (pool); + gst_buffer_pool_config_get_params (config, nullptr, &size, nullptr, + nullptr); + gst_structure_free (config); + } else { + pool = gst_video_buffer_pool_new (); + auto config = gst_buffer_pool_get_config (pool); + gst_buffer_pool_config_add_option (config, + GST_BUFFER_POOL_OPTION_VIDEO_META); + gst_buffer_pool_config_add_option (config, + GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT); + + gst_buffer_pool_config_set_params (config, caps, size, 0, 0); + + if (!gst_buffer_pool_set_config (pool, config)) { + GST_ERROR_OBJECT (pool, "Couldn't set config"); + gst_object_unref (pool); + + return FALSE; + } + } + } + + gst_query_add_allocation_pool (query, pool, size, 0, 0); + gst_clear_object (&pool); + + gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL); + + return TRUE; +} + +static gboolean +gst_d3d12_ipc_sink_query (GstBaseSink * sink, GstQuery * query) +{ + auto self = GST_D3D12_IPC_SINK (sink); + auto priv = self->priv; + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_CONTEXT: + if (gst_d3d12_handle_context_query (GST_ELEMENT (self), query, + priv->device)) { + return TRUE; + } + break; + default: + break; + } + + return GST_BASE_SINK_CLASS (parent_class)->query (sink, query); +} + +static GstBuffer * +gst_d3d12_ipc_upload (GstD3D12IpcSink * self, GstBuffer * buf) +{ + auto priv = self->priv; + GstBuffer *uploaded = nullptr; + GstFlowReturn ret; + GstMemory *mem; + + mem = gst_buffer_peek_memory (buf, 0); + if (gst_is_d3d12_memory (mem)) { + auto dmem = GST_D3D12_MEMORY_CAST (mem); + if (dmem->device == priv->device) { + D3D12_RESOURCE_DESC desc; + D3D12_HEAP_FLAGS heap_flags = D3D12_HEAP_FLAG_NONE; + + auto resource = gst_d3d12_memory_get_resource_handle (dmem); + desc = resource->GetDesc (); + resource->GetHeapProperties (nullptr, &heap_flags); + if ((desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS) != 0 && + (heap_flags & D3D12_HEAP_FLAG_SHARED) != 0) { + return gst_buffer_ref (buf); + } + } + } + + if (!priv->fallback_pool) { + priv->fallback_pool = gst_d3d12_ipc_sink_create_pool (self, &priv->info, + priv->caps); + if (!priv->fallback_pool) { + GST_ERROR_OBJECT (self, "Couldn't create fallback pool"); + return nullptr; + } + + if (!gst_buffer_pool_set_active (priv->fallback_pool, TRUE)) { + GST_ERROR_OBJECT (self, "Couldn't active pool"); + gst_clear_object (&priv->fallback_pool); + return nullptr; + } + } + + ret = gst_buffer_pool_acquire_buffer (priv->fallback_pool, + &uploaded, nullptr); + if (ret != GST_FLOW_OK) { + GST_ERROR_OBJECT (self, "Couldn't acquire fallback buffer"); + return nullptr; + } + + if (!gst_d3d12_buffer_copy_into (uploaded, buf, &priv->info)) { + GST_ERROR_OBJECT (self, "Couldn't copy buffer"); + gst_buffer_unref (uploaded); + return nullptr; + } + + return uploaded; +} + +static gboolean +gst_d3d12_ipc_sink_ensure_server (GstD3D12IpcSink * self, GstBuffer * buffer) +{ + auto priv = self->priv; + GstMemory *mem; + gint64 adapter_luid; + + if (priv->server) + return TRUE; + + g_object_get (priv->device, "adapter-luid", &adapter_luid, nullptr); + + mem = gst_buffer_peek_memory (buffer, 0); + if (gst_is_d3d12_memory (mem)) { + GstD3D12Memory *dmem = GST_D3D12_MEMORY_CAST (mem); + if (dmem->device != priv->device) { + g_object_get (dmem->device, "adapter-luid", &adapter_luid, nullptr); + gst_object_unref (priv->device); + priv->device = (GstD3D12Device *) gst_object_ref (dmem->device); + } + } + + auto queue = gst_d3d12_device_get_command_queue (priv->device, + D3D12_COMMAND_LIST_TYPE_DIRECT); + ComPtr < ID3D12Fence > fence; + gst_d3d12_command_queue_get_fence (queue, &fence); + + priv->server = gst_d3d12_ipc_server_new (priv->pipe_name, adapter_luid, + fence.Get ()); + if (!priv->server) { + GST_ERROR_OBJECT (self, "Couldn't create server"); + return FALSE; + } + + return TRUE; +} + +static GstFlowReturn +gst_d3d12_ipc_sink_prepare (GstBaseSink * sink, GstBuffer * buf) +{ + auto self = GST_D3D12_IPC_SINK (sink); + auto priv = self->priv; + GstBuffer *uploaded; + GstD3D12Memory *dmem; + GstVideoFrame frame; + HANDLE nt_handle = nullptr; + + gst_clear_sample (&priv->prepared_sample); + + if (!gst_d3d12_ipc_sink_ensure_server (self, buf)) + return GST_FLOW_ERROR; + + uploaded = gst_d3d12_ipc_upload (self, buf); + if (!uploaded) { + GST_ERROR_OBJECT (self, "Couldn't upload buffer"); + return GST_FLOW_ERROR; + } + + dmem = (GstD3D12Memory *) gst_buffer_peek_memory (uploaded, 0); + + /* Upload staging to device memory */ + if (!gst_video_frame_map (&frame, &priv->info, uploaded, + (GstMapFlags) (GST_MAP_READ | GST_MAP_D3D12))) { + GST_ERROR_OBJECT (self, "Couldn't upload memory"); + gst_buffer_unref (uploaded); + return GST_FLOW_ERROR; + } + + priv->layout.pitch = frame.info.stride[0]; + for (guint i = 0; i < GST_VIDEO_FRAME_N_PLANES (&frame); i++) + priv->layout.offset[i] = frame.info.offset[i]; + + gst_video_frame_unmap (&frame); + + if (!gst_d3d12_memory_get_nt_handle (dmem, &nt_handle)) { + GST_ERROR_OBJECT (self, "Couldn't get NT handle"); + gst_buffer_unref (uploaded); + return GST_FLOW_ERROR; + } + + priv->prepared_sample = gst_sample_new (uploaded, + priv->caps, nullptr, nullptr); + priv->prepared_handle = nt_handle; + + gst_buffer_unref (uploaded); + + return GST_FLOW_OK; +} + +static GstFlowReturn +gst_d3d12_ipc_sink_render (GstBaseSink * sink, GstBuffer * buf) +{ + auto self = GST_D3D12_IPC_SINK (sink); + auto priv = self->priv; + GstClockTime pts; + GstClockTime now_system; + GstClockTime buf_pts; + GstClockTime buffer_clock = GST_CLOCK_TIME_NONE; + GstFlowReturn ret; + + if (!priv->prepared_sample) { + GST_ERROR_OBJECT (self, "Have no prepared sample"); + return GST_FLOW_ERROR; + } + + pts = now_system = gst_util_get_timestamp (); + buf_pts = GST_BUFFER_PTS (buf); + if (!GST_CLOCK_TIME_IS_VALID (buf_pts)) + buf_pts = GST_BUFFER_DTS (buf); + + if (GST_CLOCK_TIME_IS_VALID (buf_pts)) { + buffer_clock = gst_segment_to_running_time (&sink->segment, + GST_FORMAT_TIME, buf_pts) + + GST_ELEMENT_CAST (sink)->base_time + gst_base_sink_get_latency (sink); + } + + if (GST_CLOCK_TIME_IS_VALID (buffer_clock)) { + GstClock *clock = gst_element_get_clock (GST_ELEMENT_CAST (sink)); + if (!gst_d3d12_ipc_clock_is_system (clock)) { + GstClockTime now_gst = gst_clock_get_time (clock); + GstClockTimeDiff converted = buffer_clock; + + converted -= now_gst; + converted += now_system; + + if (converted < 0) { + /* Shouldn't happen */ + GST_WARNING_OBJECT (self, "Negative buffer clock"); + pts = 0; + } else { + pts = converted; + } + } else { + /* buffer clock is already system time */ + pts = buffer_clock; + } + gst_object_unref (clock); + } + + ret = gst_d3d12_ipc_server_send_data (priv->server, priv->prepared_sample, + priv->layout, priv->prepared_handle, pts); + + return ret; +} diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12ipcsink.h b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12ipcsink.h new file mode 100644 index 0000000000..0b3cb86bd3 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12ipcsink.h @@ -0,0 +1,33 @@ +/* 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. + */ + +#pragma once + +#include +#include +#include +#include "gstd3d12.h" + +G_BEGIN_DECLS + +#define GST_TYPE_D3D12_IPC_SINK (gst_d3d12_ipc_sink_get_type()) +G_DECLARE_FINAL_TYPE (GstD3D12IpcSink, gst_d3d12_ipc_sink, + GST, D3D12_IPC_SINK, GstBaseSink); + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12ipcsrc.cpp b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12ipcsrc.cpp new file mode 100644 index 0000000000..3bdf3d315e --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12ipcsrc.cpp @@ -0,0 +1,523 @@ +/* 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. + */ + +/** + * SECTION:element-d3d12ipcsrc + * @title: d3d12ipcsrc + * @short_description: Direct3D12 Inter Process Communication (IPC) src + * + * d3d12ipcsrc imports Direct3D12 texture exported by peer d3d12ipcsrc element + * + * ## Example launch line + * ``` + * gst-launch-1.0 d3d12ipcsrc ! queue ! d3d12videosink + * ``` + * + * Since: 1.26 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstd3d12ipcsrc.h" +#include "gstd3d12ipcclient.h" +#include "gstd3d12pluginutils.h" +#include +#include + +GST_DEBUG_CATEGORY_STATIC (gst_d3d12_ipc_src_debug); +#define GST_CAT_DEFAULT gst_d3d12_ipc_src_debug + +static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES + (GST_CAPS_FEATURE_MEMORY_D3D12_MEMORY, GST_D3D12_IPC_FORMATS))); + +enum +{ + PROP_0, + PROP_PIPE_NAME, + PROP_PROCESSING_DEADLINE, + PROP_IO_MODE, + PROP_CONN_TIMEOUT, +}; + +#define DEFAULT_PIPE_NAME "\\\\.\\pipe\\gst.d3d12.ipc" +#define DEFAULT_PROCESSING_DEADLINE (20 * GST_MSECOND) +#define DEFAULT_IO_MODE GST_D3D12_IPC_IO_COPY +#define DEFAULT_CONN_TIMEOUT 5 + +/* *INDENT-OFF* */ +struct GstD3D12IpcSrcPrivate +{ + GstD3D12Device *device = nullptr; + + GstD3D12IpcClient *client = nullptr; + GstCaps *caps = nullptr; + + GstVideoInfo info; + std::mutex lock; + bool flushing = false; + + /* properties */ + std::string pipe_name = DEFAULT_PIPE_NAME; + GstClockTime processing_deadline = DEFAULT_PROCESSING_DEADLINE; + GstD3D12IpcIOMode io_mode = DEFAULT_IO_MODE; + guint conn_timeout = DEFAULT_CONN_TIMEOUT; +}; +/* *INDENT-ON* */ + +struct _GstD3D12IpcSrc +{ + GstBaseSrc parent; + + GstD3D12IpcSrcPrivate *priv; +}; + +static void gst_d3d12_ipc_src_finalize (GObject * object); +static void gst_d3d12_ipc_src_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec); +static void gst_win32_video_src_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static GstClock *gst_d3d12_ipc_src_provide_clock (GstElement * elem); +static void gst_d3d12_ipc_src_set_context (GstElement * elem, + GstContext * context); + +static gboolean gst_d3d12_ipc_src_start (GstBaseSrc * src); +static gboolean gst_d3d12_ipc_src_stop (GstBaseSrc * src); +static gboolean gst_d3d12_ipc_src_unlock (GstBaseSrc * src); +static gboolean gst_d3d12_ipc_src_unlock_stop (GstBaseSrc * src); +static gboolean gst_d3d12_ipc_src_query (GstBaseSrc * src, GstQuery * query); +static GstCaps *gst_d3d12_ipc_src_get_caps (GstBaseSrc * src, GstCaps * filter); +static GstCaps *gst_d3d12_ipc_src_fixate (GstBaseSrc * src, GstCaps * caps); +static GstFlowReturn gst_d3d12_ipc_src_create (GstBaseSrc * src, guint64 offset, + guint size, GstBuffer ** buf); + +#define gst_d3d12_ipc_src_parent_class parent_class +G_DEFINE_TYPE (GstD3D12IpcSrc, gst_d3d12_ipc_src, GST_TYPE_BASE_SRC); + +static void +gst_d3d12_ipc_src_class_init (GstD3D12IpcSrcClass * klass) +{ + auto object_class = G_OBJECT_CLASS (klass); + auto element_class = GST_ELEMENT_CLASS (klass); + auto src_class = GST_BASE_SRC_CLASS (klass); + + object_class->finalize = gst_d3d12_ipc_src_finalize; + object_class->set_property = gst_d3d12_ipc_src_set_property; + object_class->get_property = gst_win32_video_src_get_property; + + g_object_class_install_property (object_class, PROP_PIPE_NAME, + g_param_spec_string ("pipe-name", "Pipe Name", + "The name of Win32 named pipe to communicate with clients. " + "Validation of the pipe name is caller's responsibility", + DEFAULT_PIPE_NAME, (GParamFlags) (G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS | GST_PARAM_MUTABLE_READY))); + + g_object_class_install_property (object_class, PROP_PROCESSING_DEADLINE, + g_param_spec_uint64 ("processing-deadline", "Processing deadline", + "Maximum processing time for a buffer in nanoseconds", 0, G_MAXUINT64, + DEFAULT_PROCESSING_DEADLINE, (GParamFlags) (G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS | GST_PARAM_MUTABLE_PLAYING))); + + g_object_class_install_property (object_class, PROP_IO_MODE, + g_param_spec_enum ("io-mode", "IO Mode", "Memory I/O mode to use", + GST_TYPE_D3D12_IPC_IO_MODE, DEFAULT_IO_MODE, + (GParamFlags) (G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY | + G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property (object_class, PROP_CONN_TIMEOUT, + g_param_spec_uint ("connection-timeout", "Connection Timeout", + "Connection timeout in seconds (0 = never timeout)", 0, G_MAXINT, + DEFAULT_CONN_TIMEOUT, + (GParamFlags) (G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY | + G_PARAM_STATIC_STRINGS))); + + gst_element_class_set_static_metadata (element_class, + "Direct3D12 IPC Source", "Source/Video", + "Receives Direct3D12 shared handle from the d3d12ipcsink element", + "Seungha Yang "); + + gst_element_class_add_static_pad_template (element_class, &src_template); + + element_class->provide_clock = + GST_DEBUG_FUNCPTR (gst_d3d12_ipc_src_provide_clock); + element_class->set_context = + GST_DEBUG_FUNCPTR (gst_d3d12_ipc_src_set_context); + + src_class->start = GST_DEBUG_FUNCPTR (gst_d3d12_ipc_src_start); + src_class->stop = GST_DEBUG_FUNCPTR (gst_d3d12_ipc_src_stop); + src_class->unlock = GST_DEBUG_FUNCPTR (gst_d3d12_ipc_src_unlock); + src_class->unlock_stop = GST_DEBUG_FUNCPTR (gst_d3d12_ipc_src_unlock_stop); + src_class->query = GST_DEBUG_FUNCPTR (gst_d3d12_ipc_src_query); + src_class->get_caps = GST_DEBUG_FUNCPTR (gst_d3d12_ipc_src_get_caps); + src_class->fixate = GST_DEBUG_FUNCPTR (gst_d3d12_ipc_src_fixate); + src_class->create = GST_DEBUG_FUNCPTR (gst_d3d12_ipc_src_create); + + GST_DEBUG_CATEGORY_INIT (gst_d3d12_ipc_src_debug, "d3d12ipcsrc", + 0, "d3d12ipcsrc"); + + gst_type_mark_as_plugin_api (GST_TYPE_D3D12_IPC_IO_MODE, + (GstPluginAPIFlags) 0); +} + +static void +gst_d3d12_ipc_src_init (GstD3D12IpcSrc * self) +{ + gst_base_src_set_format (GST_BASE_SRC (self), GST_FORMAT_TIME); + gst_base_src_set_live (GST_BASE_SRC (self), TRUE); + + self->priv = new GstD3D12IpcSrcPrivate (); + + GST_OBJECT_FLAG_SET (self, GST_ELEMENT_FLAG_PROVIDE_CLOCK); + GST_OBJECT_FLAG_SET (self, GST_ELEMENT_FLAG_REQUIRE_CLOCK); +} + +static void +gst_d3d12_ipc_src_finalize (GObject * object) +{ + auto self = GST_D3D12_IPC_SRC (object); + + delete self->priv; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_d3d12_ipc_src_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + auto self = GST_D3D12_IPC_SRC (object); + auto priv = self->priv; + std::unique_lock < std::mutex > lk (priv->lock); + + switch (prop_id) { + case PROP_PIPE_NAME: + { + const gchar *pipe_name = g_value_get_string (value); + priv->pipe_name.clear (); + + if (pipe_name) + priv->pipe_name = pipe_name; + else + priv->pipe_name = DEFAULT_PIPE_NAME; + break; + } + case PROP_PROCESSING_DEADLINE: + { + GstClockTime prev_val, new_val; + prev_val = priv->processing_deadline; + new_val = g_value_get_uint64 (value); + priv->processing_deadline = new_val; + + if (prev_val != new_val) { + lk.unlock (); + GST_DEBUG_OBJECT (self, "Posting latency message"); + gst_element_post_message (GST_ELEMENT_CAST (self), + gst_message_new_latency (GST_OBJECT_CAST (self))); + } + break; + } + case PROP_IO_MODE: + priv->io_mode = (GstD3D12IpcIOMode) g_value_get_enum (value); + break; + case PROP_CONN_TIMEOUT: + priv->conn_timeout = g_value_get_uint (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_win32_video_src_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + auto self = GST_D3D12_IPC_SRC (object); + auto priv = self->priv; + std::lock_guard < std::mutex > lk (priv->lock); + + switch (prop_id) { + case PROP_PIPE_NAME: + g_value_set_string (value, priv->pipe_name.c_str ()); + break; + case PROP_PROCESSING_DEADLINE: + g_value_set_uint64 (value, priv->processing_deadline); + break; + case PROP_IO_MODE: + g_value_set_enum (value, priv->io_mode); + break; + case PROP_CONN_TIMEOUT: + g_value_set_uint (value, priv->conn_timeout); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GstClock * +gst_d3d12_ipc_src_provide_clock (GstElement * elem) +{ + return gst_system_clock_obtain (); +} + +static void +gst_d3d12_ipc_src_set_context (GstElement * elem, GstContext * context) +{ + auto self = GST_D3D12_IPC_SRC (elem); + auto priv = self->priv; + + gst_d3d12_handle_set_context (elem, context, -1, &priv->device); + + GST_ELEMENT_CLASS (parent_class)->set_context (elem, context); +} + +static gboolean +gst_d3d12_ipc_src_start (GstBaseSrc * src) +{ + auto self = GST_D3D12_IPC_SRC (src); + auto priv = self->priv; + + GST_DEBUG_OBJECT (self, "Start"); + + if (!gst_d3d12_ensure_element_data (GST_ELEMENT_CAST (self), + -1, &priv->device)) { + GST_ERROR_OBJECT (self, "Couldn't get D3D12 context"); + return FALSE; + } + + std::lock_guard < std::mutex > lk (priv->lock); + priv->client = gst_d3d12_ipc_client_new (priv->pipe_name, priv->device, + priv->io_mode, priv->conn_timeout); + + return TRUE; +} + +static gboolean +gst_d3d12_ipc_src_stop (GstBaseSrc * src) +{ + auto self = GST_D3D12_IPC_SRC (src); + auto priv = self->priv; + + std::lock_guard < std::mutex > lk (priv->lock); + + GST_DEBUG_OBJECT (self, "Stop"); + + if (priv->client) + gst_d3d12_ipc_client_stop (priv->client); + + gst_clear_object (&priv->client); + gst_clear_object (&priv->device); + gst_clear_caps (&priv->caps); + + return TRUE; +} + +static gboolean +gst_d3d12_ipc_src_unlock (GstBaseSrc * src) +{ + auto self = GST_D3D12_IPC_SRC (src); + auto priv = self->priv; + + GST_DEBUG_OBJECT (self, "Unlock"); + + std::lock_guard < std::mutex > lk (priv->lock); + priv->flushing = true; + if (priv->client) + gst_d3d12_ipc_client_set_flushing (priv->client, true); + + return TRUE; +} + +static gboolean +gst_d3d12_ipc_src_unlock_stop (GstBaseSrc * src) +{ + auto self = GST_D3D12_IPC_SRC (src); + auto priv = self->priv; + + GST_DEBUG_OBJECT (self, "Unlock stop"); + + std::lock_guard < std::mutex > lk (priv->lock); + priv->flushing = false; + if (priv->client) + gst_d3d12_ipc_client_set_flushing (priv->client, false); + + return TRUE; +} + +static gboolean +gst_d3d12_ipc_src_query (GstBaseSrc * src, GstQuery * query) +{ + auto self = GST_D3D12_IPC_SRC (src); + auto priv = self->priv; + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_LATENCY: + { + std::lock_guard < std::mutex > lk (priv->lock); + if (GST_CLOCK_TIME_IS_VALID (priv->processing_deadline)) { + gst_query_set_latency (query, TRUE, priv->processing_deadline, + GST_CLOCK_TIME_NONE); + } else { + gst_query_set_latency (query, TRUE, 0, 0); + } + return TRUE; + } + case GST_QUERY_CONTEXT: + if (gst_d3d12_handle_context_query (GST_ELEMENT (self), query, + priv->device)) { + return TRUE; + } + break; + default: + break; + } + + return GST_BASE_SRC_CLASS (parent_class)->query (src, query); +} + +static GstCaps * +gst_d3d12_ipc_src_get_caps (GstBaseSrc * src, GstCaps * filter) +{ + auto self = GST_D3D12_IPC_SRC (src); + auto priv = self->priv; + GstD3D12IpcClient *client = nullptr; + GstCaps *caps = nullptr; + + GST_DEBUG_OBJECT (self, "Get caps"); + + priv->lock.lock (); + if (priv->caps) + caps = gst_caps_ref (priv->caps); + else if (priv->client) + client = (GstD3D12IpcClient *) gst_object_ref (priv->client); + priv->lock.unlock (); + + if (!caps && client) + caps = gst_d3d12_ipc_client_get_caps (priv->client); + + if (!caps) + caps = gst_pad_get_pad_template_caps (GST_BASE_SRC_PAD (src)); + + if (filter) { + GstCaps *tmp = gst_caps_intersect_full (filter, + caps, GST_CAPS_INTERSECT_FIRST); + gst_caps_unref (caps); + caps = tmp; + } + + gst_clear_object (&client); + GST_DEBUG_OBJECT (self, "Returning caps %" GST_PTR_FORMAT, caps); + + return caps; +} + +static GstCaps * +gst_d3d12_ipc_src_fixate (GstBaseSrc * src, GstCaps * caps) +{ + /* We don't negotiate with server. In here, we do fixate resolution to + * 320 x 240 (same as default of videotestsrc) which makes a little more + * sense than 1x1 */ + caps = gst_caps_make_writable (caps); + + for (guint i = 0; i < gst_caps_get_size (caps); i++) { + GstStructure *s = gst_caps_get_structure (caps, i); + + gst_structure_fixate_field_nearest_int (s, "width", 320); + gst_structure_fixate_field_nearest_int (s, "height", 240); + } + + return gst_caps_fixate (caps); +} + +static GstFlowReturn +gst_d3d12_ipc_src_create (GstBaseSrc * src, guint64 offset, guint size, + GstBuffer ** buf) +{ + auto self = GST_D3D12_IPC_SRC (src); + auto priv = self->priv; + GstFlowReturn ret; + GstSample *sample = nullptr; + GstCaps *caps; + GstClock *clock; + bool is_system_clock = true; + GstClockTime pts; + GstClockTime base_time; + GstClockTime now_system; + GstClockTime now_gst; + GstClockTime remote_pts; + GstBuffer *buffer; + + ret = gst_d3d12_ipc_client_run (priv->client); + if (ret != GST_FLOW_OK) + return ret; + + ret = gst_d3d12_ipc_client_get_sample (priv->client, &sample); + if (ret != GST_FLOW_OK) + return ret; + + now_system = gst_util_get_timestamp (); + clock = gst_element_get_clock (GST_ELEMENT_CAST (self)); + now_gst = gst_clock_get_time (clock); + base_time = GST_ELEMENT_CAST (self)->base_time; + is_system_clock = gst_d3d12_ipc_clock_is_system (clock); + gst_object_unref (clock); + + buffer = gst_sample_get_buffer (sample); + remote_pts = GST_BUFFER_PTS (buffer); + + if (!is_system_clock) { + GstClockTimeDiff now_pts = now_gst - base_time + remote_pts - now_system; + + if (now_pts >= 0) + pts = now_pts; + else + pts = 0; + } else { + if (remote_pts >= base_time) { + pts = remote_pts - base_time; + } else { + GST_WARNING_OBJECT (self, + "Remote clock is smaller than our base time, remote %" + GST_TIME_FORMAT ", base_time %" GST_TIME_FORMAT, + GST_TIME_ARGS (remote_pts), GST_TIME_ARGS (base_time)); + pts = 0; + } + } + + GST_BUFFER_PTS (buffer) = pts; + + std::unique_lock < std::mutex > lk (priv->lock); + caps = gst_sample_get_caps (sample); + if (!priv->caps || !gst_caps_is_equal (priv->caps, caps)) { + gst_caps_replace (&priv->caps, caps); + lk.unlock (); + gst_base_src_set_caps (src, priv->caps); + } + + *buf = gst_buffer_ref (buffer); + gst_sample_unref (sample); + + return GST_FLOW_OK; +} diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12ipcsrc.h b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12ipcsrc.h new file mode 100644 index 0000000000..1429325c2c --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12ipcsrc.h @@ -0,0 +1,33 @@ +/* GStreamer + * Copyright (C) 2023 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. + */ + +#pragma once + +#include +#include +#include +#include "gstd3d12.h" + +G_BEGIN_DECLS + +#define GST_TYPE_D3D12_IPC_SRC (gst_d3d12_ipc_src_get_type()) +G_DECLARE_FINAL_TYPE (GstD3D12IpcSrc, gst_d3d12_ipc_src, + GST, D3D12_IPC_SRC, GstBaseSrc); + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/sys/d3d12/meson.build b/subprojects/gst-plugins-bad/sys/d3d12/meson.build index 10b0f9f800..0af0e36736 100644 --- a/subprojects/gst-plugins-bad/sys/d3d12/meson.build +++ b/subprojects/gst-plugins-bad/sys/d3d12/meson.build @@ -24,6 +24,11 @@ d3d12_sources = [ 'gstd3d12h264dec.cpp', 'gstd3d12h264enc.cpp', 'gstd3d12h265dec.cpp', + 'gstd3d12ipc.cpp', + 'gstd3d12ipcclient.cpp', + 'gstd3d12ipcserver.cpp', + 'gstd3d12ipcsink.cpp', + 'gstd3d12ipcsrc.cpp', 'gstd3d12memory.cpp', 'gstd3d12mpeg2dec.cpp', 'gstd3d12overlaycompositor.cpp', diff --git a/subprojects/gst-plugins-bad/sys/d3d12/plugin.cpp b/subprojects/gst-plugins-bad/sys/d3d12/plugin.cpp index 488635adfb..ada348a825 100644 --- a/subprojects/gst-plugins-bad/sys/d3d12/plugin.cpp +++ b/subprojects/gst-plugins-bad/sys/d3d12/plugin.cpp @@ -45,6 +45,9 @@ #include "gstd3d12vp8dec.h" #include "gstd3d12vp9dec.h" #include "gstd3d12av1dec.h" +#include "gstd3d12ipcclient.h" +#include "gstd3d12ipcsrc.h" +#include "gstd3d12ipcsink.h" #include #include #include @@ -64,6 +67,7 @@ GST_DEBUG_CATEGORY (gst_d3d12_utils_debug); static void plugin_deinit (gpointer data) { + gst_d3d12_ipc_client_deinit (); } static gboolean @@ -142,6 +146,10 @@ plugin_init (GstPlugin * plugin) gst_device_provider_register (plugin, "d3d12screencapturedeviceprovider", GST_RANK_PRIMARY, GST_TYPE_D3D12_SCREEN_CAPTURE_DEVICE_PROVIDER); + gst_element_register (plugin, + "d3d12ipcsrc", GST_RANK_NONE, GST_TYPE_D3D12_IPC_SRC); + gst_element_register (plugin, + "d3d12ipcsink", GST_RANK_NONE, GST_TYPE_D3D12_IPC_SINK); g_object_set_data_full (G_OBJECT (plugin), "plugin-d3d12-shutdown", (gpointer) "shutdown-data",