mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-25 01:30:38 +00:00
7c06001705
Update names of various objects and method to be shorter, for instance GstD3D12CommandAllocator is changed to GstD3D12CmdAlloc. Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/7642>
2581 lines
80 KiB
C++
2581 lines
80 KiB
C++
/* GStreamer
|
|
* Copyright (C) 2023 Seungha Yang <seungha@centricular.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
/**
|
|
* SECTION:element-d3d12compositor
|
|
* @title: d3d12compositor
|
|
*
|
|
* A Direct3D12 based video compositing element.
|
|
*
|
|
* ## Example launch line
|
|
* ```
|
|
* gst-launch-1.0 d3d12compositor name=c ! d3d12videosink \
|
|
* videotestsrc ! video/x-raw,width=320,height=240 ! c. \
|
|
* videotestsrc pattern=ball ! video/x-raw,width=100,height=100 ! c.
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "gstd3d12compositor.h"
|
|
#include "gstd3d12pluginutils.h"
|
|
#include <directx/d3dx12.h>
|
|
#include <mutex>
|
|
#include <future>
|
|
#include <vector>
|
|
#include <queue>
|
|
#include <atomic>
|
|
#include <string.h>
|
|
#include <wrl.h>
|
|
#include <gst/d3dshader/gstd3dshader.h>
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (gst_d3d12_compositor_debug);
|
|
#define GST_CAT_DEFAULT gst_d3d12_compositor_debug
|
|
|
|
/* *INDENT-OFF* */
|
|
using namespace Microsoft::WRL;
|
|
/* *INDENT-ON* */
|
|
|
|
enum GstD3D12CompositorBackground
|
|
{
|
|
GST_D3D12_COMPOSITOR_BACKGROUND_CHECKER,
|
|
GST_D3D12_COMPOSITOR_BACKGROUND_BLACK,
|
|
GST_D3D12_COMPOSITOR_BACKGROUND_WHITE,
|
|
GST_D3D12_COMPOSITOR_BACKGROUND_TRANSPARENT,
|
|
};
|
|
|
|
#define GST_TYPE_D3D12_COMPOSITOR_BACKGROUND (gst_d3d12_compositor_background_get_type())
|
|
static GType
|
|
gst_d3d12_compositor_background_get_type (void)
|
|
{
|
|
static GType compositor_background_type = 0;
|
|
static const GEnumValue compositor_background[] = {
|
|
{GST_D3D12_COMPOSITOR_BACKGROUND_CHECKER, "Checker pattern", "checker"},
|
|
{GST_D3D12_COMPOSITOR_BACKGROUND_BLACK, "Black", "black"},
|
|
{GST_D3D12_COMPOSITOR_BACKGROUND_WHITE, "White", "white"},
|
|
{GST_D3D12_COMPOSITOR_BACKGROUND_TRANSPARENT,
|
|
"Transparent Background to enable further compositing", "transparent"},
|
|
{0, nullptr, nullptr},
|
|
};
|
|
|
|
GST_D3D12_CALL_ONCE_BEGIN {
|
|
compositor_background_type =
|
|
g_enum_register_static ("GstD3D12CompositorBackground",
|
|
compositor_background);
|
|
} GST_D3D12_CALL_ONCE_END;
|
|
|
|
return compositor_background_type;
|
|
}
|
|
|
|
enum GstD3D12CompositorOperator
|
|
{
|
|
GST_D3D12_COMPOSITOR_OPERATOR_SOURCE,
|
|
GST_D3D12_COMPOSITOR_OPERATOR_OVER,
|
|
};
|
|
|
|
#define GST_TYPE_D3D12_COMPOSITOR_OPERATOR (gst_d3d12_compositor_operator_get_type())
|
|
static GType
|
|
gst_d3d12_compositor_operator_get_type (void)
|
|
{
|
|
static GType compositor_operator_type = 0;
|
|
static const GEnumValue compositor_operator[] = {
|
|
{GST_D3D12_COMPOSITOR_OPERATOR_SOURCE, "Source", "source"},
|
|
{GST_D3D12_COMPOSITOR_OPERATOR_OVER, "Over", "over"},
|
|
{0, nullptr, nullptr},
|
|
};
|
|
|
|
GST_D3D12_CALL_ONCE_BEGIN {
|
|
compositor_operator_type =
|
|
g_enum_register_static ("GstD3D12CompositorOperator",
|
|
compositor_operator);
|
|
} GST_D3D12_CALL_ONCE_END;
|
|
|
|
return compositor_operator_type;
|
|
}
|
|
|
|
enum GstD3D12CompositorSizingPolicy
|
|
{
|
|
GST_D3D12_COMPOSITOR_SIZING_POLICY_NONE,
|
|
GST_D3D12_COMPOSITOR_SIZING_POLICY_KEEP_ASPECT_RATIO,
|
|
};
|
|
|
|
#define GST_TYPE_D3D12_COMPOSITOR_SIZING_POLICY (gst_d3d12_compositor_sizing_policy_get_type())
|
|
static GType
|
|
gst_d3d12_compositor_sizing_policy_get_type (void)
|
|
{
|
|
static GType sizing_policy_type = 0;
|
|
|
|
static const GEnumValue sizing_polices[] = {
|
|
{GST_D3D12_COMPOSITOR_SIZING_POLICY_NONE,
|
|
"None: Image is scaled to fill configured destination rectangle without "
|
|
"padding or keeping the aspect ratio", "none"},
|
|
{GST_D3D12_COMPOSITOR_SIZING_POLICY_KEEP_ASPECT_RATIO,
|
|
"Keep Aspect Ratio: Image is scaled to fit destination rectangle "
|
|
"specified by GstD3D12CompositorPad:{xpos, ypos, width, height} "
|
|
"with preserved aspect ratio. Resulting image will be centered in "
|
|
"the destination rectangle with padding if necessary",
|
|
"keep-aspect-ratio"},
|
|
{0, nullptr, nullptr},
|
|
};
|
|
|
|
GST_D3D12_CALL_ONCE_BEGIN {
|
|
sizing_policy_type =
|
|
g_enum_register_static ("GstD3D12CompositorSizingPolicy",
|
|
sizing_polices);
|
|
} GST_D3D12_CALL_ONCE_END;
|
|
|
|
return sizing_policy_type;
|
|
}
|
|
|
|
enum
|
|
{
|
|
PROP_PAD_0,
|
|
PROP_PAD_XPOS,
|
|
PROP_PAD_YPOS,
|
|
PROP_PAD_WIDTH,
|
|
PROP_PAD_HEIGHT,
|
|
PROP_PAD_ALPHA,
|
|
PROP_PAD_OPERATOR,
|
|
PROP_PAD_SIZING_POLICY,
|
|
PROP_PAD_GAMMA_MODE,
|
|
PROP_PAD_PRIMARIES_MODE,
|
|
};
|
|
|
|
#define DEFAULT_PAD_XPOS 0
|
|
#define DEFAULT_PAD_YPOS 0
|
|
#define DEFAULT_PAD_WIDTH 0
|
|
#define DEFAULT_PAD_HEIGHT 0
|
|
#define DEFAULT_PAD_ALPHA 1.0
|
|
#define DEFAULT_PAD_OPERATOR GST_D3D12_COMPOSITOR_OPERATOR_OVER
|
|
#define DEFAULT_PAD_SIZING_POLICY GST_D3D12_COMPOSITOR_SIZING_POLICY_NONE
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_ADAPTER,
|
|
PROP_BACKGROUND,
|
|
PROP_IGNORE_INACTIVE_PADS,
|
|
PROP_ASYNC_DEPTH,
|
|
};
|
|
|
|
#define DEFAULT_ADAPTER -1
|
|
#define DEFAULT_BACKGROUND GST_D3D12_COMPOSITOR_BACKGROUND_CHECKER
|
|
#define DEFAULT_ASYNC_DEPTH 0
|
|
|
|
static const D3D12_RENDER_TARGET_BLEND_DESC g_blend_source = {
|
|
TRUE,
|
|
FALSE,
|
|
D3D12_BLEND_ONE,
|
|
D3D12_BLEND_ZERO,
|
|
D3D12_BLEND_OP_ADD,
|
|
D3D12_BLEND_ONE,
|
|
D3D12_BLEND_ZERO,
|
|
D3D12_BLEND_OP_ADD,
|
|
D3D12_LOGIC_OP_NOOP,
|
|
D3D12_COLOR_WRITE_ENABLE_ALL,
|
|
};
|
|
|
|
static const D3D12_RENDER_TARGET_BLEND_DESC g_blend_over = {
|
|
TRUE,
|
|
FALSE,
|
|
D3D12_BLEND_SRC_ALPHA,
|
|
D3D12_BLEND_INV_SRC_ALPHA,
|
|
D3D12_BLEND_OP_ADD,
|
|
D3D12_BLEND_ONE,
|
|
D3D12_BLEND_INV_SRC_ALPHA,
|
|
D3D12_BLEND_OP_ADD,
|
|
D3D12_LOGIC_OP_NOOP,
|
|
D3D12_COLOR_WRITE_ENABLE_ALL,
|
|
};
|
|
|
|
static const D3D12_RENDER_TARGET_BLEND_DESC g_blend_over_factor = {
|
|
TRUE,
|
|
FALSE,
|
|
D3D12_BLEND_BLEND_FACTOR,
|
|
D3D12_BLEND_INV_BLEND_FACTOR,
|
|
D3D12_BLEND_OP_ADD,
|
|
D3D12_BLEND_BLEND_FACTOR,
|
|
D3D12_BLEND_INV_BLEND_FACTOR,
|
|
D3D12_BLEND_OP_ADD,
|
|
D3D12_LOGIC_OP_NOOP,
|
|
D3D12_COLOR_WRITE_ENABLE_ALL,
|
|
};
|
|
|
|
static const D3D12_ROOT_SIGNATURE_FLAGS g_rs_flags =
|
|
D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT |
|
|
D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS |
|
|
D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS |
|
|
D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS |
|
|
D3D12_ROOT_SIGNATURE_FLAG_DENY_AMPLIFICATION_SHADER_ROOT_ACCESS |
|
|
D3D12_ROOT_SIGNATURE_FLAG_DENY_MESH_SHADER_ROOT_ACCESS;
|
|
|
|
/* *INDENT-OFF* */
|
|
struct PadContext
|
|
{
|
|
PadContext (GstD3D12Device * dev)
|
|
{
|
|
device = (GstD3D12Device *) gst_object_ref (dev);
|
|
auto device_handle = gst_d3d12_device_get_device_handle (device);
|
|
ca_pool = gst_d3d12_cmd_alloc_pool_new (device_handle,
|
|
D3D12_COMMAND_LIST_TYPE_DIRECT);
|
|
gst_video_info_init (&info);
|
|
}
|
|
|
|
PadContext () = delete;
|
|
|
|
~PadContext ()
|
|
{
|
|
gst_d3d12_device_fence_wait (device, D3D12_COMMAND_LIST_TYPE_DIRECT,
|
|
fence_val);
|
|
|
|
gst_clear_d3d12_fence_data (&fence_data);
|
|
gst_clear_object (&conv);
|
|
gst_clear_object (&ca_pool);
|
|
gst_clear_object (&device);
|
|
}
|
|
|
|
GstVideoInfo info;
|
|
GstD3D12CmdAllocPool *ca_pool;
|
|
ComPtr < ID3D12GraphicsCommandList > cl;
|
|
GstD3D12FenceData *fence_data = nullptr;
|
|
GstD3D12Device *device;
|
|
GstD3D12Converter *conv = nullptr;
|
|
guint64 fence_val = 0;
|
|
};
|
|
/* *INDENT-ON* */
|
|
|
|
struct GstD3D12CompositorPadPrivate
|
|
{
|
|
GstD3D12CompositorPadPrivate ()
|
|
{
|
|
blend_desc = CD3DX12_BLEND_DESC (D3D12_DEFAULT);
|
|
blend_desc.RenderTarget[0] = g_blend_over;
|
|
for (guint i = 0; i < 4; i++)
|
|
blend_factor[i] = 1.0f;
|
|
}
|
|
|
|
std::unique_ptr < PadContext > ctx;
|
|
std::future < gboolean > prepare_rst;
|
|
|
|
gboolean position_updated = FALSE;
|
|
gboolean alpha_updated = FALSE;
|
|
gboolean blend_desc_updated = FALSE;
|
|
D3D12_BLEND_DESC blend_desc;
|
|
gfloat blend_factor[4];
|
|
|
|
std::recursive_mutex lock;
|
|
|
|
/* properties */
|
|
gint xpos = DEFAULT_PAD_XPOS;
|
|
gint ypos = DEFAULT_PAD_YPOS;
|
|
gint width = DEFAULT_PAD_WIDTH;
|
|
gint height = DEFAULT_PAD_HEIGHT;
|
|
gdouble alpha = DEFAULT_PAD_ALPHA;
|
|
GstD3D12CompositorOperator op = DEFAULT_PAD_OPERATOR;
|
|
GstD3D12CompositorSizingPolicy sizing_policy = DEFAULT_PAD_SIZING_POLICY;
|
|
};
|
|
|
|
struct _GstD3D12CompositorPad
|
|
{
|
|
GstVideoAggregatorConvertPad parent;
|
|
|
|
GstD3D12CompositorPadPrivate *priv;
|
|
};
|
|
|
|
struct VertexData
|
|
{
|
|
struct
|
|
{
|
|
FLOAT x;
|
|
FLOAT y;
|
|
FLOAT z;
|
|
} position;
|
|
struct
|
|
{
|
|
FLOAT u;
|
|
FLOAT v;
|
|
} texture;
|
|
};
|
|
|
|
/* *INDENT-OFF* */
|
|
struct BackgroundRender
|
|
{
|
|
BackgroundRender (GstD3D12Device * dev, const GstVideoInfo & info)
|
|
{
|
|
device = (GstD3D12Device *) gst_object_ref (dev);
|
|
auto device_handle = gst_d3d12_device_get_device_handle (device);
|
|
ca_pool = gst_d3d12_cmd_alloc_pool_new (device_handle,
|
|
D3D12_COMMAND_LIST_TYPE_DIRECT);
|
|
|
|
D3D12_VERSIONED_ROOT_SIGNATURE_DESC rs_desc = { };
|
|
CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC::Init_1_0 (rs_desc,
|
|
0, nullptr, 0, nullptr, g_rs_flags);
|
|
|
|
ComPtr < ID3DBlob > rs_blob;
|
|
ComPtr < ID3DBlob > error_blob;
|
|
auto hr = D3DX12SerializeVersionedRootSignature (&rs_desc,
|
|
D3D_ROOT_SIGNATURE_VERSION_1_1, &rs_blob, &error_blob);
|
|
if (!gst_d3d12_result (hr, device)) {
|
|
const gchar *error_msg = nullptr;
|
|
if (error_blob)
|
|
error_msg = (const gchar *) error_blob->GetBufferPointer ();
|
|
|
|
GST_ERROR_OBJECT (device, "Couldn't serialize root signature, error: %s",
|
|
GST_STR_NULL (error_msg));
|
|
return;
|
|
}
|
|
|
|
hr = device_handle->CreateRootSignature (0, rs_blob->GetBufferPointer (),
|
|
rs_blob->GetBufferSize (), IID_PPV_ARGS (&rs));
|
|
if (!gst_d3d12_result (hr, device)) {
|
|
GST_ERROR_OBJECT (device, "Couldn't create root signature");
|
|
return;
|
|
}
|
|
|
|
GstD3D12Format format;
|
|
gst_d3d12_device_get_format (device, GST_VIDEO_INFO_FORMAT (&info),
|
|
&format);
|
|
|
|
D3D12_INPUT_ELEMENT_DESC input_desc;
|
|
input_desc.SemanticName = "POSITION";
|
|
input_desc.SemanticIndex = 0;
|
|
input_desc.Format = DXGI_FORMAT_R32G32B32_FLOAT;
|
|
input_desc.InputSlot = 0;
|
|
input_desc.AlignedByteOffset = D3D12_APPEND_ALIGNED_ELEMENT;
|
|
input_desc.InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA;
|
|
input_desc.InstanceDataStepRate = 0;
|
|
|
|
GstD3DShaderByteCode vs_code;
|
|
if (!gst_d3d_plugin_shader_get_vs_blob (GST_D3D_PLUGIN_VS_POS,
|
|
GST_D3D_SM_5_0, &vs_code)) {
|
|
GST_ERROR_OBJECT (device, "Couldn't get vs bytecode");
|
|
return;
|
|
}
|
|
|
|
GstD3DShaderByteCode ps_code;
|
|
GstD3DPluginPS ps_type;
|
|
if (GST_VIDEO_INFO_IS_RGB (&info))
|
|
ps_type = GST_D3D_PLUGIN_PS_CHECKER_RGB;
|
|
else if (GST_VIDEO_INFO_FORMAT (&info) == GST_VIDEO_FORMAT_VUYA)
|
|
ps_type = GST_D3D_PLUGIN_PS_CHECKER_VUYA;
|
|
else
|
|
ps_type = GST_D3D_PLUGIN_PS_CHECKER_LUMA;
|
|
|
|
if (!gst_d3d_plugin_shader_get_ps_blob (ps_type,
|
|
GST_D3D_SM_5_0, &ps_code)) {
|
|
GST_ERROR_OBJECT (device, "Couldn't get ps bytecode");
|
|
return;
|
|
}
|
|
|
|
D3D12_GRAPHICS_PIPELINE_STATE_DESC pso_desc = { };
|
|
pso_desc.pRootSignature = rs.Get ();
|
|
pso_desc.VS.BytecodeLength = vs_code.byte_code_len;
|
|
pso_desc.VS.pShaderBytecode = vs_code.byte_code;
|
|
pso_desc.PS.BytecodeLength = ps_code.byte_code_len;
|
|
pso_desc.PS.pShaderBytecode = ps_code.byte_code;
|
|
pso_desc.BlendState = CD3DX12_BLEND_DESC (D3D12_DEFAULT);
|
|
pso_desc.SampleMask = UINT_MAX;
|
|
pso_desc.RasterizerState = CD3DX12_RASTERIZER_DESC (D3D12_DEFAULT);
|
|
pso_desc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
|
|
pso_desc.DepthStencilState.DepthEnable = FALSE;
|
|
pso_desc.DepthStencilState.StencilEnable = FALSE;
|
|
pso_desc.InputLayout.pInputElementDescs = &input_desc;
|
|
pso_desc.InputLayout.NumElements = 1;
|
|
pso_desc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
|
|
pso_desc.NumRenderTargets = 1;
|
|
pso_desc.RTVFormats[0] = format.resource_format[0];
|
|
pso_desc.SampleDesc.Count = 1;
|
|
|
|
hr = device_handle->CreateGraphicsPipelineState (&pso_desc,
|
|
IID_PPV_ARGS (&pso));
|
|
if (!gst_d3d12_result (hr, device)) {
|
|
GST_ERROR_OBJECT (device, "Couldn't create pso");
|
|
return;
|
|
}
|
|
|
|
VertexData vertex_data[4];
|
|
const WORD indices[6] = { 0, 1, 2, 3, 0, 2 };
|
|
|
|
/* bottom left */
|
|
vertex_data[0].position.x = -1.0f;
|
|
vertex_data[0].position.y = -1.0f;
|
|
vertex_data[0].position.z = 0.0f;
|
|
vertex_data[0].texture.u = 0.0f;
|
|
vertex_data[0].texture.v = 1.0f;
|
|
|
|
/* top left */
|
|
vertex_data[1].position.x = -1.0f;
|
|
vertex_data[1].position.y = 1.0f;
|
|
vertex_data[1].position.z = 0.0f;
|
|
vertex_data[1].texture.u = 0.0f;
|
|
vertex_data[1].texture.v = 0.0f;
|
|
|
|
/* top right */
|
|
vertex_data[2].position.x = 1.0f;
|
|
vertex_data[2].position.y = 1.0f;
|
|
vertex_data[2].position.z = 0.0f;
|
|
vertex_data[2].texture.u = 1.0f;
|
|
vertex_data[2].texture.v = 0.0f;
|
|
|
|
/* bottom right */
|
|
vertex_data[3].position.x = 1.0f;
|
|
vertex_data[3].position.y = -1.0f;
|
|
vertex_data[3].position.z = 0.0f;
|
|
vertex_data[3].texture.u = 1.0f;
|
|
vertex_data[3].texture.v = 1.0f;
|
|
|
|
D3D12_HEAP_PROPERTIES heap_prop =
|
|
CD3DX12_HEAP_PROPERTIES (D3D12_HEAP_TYPE_UPLOAD);
|
|
D3D12_RESOURCE_DESC buffer_desc =
|
|
CD3DX12_RESOURCE_DESC::Buffer (sizeof (VertexData) * 4 +
|
|
sizeof (indices));
|
|
D3D12_HEAP_FLAGS heap_flags = D3D12_HEAP_FLAG_NONE;
|
|
if (gst_d3d12_device_non_zeroed_supported (device))
|
|
heap_flags = D3D12_HEAP_FLAG_CREATE_NOT_ZEROED;
|
|
|
|
hr = device_handle->CreateCommittedResource (&heap_prop,
|
|
heap_flags, &buffer_desc, D3D12_RESOURCE_STATE_GENERIC_READ,
|
|
nullptr, IID_PPV_ARGS (&vertex_index_upload));
|
|
if (!gst_d3d12_result (hr, device)) {
|
|
GST_ERROR_OBJECT (device, "Couldn't create vertex upload buf");
|
|
return;
|
|
}
|
|
|
|
guint8 *data;
|
|
CD3DX12_RANGE range (0, 0);
|
|
hr = vertex_index_upload->Map (0, &range, (void **) &data);
|
|
if (!gst_d3d12_result (hr, device)) {
|
|
GST_ERROR_OBJECT (device, "Couldn't map index buffer");
|
|
return;
|
|
}
|
|
|
|
memcpy (data, vertex_data, sizeof (VertexData) * 4);
|
|
memcpy (data + sizeof (VertexData) * 4, indices, sizeof (indices));
|
|
vertex_index_upload->Unmap (0, nullptr);
|
|
|
|
heap_prop = CD3DX12_HEAP_PROPERTIES (D3D12_HEAP_TYPE_DEFAULT);
|
|
hr = device_handle->CreateCommittedResource (&heap_prop,
|
|
heap_flags, &buffer_desc,
|
|
D3D12_RESOURCE_STATE_COMMON, nullptr, IID_PPV_ARGS (&vertex_index_buf));
|
|
if (!gst_d3d12_result (hr, device)) {
|
|
GST_ERROR_OBJECT (device, "Couldn't create index buffer");
|
|
return;
|
|
}
|
|
|
|
vbv.BufferLocation = vertex_index_buf->GetGPUVirtualAddress ();
|
|
vbv.SizeInBytes = sizeof (VertexData) * 4;
|
|
vbv.StrideInBytes = sizeof (VertexData);
|
|
ibv.BufferLocation = vbv.BufferLocation + vbv.SizeInBytes;
|
|
ibv.SizeInBytes = sizeof (indices);
|
|
ibv.Format = DXGI_FORMAT_R16_UINT;
|
|
|
|
viewport.TopLeftX = 0;
|
|
viewport.TopLeftY = 0;
|
|
viewport.Width = info.width;
|
|
viewport.Height = info.height;
|
|
viewport.MinDepth = 0;
|
|
viewport.MaxDepth = 1;
|
|
|
|
scissor_rect.left = 0;
|
|
scissor_rect.top = 0;
|
|
scissor_rect.right = info.width;
|
|
scissor_rect.bottom = info.height;
|
|
|
|
rtv_inc_size =
|
|
device_handle->GetDescriptorHandleIncrementSize
|
|
(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
|
|
|
|
is_valid = true;
|
|
}
|
|
BackgroundRender () = delete;
|
|
|
|
~BackgroundRender ()
|
|
{
|
|
gst_d3d12_device_fence_wait (device, D3D12_COMMAND_LIST_TYPE_DIRECT,
|
|
fence_val);
|
|
|
|
gst_clear_object (&ca_pool);
|
|
gst_clear_object (&device);
|
|
}
|
|
|
|
GstD3D12Device *device;
|
|
ComPtr < ID3D12RootSignature > rs;
|
|
ComPtr < ID3D12PipelineState > pso;
|
|
ComPtr < ID3D12Resource > vertex_index_buf;
|
|
ComPtr < ID3D12Resource > vertex_index_upload;
|
|
D3D12_VERTEX_BUFFER_VIEW vbv;
|
|
D3D12_INDEX_BUFFER_VIEW ibv;
|
|
ComPtr < ID3D12GraphicsCommandList > cl;
|
|
GstD3D12CmdAllocPool *ca_pool;
|
|
D3D12_VIEWPORT viewport;
|
|
D3D12_RECT scissor_rect;
|
|
guint rtv_inc_size;
|
|
bool need_upload = true;
|
|
bool is_valid = false;
|
|
guint64 fence_val = 0;
|
|
};
|
|
|
|
struct ClearColor
|
|
{
|
|
/* [rtv][colors] */
|
|
FLOAT color[4][4];
|
|
};
|
|
|
|
struct GStD3D12CompositorPrivate
|
|
{
|
|
GStD3D12CompositorPrivate ()
|
|
{
|
|
fence_data_pool = gst_d3d12_fence_data_pool_new ();
|
|
gst_video_info_init (&negotiated_info);
|
|
}
|
|
|
|
~GStD3D12CompositorPrivate ()
|
|
{
|
|
gst_clear_buffer (&fallback_buf);
|
|
gst_clear_object (&fence_data_pool);
|
|
}
|
|
|
|
GstBuffer *fallback_buf = nullptr;
|
|
GstBuffer *generated_output_buf = nullptr;
|
|
|
|
std::unique_ptr < BackgroundRender > bg_render;
|
|
/* black/white/transparent */
|
|
ClearColor clear_color[3];
|
|
GstD3D12FenceDataPool *fence_data_pool;
|
|
std::vector<D3D12_CPU_DESCRIPTOR_HANDLE> rtv_handles;
|
|
std::queue<guint64> scheduled;
|
|
|
|
GstVideoInfo negotiated_info;
|
|
|
|
gboolean downstream_supports_d3d12 = FALSE;
|
|
|
|
std::recursive_mutex lock;
|
|
|
|
/* properties */
|
|
gint adapter = DEFAULT_ADAPTER;
|
|
GstD3D12CompositorBackground background = DEFAULT_BACKGROUND;
|
|
std::atomic<guint> async_depth = { DEFAULT_ASYNC_DEPTH };
|
|
};
|
|
/* *INDENT-ON* */
|
|
|
|
struct _GstD3D12Compositor
|
|
{
|
|
GstVideoAggregator parent;
|
|
|
|
GstD3D12Device *device;
|
|
|
|
GStD3D12CompositorPrivate *priv;
|
|
};
|
|
|
|
static void gst_d3d12_compositor_pad_finalize (GObject * object);
|
|
static void gst_d3d12_compositor_pad_set_property (GObject * object,
|
|
guint prop_id, const GValue * value, GParamSpec * pspec);
|
|
static void gst_d3d12_compositor_pad_get_property (GObject * object,
|
|
guint prop_id, GValue * value, GParamSpec * pspec);
|
|
static void
|
|
gst_d3d12_compositor_pad_update_conversion_info (GstVideoAggregatorPad * pad);
|
|
static void
|
|
gst_d3d12_compositor_pad_prepare_frame_start (GstVideoAggregatorPad * pad,
|
|
GstVideoAggregator * vagg, GstBuffer * buffer,
|
|
GstVideoFrame * prepared_frame);
|
|
static void
|
|
gst_d3d12_compositor_pad_prepare_frame_finish (GstVideoAggregatorPad * pad,
|
|
GstVideoAggregator * vagg, GstVideoFrame * prepared_frame);
|
|
static void gst_d3d12_compositor_pad_clean_frame (GstVideoAggregatorPad * vpad,
|
|
GstVideoAggregator * vagg, GstVideoFrame * prepared_frame);
|
|
|
|
#define gst_d3d12_compositor_pad_parent_class parent_pad_class
|
|
G_DEFINE_TYPE (GstD3D12CompositorPad, gst_d3d12_compositor_pad,
|
|
GST_TYPE_VIDEO_AGGREGATOR_PAD);
|
|
|
|
static void
|
|
gst_d3d12_compositor_pad_class_init (GstD3D12CompositorPadClass * klass)
|
|
{
|
|
auto object_class = G_OBJECT_CLASS (klass);
|
|
auto vagg_pad_class = GST_VIDEO_AGGREGATOR_PAD_CLASS (klass);
|
|
GParamFlags param_flags = (GParamFlags)
|
|
(G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS);
|
|
|
|
object_class->finalize = gst_d3d12_compositor_pad_finalize;
|
|
object_class->set_property = gst_d3d12_compositor_pad_set_property;
|
|
object_class->get_property = gst_d3d12_compositor_pad_get_property;
|
|
|
|
g_object_class_install_property (object_class, PROP_PAD_XPOS,
|
|
g_param_spec_int ("xpos", "X Position", "X position of the picture",
|
|
G_MININT, G_MAXINT, DEFAULT_PAD_XPOS, param_flags));
|
|
g_object_class_install_property (object_class, PROP_PAD_YPOS,
|
|
g_param_spec_int ("ypos", "Y Position", "Y position of the picture",
|
|
G_MININT, G_MAXINT, DEFAULT_PAD_YPOS, param_flags));
|
|
g_object_class_install_property (object_class, PROP_PAD_WIDTH,
|
|
g_param_spec_int ("width", "Width", "Width of the picture",
|
|
G_MININT, G_MAXINT, DEFAULT_PAD_WIDTH, param_flags));
|
|
g_object_class_install_property (object_class, PROP_PAD_HEIGHT,
|
|
g_param_spec_int ("height", "Height", "Height of the picture",
|
|
G_MININT, G_MAXINT, DEFAULT_PAD_HEIGHT, param_flags));
|
|
g_object_class_install_property (object_class, PROP_PAD_ALPHA,
|
|
g_param_spec_double ("alpha", "Alpha", "Alpha of the picture", 0.0, 1.0,
|
|
DEFAULT_PAD_ALPHA, param_flags));
|
|
g_object_class_install_property (object_class, PROP_PAD_OPERATOR,
|
|
g_param_spec_enum ("operator", "Operator",
|
|
"Blending operator to use for blending this pad over the previous ones",
|
|
GST_TYPE_D3D12_COMPOSITOR_OPERATOR, DEFAULT_PAD_OPERATOR,
|
|
param_flags));
|
|
g_object_class_install_property (object_class, PROP_PAD_SIZING_POLICY,
|
|
g_param_spec_enum ("sizing-policy", "Sizing policy",
|
|
"Sizing policy to use for image scaling",
|
|
GST_TYPE_D3D12_COMPOSITOR_SIZING_POLICY, DEFAULT_PAD_SIZING_POLICY,
|
|
param_flags));
|
|
|
|
vagg_pad_class->update_conversion_info =
|
|
GST_DEBUG_FUNCPTR (gst_d3d12_compositor_pad_update_conversion_info);
|
|
vagg_pad_class->prepare_frame_start =
|
|
GST_DEBUG_FUNCPTR (gst_d3d12_compositor_pad_prepare_frame_start);
|
|
vagg_pad_class->prepare_frame_finish =
|
|
GST_DEBUG_FUNCPTR (gst_d3d12_compositor_pad_prepare_frame_finish);
|
|
vagg_pad_class->clean_frame =
|
|
GST_DEBUG_FUNCPTR (gst_d3d12_compositor_pad_clean_frame);
|
|
|
|
gst_type_mark_as_plugin_api (GST_TYPE_D3D12_COMPOSITOR_OPERATOR,
|
|
(GstPluginAPIFlags) 0);
|
|
gst_type_mark_as_plugin_api (GST_TYPE_D3D12_COMPOSITOR_SIZING_POLICY,
|
|
(GstPluginAPIFlags) 0);
|
|
}
|
|
|
|
static void
|
|
gst_d3d12_compositor_pad_init (GstD3D12CompositorPad * pad)
|
|
{
|
|
pad->priv = new GstD3D12CompositorPadPrivate ();
|
|
}
|
|
|
|
static void
|
|
gst_d3d12_compositor_pad_finalize (GObject * object)
|
|
{
|
|
auto self = GST_D3D12_COMPOSITOR_PAD (object);
|
|
|
|
delete self->priv;
|
|
|
|
G_OBJECT_CLASS (parent_pad_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
gst_d3d12_compositor_pad_update_position (GstD3D12CompositorPad * pad,
|
|
gint * old, const GValue * value)
|
|
{
|
|
auto priv = pad->priv;
|
|
gint tmp = g_value_get_int (value);
|
|
|
|
if (*old != tmp) {
|
|
*old = tmp;
|
|
priv->position_updated = TRUE;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_d3d12_compositor_pad_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
auto pad = GST_D3D12_COMPOSITOR_PAD (object);
|
|
auto priv = pad->priv;
|
|
|
|
std::lock_guard < std::recursive_mutex > lk (priv->lock);
|
|
switch (prop_id) {
|
|
case PROP_PAD_XPOS:
|
|
gst_d3d12_compositor_pad_update_position (pad, &priv->xpos, value);
|
|
break;
|
|
case PROP_PAD_YPOS:
|
|
gst_d3d12_compositor_pad_update_position (pad, &priv->ypos, value);
|
|
break;
|
|
case PROP_PAD_WIDTH:
|
|
gst_d3d12_compositor_pad_update_position (pad, &priv->width, value);
|
|
break;
|
|
case PROP_PAD_HEIGHT:
|
|
gst_d3d12_compositor_pad_update_position (pad, &priv->height, value);
|
|
break;
|
|
case PROP_PAD_ALPHA:{
|
|
gdouble alpha = g_value_get_double (value);
|
|
if (priv->alpha != alpha) {
|
|
priv->alpha_updated = TRUE;
|
|
priv->alpha = alpha;
|
|
}
|
|
break;
|
|
}
|
|
case PROP_PAD_OPERATOR:{
|
|
GstD3D12CompositorOperator op =
|
|
(GstD3D12CompositorOperator) g_value_get_enum (value);
|
|
if (op != priv->op) {
|
|
priv->op = op;
|
|
priv->blend_desc_updated = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case PROP_PAD_SIZING_POLICY:{
|
|
GstD3D12CompositorSizingPolicy policy =
|
|
(GstD3D12CompositorSizingPolicy) g_value_get_enum (value);
|
|
if (priv->sizing_policy != policy) {
|
|
priv->sizing_policy = policy;
|
|
priv->position_updated = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_d3d12_compositor_pad_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
auto pad = GST_D3D12_COMPOSITOR_PAD (object);
|
|
auto priv = pad->priv;
|
|
|
|
std::lock_guard < std::recursive_mutex > lk (priv->lock);
|
|
switch (prop_id) {
|
|
case PROP_PAD_XPOS:
|
|
g_value_set_int (value, priv->xpos);
|
|
break;
|
|
case PROP_PAD_YPOS:
|
|
g_value_set_int (value, priv->ypos);
|
|
break;
|
|
case PROP_PAD_WIDTH:
|
|
g_value_set_int (value, priv->width);
|
|
break;
|
|
case PROP_PAD_HEIGHT:
|
|
g_value_set_int (value, priv->height);
|
|
break;
|
|
case PROP_PAD_ALPHA:
|
|
g_value_set_double (value, priv->alpha);
|
|
break;
|
|
case PROP_PAD_OPERATOR:
|
|
g_value_set_enum (value, priv->op);
|
|
break;
|
|
case PROP_PAD_SIZING_POLICY:
|
|
g_value_set_enum (value, priv->sizing_policy);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_d3d12_compositor_pad_update_conversion_info (GstVideoAggregatorPad * pad)
|
|
{
|
|
auto cpad = GST_D3D12_COMPOSITOR_PAD (pad);
|
|
auto priv = cpad->priv;
|
|
|
|
std::lock_guard < std::recursive_mutex > lk (priv->lock);
|
|
priv->position_updated = TRUE;
|
|
}
|
|
|
|
static void
|
|
gst_d3d12_compositor_pad_get_output_size (GstD3D12CompositorPad * pad,
|
|
gint out_par_n, gint out_par_d, gint * width, gint * height,
|
|
gint * x_offset, gint * y_offset)
|
|
{
|
|
auto priv = pad->priv;
|
|
auto vagg_pad = GST_VIDEO_AGGREGATOR_PAD (pad);
|
|
gint pad_width, pad_height;
|
|
guint dar_n, dar_d;
|
|
|
|
*x_offset = 0;
|
|
*y_offset = 0;
|
|
*width = 0;
|
|
*height = 0;
|
|
|
|
/* FIXME: Anything better we can do here? */
|
|
if (!vagg_pad->info.finfo
|
|
|| vagg_pad->info.finfo->format == GST_VIDEO_FORMAT_UNKNOWN) {
|
|
GST_DEBUG_OBJECT (pad, "Have no caps yet");
|
|
return;
|
|
}
|
|
|
|
std::lock_guard < std::recursive_mutex > lk (priv->lock);
|
|
|
|
pad_width = priv->width <= 0 ? GST_VIDEO_INFO_WIDTH (&vagg_pad->info) :
|
|
priv->width;
|
|
pad_height = priv->height <= 0 ? GST_VIDEO_INFO_HEIGHT (&vagg_pad->info) :
|
|
priv->height;
|
|
|
|
if (pad_width == 0 || pad_height == 0)
|
|
return;
|
|
|
|
if (!gst_video_calculate_display_ratio (&dar_n, &dar_d, pad_width, pad_height,
|
|
GST_VIDEO_INFO_PAR_N (&vagg_pad->info),
|
|
GST_VIDEO_INFO_PAR_D (&vagg_pad->info), out_par_n, out_par_d)) {
|
|
GST_WARNING_OBJECT (pad, "Cannot calculate display aspect ratio");
|
|
return;
|
|
}
|
|
|
|
GST_TRACE_OBJECT (pad, "scaling %ux%u by %u/%u (%u/%u / %u/%u)",
|
|
pad_width, pad_height, dar_n, dar_d,
|
|
GST_VIDEO_INFO_PAR_N (&vagg_pad->info),
|
|
GST_VIDEO_INFO_PAR_D (&vagg_pad->info), out_par_n, out_par_d);
|
|
|
|
switch (priv->sizing_policy) {
|
|
case GST_D3D12_COMPOSITOR_SIZING_POLICY_NONE:
|
|
/* Pick either height or width, whichever is an integer multiple of the
|
|
* display aspect ratio. However, prefer preserving the height to account
|
|
* for interlaced video. */
|
|
if (pad_height % dar_n == 0) {
|
|
pad_width = gst_util_uint64_scale_int (pad_height, dar_n, dar_d);
|
|
} else if (pad_width % dar_d == 0) {
|
|
pad_height = gst_util_uint64_scale_int (pad_width, dar_d, dar_n);
|
|
} else {
|
|
pad_width = gst_util_uint64_scale_int (pad_height, dar_n, dar_d);
|
|
}
|
|
break;
|
|
case GST_D3D12_COMPOSITOR_SIZING_POLICY_KEEP_ASPECT_RATIO:{
|
|
gint from_dar_n, from_dar_d, to_dar_n, to_dar_d, num, den;
|
|
|
|
/* Calculate DAR again with actual video size */
|
|
if (!gst_util_fraction_multiply (GST_VIDEO_INFO_WIDTH (&vagg_pad->info),
|
|
GST_VIDEO_INFO_HEIGHT (&vagg_pad->info),
|
|
GST_VIDEO_INFO_PAR_N (&vagg_pad->info),
|
|
GST_VIDEO_INFO_PAR_D (&vagg_pad->info), &from_dar_n,
|
|
&from_dar_d)) {
|
|
from_dar_n = from_dar_d = -1;
|
|
}
|
|
|
|
if (!gst_util_fraction_multiply (pad_width, pad_height,
|
|
out_par_n, out_par_d, &to_dar_n, &to_dar_d)) {
|
|
to_dar_n = to_dar_d = -1;
|
|
}
|
|
|
|
if (from_dar_n != to_dar_n || from_dar_d != to_dar_d) {
|
|
/* Calculate new output resolution */
|
|
if (from_dar_n != -1 && from_dar_d != -1
|
|
&& gst_util_fraction_multiply (from_dar_n, from_dar_d,
|
|
out_par_d, out_par_n, &num, &den)) {
|
|
GstVideoRectangle src_rect, dst_rect, rst_rect;
|
|
|
|
src_rect.h = gst_util_uint64_scale_int (pad_width, den, num);
|
|
if (src_rect.h == 0) {
|
|
pad_width = 0;
|
|
pad_height = 0;
|
|
break;
|
|
}
|
|
|
|
src_rect.x = src_rect.y = 0;
|
|
src_rect.w = pad_width;
|
|
|
|
dst_rect.x = dst_rect.y = 0;
|
|
dst_rect.w = pad_width;
|
|
dst_rect.h = pad_height;
|
|
|
|
/* Scale rect to be centered in destination rect */
|
|
gst_video_center_rect (&src_rect, &dst_rect, &rst_rect, TRUE);
|
|
|
|
GST_LOG_OBJECT (pad,
|
|
"Re-calculated size %dx%d -> %dx%d (x-offset %d, y-offset %d)",
|
|
pad_width, pad_height, rst_rect.w, rst_rect.h, rst_rect.x,
|
|
rst_rect.h);
|
|
|
|
*x_offset = rst_rect.x;
|
|
*y_offset = rst_rect.y;
|
|
pad_width = rst_rect.w;
|
|
pad_height = rst_rect.h;
|
|
} else {
|
|
GST_WARNING_OBJECT (pad, "Failed to calculate output size");
|
|
|
|
*x_offset = 0;
|
|
*y_offset = 0;
|
|
pad_width = 0;
|
|
pad_height = 0;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
*width = pad_width;
|
|
*height = pad_height;
|
|
}
|
|
|
|
static GstVideoRectangle
|
|
clamp_rectangle (gint x, gint y, gint w, gint h, gint outer_width,
|
|
gint outer_height)
|
|
{
|
|
gint x2 = x + w;
|
|
gint y2 = y + h;
|
|
GstVideoRectangle clamped;
|
|
|
|
/* Clamp the x/y coordinates of this frame to the output boundaries to cover
|
|
* the case where (say, with negative xpos/ypos or w/h greater than the output
|
|
* size) the non-obscured portion of the frame could be outside the bounds of
|
|
* the video itself and hence not visible at all */
|
|
clamped.x = CLAMP (x, 0, outer_width);
|
|
clamped.y = CLAMP (y, 0, outer_height);
|
|
clamped.w = CLAMP (x2, 0, outer_width) - clamped.x;
|
|
clamped.h = CLAMP (y2, 0, outer_height) - clamped.y;
|
|
|
|
return clamped;
|
|
}
|
|
|
|
static gboolean
|
|
gst_d3d12_compositor_pad_check_frame_obscured (GstVideoAggregatorPad * pad,
|
|
GstVideoAggregator * vagg)
|
|
{
|
|
auto cpad = GST_D3D12_COMPOSITOR_PAD (pad);
|
|
auto priv = cpad->priv;
|
|
gint width, height;
|
|
GstVideoInfo *info = &vagg->info;
|
|
/* The rectangle representing this frame, clamped to the video's boundaries.
|
|
* Due to the clamping, this is different from the frame width/height above. */
|
|
GstVideoRectangle frame_rect;
|
|
gint x_offset, y_offset;
|
|
|
|
/* There's three types of width/height here:
|
|
* 1. GST_VIDEO_FRAME_WIDTH/HEIGHT:
|
|
* The frame width/height (same as pad->info.height/width;
|
|
* see gst_video_frame_map())
|
|
* 2. cpad->width/height:
|
|
* The optional pad property for scaling the frame (if zero, the video is
|
|
* left unscaled)
|
|
*/
|
|
|
|
std::lock_guard < std::recursive_mutex > lk (priv->lock);
|
|
if (priv->alpha == 0)
|
|
return TRUE;
|
|
|
|
gst_d3d12_compositor_pad_get_output_size (cpad, GST_VIDEO_INFO_PAR_N (info),
|
|
GST_VIDEO_INFO_PAR_D (info), &width, &height, &x_offset, &y_offset);
|
|
|
|
frame_rect = clamp_rectangle (priv->xpos + x_offset, priv->ypos + y_offset,
|
|
width, height, GST_VIDEO_INFO_WIDTH (info), GST_VIDEO_INFO_HEIGHT (info));
|
|
|
|
if (frame_rect.w == 0 || frame_rect.h == 0) {
|
|
GST_DEBUG_OBJECT (pad, "Resulting frame is zero-width or zero-height "
|
|
"(w: %i, h: %i), skipping", frame_rect.w, frame_rect.h);
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_d3d12_compositor_pad_setup_converter (GstVideoAggregatorPad * pad,
|
|
GstVideoAggregator * vagg)
|
|
{
|
|
auto cpad = GST_D3D12_COMPOSITOR_PAD (pad);
|
|
auto priv = cpad->priv;
|
|
auto self = GST_D3D12_COMPOSITOR (vagg);
|
|
gint width, height;
|
|
GstVideoInfo *info = &vagg->info;
|
|
GstVideoRectangle frame_rect;
|
|
gboolean output_has_alpha_comp = FALSE;
|
|
gint x_offset, y_offset;
|
|
|
|
std::lock_guard < std::recursive_mutex > lk (priv->lock);
|
|
|
|
if (GST_VIDEO_INFO_HAS_ALPHA (info) ||
|
|
GST_VIDEO_INFO_FORMAT (info) == GST_VIDEO_FORMAT_BGRx ||
|
|
GST_VIDEO_INFO_FORMAT (info) == GST_VIDEO_FORMAT_RGBx) {
|
|
output_has_alpha_comp = TRUE;
|
|
}
|
|
|
|
if (priv->ctx) {
|
|
if (GST_VIDEO_INFO_FORMAT (&priv->ctx->info) !=
|
|
GST_VIDEO_INFO_FORMAT (&pad->info)) {
|
|
priv->ctx = nullptr;
|
|
}
|
|
}
|
|
|
|
if (!priv->ctx || priv->blend_desc_updated) {
|
|
switch (priv->op) {
|
|
case GST_D3D12_COMPOSITOR_OPERATOR_SOURCE:
|
|
priv->blend_desc.RenderTarget[0] = g_blend_source;
|
|
break;
|
|
case GST_D3D12_COMPOSITOR_OPERATOR_OVER:
|
|
if (output_has_alpha_comp)
|
|
priv->blend_desc.RenderTarget[0] = g_blend_over;
|
|
else
|
|
priv->blend_desc.RenderTarget[0] = g_blend_over_factor;
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if (!priv->ctx || priv->alpha_updated) {
|
|
for (guint i = 0; i < 4; i++)
|
|
priv->blend_factor[i] = priv->alpha;
|
|
}
|
|
|
|
if (!priv->ctx) {
|
|
auto ctx = std::make_unique < PadContext > (self->device);
|
|
ctx->info = pad->info;
|
|
|
|
ctx->conv = gst_d3d12_converter_new (self->device, nullptr, &pad->info,
|
|
info, &priv->blend_desc, priv->blend_factor, nullptr);
|
|
if (!ctx->conv) {
|
|
GST_ERROR_OBJECT (pad, "Couldn't create converter");
|
|
return FALSE;
|
|
}
|
|
|
|
priv->ctx = std::move (ctx);
|
|
}
|
|
|
|
if (priv->ctx->fence_val == 0 || priv->alpha_updated) {
|
|
g_object_set (priv->ctx->conv, "alpha", priv->alpha, nullptr);
|
|
gst_d3d12_converter_update_blend_state (priv->ctx->conv,
|
|
&priv->blend_desc, priv->blend_factor);
|
|
}
|
|
|
|
priv->alpha_updated = FALSE;
|
|
priv->blend_desc_updated = FALSE;
|
|
|
|
if (priv->ctx->fence_val != 0 && !priv->position_updated)
|
|
return TRUE;
|
|
|
|
gst_d3d12_compositor_pad_get_output_size (cpad, GST_VIDEO_INFO_PAR_N (info),
|
|
GST_VIDEO_INFO_PAR_D (info), &width, &height, &x_offset, &y_offset);
|
|
|
|
frame_rect = clamp_rectangle (priv->xpos + x_offset, priv->ypos + y_offset,
|
|
width, height, GST_VIDEO_INFO_WIDTH (info), GST_VIDEO_INFO_HEIGHT (info));
|
|
|
|
#ifndef GST_DISABLE_GST_DEBUG
|
|
guint zorder = 0;
|
|
g_object_get (pad, "zorder", &zorder, nullptr);
|
|
|
|
GST_LOG_OBJECT (pad, "Update position, pad-xpos %d, pad-ypos %d, "
|
|
"pad-zorder %d, pad-width %d, pad-height %d, in-resolution %dx%d, "
|
|
"out-resoution %dx%d, dst-{x,y,width,height} %d-%d-%d-%d",
|
|
priv->xpos, priv->ypos, zorder, priv->width, priv->height,
|
|
GST_VIDEO_INFO_WIDTH (&pad->info), GST_VIDEO_INFO_HEIGHT (&pad->info),
|
|
GST_VIDEO_INFO_WIDTH (info), GST_VIDEO_INFO_HEIGHT (info),
|
|
frame_rect.x, frame_rect.y, frame_rect.w, frame_rect.h);
|
|
#endif
|
|
|
|
priv->position_updated = FALSE;
|
|
|
|
g_object_set (priv->ctx->conv, "dest-x", frame_rect.x,
|
|
"dest-y", frame_rect.y, "dest-width", frame_rect.w,
|
|
"dest-height", frame_rect.h, nullptr);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_d3d12_compositor_preprare_func (GstVideoAggregatorPad * pad,
|
|
GstVideoAggregator * vagg, GstBuffer * buffer,
|
|
GstVideoFrame * prepared_frame)
|
|
{
|
|
auto self = GST_D3D12_COMPOSITOR (vagg);
|
|
auto cpad = GST_D3D12_COMPOSITOR_PAD (pad);
|
|
auto priv = cpad->priv;
|
|
|
|
GST_LOG_OBJECT (pad, "Building command list");
|
|
|
|
if (!self->priv->generated_output_buf) {
|
|
GST_ERROR_OBJECT (cpad, "Have no generated output buf");
|
|
return FALSE;
|
|
}
|
|
|
|
/* Skip this frame */
|
|
if (gst_d3d12_compositor_pad_check_frame_obscured (pad, vagg))
|
|
return TRUE;
|
|
|
|
if (!gst_d3d12_compositor_pad_setup_converter (pad, vagg))
|
|
return FALSE;
|
|
|
|
gint x, y, w, h;
|
|
auto crop_meta = gst_buffer_get_video_crop_meta (buffer);
|
|
if (crop_meta) {
|
|
x = crop_meta->x;
|
|
y = crop_meta->y;
|
|
w = crop_meta->width;
|
|
h = crop_meta->height;
|
|
} else {
|
|
x = y = 0;
|
|
w = pad->info.width;
|
|
h = pad->info.height;
|
|
}
|
|
|
|
g_assert (priv->ctx);
|
|
|
|
g_object_set (priv->ctx->conv, "src-x", x, "src-y", y, "src-width", w,
|
|
"src-height", h, nullptr);
|
|
|
|
GstD3D12CmdAlloc *gst_ca;
|
|
if (!gst_d3d12_cmd_alloc_pool_acquire (priv->ctx->ca_pool, &gst_ca)) {
|
|
GST_ERROR_OBJECT (cpad, "Couldn't acquire command allocator");
|
|
return FALSE;
|
|
}
|
|
|
|
GstD3D12FenceData *fence_data;
|
|
gst_d3d12_fence_data_pool_acquire (self->priv->fence_data_pool, &fence_data);
|
|
gst_d3d12_fence_data_push (fence_data, FENCE_NOTIFY_MINI_OBJECT (gst_ca));
|
|
|
|
auto ca = gst_d3d12_cmd_alloc_get_handle (gst_ca);
|
|
|
|
auto hr = ca->Reset ();
|
|
if (!gst_d3d12_result (hr, priv->ctx->device)) {
|
|
GST_ERROR_OBJECT (cpad, "Couldn't reset command allocator");
|
|
gst_d3d12_fence_data_unref (fence_data);
|
|
return FALSE;
|
|
}
|
|
|
|
if (!priv->ctx->cl) {
|
|
auto device = gst_d3d12_device_get_device_handle (priv->ctx->device);
|
|
hr = device->CreateCommandList (0, D3D12_COMMAND_LIST_TYPE_DIRECT,
|
|
ca, nullptr, IID_PPV_ARGS (&priv->ctx->cl));
|
|
if (!gst_d3d12_result (hr, priv->ctx->device)) {
|
|
GST_ERROR_OBJECT (cpad, "Couldn't create command list");
|
|
gst_d3d12_fence_data_unref (fence_data);
|
|
return FALSE;
|
|
}
|
|
} else {
|
|
hr = priv->ctx->cl->Reset (ca, nullptr);
|
|
if (!gst_d3d12_result (hr, priv->ctx->device)) {
|
|
GST_ERROR_OBJECT (self, "Couldn't reset command list");
|
|
gst_d3d12_fence_data_unref (fence_data);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if (!gst_d3d12_converter_convert_buffer (priv->ctx->conv,
|
|
buffer, self->priv->generated_output_buf, fence_data,
|
|
priv->ctx->cl.Get (), TRUE)) {
|
|
GST_ERROR_OBJECT (self, "Couldn't build command list");
|
|
gst_d3d12_fence_data_unref (fence_data);
|
|
return FALSE;
|
|
}
|
|
|
|
hr = priv->ctx->cl->Close ();
|
|
if (!gst_d3d12_result (hr, priv->ctx->device)) {
|
|
GST_ERROR_OBJECT (self, "Couldn't close command list");
|
|
gst_d3d12_fence_data_unref (fence_data);
|
|
return FALSE;
|
|
}
|
|
|
|
prepared_frame->buffer = buffer;
|
|
|
|
priv->ctx->fence_data = fence_data;
|
|
|
|
GST_LOG_OBJECT (pad, "Command list prepared");
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gst_d3d12_compositor_pad_prepare_frame_start (GstVideoAggregatorPad * pad,
|
|
GstVideoAggregator * vagg, GstBuffer * buffer,
|
|
GstVideoFrame * prepared_frame)
|
|
{
|
|
auto cpad = GST_D3D12_COMPOSITOR_PAD (pad);
|
|
auto priv = cpad->priv;
|
|
|
|
GST_LOG_OBJECT (cpad, "Prepare start");
|
|
|
|
priv->prepare_rst = std::async (std::launch::async,
|
|
gst_d3d12_compositor_preprare_func, pad, vagg, buffer, prepared_frame);
|
|
}
|
|
|
|
static void
|
|
gst_d3d12_compositor_pad_prepare_frame_finish (GstVideoAggregatorPad * pad,
|
|
GstVideoAggregator * vagg, GstVideoFrame * prepared_frame)
|
|
{
|
|
/* Will wait on aggregate() function */
|
|
}
|
|
|
|
static void
|
|
gst_d3d12_compositor_pad_clean_frame (GstVideoAggregatorPad * pad,
|
|
GstVideoAggregator * vagg, GstVideoFrame * prepared_frame)
|
|
{
|
|
auto cpad = GST_D3D12_COMPOSITOR_PAD (pad);
|
|
auto priv = cpad->priv;
|
|
|
|
if (priv->prepare_rst.valid ()) {
|
|
GST_WARNING_OBJECT (cpad, "Async task still pending");
|
|
priv->prepare_rst.get ();
|
|
}
|
|
|
|
memset (prepared_frame, 0, sizeof (GstVideoFrame));
|
|
|
|
if (priv->ctx && priv->ctx->fence_data) {
|
|
gst_d3d12_device_set_fence_notify (priv->ctx->device,
|
|
D3D12_COMMAND_LIST_TYPE_DIRECT, priv->ctx->fence_val,
|
|
FENCE_NOTIFY_MINI_OBJECT (priv->ctx->fence_data));
|
|
priv->ctx->fence_data = nullptr;
|
|
}
|
|
}
|
|
|
|
static GstStaticPadTemplate sink_template =
|
|
GST_STATIC_PAD_TEMPLATE ("sink_%u", GST_PAD_SINK, GST_PAD_REQUEST,
|
|
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES
|
|
(GST_CAPS_FEATURE_MEMORY_D3D12_MEMORY, GST_D3D12_ALL_FORMATS) "; "
|
|
GST_VIDEO_CAPS_MAKE (GST_D3D12_ALL_FORMATS)));
|
|
|
|
/* formats we can output without conversion.
|
|
* Excludes 10/12 bits planar YUV (needs bitshift) and
|
|
* AYUV/AYUV64 (d3d12 runtime does not understand the ayuv order) */
|
|
#define COMPOSITOR_SRC_FORMATS \
|
|
"{ RGBA64_LE, RGB10A2_LE, BGRA, RGBA, BGRx, RGBx, VUYA, NV12, NV21, " \
|
|
"P010_10LE, P012_LE, P016_LE, I420, YV12, Y42B, Y444, Y444_16LE, " \
|
|
"GRAY8, GRAY16_LE }"
|
|
|
|
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, COMPOSITOR_SRC_FORMATS) "; "
|
|
GST_VIDEO_CAPS_MAKE (COMPOSITOR_SRC_FORMATS)));
|
|
|
|
static void gst_d3d12_compositor_child_proxy_init (gpointer g_iface,
|
|
gpointer iface_data);
|
|
static void gst_d3d12_compositor_finalize (GObject * object);
|
|
static void gst_d3d12_compositor_set_property (GObject * object,
|
|
guint prop_id, const GValue * value, GParamSpec * pspec);
|
|
static void gst_d3d12_compositor_get_property (GObject * object,
|
|
guint prop_id, GValue * value, GParamSpec * pspec);
|
|
|
|
static GstPad *gst_d3d12_compositor_request_new_pad (GstElement * element,
|
|
GstPadTemplate * templ, const gchar * name, const GstCaps * caps);
|
|
static void gst_d3d12_compositor_release_pad (GstElement * element,
|
|
GstPad * pad);
|
|
static void gst_d3d12_compositor_set_context (GstElement * element,
|
|
GstContext * context);
|
|
|
|
static gboolean gst_d3d12_compositor_start (GstAggregator * agg);
|
|
static gboolean gst_d3d12_compositor_stop (GstAggregator * agg);
|
|
static gboolean gst_d3d12_compositor_sink_query (GstAggregator * agg,
|
|
GstAggregatorPad * pad, GstQuery * query);
|
|
static gboolean gst_d3d12_compositor_src_query (GstAggregator * agg,
|
|
GstQuery * query);
|
|
static GstCaps *gst_d3d12_compositor_fixate_src_caps (GstAggregator * agg,
|
|
GstCaps * caps);
|
|
static gboolean gst_d3d12_compositor_negotiated_src_caps (GstAggregator * agg,
|
|
GstCaps * caps);
|
|
static gboolean
|
|
gst_d3d12_compositor_propose_allocation (GstAggregator * agg,
|
|
GstAggregatorPad * pad, GstQuery * decide_query, GstQuery * query);
|
|
static gboolean gst_d3d12_compositor_decide_allocation (GstAggregator * agg,
|
|
GstQuery * query);
|
|
static GstFlowReturn
|
|
gst_d3d12_compositor_aggregate_frames (GstVideoAggregator * vagg,
|
|
GstBuffer * outbuf);
|
|
static GstFlowReturn
|
|
gst_d3d12_compositor_create_output_buffer (GstVideoAggregator * vagg,
|
|
GstBuffer ** outbuffer);
|
|
|
|
#define gst_d3d12_compositor_parent_class parent_class
|
|
G_DEFINE_TYPE_WITH_CODE (GstD3D12Compositor, gst_d3d12_compositor,
|
|
GST_TYPE_VIDEO_AGGREGATOR, G_IMPLEMENT_INTERFACE (GST_TYPE_CHILD_PROXY,
|
|
gst_d3d12_compositor_child_proxy_init));
|
|
|
|
static void
|
|
gst_d3d12_compositor_class_init (GstD3D12CompositorClass * klass)
|
|
{
|
|
auto object_class = G_OBJECT_CLASS (klass);
|
|
auto element_class = GST_ELEMENT_CLASS (klass);
|
|
auto agg_class = GST_AGGREGATOR_CLASS (klass);
|
|
auto vagg_class = GST_VIDEO_AGGREGATOR_CLASS (klass);
|
|
|
|
object_class->finalize = gst_d3d12_compositor_finalize;
|
|
object_class->set_property = gst_d3d12_compositor_set_property;
|
|
object_class->get_property = gst_d3d12_compositor_get_property;
|
|
|
|
g_object_class_install_property (object_class, PROP_ADAPTER,
|
|
g_param_spec_int ("adapter", "Adapter",
|
|
"Adapter index for creating device (-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_BACKGROUND,
|
|
g_param_spec_enum ("background", "Background", "Background type",
|
|
GST_TYPE_D3D12_COMPOSITOR_BACKGROUND,
|
|
DEFAULT_BACKGROUND,
|
|
(GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
|
|
|
g_object_class_install_property (object_class,
|
|
PROP_IGNORE_INACTIVE_PADS, g_param_spec_boolean ("ignore-inactive-pads",
|
|
"Ignore inactive pads",
|
|
"Avoid timing out waiting for inactive pads", FALSE,
|
|
(GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
|
|
|
g_object_class_install_property (object_class, PROP_ASYNC_DEPTH,
|
|
g_param_spec_uint ("async-depth", "Async Depth",
|
|
"Number of in-flight GPU commands which can be scheduled without "
|
|
"synchronization (0 = unlimited)", 0, G_MAXINT, DEFAULT_ASYNC_DEPTH,
|
|
(GParamFlags) (GST_PARAM_MUTABLE_PLAYING |
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
|
|
|
element_class->request_new_pad =
|
|
GST_DEBUG_FUNCPTR (gst_d3d12_compositor_request_new_pad);
|
|
element_class->release_pad =
|
|
GST_DEBUG_FUNCPTR (gst_d3d12_compositor_release_pad);
|
|
element_class->set_context =
|
|
GST_DEBUG_FUNCPTR (gst_d3d12_compositor_set_context);
|
|
|
|
agg_class->start = GST_DEBUG_FUNCPTR (gst_d3d12_compositor_start);
|
|
agg_class->stop = GST_DEBUG_FUNCPTR (gst_d3d12_compositor_stop);
|
|
agg_class->sink_query = GST_DEBUG_FUNCPTR (gst_d3d12_compositor_sink_query);
|
|
agg_class->src_query = GST_DEBUG_FUNCPTR (gst_d3d12_compositor_src_query);
|
|
agg_class->fixate_src_caps =
|
|
GST_DEBUG_FUNCPTR (gst_d3d12_compositor_fixate_src_caps);
|
|
agg_class->negotiated_src_caps =
|
|
GST_DEBUG_FUNCPTR (gst_d3d12_compositor_negotiated_src_caps);
|
|
agg_class->propose_allocation =
|
|
GST_DEBUG_FUNCPTR (gst_d3d12_compositor_propose_allocation);
|
|
agg_class->decide_allocation =
|
|
GST_DEBUG_FUNCPTR (gst_d3d12_compositor_decide_allocation);
|
|
|
|
vagg_class->aggregate_frames =
|
|
GST_DEBUG_FUNCPTR (gst_d3d12_compositor_aggregate_frames);
|
|
vagg_class->create_output_buffer =
|
|
GST_DEBUG_FUNCPTR (gst_d3d12_compositor_create_output_buffer);
|
|
|
|
gst_element_class_add_static_pad_template_with_gtype (element_class,
|
|
&sink_template, GST_TYPE_D3D12_COMPOSITOR_PAD);
|
|
gst_element_class_add_static_pad_template_with_gtype (element_class,
|
|
&src_template, GST_TYPE_AGGREGATOR_PAD);
|
|
|
|
gst_element_class_set_static_metadata (element_class, "Direct3D12 Compositor",
|
|
"Filter/Editor/Video/Compositor", "A Direct3D12 compositor",
|
|
"Seungha Yang <seungha@centricular.com>");
|
|
|
|
gst_type_mark_as_plugin_api (GST_TYPE_D3D12_COMPOSITOR_BACKGROUND,
|
|
(GstPluginAPIFlags) 0);
|
|
gst_type_mark_as_plugin_api (GST_TYPE_D3D12_COMPOSITOR_PAD,
|
|
(GstPluginAPIFlags) 0);
|
|
|
|
GST_DEBUG_CATEGORY_INIT (gst_d3d12_compositor_debug,
|
|
"d3d12compositor", 0, "d3d12compositor element");
|
|
}
|
|
|
|
static void
|
|
gst_d3d12_compositor_init (GstD3D12Compositor * self)
|
|
{
|
|
self->priv = new GStD3D12CompositorPrivate ();
|
|
}
|
|
|
|
static void
|
|
gst_d3d12_compositor_finalize (GObject * object)
|
|
{
|
|
auto self = GST_D3D12_COMPOSITOR (object);
|
|
|
|
delete self->priv;
|
|
|
|
gst_clear_object (&self->device);
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
gst_d3d12_compositor_set_property (GObject * object,
|
|
guint prop_id, const GValue * value, GParamSpec * pspec)
|
|
{
|
|
auto self = GST_D3D12_COMPOSITOR (object);
|
|
auto priv = self->priv;
|
|
|
|
std::lock_guard < std::recursive_mutex > lk (priv->lock);
|
|
switch (prop_id) {
|
|
case PROP_ADAPTER:
|
|
priv->adapter = g_value_get_int (value);
|
|
break;
|
|
case PROP_BACKGROUND:
|
|
priv->background =
|
|
(GstD3D12CompositorBackground) g_value_get_enum (value);
|
|
break;
|
|
case PROP_IGNORE_INACTIVE_PADS:
|
|
gst_aggregator_set_ignore_inactive_pads (GST_AGGREGATOR (object),
|
|
g_value_get_boolean (value));
|
|
break;
|
|
case PROP_ASYNC_DEPTH:
|
|
priv->async_depth = g_value_get_uint (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_d3d12_compositor_get_property (GObject * object,
|
|
guint prop_id, GValue * value, GParamSpec * pspec)
|
|
{
|
|
auto self = GST_D3D12_COMPOSITOR (object);
|
|
auto priv = self->priv;
|
|
|
|
std::lock_guard < std::recursive_mutex > lk (priv->lock);
|
|
switch (prop_id) {
|
|
case PROP_ADAPTER:
|
|
g_value_set_int (value, priv->adapter);
|
|
break;
|
|
case PROP_BACKGROUND:
|
|
g_value_set_enum (value, priv->background);
|
|
break;
|
|
case PROP_IGNORE_INACTIVE_PADS:
|
|
g_value_set_boolean (value,
|
|
gst_aggregator_get_ignore_inactive_pads (GST_AGGREGATOR (object)));
|
|
break;
|
|
case PROP_ASYNC_DEPTH:
|
|
g_value_set_uint (value, priv->async_depth);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static GObject *
|
|
gst_d3d12_compositor_child_proxy_get_child_by_index (GstChildProxy * proxy,
|
|
guint index)
|
|
{
|
|
auto self = GST_D3D12_COMPOSITOR (proxy);
|
|
GObject *obj = nullptr;
|
|
|
|
GST_OBJECT_LOCK (self);
|
|
obj = (GObject *) g_list_nth_data (GST_ELEMENT_CAST (self)->sinkpads, index);
|
|
if (obj)
|
|
gst_object_ref (obj);
|
|
GST_OBJECT_UNLOCK (self);
|
|
|
|
return obj;
|
|
}
|
|
|
|
static guint
|
|
gst_d3d12_compositor_child_proxy_get_children_count (GstChildProxy * proxy)
|
|
{
|
|
auto self = GST_D3D12_COMPOSITOR (proxy);
|
|
guint count = 0;
|
|
|
|
GST_OBJECT_LOCK (self);
|
|
count = GST_ELEMENT_CAST (self)->numsinkpads;
|
|
GST_OBJECT_UNLOCK (self);
|
|
GST_INFO_OBJECT (self, "Children Count: %d", count);
|
|
|
|
return count;
|
|
}
|
|
|
|
static void
|
|
gst_d3d12_compositor_child_proxy_init (gpointer g_iface, gpointer iface_data)
|
|
{
|
|
GstChildProxyInterface *iface = (GstChildProxyInterface *) g_iface;
|
|
|
|
iface->get_child_by_index =
|
|
gst_d3d12_compositor_child_proxy_get_child_by_index;
|
|
iface->get_children_count =
|
|
gst_d3d12_compositor_child_proxy_get_children_count;
|
|
}
|
|
|
|
static GstPad *
|
|
gst_d3d12_compositor_request_new_pad (GstElement * element,
|
|
GstPadTemplate * templ, const gchar * name, const GstCaps * caps)
|
|
{
|
|
GstPad *pad;
|
|
|
|
pad = GST_ELEMENT_CLASS (parent_class)->request_new_pad (element,
|
|
templ, name, caps);
|
|
|
|
if (!pad) {
|
|
GST_DEBUG_OBJECT (element, "could not create/add pad");
|
|
return nullptr;
|
|
}
|
|
|
|
gst_child_proxy_child_added (GST_CHILD_PROXY (element), G_OBJECT (pad),
|
|
GST_OBJECT_NAME (pad));
|
|
|
|
GST_DEBUG_OBJECT (element, "Created new pad %s:%s", GST_DEBUG_PAD_NAME (pad));
|
|
|
|
return pad;
|
|
}
|
|
|
|
static void
|
|
gst_d3d12_compositor_release_pad (GstElement * element, GstPad * pad)
|
|
{
|
|
auto self = GST_D3D12_COMPOSITOR (element);
|
|
|
|
GST_DEBUG_OBJECT (self, "Releasing pad %s:%s", GST_DEBUG_PAD_NAME (pad));
|
|
|
|
gst_child_proxy_child_removed (GST_CHILD_PROXY (self), G_OBJECT (pad),
|
|
GST_OBJECT_NAME (pad));
|
|
|
|
GST_ELEMENT_CLASS (parent_class)->release_pad (element, pad);
|
|
}
|
|
|
|
static void
|
|
gst_d3d12_compositor_set_context (GstElement * element, GstContext * context)
|
|
{
|
|
auto self = GST_D3D12_COMPOSITOR (element);
|
|
auto priv = self->priv;
|
|
|
|
{
|
|
std::lock_guard < std::recursive_mutex > lk (priv->lock);
|
|
gst_d3d12_handle_set_context (element, context, priv->adapter,
|
|
&self->device);
|
|
}
|
|
|
|
GST_ELEMENT_CLASS (parent_class)->set_context (element, context);
|
|
}
|
|
|
|
static gboolean
|
|
gst_d3d12_compositor_start (GstAggregator * agg)
|
|
{
|
|
auto self = GST_D3D12_COMPOSITOR (agg);
|
|
auto priv = self->priv;
|
|
|
|
{
|
|
std::lock_guard < std::recursive_mutex > lk (priv->lock);
|
|
if (!gst_d3d12_ensure_element_data (GST_ELEMENT_CAST (self),
|
|
priv->adapter, &self->device)) {
|
|
GST_ERROR_OBJECT (self, "Failed to get D3D12 device");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
priv->scheduled = { };
|
|
|
|
return GST_AGGREGATOR_CLASS (parent_class)->start (agg);
|
|
}
|
|
|
|
static gboolean
|
|
gst_d3d12_compositor_stop (GstAggregator * agg)
|
|
{
|
|
auto self = GST_D3D12_COMPOSITOR (agg);
|
|
auto priv = self->priv;
|
|
|
|
{
|
|
std::lock_guard < std::recursive_mutex > lk (priv->lock);
|
|
priv->bg_render = nullptr;
|
|
gst_clear_object (&self->device);
|
|
}
|
|
|
|
return GST_AGGREGATOR_CLASS (parent_class)->stop (agg);
|
|
}
|
|
|
|
static GstCaps *
|
|
gst_d3d12_compositor_sink_getcaps (GstPad * pad, GstCaps * filter)
|
|
{
|
|
GstCaps *sinkcaps;
|
|
GstCaps *template_caps;
|
|
GstCaps *filtered_caps;
|
|
GstCaps *returned_caps;
|
|
|
|
template_caps = gst_pad_get_pad_template_caps (pad);
|
|
|
|
sinkcaps = gst_pad_get_current_caps (pad);
|
|
if (sinkcaps == nullptr) {
|
|
sinkcaps = gst_caps_ref (template_caps);
|
|
} else {
|
|
sinkcaps = gst_caps_merge (sinkcaps, gst_caps_ref (template_caps));
|
|
}
|
|
|
|
if (filter) {
|
|
filtered_caps = gst_caps_intersect (sinkcaps, filter);
|
|
gst_caps_unref (sinkcaps);
|
|
} else {
|
|
filtered_caps = sinkcaps; /* pass ownership */
|
|
}
|
|
|
|
returned_caps = gst_caps_intersect (filtered_caps, template_caps);
|
|
|
|
gst_caps_unref (template_caps);
|
|
gst_caps_unref (filtered_caps);
|
|
|
|
GST_DEBUG_OBJECT (pad, "returning %" GST_PTR_FORMAT, returned_caps);
|
|
|
|
return returned_caps;
|
|
}
|
|
|
|
static gboolean
|
|
gst_d3d12_compositor_sink_acceptcaps (GstPad * pad, GstCaps * caps)
|
|
{
|
|
gboolean ret;
|
|
GstCaps *template_caps;
|
|
|
|
GST_DEBUG_OBJECT (pad, "try accept caps of %" GST_PTR_FORMAT, caps);
|
|
|
|
template_caps = gst_pad_get_pad_template_caps (pad);
|
|
template_caps = gst_caps_make_writable (template_caps);
|
|
|
|
ret = gst_caps_can_intersect (caps, template_caps);
|
|
GST_DEBUG_OBJECT (pad, "%saccepted caps %" GST_PTR_FORMAT,
|
|
(ret ? "" : "not "), caps);
|
|
gst_caps_unref (template_caps);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
gst_d3d12_compositor_sink_query (GstAggregator * agg,
|
|
GstAggregatorPad * pad, GstQuery * query)
|
|
{
|
|
auto self = GST_D3D12_COMPOSITOR (agg);
|
|
auto priv = self->priv;
|
|
|
|
switch (GST_QUERY_TYPE (query)) {
|
|
case GST_QUERY_CONTEXT:
|
|
{
|
|
std::lock_guard < std::recursive_mutex > lk (priv->lock);
|
|
if (gst_d3d12_handle_context_query (GST_ELEMENT (agg), query,
|
|
self->device)) {
|
|
return TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case GST_QUERY_CAPS:{
|
|
GstCaps *filter, *caps;
|
|
|
|
gst_query_parse_caps (query, &filter);
|
|
caps = gst_d3d12_compositor_sink_getcaps (GST_PAD (pad), filter);
|
|
gst_query_set_caps_result (query, caps);
|
|
gst_caps_unref (caps);
|
|
return TRUE;
|
|
}
|
|
case GST_QUERY_ACCEPT_CAPS:{
|
|
GstCaps *caps;
|
|
gboolean ret;
|
|
|
|
gst_query_parse_accept_caps (query, &caps);
|
|
ret = gst_d3d12_compositor_sink_acceptcaps (GST_PAD (pad), caps);
|
|
gst_query_set_accept_caps_result (query, ret);
|
|
return TRUE;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return GST_AGGREGATOR_CLASS (parent_class)->sink_query (agg, pad, query);
|
|
}
|
|
|
|
static gboolean
|
|
gst_d3d12_compositor_src_query (GstAggregator * agg, GstQuery * query)
|
|
{
|
|
auto self = GST_D3D12_COMPOSITOR (agg);
|
|
|
|
switch (GST_QUERY_TYPE (query)) {
|
|
case GST_QUERY_CONTEXT:
|
|
if (gst_d3d12_handle_context_query (GST_ELEMENT (agg), query,
|
|
self->device)) {
|
|
return TRUE;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return GST_AGGREGATOR_CLASS (parent_class)->src_query (agg, query);
|
|
}
|
|
|
|
static GstCaps *
|
|
gst_d3d12_compositor_fixate_src_caps (GstAggregator * agg, GstCaps * caps)
|
|
{
|
|
auto vagg = GST_VIDEO_AGGREGATOR (agg);
|
|
GList *l;
|
|
gint best_width = -1, best_height = -1;
|
|
gint best_fps_n = -1, best_fps_d = -1;
|
|
gint par_n, par_d;
|
|
gdouble best_fps = 0.;
|
|
GstCaps *ret = nullptr;
|
|
GstStructure *s;
|
|
|
|
ret = gst_caps_make_writable (caps);
|
|
|
|
/* we need this to calculate how large to make the output frame */
|
|
s = gst_caps_get_structure (ret, 0);
|
|
if (gst_structure_has_field (s, "pixel-aspect-ratio")) {
|
|
gst_structure_fixate_field_nearest_fraction (s, "pixel-aspect-ratio", 1, 1);
|
|
gst_structure_get_fraction (s, "pixel-aspect-ratio", &par_n, &par_d);
|
|
} else {
|
|
par_n = par_d = 1;
|
|
}
|
|
|
|
GST_OBJECT_LOCK (vagg);
|
|
for (l = GST_ELEMENT (vagg)->sinkpads; l; l = l->next) {
|
|
auto vaggpad = GST_VIDEO_AGGREGATOR_PAD (l->data);
|
|
auto cpad = GST_D3D12_COMPOSITOR_PAD (vaggpad);
|
|
auto priv = cpad->priv;
|
|
gint this_width, this_height;
|
|
gint width, height;
|
|
gint fps_n, fps_d;
|
|
gdouble cur_fps;
|
|
gint x_offset;
|
|
gint y_offset;
|
|
|
|
fps_n = GST_VIDEO_INFO_FPS_N (&vaggpad->info);
|
|
fps_d = GST_VIDEO_INFO_FPS_D (&vaggpad->info);
|
|
gst_d3d12_compositor_pad_get_output_size (cpad,
|
|
par_n, par_d, &width, &height, &x_offset, &y_offset);
|
|
|
|
if (width == 0 || height == 0)
|
|
continue;
|
|
|
|
/* {x,y}_offset represent padding size of each top and left area.
|
|
* To calculate total resolution, count bottom and right padding area
|
|
* as well here */
|
|
this_width = width + MAX (priv->xpos + 2 * x_offset, 0);
|
|
this_height = height + MAX (priv->ypos + 2 * y_offset, 0);
|
|
|
|
if (best_width < this_width)
|
|
best_width = this_width;
|
|
if (best_height < this_height)
|
|
best_height = this_height;
|
|
|
|
if (fps_d == 0)
|
|
cur_fps = 0.0;
|
|
else
|
|
gst_util_fraction_to_double (fps_n, fps_d, &cur_fps);
|
|
|
|
if (best_fps < cur_fps) {
|
|
best_fps = cur_fps;
|
|
best_fps_n = fps_n;
|
|
best_fps_d = fps_d;
|
|
}
|
|
}
|
|
GST_OBJECT_UNLOCK (vagg);
|
|
|
|
if (best_fps_n <= 0 || best_fps_d <= 0 || best_fps == 0.0) {
|
|
best_fps_n = 25;
|
|
best_fps_d = 1;
|
|
best_fps = 25.0;
|
|
}
|
|
|
|
if (best_width <= 0 || best_height <= 0) {
|
|
best_width = 320;
|
|
best_height = 240;
|
|
}
|
|
|
|
gst_structure_fixate_field_nearest_int (s, "width", best_width);
|
|
gst_structure_fixate_field_nearest_int (s, "height", best_height);
|
|
gst_structure_fixate_field_nearest_fraction (s, "framerate", best_fps_n,
|
|
best_fps_d);
|
|
ret = gst_caps_fixate (ret);
|
|
|
|
GST_LOG_OBJECT (agg, "Fixated caps %" GST_PTR_FORMAT, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
convert_info_gray_to_yuv (const GstVideoInfo * gray, GstVideoInfo * yuv)
|
|
{
|
|
GstVideoInfo tmp;
|
|
|
|
if (GST_VIDEO_INFO_IS_YUV (gray)) {
|
|
*yuv = *gray;
|
|
return;
|
|
}
|
|
|
|
if (gray->finfo->depth[0] == 8) {
|
|
gst_video_info_set_format (&tmp,
|
|
GST_VIDEO_FORMAT_Y444, gray->width, gray->height);
|
|
} else {
|
|
gst_video_info_set_format (&tmp,
|
|
GST_VIDEO_FORMAT_Y444_16LE, gray->width, gray->height);
|
|
}
|
|
|
|
tmp.colorimetry.range = gray->colorimetry.range;
|
|
if (tmp.colorimetry.range == GST_VIDEO_COLOR_RANGE_UNKNOWN)
|
|
tmp.colorimetry.range = GST_VIDEO_COLOR_RANGE_0_255;
|
|
|
|
tmp.colorimetry.primaries = gray->colorimetry.primaries;
|
|
if (tmp.colorimetry.primaries == GST_VIDEO_COLOR_PRIMARIES_UNKNOWN)
|
|
tmp.colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT709;
|
|
|
|
tmp.colorimetry.transfer = gray->colorimetry.transfer;
|
|
if (tmp.colorimetry.transfer == GST_VIDEO_TRANSFER_UNKNOWN)
|
|
tmp.colorimetry.transfer = GST_VIDEO_TRANSFER_BT709;
|
|
|
|
tmp.colorimetry.matrix = gray->colorimetry.matrix;
|
|
if (tmp.colorimetry.matrix == GST_VIDEO_COLOR_MATRIX_UNKNOWN)
|
|
tmp.colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT709;
|
|
|
|
*yuv = tmp;
|
|
}
|
|
|
|
static void
|
|
gst_d3d12_compositor_calculate_background_color (GstD3D12Compositor * self,
|
|
const GstVideoInfo * info)
|
|
{
|
|
auto priv = self->priv;
|
|
GstD3D12ColorMatrix clear_color_matrix;
|
|
gdouble rgb[3];
|
|
gdouble converted[3];
|
|
GstVideoFormat format = GST_VIDEO_INFO_FORMAT (info);
|
|
|
|
if (GST_VIDEO_INFO_IS_RGB (info)) {
|
|
GstVideoInfo rgb_info = *info;
|
|
rgb_info.colorimetry.range = GST_VIDEO_COLOR_RANGE_0_255;
|
|
|
|
gst_d3d12_color_range_adjust_matrix_unorm (&rgb_info, info,
|
|
&clear_color_matrix);
|
|
} else {
|
|
GstVideoInfo rgb_info;
|
|
GstVideoInfo yuv_info;
|
|
|
|
gst_video_info_set_format (&rgb_info, GST_VIDEO_FORMAT_RGBA64_LE,
|
|
info->width, info->height);
|
|
convert_info_gray_to_yuv (info, &yuv_info);
|
|
|
|
if (yuv_info.colorimetry.matrix == GST_VIDEO_COLOR_MATRIX_UNKNOWN ||
|
|
yuv_info.colorimetry.matrix == GST_VIDEO_COLOR_MATRIX_RGB) {
|
|
GST_WARNING_OBJECT (self, "Invalid matrix is detected");
|
|
yuv_info.colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT709;
|
|
}
|
|
|
|
gst_d3d12_rgb_to_yuv_matrix_unorm (&rgb_info,
|
|
&yuv_info, &clear_color_matrix);
|
|
}
|
|
|
|
/* Calculate black and white color values */
|
|
for (guint i = 0; i < 2; i++) {
|
|
ClearColor *clear_color = &priv->clear_color[i];
|
|
rgb[0] = rgb[1] = rgb[2] = (gdouble) i;
|
|
|
|
for (guint j = 0; j < 3; j++) {
|
|
converted[j] = 0;
|
|
for (guint k = 0; k < 3; k++) {
|
|
converted[j] += clear_color_matrix.matrix[j][k] * rgb[k];
|
|
}
|
|
converted[j] += clear_color_matrix.offset[j];
|
|
converted[j] = CLAMP (converted[j],
|
|
clear_color_matrix.min[j], clear_color_matrix.max[j]);
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (self, "Calculated background color RGB: %f, %f, %f",
|
|
converted[0], converted[1], converted[2]);
|
|
|
|
if (GST_VIDEO_INFO_IS_RGB (info) || GST_VIDEO_INFO_IS_GRAY (info)) {
|
|
for (guint j = 0; j < 3; j++)
|
|
clear_color->color[0][j] = converted[j];
|
|
clear_color->color[0][3] = 1.0;
|
|
} else {
|
|
switch (format) {
|
|
case GST_VIDEO_FORMAT_VUYA:
|
|
clear_color->color[0][0] = converted[2];
|
|
clear_color->color[0][1] = converted[1];
|
|
clear_color->color[0][2] = converted[0];
|
|
clear_color->color[0][3] = 1.0;
|
|
break;
|
|
case GST_VIDEO_FORMAT_NV12:
|
|
case GST_VIDEO_FORMAT_NV21:
|
|
case GST_VIDEO_FORMAT_P010_10LE:
|
|
case GST_VIDEO_FORMAT_P012_LE:
|
|
case GST_VIDEO_FORMAT_P016_LE:
|
|
clear_color->color[0][0] = converted[0];
|
|
clear_color->color[0][1] = 0;
|
|
clear_color->color[0][2] = 0;
|
|
clear_color->color[0][3] = 1.0;
|
|
if (format == GST_VIDEO_FORMAT_NV21) {
|
|
clear_color->color[1][0] = converted[2];
|
|
clear_color->color[1][1] = converted[1];
|
|
} else {
|
|
clear_color->color[1][0] = converted[1];
|
|
clear_color->color[1][1] = converted[2];
|
|
}
|
|
clear_color->color[1][2] = 0;
|
|
clear_color->color[1][3] = 1.0;
|
|
break;
|
|
case GST_VIDEO_FORMAT_I420:
|
|
case GST_VIDEO_FORMAT_YV12:
|
|
case GST_VIDEO_FORMAT_I420_10LE:
|
|
case GST_VIDEO_FORMAT_I420_12LE:
|
|
case GST_VIDEO_FORMAT_Y42B:
|
|
case GST_VIDEO_FORMAT_I422_10LE:
|
|
case GST_VIDEO_FORMAT_I422_12LE:
|
|
case GST_VIDEO_FORMAT_Y444:
|
|
case GST_VIDEO_FORMAT_Y444_10LE:
|
|
case GST_VIDEO_FORMAT_Y444_12LE:
|
|
case GST_VIDEO_FORMAT_Y444_16LE:
|
|
clear_color->color[0][0] = converted[0];
|
|
clear_color->color[0][1] = 0;
|
|
clear_color->color[0][2] = 0;
|
|
clear_color->color[0][3] = 1.0;
|
|
if (format == GST_VIDEO_FORMAT_YV12) {
|
|
clear_color->color[1][0] = converted[2];
|
|
clear_color->color[2][0] = converted[1];
|
|
} else {
|
|
clear_color->color[1][0] = converted[1];
|
|
clear_color->color[2][0] = converted[2];
|
|
}
|
|
clear_color->color[1][1] = 0;
|
|
clear_color->color[1][2] = 0;
|
|
clear_color->color[1][3] = 1.0;
|
|
clear_color->color[2][1] = 0;
|
|
clear_color->color[2][2] = 0;
|
|
clear_color->color[2][3] = 1.0;
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
priv->clear_color[2] = priv->clear_color[0];
|
|
priv->clear_color[2].color[0][3] = 0.0;
|
|
}
|
|
|
|
static gboolean
|
|
gst_d3d12_compositor_clear_pad_context (GstD3D12Compositor * self,
|
|
GstD3D12CompositorPad * cpad, gpointer user_data)
|
|
{
|
|
auto priv = cpad->priv;
|
|
priv->ctx = nullptr;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_d3d12_compositor_negotiated_src_caps (GstAggregator * agg, GstCaps * caps)
|
|
{
|
|
auto self = GST_D3D12_COMPOSITOR (agg);
|
|
auto priv = self->priv;
|
|
GstVideoInfo info;
|
|
|
|
if (!gst_video_info_from_caps (&info, caps)) {
|
|
GST_ERROR_OBJECT (self, "Failed to convert caps to info");
|
|
return FALSE;
|
|
}
|
|
|
|
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, "Negotiated with D3D12 memory caps");
|
|
priv->downstream_supports_d3d12 = TRUE;
|
|
} else {
|
|
GST_DEBUG_OBJECT (self, "Negotiated with system memory caps");
|
|
priv->downstream_supports_d3d12 = FALSE;
|
|
}
|
|
|
|
if (GST_VIDEO_INFO_FORMAT (&info) !=
|
|
GST_VIDEO_INFO_FORMAT (&priv->negotiated_info)) {
|
|
gst_element_foreach_sink_pad (GST_ELEMENT_CAST (self),
|
|
(GstElementForeachPadFunc) gst_d3d12_compositor_clear_pad_context,
|
|
nullptr);
|
|
priv->bg_render = nullptr;
|
|
}
|
|
gst_clear_buffer (&priv->fallback_buf);
|
|
|
|
priv->negotiated_info = info;
|
|
gst_d3d12_compositor_calculate_background_color (self, &info);
|
|
|
|
if (!priv->bg_render) {
|
|
auto bg_render = std::make_unique < BackgroundRender > (self->device, info);
|
|
if (!bg_render->is_valid) {
|
|
GST_ERROR_OBJECT (self, "Couldn't configure background render object");
|
|
return FALSE;
|
|
}
|
|
|
|
priv->bg_render = std::move (bg_render);
|
|
} else {
|
|
priv->bg_render->viewport.Width = info.width;
|
|
priv->bg_render->viewport.Height = info.height;
|
|
priv->bg_render->scissor_rect.right = info.width;
|
|
priv->bg_render->scissor_rect.bottom = info.height;
|
|
}
|
|
|
|
if (!priv->downstream_supports_d3d12) {
|
|
auto pool = gst_d3d12_buffer_pool_new (self->device);
|
|
auto config = gst_buffer_pool_get_config (pool);
|
|
auto params = gst_d3d12_allocation_params_new (self->device, &info,
|
|
GST_D3D12_ALLOCATION_FLAG_DEFAULT,
|
|
D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET |
|
|
D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS, D3D12_HEAP_FLAG_NONE);
|
|
gst_buffer_pool_config_set_d3d12_allocation_params (config, params);
|
|
gst_d3d12_allocation_params_free (params);
|
|
gst_buffer_pool_config_set_params (config, caps, info.size, 0, 0);
|
|
|
|
if (!gst_buffer_pool_set_config (pool, config)) {
|
|
GST_ERROR_OBJECT (self, "Couldn't set pool config");
|
|
gst_object_unref (pool);
|
|
return FALSE;
|
|
}
|
|
|
|
if (!gst_buffer_pool_set_active (pool, TRUE)) {
|
|
GST_ERROR_OBJECT (self, "Failed to set active");
|
|
gst_object_unref (pool);
|
|
return FALSE;
|
|
}
|
|
|
|
gst_buffer_pool_acquire_buffer (pool, &priv->fallback_buf, nullptr);
|
|
gst_buffer_pool_set_active (pool, FALSE);
|
|
gst_object_unref (pool);
|
|
|
|
if (!priv->fallback_buf) {
|
|
GST_ERROR_OBJECT (self, "Couldn't acquire fallback buf");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return GST_AGGREGATOR_CLASS (parent_class)->negotiated_src_caps (agg, caps);
|
|
}
|
|
|
|
static gboolean
|
|
gst_d3d12_compositor_propose_allocation (GstAggregator * agg,
|
|
GstAggregatorPad * pad, GstQuery * decide_query, GstQuery * query)
|
|
{
|
|
auto self = GST_D3D12_COMPOSITOR (agg);
|
|
GstVideoInfo info;
|
|
GstBufferPool *pool;
|
|
GstCaps *caps;
|
|
gboolean is_d3d12 = FALSE;
|
|
guint size;
|
|
|
|
gst_query_parse_allocation (query, &caps, nullptr);
|
|
|
|
if (!caps)
|
|
return FALSE;
|
|
|
|
if (!gst_video_info_from_caps (&info, caps))
|
|
return FALSE;
|
|
|
|
auto features = gst_caps_get_features (caps, 0);
|
|
if (gst_caps_features_contains (features,
|
|
GST_CAPS_FEATURE_MEMORY_D3D12_MEMORY)) {
|
|
GST_DEBUG_OBJECT (pad, "Upstream support d3d12 memory");
|
|
is_d3d12 = TRUE;
|
|
}
|
|
|
|
if (gst_query_get_n_allocation_pools (query) == 0) {
|
|
if (is_d3d12)
|
|
pool = gst_d3d12_buffer_pool_new (self->device);
|
|
else
|
|
pool = gst_video_buffer_pool_new ();
|
|
|
|
if (!pool) {
|
|
GST_ERROR_OBJECT (self, "Failed to create buffer pool");
|
|
return FALSE;
|
|
}
|
|
|
|
auto config = gst_buffer_pool_get_config (pool);
|
|
gst_buffer_pool_config_add_option (config,
|
|
GST_BUFFER_POOL_OPTION_VIDEO_META);
|
|
|
|
size = GST_VIDEO_INFO_SIZE (&info);
|
|
if (is_d3d12) {
|
|
auto params = gst_d3d12_allocation_params_new (self->device,
|
|
&info, GST_D3D12_ALLOCATION_FLAG_DEFAULT,
|
|
D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET |
|
|
D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS, D3D12_HEAP_FLAG_NONE);
|
|
|
|
gst_buffer_pool_config_set_d3d12_allocation_params (config, params);
|
|
gst_d3d12_allocation_params_free (params);
|
|
} else {
|
|
gst_buffer_pool_config_add_option (config,
|
|
GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT);
|
|
}
|
|
|
|
gst_buffer_pool_config_set_params (config, caps, (guint) 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;
|
|
}
|
|
|
|
/* d3d12 buffer pool will update buffer size based on allocated texture,
|
|
* get size from config again */
|
|
config = gst_buffer_pool_get_config (pool);
|
|
gst_buffer_pool_config_get_params (config,
|
|
nullptr, &size, nullptr, nullptr);
|
|
gst_structure_free (config);
|
|
|
|
gst_query_add_allocation_pool (query, pool, size, 0, 0);
|
|
gst_object_unref (pool);
|
|
}
|
|
|
|
gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, nullptr);
|
|
if (is_d3d12) {
|
|
gst_query_add_allocation_meta (query, GST_VIDEO_CROP_META_API_TYPE,
|
|
nullptr);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_d3d12_compositor_decide_allocation (GstAggregator * agg, GstQuery * query)
|
|
{
|
|
auto self = GST_D3D12_COMPOSITOR (agg);
|
|
auto priv = self->priv;
|
|
GstCaps *caps;
|
|
GstBufferPool *pool = nullptr;
|
|
guint n, size, min, max;
|
|
GstVideoInfo info;
|
|
gboolean use_d3d12_pool;
|
|
|
|
gst_query_parse_allocation (query, &caps, nullptr);
|
|
|
|
if (!caps) {
|
|
GST_DEBUG_OBJECT (self, "No output caps");
|
|
return FALSE;
|
|
}
|
|
|
|
if (!gst_video_info_from_caps (&info, caps)) {
|
|
GST_ERROR_OBJECT (self, "Invalid caps");
|
|
return FALSE;
|
|
}
|
|
|
|
use_d3d12_pool = priv->downstream_supports_d3d12;
|
|
|
|
n = gst_query_get_n_allocation_pools (query);
|
|
if (n > 0)
|
|
gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max);
|
|
|
|
/* create our own pool */
|
|
if (pool && use_d3d12_pool) {
|
|
if (!GST_IS_D3D12_BUFFER_POOL (pool)) {
|
|
GST_DEBUG_OBJECT (self,
|
|
"Downstream pool is not d3d12, will create new one");
|
|
gst_clear_object (&pool);
|
|
} else {
|
|
GstD3D12BufferPool *dpool = GST_D3D12_BUFFER_POOL (pool);
|
|
if (!gst_d3d12_device_is_equal (dpool->device, self->device)) {
|
|
GST_DEBUG_OBJECT (self, "Different device, will create new one");
|
|
gst_clear_object (&pool);
|
|
}
|
|
}
|
|
}
|
|
|
|
size = (guint) info.size;
|
|
|
|
if (!pool) {
|
|
if (use_d3d12_pool)
|
|
pool = gst_d3d12_buffer_pool_new (self->device);
|
|
else
|
|
pool = gst_video_buffer_pool_new ();
|
|
|
|
min = 0;
|
|
max = 0;
|
|
}
|
|
|
|
auto config = gst_buffer_pool_get_config (pool);
|
|
gst_buffer_pool_config_set_params (config, caps, size, min, max);
|
|
gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META);
|
|
|
|
if (use_d3d12_pool) {
|
|
auto params = gst_buffer_pool_config_get_d3d12_allocation_params (config);
|
|
if (!params) {
|
|
params = gst_d3d12_allocation_params_new (self->device, &info,
|
|
GST_D3D12_ALLOCATION_FLAG_DEFAULT,
|
|
D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET |
|
|
D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS,
|
|
D3D12_HEAP_FLAG_SHARED);
|
|
} else {
|
|
gst_d3d12_allocation_params_set_resource_flags (params,
|
|
D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET |
|
|
D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS);
|
|
}
|
|
|
|
gst_buffer_pool_config_set_d3d12_allocation_params (config, params);
|
|
gst_d3d12_allocation_params_free (params);
|
|
}
|
|
|
|
gst_buffer_pool_set_config (pool, config);
|
|
|
|
/* d3d12 buffer pool will update buffer size based on allocated texture,
|
|
* get size from config again */
|
|
config = gst_buffer_pool_get_config (pool);
|
|
gst_buffer_pool_config_get_params (config, nullptr, &size, nullptr, nullptr);
|
|
gst_structure_free (config);
|
|
|
|
if (n > 0)
|
|
gst_query_set_nth_allocation_pool (query, 0, pool, size, min, max);
|
|
else
|
|
gst_query_add_allocation_pool (query, pool, size, min, max);
|
|
gst_object_unref (pool);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_d3d12_compositor_draw_background (GstD3D12Compositor * self)
|
|
{
|
|
auto priv = self->priv;
|
|
ClearColor *color = &priv->clear_color[0];
|
|
auto bg_render = priv->bg_render.get ();
|
|
auto & rtv_handles = priv->rtv_handles;
|
|
std::vector < D3D12_RECT > rtv_rects;
|
|
|
|
rtv_handles.clear ();
|
|
for (guint i = 0; i < gst_buffer_n_memory (priv->generated_output_buf); i++) {
|
|
auto mem = (GstD3D12Memory *)
|
|
gst_buffer_peek_memory (priv->generated_output_buf, i);
|
|
auto num_planes = gst_d3d12_memory_get_plane_count (mem);
|
|
auto rtv_heap = gst_d3d12_memory_get_render_target_view_heap (mem);
|
|
|
|
if (!rtv_heap) {
|
|
GST_ERROR_OBJECT (self, "Couldn't get rtv heap");
|
|
return FALSE;
|
|
}
|
|
|
|
auto cpu_handle =
|
|
CD3DX12_CPU_DESCRIPTOR_HANDLE (GetCPUDescriptorHandleForHeapStart
|
|
(rtv_heap));
|
|
|
|
for (guint plane = 0; plane < num_planes; plane++) {
|
|
D3D12_RECT rect = { };
|
|
gst_d3d12_memory_get_plane_rectangle (mem, plane, &rect);
|
|
rtv_rects.push_back (rect);
|
|
rtv_handles.push_back (cpu_handle);
|
|
cpu_handle.Offset (bg_render->rtv_inc_size);
|
|
}
|
|
}
|
|
|
|
GstD3D12CmdAlloc *gst_ca;
|
|
if (!gst_d3d12_cmd_alloc_pool_acquire (bg_render->ca_pool, &gst_ca)) {
|
|
GST_ERROR_OBJECT (self, "Couldn't acquire command allocator");
|
|
return FALSE;
|
|
}
|
|
|
|
GstD3D12FenceData *fence_data;
|
|
gst_d3d12_fence_data_pool_acquire (priv->fence_data_pool, &fence_data);
|
|
gst_d3d12_fence_data_push (fence_data, FENCE_NOTIFY_MINI_OBJECT (gst_ca));
|
|
|
|
auto ca = gst_d3d12_cmd_alloc_get_handle (gst_ca);
|
|
|
|
auto hr = ca->Reset ();
|
|
if (!gst_d3d12_result (hr, self->device)) {
|
|
GST_ERROR_OBJECT (self, "Couldn't reset command allocator");
|
|
gst_d3d12_fence_data_unref (fence_data);
|
|
return FALSE;
|
|
}
|
|
|
|
if (!bg_render->cl) {
|
|
auto device = gst_d3d12_device_get_device_handle (self->device);
|
|
hr = device->CreateCommandList (0, D3D12_COMMAND_LIST_TYPE_DIRECT,
|
|
ca, bg_render->pso.Get (), IID_PPV_ARGS (&bg_render->cl));
|
|
if (!gst_d3d12_result (hr, self->device)) {
|
|
GST_ERROR_OBJECT (self, "Couldn't create command list");
|
|
gst_d3d12_fence_data_unref (fence_data);
|
|
return FALSE;
|
|
}
|
|
} else {
|
|
hr = bg_render->cl->Reset (ca, bg_render->pso.Get ());
|
|
if (!gst_d3d12_result (hr, self->device)) {
|
|
GST_ERROR_OBJECT (self, "Couldn't reset command list");
|
|
gst_d3d12_fence_data_unref (fence_data);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
auto cl = bg_render->cl;
|
|
if (bg_render->vertex_index_upload) {
|
|
cl->CopyResource (bg_render->vertex_index_buf.Get (),
|
|
bg_render->vertex_index_upload.Get ());
|
|
D3D12_RESOURCE_BARRIER barrier =
|
|
CD3DX12_RESOURCE_BARRIER::Transition (bg_render->
|
|
vertex_index_buf.Get (),
|
|
D3D12_RESOURCE_STATE_COPY_DEST,
|
|
D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER |
|
|
D3D12_RESOURCE_STATE_INDEX_BUFFER);
|
|
cl->ResourceBarrier (1, &barrier);
|
|
}
|
|
|
|
if (priv->background == GST_D3D12_COMPOSITOR_BACKGROUND_CHECKER) {
|
|
cl->SetGraphicsRootSignature (bg_render->rs.Get ());
|
|
cl->IASetPrimitiveTopology (D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
|
cl->IASetIndexBuffer (&bg_render->ibv);
|
|
cl->IASetVertexBuffers (0, 1, &bg_render->vbv);
|
|
cl->RSSetViewports (1, &bg_render->viewport);
|
|
cl->RSSetScissorRects (1, &bg_render->scissor_rect);
|
|
cl->OMSetRenderTargets (1, rtv_handles.data (), FALSE, nullptr);
|
|
cl->DrawIndexedInstanced (6, 1, 0, 0, 0);
|
|
|
|
/* clear U and V components if needed */
|
|
for (size_t i = 1; i < rtv_handles.size (); i++) {
|
|
cl->ClearRenderTargetView (rtv_handles[i], color->color[i], 1,
|
|
&rtv_rects[i]);
|
|
}
|
|
} else {
|
|
switch (priv->background) {
|
|
case GST_D3D12_COMPOSITOR_BACKGROUND_BLACK:
|
|
color = &priv->clear_color[0];
|
|
break;
|
|
case GST_D3D12_COMPOSITOR_BACKGROUND_WHITE:
|
|
color = &priv->clear_color[1];
|
|
break;
|
|
case GST_D3D12_COMPOSITOR_BACKGROUND_TRANSPARENT:
|
|
color = &priv->clear_color[2];
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
return FALSE;
|
|
}
|
|
|
|
for (size_t i = 0; i < priv->rtv_handles.size (); i++) {
|
|
cl->ClearRenderTargetView (rtv_handles[i], color->color[i], 1,
|
|
&rtv_rects[i]);
|
|
}
|
|
}
|
|
|
|
hr = cl->Close ();
|
|
if (!gst_d3d12_result (hr, self->device)) {
|
|
GST_ERROR_OBJECT (self, "Couldn't close command list");
|
|
gst_d3d12_fence_data_unref (fence_data);
|
|
return FALSE;
|
|
}
|
|
|
|
ID3D12CommandList *cmd_list[] = { cl.Get () };
|
|
|
|
hr = gst_d3d12_device_execute_command_lists (self->device,
|
|
D3D12_COMMAND_LIST_TYPE_DIRECT, 1, cmd_list, &priv->bg_render->fence_val);
|
|
if (!gst_d3d12_result (hr, self->device)) {
|
|
GST_ERROR_OBJECT (self, "Couldn't execute command list");
|
|
gst_d3d12_fence_data_unref (fence_data);
|
|
return FALSE;
|
|
}
|
|
|
|
auto fence = gst_d3d12_device_get_fence_handle (self->device,
|
|
D3D12_COMMAND_LIST_TYPE_DIRECT);
|
|
gst_d3d12_buffer_set_fence (priv->generated_output_buf, fence,
|
|
bg_render->fence_val, FALSE);
|
|
|
|
if (bg_render->vertex_index_upload) {
|
|
gst_d3d12_fence_data_push (fence_data,
|
|
FENCE_NOTIFY_COM (bg_render->vertex_index_upload.Detach ()));
|
|
}
|
|
|
|
gst_d3d12_device_set_fence_notify (self->device,
|
|
D3D12_COMMAND_LIST_TYPE_DIRECT, priv->bg_render->fence_val,
|
|
FENCE_NOTIFY_MINI_OBJECT (fence_data));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_d3d12_compositor_aggregate_frames (GstVideoAggregator * vagg,
|
|
GstBuffer * outbuf)
|
|
{
|
|
auto self = GST_D3D12_COMPOSITOR (vagg);
|
|
auto priv = self->priv;
|
|
GList *iter;
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
|
|
GST_LOG_OBJECT (self, "aggregate");
|
|
|
|
if (!priv->generated_output_buf) {
|
|
GST_ERROR_OBJECT (self, "No generated output buffer");
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
|
|
if (!gst_d3d12_compositor_draw_background (self)) {
|
|
GST_ERROR_OBJECT (self, "Couldn't draw background");
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
|
|
guint64 fence_val = priv->bg_render->fence_val;
|
|
auto fence = gst_d3d12_device_get_fence_handle (self->device,
|
|
D3D12_COMMAND_LIST_TYPE_DIRECT);
|
|
GST_OBJECT_LOCK (self);
|
|
for (iter = GST_ELEMENT (vagg)->sinkpads; iter; iter = g_list_next (iter)) {
|
|
auto pad = GST_VIDEO_AGGREGATOR_PAD (iter->data);
|
|
auto cpad = GST_D3D12_COMPOSITOR_PAD (pad);
|
|
auto pad_priv = cpad->priv;
|
|
|
|
/* Might be a case where pad was added between prepare_frame() and
|
|
* aggregate_frames() */
|
|
if (!pad_priv->prepare_rst.valid ()) {
|
|
GST_DEBUG_OBJECT (pad, "Ignoring non-prepared pad");
|
|
continue;
|
|
}
|
|
|
|
GST_LOG_OBJECT (cpad, "Waiting for command list building thread");
|
|
auto prepare_ret = pad_priv->prepare_rst.get ();
|
|
if (!prepare_ret) {
|
|
GST_ERROR_OBJECT (pad, "Couldn't build command list");
|
|
ret = GST_FLOW_ERROR;
|
|
break;
|
|
}
|
|
|
|
if (!gst_video_aggregator_pad_get_prepared_frame (pad))
|
|
continue;
|
|
|
|
GST_LOG_OBJECT (cpad, "Command list prepared");
|
|
|
|
ID3D12CommandList *cmd_list[] = { pad_priv->ctx->cl.Get () };
|
|
|
|
auto hr = gst_d3d12_device_execute_command_lists (self->device,
|
|
D3D12_COMMAND_LIST_TYPE_DIRECT, 1, cmd_list,
|
|
&pad_priv->ctx->fence_val);
|
|
if (!gst_d3d12_result (hr, self->device)) {
|
|
GST_ERROR_OBJECT (self, "Couldn't execute command list");
|
|
ret = GST_FLOW_ERROR;
|
|
break;
|
|
}
|
|
|
|
fence_val = pad_priv->ctx->fence_val;
|
|
gst_d3d12_buffer_set_fence (priv->generated_output_buf,
|
|
fence, fence_val, FALSE);
|
|
}
|
|
GST_OBJECT_UNLOCK (self);
|
|
|
|
if (ret != GST_FLOW_OK)
|
|
return ret;
|
|
|
|
priv->scheduled.push (fence_val);
|
|
|
|
auto completed = gst_d3d12_device_get_completed_value (self->device,
|
|
D3D12_COMMAND_LIST_TYPE_DIRECT);
|
|
while (!priv->scheduled.empty ()) {
|
|
if (priv->scheduled.front () > completed)
|
|
break;
|
|
|
|
priv->scheduled.pop ();
|
|
}
|
|
|
|
auto async_depth = priv->async_depth.load ();
|
|
if (async_depth > 0 && priv->scheduled.size () > async_depth) {
|
|
auto fence_to_wait = priv->scheduled.front ();
|
|
priv->scheduled.pop ();
|
|
GST_LOG_OBJECT (self, "Waiting for previous command, %" G_GUINT64_FORMAT,
|
|
fence_to_wait);
|
|
gst_d3d12_device_fence_wait (self->device,
|
|
D3D12_COMMAND_LIST_TYPE_DIRECT, fence_to_wait);
|
|
}
|
|
|
|
if (priv->generated_output_buf != outbuf) {
|
|
GstVideoFrame out_frame, in_frame;
|
|
if (!gst_video_frame_map (&in_frame, &vagg->info,
|
|
priv->generated_output_buf, GST_MAP_READ)) {
|
|
GST_ERROR_OBJECT (self, "Couldn't map generated buffer");
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
|
|
if (!gst_video_frame_map (&out_frame, &vagg->info, outbuf, GST_MAP_WRITE)) {
|
|
GST_ERROR_OBJECT (self, "Couldn't map output buffer");
|
|
gst_video_frame_unmap (&in_frame);
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
|
|
auto copy_ret = gst_video_frame_copy (&out_frame, &in_frame);
|
|
gst_video_frame_unmap (&out_frame);
|
|
gst_video_frame_unmap (&in_frame);
|
|
if (!copy_ret) {
|
|
GST_ERROR_OBJECT (self, "Couldn't copy frame");
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
}
|
|
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
struct DeviceCheckData
|
|
{
|
|
/* without holding ref */
|
|
GstD3D12Device *other_device = nullptr;
|
|
gboolean have_same_device = FALSE;
|
|
};
|
|
|
|
static gboolean
|
|
gst_d3d12_compositor_check_device_update (GstElement * agg,
|
|
GstVideoAggregatorPad * vpad, DeviceCheckData * data)
|
|
{
|
|
auto self = GST_D3D12_COMPOSITOR (agg);
|
|
GstBuffer *buf;
|
|
GstMemory *mem;
|
|
GstD3D12Memory *dmem;
|
|
|
|
buf = gst_video_aggregator_pad_get_current_buffer (vpad);
|
|
if (!buf)
|
|
return TRUE;
|
|
|
|
/* Ignore gap buffer */
|
|
if (GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_GAP) ||
|
|
gst_buffer_get_size (buf) == 0) {
|
|
return TRUE;
|
|
}
|
|
|
|
mem = gst_buffer_peek_memory (buf, 0);
|
|
if (!gst_is_d3d12_memory (mem))
|
|
return TRUE;
|
|
|
|
dmem = GST_D3D12_MEMORY_CAST (mem);
|
|
|
|
/* We can use existing device */
|
|
if (gst_d3d12_device_is_equal (dmem->device, self->device)) {
|
|
data->have_same_device = TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
data->other_device = dmem->device;
|
|
|
|
/* Keep iterate since there might be one buffer which holds the same device
|
|
* as ours */
|
|
return TRUE;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_d3d12_compositor_create_output_buffer (GstVideoAggregator * vagg,
|
|
GstBuffer ** outbuffer)
|
|
{
|
|
auto self = GST_D3D12_COMPOSITOR (vagg);
|
|
auto priv = self->priv;
|
|
DeviceCheckData data;
|
|
|
|
/* Check whether there is at least one sinkpad which holds d3d12 buffer
|
|
* with compatible device, and if not, update our device */
|
|
data.other_device = nullptr;
|
|
data.have_same_device = FALSE;
|
|
|
|
gst_element_foreach_sink_pad (GST_ELEMENT_CAST (vagg),
|
|
(GstElementForeachPadFunc) gst_d3d12_compositor_check_device_update,
|
|
&data);
|
|
|
|
priv->generated_output_buf = nullptr;
|
|
if (data.have_same_device || !data.other_device) {
|
|
GstBuffer *buf = nullptr;
|
|
auto ret =
|
|
GST_VIDEO_AGGREGATOR_CLASS (parent_class)->create_output_buffer (vagg,
|
|
&buf);
|
|
if (ret != GST_FLOW_OK)
|
|
return ret;
|
|
|
|
if (priv->downstream_supports_d3d12)
|
|
priv->generated_output_buf = buf;
|
|
else
|
|
priv->generated_output_buf = priv->fallback_buf;
|
|
|
|
*outbuffer = buf;
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
/* Clear all device dependent resources */
|
|
gst_element_foreach_sink_pad (GST_ELEMENT_CAST (vagg),
|
|
(GstElementForeachPadFunc) gst_d3d12_compositor_clear_pad_context,
|
|
nullptr);
|
|
|
|
gst_clear_buffer (&priv->fallback_buf);
|
|
priv->bg_render = nullptr;
|
|
priv->scheduled = { };
|
|
|
|
GST_INFO_OBJECT (self, "Updating device %" GST_PTR_FORMAT " -> %"
|
|
GST_PTR_FORMAT, self->device, data.other_device);
|
|
{
|
|
std::lock_guard < std::recursive_mutex > lk (priv->lock);
|
|
gst_object_unref (self->device);
|
|
self->device = (GstD3D12Device *) gst_object_ref (data.other_device);
|
|
}
|
|
|
|
/* We cannot call gst_aggregator_negotiate() here, since GstVideoAggregator
|
|
* is holding GST_VIDEO_AGGREGATOR_LOCK() already.
|
|
* Mark reconfigure and do reconfigure later */
|
|
gst_pad_mark_reconfigure (GST_AGGREGATOR_SRC_PAD (vagg));
|
|
|
|
return GST_AGGREGATOR_FLOW_NEED_DATA;
|
|
}
|