3 * Copyright (C) 2022 Seungha Yang <seungha@centricular.com>
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
15 * You should have received a copy of the GNU Library General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
26 #include <gst/video/video.h>
27 #include <gst/d3d11/gstd3d11.h>
35 using namespace Microsoft::WRL;
39 HWND window_handle = nullptr;
40 GstElement *pipeline = nullptr;
41 GstElement *sink = nullptr;
42 GIOChannel *io_ch = nullptr;
44 bool enable_overlay = false;
46 ID2D1Factory *d2d_factory = nullptr;
47 IDWriteFactory *dwrite_factory = nullptr;
49 IDWriteTextFormat *format = nullptr;
50 IDWriteTextLayout *layout = nullptr;
52 /* D3D objects for background redraw with alpha blending */
53 ID3D11BlendState *blend = nullptr;
54 ID3D11PixelShader *ps = nullptr;
55 ID3D11VertexShader *vs = nullptr;
56 ID3D11InputLayout *input_layout = nullptr;
57 ID3D11Buffer *index_buf = nullptr;
58 ID3D11Buffer *vertex_buf = nullptr;
63 SRWLOCK lock = RTL_SRWLOCK_INIT;
64 double avg_framerate = 0;
65 double last_framerate = 0;
67 LARGE_INTEGER frequency;
68 std::queue < LARGE_INTEGER > render_timestamp;
70 GMainLoop *loop = nullptr;
73 static const gchar templ_vs_color[] =
75 " float4 Position: POSITION;\n"
76 " float4 Color: COLOR;\n"
78 "struct VS_OUTPUT {\n"
79 " float4 Position: SV_POSITION;\n"
80 " float4 Color: COLOR;\n"
82 "VS_OUTPUT main (VS_INPUT input)\n"
87 static const gchar templ_ps_color[] =
89 " float4 Position: SV_POSITION;\n"
90 " float4 Color: COLOR;\n"
92 "float4 main(PS_INPUT input) : SV_TARGET\n"
94 " return input.Color;\n"
98 #define DISPLAY_CONTEXT_PROP "d3d11videosink.example.context"
100 #define CLEAR_COM(obj) do { \
107 static LRESULT CALLBACK
108 window_proc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
110 DisplayContext *context = (DisplayContext *) GetPropA (hwnd,
111 DISPLAY_CONTEXT_PROP);
115 gst_println ("Window is destroying");
117 context->window_handle = nullptr;
118 RemovePropA (hwnd, DISPLAY_CONTEXT_PROP);
119 g_main_loop_quit (context->loop);
124 gst_printerrln ("Display context is not attached on HWND");
126 context->enable_overlay = !context->enable_overlay;
127 gst_println ("Enable overlay %d", context->enable_overlay);
128 /* Call expose method so that videosink can immediately
129 * redraw client area */
131 gst_video_overlay_expose (GST_VIDEO_OVERLAY (context->sink));
138 return DefWindowProc (hwnd, message, wParam, lParam);
142 bus_msg (GstBus * bus, GstMessage * msg, DisplayContext * context)
144 switch (GST_MESSAGE_TYPE (msg)) {
145 case GST_MESSAGE_ERROR:{
149 gst_message_parse_error (msg, &err, &dbg);
150 gst_printerrln ("ERROR %s", err->message);
152 gst_printerrln ("ERROR debug information: %s", dbg);
153 g_clear_error (&err);
156 g_main_loop_quit (context->loop);
159 case GST_MESSAGE_EOS:
160 gst_println ("Got EOS");
161 g_main_loop_quit (context->loop);
171 msg_cb (GIOChannel * source, GIOCondition condition, gpointer data)
175 if (!PeekMessage (&msg, nullptr, 0, 0, PM_REMOVE))
176 return G_SOURCE_CONTINUE;
178 TranslateMessage (&msg);
179 DispatchMessage (&msg);
181 return G_SOURCE_CONTINUE;
202 ensure_d3d11_resource (DisplayContext * context, GstD3D11Device * device)
204 D3D11_BLEND_DESC blend_desc;
205 D3D11_INPUT_ELEMENT_DESC input_desc[2];
206 D3D11_BUFFER_DESC buffer_desc;
207 D3D11_MAPPED_SUBRESOURCE map;
208 VertexData *vertex_data;
211 ID3D11Device *device_handle;
212 ID3D11DeviceContext *context_handle;
217 device_handle = gst_d3d11_device_get_device_handle (device);
218 context_handle = gst_d3d11_device_get_device_context_handle (device);
220 ZeroMemory (&blend_desc, sizeof (blend_desc));
221 ZeroMemory (input_desc, sizeof (input_desc));
222 ZeroMemory (&buffer_desc, sizeof (buffer_desc));
224 input_desc[0].SemanticName = "POSITION";
225 input_desc[0].SemanticIndex = 0;
226 input_desc[0].Format = DXGI_FORMAT_R32G32B32_FLOAT;
227 input_desc[0].InputSlot = 0;
228 input_desc[0].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
229 input_desc[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
230 input_desc[0].InstanceDataStepRate = 0;
232 input_desc[1].SemanticName = "COLOR";
233 input_desc[1].SemanticIndex = 0;
234 input_desc[1].Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
235 input_desc[1].InputSlot = 0;
236 input_desc[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
237 input_desc[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
238 input_desc[1].InstanceDataStepRate = 0;
240 hr = gst_d3d11_create_vertex_shader_simple (device, templ_vs_color,
241 "main", input_desc, G_N_ELEMENTS (input_desc), &context->vs,
242 &context->input_layout);
243 g_assert (SUCCEEDED (hr));
245 hr = gst_d3d11_create_pixel_shader_simple (device,
246 templ_ps_color, "main", &context->ps);
248 buffer_desc.Usage = D3D11_USAGE_DYNAMIC;
249 buffer_desc.ByteWidth = sizeof (VertexData) * 4;
250 buffer_desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
251 buffer_desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
253 hr = device_handle->CreateBuffer (&buffer_desc, nullptr,
254 &context->vertex_buf);
255 g_assert (SUCCEEDED (hr));
257 buffer_desc.Usage = D3D11_USAGE_DYNAMIC;
258 buffer_desc.ByteWidth = sizeof (WORD) * 6;
259 buffer_desc.BindFlags = D3D11_BIND_INDEX_BUFFER;
260 buffer_desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
262 hr = device_handle->CreateBuffer (&buffer_desc, nullptr, &context->index_buf);
263 g_assert (SUCCEEDED (hr));
265 blend_desc.AlphaToCoverageEnable = FALSE;
266 blend_desc.IndependentBlendEnable = FALSE;
267 blend_desc.RenderTarget[0].BlendEnable = TRUE;
268 blend_desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
269 blend_desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
270 blend_desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
271 blend_desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
272 blend_desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA;
273 blend_desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
274 blend_desc.RenderTarget[0].RenderTargetWriteMask =
275 D3D11_COLOR_WRITE_ENABLE_ALL;
277 hr = device_handle->CreateBlendState (&blend_desc, &context->blend);
278 g_assert (SUCCEEDED (hr));
280 hr = context_handle->Map (context->vertex_buf, 0, D3D11_MAP_WRITE_DISCARD, 0,
282 g_assert (SUCCEEDED (hr));
283 vertex_data = (VertexData *) map.pData;
285 hr = context_handle->Map (context->index_buf, 0, D3D11_MAP_WRITE_DISCARD, 0,
287 g_assert (SUCCEEDED (hr));
288 indices = (WORD *) map.pData;
289 for (guint i = 0; i < 4; i++) {
290 vertex_data[i].color.r = 0.0f;
291 vertex_data[i].color.g = 0.5f;
292 vertex_data[i].color.b = 0.5f;
293 vertex_data[i].color.a = 0.5f;
297 vertex_data[0].position.x = -1.0f;
298 vertex_data[0].position.y = -1.0f;
299 vertex_data[0].position.z = 0.0f;
302 vertex_data[1].position.x = -1.0f;
303 vertex_data[1].position.y = 1.0f;
304 vertex_data[1].position.z = 0.0f;
307 vertex_data[2].position.x = 1.0f;
308 vertex_data[2].position.y = 1.0f;
309 vertex_data[2].position.z = 0.0f;
312 vertex_data[3].position.x = 1.0f;
313 vertex_data[3].position.y = -1.0f;
314 vertex_data[3].position.z = 0.0f;
316 /* clockwise indexing */
317 indices[0] = 0; /* bottom left */
318 indices[1] = 1; /* top left */
319 indices[2] = 2; /* top right */
321 indices[3] = 3; /* bottom right */
322 indices[4] = 0; /* bottom left */
323 indices[5] = 2; /* top right */
325 context_handle->Unmap (context->vertex_buf, 0);
326 context_handle->Unmap (context->index_buf, 0);
329 /* This callback will be called with gst_d3d11_device_lock() taken by
330 * d3d11videosink. We can perform GPU operation here safely */
332 on_present (GstElement * sink, GstD3D11Device * device,
333 ID3D11RenderTargetView * rtv, DisplayContext * context)
335 ComPtr < ID3D11Resource > resource;
336 ComPtr < ID3D11Texture2D > texture;
337 ComPtr < IDXGISurface > surface;
338 ComPtr < ID2D1RenderTarget > d2d_target;
339 ComPtr < ID2D1SolidColorBrush > text_brush;
340 ID3D11DeviceContext *device_context;
342 D3D11_TEXTURE2D_DESC desc;
343 ID2D1Factory *d2d_factory;
345 D3D11_VIEWPORT viewport;
346 UINT vertex_stride = sizeof (VertexData);
349 if (!context->enable_overlay)
352 rtv->GetResource (&resource);
353 hr = resource.As (&texture);
354 g_assert (SUCCEEDED (hr));
356 hr = texture.As (&surface);
357 g_assert (SUCCEEDED (hr));
359 texture->GetDesc (&desc);
361 ensure_d3d11_resource (context, device);
362 device_context = gst_d3d11_device_get_device_context_handle (device);
364 viewport.TopLeftX = 0;
365 viewport.TopLeftY = 0;
366 viewport.Width = desc.Width;
367 viewport.Height = desc.Height / 5.0f;
368 viewport.MinDepth = 0.0f;
369 viewport.MaxDepth = 1.0f;
371 /* Draw background using D3D11 */
372 device_context->IASetPrimitiveTopology
373 (D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
374 device_context->IASetInputLayout (context->input_layout);
375 device_context->IASetVertexBuffers (0, 1, &context->vertex_buf,
376 &vertex_stride, &offsets);
377 device_context->IASetIndexBuffer (context->index_buf, DXGI_FORMAT_R16_UINT,
379 device_context->VSSetShader (context->vs, nullptr, 0);
380 device_context->PSSetShader (context->ps, nullptr, 0);
381 device_context->RSSetViewports (1, &viewport);
382 device_context->OMSetRenderTargets (1, &rtv, nullptr);
383 device_context->OMSetBlendState (context->blend, nullptr, 0xffffffff);
384 device_context->DrawIndexed (6, 0, 0);
386 /* Creates new layout on window size or framerate change */
387 AcquireSRWLockExclusive (&context->lock);
388 framerate = context->avg_framerate;
389 if (context->layout && (context->width != desc.Width ||
390 context->height != desc.Height
391 || context->last_framerate != framerate)) {
392 CLEAR_COM (context->layout);
394 context->last_framerate = framerate;
395 ReleaseSRWLockExclusive (&context->lock);
397 context->width = desc.Width;
398 context->height = desc.Height;
400 if (!context->layout) {
401 IDWriteFactory *factory = context->dwrite_factory;
402 std::wstring overlay_string;
403 wchar_t fps_buf[128];
404 DWRITE_TEXT_METRICS metrics;
406 bool was_decreased = false;
407 DWRITE_TEXT_RANGE range;
409 overlay_string = L"Text Overlay, FPS: ";
410 std::swprintf (fps_buf, L"%.1f", framerate);
412 overlay_string += fps_buf;
414 hr = factory->CreateTextLayout (overlay_string.c_str (),
415 overlay_string.length (),
416 context->format, desc.Width, desc.Height / 5.0f, &context->layout);
417 g_assert (SUCCEEDED (hr));
419 context->layout->SetTextAlignment (DWRITE_TEXT_ALIGNMENT_CENTER);
420 context->layout->SetParagraphAlignment (DWRITE_PARAGRAPH_ALIGNMENT_CENTER);
422 range.startPosition = 0;
423 range.length = overlay_string.length ();
425 /* Calculate best font size */
427 hr = context->layout->GetMetrics (&metrics);
428 g_assert (SUCCEEDED (hr));
430 context->layout->GetFontSize (0, &font_size);
431 if (metrics.widthIncludingTrailingWhitespace >= (FLOAT) desc.Width) {
432 if (font_size > 1.0f) {
434 was_decreased = true;
435 hr = context->layout->SetFontSize (font_size, range);
436 g_assert (SUCCEEDED (hr));
446 if (metrics.widthIncludingTrailingWhitespace < (FLOAT) desc.Width) {
447 if (metrics.widthIncludingTrailingWhitespace >= desc.Width * 0.7)
451 hr = context->layout->SetFontSize (font_size, range);
452 g_assert (SUCCEEDED (hr));
458 d2d_factory = context->d2d_factory;
459 D2D1_RENDER_TARGET_PROPERTIES props;
460 props.type = D2D1_RENDER_TARGET_TYPE_DEFAULT;
461 props.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM;
462 props.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;
466 props.usage = D2D1_RENDER_TARGET_USAGE_NONE;
467 props.minLevel = D2D1_FEATURE_LEVEL_DEFAULT;
469 /* Creates D2D render target using swapchin's backbuffer */
470 hr = d2d_factory->CreateDxgiSurfaceRenderTarget (surface.Get (), props,
472 g_assert (SUCCEEDED (hr));
475 hr = d2d_target->CreateSolidColorBrush (D2D1::ColorF (D2D1::ColorF::Black),
477 g_assert (SUCCEEDED (hr));
482 rect.right = desc.Width;
483 rect.bottom = desc.Height / 5.0f;
485 d2d_target->BeginDraw ();
487 d2d_target->DrawTextLayout (D2D1::Point2F (0, 0),
488 context->layout, text_brush.Get (), D2D1_DRAW_TEXT_OPTIONS_NONE);
489 d2d_target->EndDraw ();
492 static GstPadProbeReturn
493 framerate_calculate_probe (GstPad * pad, GstPadProbeInfo * info,
494 DisplayContext * context)
498 AcquireSRWLockExclusive (&context->lock);
500 QueryPerformanceCounter (&now);
501 context->render_timestamp.push (now);
503 if (context->render_timestamp.size () > 10) {
504 LARGE_INTEGER last = context->render_timestamp.back ();
505 LARGE_INTEGER first = context->render_timestamp.front ();
506 double diff = last.QuadPart - first.QuadPart;
507 context->avg_framerate =
508 (double) context->frequency.QuadPart *
509 (context->render_timestamp.size () - 1) / diff;
511 std::queue < LARGE_INTEGER > empty_queue;
512 std::swap (context->render_timestamp, empty_queue);
516 ReleaseSRWLockExclusive (&context->lock);
518 return GST_PAD_PROBE_OK;
522 main (gint argc, gchar ** argv)
524 WNDCLASSEXA wc = { 0, };
525 HINSTANCE hinstance = GetModuleHandle (nullptr);
526 GOptionContext *option_ctx;
527 GError *error = nullptr;
528 RECT wr = { 0, 0, 320, 240 };
530 gchar *uri = nullptr;
531 GOptionEntry options[] = {
532 {"uri", 0, 0, G_OPTION_ARG_STRING, &uri, "URI to play", nullptr},
536 DisplayContext context;
540 g_option_context_new ("d3d11videosink \"present\" signal example");
541 g_option_context_add_main_entries (option_ctx, options, nullptr);
542 g_option_context_add_group (option_ctx, gst_init_get_option_group ());
543 ret = g_option_context_parse (option_ctx, &argc, &argv, &error);
544 g_option_context_free (option_ctx);
547 gst_printerrln ("option parsing failed: %s", error->message);
548 g_clear_error (&error);
553 gst_printerrln ("File name or URI must be provided");
557 if (!gst_uri_is_valid (uri)) {
558 gchar *file = gst_filename_to_uri (uri, nullptr);
564 gst_printerrln ("No valid URI");
568 /* Prepare device independent D2D objects */
569 hr = D2D1CreateFactory (D2D1_FACTORY_TYPE_MULTI_THREADED,
570 IID_PPV_ARGS (&context.d2d_factory));
572 gst_printerrln ("Couldn't create D2D factory");
576 hr = DWriteCreateFactory (DWRITE_FACTORY_TYPE_SHARED,
577 __uuidof (context.dwrite_factory),
578 reinterpret_cast < IUnknown ** >(&context.dwrite_factory));
580 gst_printerrln ("Couldn't create DirectWrite factory");
584 /* Font size will be re-calculated on present */
585 hr = context.dwrite_factory->CreateTextFormat (L"Consolas", nullptr,
586 DWRITE_FONT_WEIGHT_REGULAR, DWRITE_FONT_STYLE_NORMAL,
587 DWRITE_FONT_STRETCH_NORMAL, 12.0f, L"en-us", &context.format);
589 /* For rendered framerate calculation */
590 QueryPerformanceFrequency (&context.frequency);
592 context.loop = g_main_loop_new (nullptr, FALSE);
594 wc.cbSize = sizeof (WNDCLASSEXA);
595 wc.style = CS_HREDRAW | CS_VREDRAW;
596 wc.lpfnWndProc = (WNDPROC) window_proc;
597 wc.hInstance = hinstance;
598 wc.hCursor = LoadCursor (nullptr, IDC_ARROW);
599 wc.lpszClassName = "GstD3D11VideoSinkExample";
600 RegisterClassExA (&wc);
602 AdjustWindowRect (&wr, WS_OVERLAPPEDWINDOW, FALSE);
603 context.window_handle =
604 CreateWindowExA (0, wc.lpszClassName, "GstD3D11VideoSinkExample",
605 WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW,
606 CW_USEDEFAULT, CW_USEDEFAULT,
607 wr.right - wr.left, wr.bottom - wr.top, nullptr, nullptr,
610 context.io_ch = g_io_channel_win32_new_messages (0);
611 g_io_add_watch (context.io_ch, G_IO_IN, msg_cb, context.window_handle);
613 context.pipeline = gst_element_factory_make ("playbin", nullptr);
614 g_assert (context.pipeline);
616 context.sink = gst_element_factory_make ("d3d11videosink", nullptr);
617 g_assert (context.sink);
619 /* Enables present signal */
620 g_object_set (context.sink, "emit-present", TRUE, nullptr);
622 /* D2D <-> DXGI interop requires BGRA format */
623 g_object_set (context.sink, "display-format", DXGI_FORMAT_B8G8R8A8_UNORM,
626 g_signal_connect (context.sink, "present", G_CALLBACK (on_present), &context);
627 gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (context.sink),
628 (guintptr) context.window_handle);
630 /* Attach our display context on HWND */
631 SetPropA (context.window_handle, DISPLAY_CONTEXT_PROP, &context);
633 g_object_set (context.pipeline,
634 "uri", uri, "video-sink", context.sink, nullptr);
635 gst_bus_add_watch (GST_ELEMENT_BUS (context.pipeline),
636 (GstBusFunc) bus_msg, &context);
638 pad = gst_element_get_static_pad (context.sink, "sink");
639 gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BUFFER,
640 (GstPadProbeCallback) framerate_calculate_probe, &context, nullptr);
642 if (gst_element_set_state (context.pipeline, GST_STATE_PLAYING) ==
643 GST_STATE_CHANGE_FAILURE) {
644 gst_printerrln ("Could not set state to playing for uri %s", uri);
648 ShowWindow (context.window_handle, SW_SHOW);
649 gst_println ("Click window client area to toggle overlay");
651 g_main_loop_run (context.loop);
653 gst_element_set_state (context.pipeline, GST_STATE_NULL);
654 gst_bus_remove_watch (GST_ELEMENT_BUS (context.pipeline));
655 gst_object_unref (context.pipeline);
656 g_io_channel_unref (context.io_ch);
658 if (context.window_handle)
659 DestroyWindow (context.window_handle);
661 CLEAR_COM (context.blend);
662 CLEAR_COM (context.ps);
663 CLEAR_COM (context.vs);
664 CLEAR_COM (context.input_layout);
665 CLEAR_COM (context.index_buf);
666 CLEAR_COM (context.vertex_buf);
668 CLEAR_COM (context.layout);
670 context.format->Release ();
671 context.d2d_factory->Release ();
672 context.dwrite_factory->Release ();
674 g_main_loop_unref (context.loop);