1 /*
2 * GStreamer
3 * Copyright (C) 2020 Seungha Yang <seungha@centricular.com>
4 *
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.
9 *
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.
14 *
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.
19 */
20
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24
25 #include <gst/gst.h>
26 #include <gst/video/video.h>
27
28 #include "d3d11device.h"
29 #include <wrl.h>
30 #include <mutex>
31
32 using namespace Microsoft::WRL;
33
34 static gboolean use_keyed_mutex = FALSE;
35 static gboolean use_nt_handle = FALSE;
36 static gchar *texture_foramt = nullptr;
37 static gchar *uri = nullptr;
38
39 static GMainLoop *loop = nullptr;
40 static gboolean visible = FALSE;
41 static HWND hwnd = nullptr;
42 std::mutex lock;
43
44 /* Device handles */
45 ComPtr<ID3D11Device> device;
46 ComPtr<ID3D11DeviceContext> context;
47 ComPtr<IDXGIFactory2> factory;
48
49 /* SwapChain resources */
50 ComPtr<IDXGISwapChain1> swapchain;
51 ComPtr<ID3D11RenderTargetView> rtv;
52
53 /* Shard texture resources */
54 ComPtr<ID3D11Texture2D> shared_texture;
55 ComPtr<ID3D11ShaderResourceView> srv;
56 ComPtr<IDXGIKeyedMutex> keyed_mutex;
57 HANDLE shared_handle = nullptr;
58 UINT misc_flags = 0;
59
60 static void
on_begin_draw(GstElement * videosink,gpointer user_data)61 on_begin_draw (GstElement * videosink, gpointer user_data)
62 {
63 std::lock_guard<std::mutex> lk (lock);
64 GstElement *sink = GST_ELEMENT (user_data);
65 gboolean ret = TRUE;
66 HRESULT hr;
67
68 /* Windows was destroyed, nothing to draw */
69 if (!hwnd)
70 return;
71
72 if (!shared_handle) {
73 gst_printerrln ("Shared handle wasn't configured");
74 exit (-1);
75 }
76
77 g_signal_emit_by_name (sink,
78 "draw", shared_handle, misc_flags, 0, 0, &ret);
79
80 if (!ret) {
81 gst_printerrln ("Failed to draw on shared handle");
82 if (loop)
83 g_main_loop_quit (loop);
84
85 return;
86 }
87
88 /* Acquire sync */
89 if (keyed_mutex) {
90 hr = keyed_mutex->AcquireSync (0, INFINITE);
91 if (FAILED (hr)) {
92 gst_printerrln ("Failed to acquire sync");
93 exit (-1);
94 }
95 }
96
97 ID3D11RenderTargetView *render_target_view = rtv.Get();
98 context->OMSetRenderTargets (1, &render_target_view, nullptr);
99
100 ID3D11ShaderResourceView *shader_resource = srv.Get();
101 context->PSSetShaderResources (0, 1, &shader_resource);
102
103 context->DrawIndexed (6, 0, 0);
104 if (keyed_mutex)
105 keyed_mutex->ReleaseSync (0);
106
107 swapchain->Present (0, 0);
108 }
109
110 static void
on_resize(void)111 on_resize (void)
112 {
113 std::lock_guard<std::mutex> lk (lock);
114 ComPtr<ID3D11Texture2D> backbuffer;
115
116 rtv = nullptr;
117
118 HRESULT hr = swapchain->ResizeBuffers (0,
119 /* Specify zero width/height to use the size of current client area */
120 0, 0,
121 /* Reuse configured format */
122 DXGI_FORMAT_UNKNOWN,
123 0);
124 if (FAILED (hr)) {
125 gst_printerrln ("Couldn't resize swapchain");
126 exit(-1);
127 }
128
129 hr = swapchain->GetBuffer (0, IID_PPV_ARGS (&backbuffer));
130 if (FAILED (hr)) {
131 gst_printerrln ("Couldn't get backbuffer from swapchain");
132 exit(-1);
133 }
134
135 hr = device->CreateRenderTargetView (backbuffer.Get(), nullptr, &rtv);
136 if (FAILED (hr)) {
137 gst_printerrln ("Couldn't get ID3D11RenderTargetView from backbuffer");
138 exit(-1);
139 }
140
141 D3D11_TEXTURE2D_DESC desc;
142 backbuffer->GetDesc(&desc);
143
144 D3D11_VIEWPORT viewport;
145 viewport.TopLeftX = 0;
146 viewport.TopLeftY = 0;
147 viewport.Width = desc.Width;
148 viewport.Height = desc.Height;
149 viewport.MinDepth = 0.0f;
150 viewport.MaxDepth = 1.0f;
151
152 context->RSSetViewports (1, &viewport);
153 }
154
155 static LRESULT CALLBACK
window_proc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)156 window_proc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
157 {
158 switch (message) {
159 case WM_DESTROY:
160 {
161 std::lock_guard<std::mutex> lk (lock);
162 hwnd = NULL;
163 if (loop)
164 g_main_loop_quit (loop);
165 break;
166 }
167 case WM_SIZE:
168 on_resize ();
169 break;
170 default:
171 break;
172 }
173
174 return DefWindowProc (hWnd, message, wParam, lParam);
175 }
176
177 static gboolean
bus_msg(GstBus * bus,GstMessage * msg,GstElement * pipeline)178 bus_msg (GstBus * bus, GstMessage * msg, GstElement * pipeline)
179 {
180 switch (GST_MESSAGE_TYPE (msg)) {
181 case GST_MESSAGE_ASYNC_DONE:
182 /* make window visible when we have something to show */
183 if (!visible && hwnd) {
184 ShowWindow (hwnd, SW_SHOW);
185 visible = TRUE;
186 }
187 gst_element_set_state (pipeline, GST_STATE_PLAYING);
188 break;
189 case GST_MESSAGE_ERROR:{
190 GError *err;
191 gchar *dbg;
192
193 gst_message_parse_error (msg, &err, &dbg);
194 g_printerr ("ERROR %s \n", err->message);
195 if (dbg != NULL)
196 g_printerr ("ERROR debug information: %s\n", dbg);
197 g_clear_error (&err);
198 g_free (dbg);
199
200 g_main_loop_quit (loop);
201 break;
202 }
203 default:
204 break;
205 }
206
207 return TRUE;
208 }
209
210 static gboolean
msg_cb(GIOChannel * source,GIOCondition condition,gpointer data)211 msg_cb (GIOChannel * source, GIOCondition condition, gpointer data)
212 {
213 MSG msg;
214
215 if (!PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
216 return G_SOURCE_CONTINUE;
217
218 TranslateMessage (&msg);
219 DispatchMessage (&msg);
220
221 return G_SOURCE_CONTINUE;
222 }
223
224 gint
main(gint argc,gchar ** argv)225 main (gint argc, gchar ** argv)
226 {
227 GstElement *pipeline, *sink;
228 GstStateChangeReturn sret;
229 WNDCLASSEX wc = { 0, };
230 HINSTANCE hinstance = GetModuleHandle (NULL);
231 GIOChannel *msg_io_channel = NULL;
232 RECT wr = { 0, 0, 320, 240 };
233 ComPtr<ID3D11SamplerState> sampler;
234 ComPtr<ID3D11PixelShader> ps;
235 ComPtr<ID3D11VertexShader> vs;
236 ComPtr<ID3D11InputLayout> layout;
237 ComPtr<ID3D11Buffer> vertex;
238 ComPtr<ID3D11Buffer> index;
239 HRESULT hr;
240 GOptionEntry options[] = {
241 {"use-keyed-mutex", 0, 0, G_OPTION_ARG_NONE, &use_keyed_mutex,
242 "Allocate shared texture with D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX flag",
243 NULL},
244 {"use-nt-handle", 0, 0, G_OPTION_ARG_NONE, &use_nt_handle,
245 "Allocate shared texture with D3D11_RESOURCE_MISC_SHARED_NTHANDLE flag",
246 NULL},
247 {"texture-format", 0, 0, G_OPTION_ARG_STRING, &texture_foramt,
248 "texture format to test, supported arguments are { BGRA, RGBA, RGB10A2_LE }",
249 NULL},
250 {"uri", 0, 0, G_OPTION_ARG_STRING, &uri,
251 "URI to test (if unspecified, videotestsrc will be used)",
252 NULL},
253 {NULL}
254 };
255 GOptionContext *option_ctx;
256 gboolean ret;
257 GError *error = nullptr;
258 DXGI_FORMAT format = DXGI_FORMAT_B8G8R8A8_UNORM;
259
260 option_ctx = g_option_context_new ("d3d11videosink shard-texture example");
261 g_option_context_add_main_entries (option_ctx, options, NULL);
262 g_option_context_add_group (option_ctx, gst_init_get_option_group ());
263 ret = g_option_context_parse (option_ctx, &argc, &argv, &error);
264 g_option_context_free (option_ctx);
265
266 if (!ret) {
267 g_printerr ("option parsing failed: %s\n", error->message);
268 g_clear_error (&error);
269 exit (1);
270 }
271
272 if (g_strcmp0 (texture_foramt, "RGBA") == 0) {
273 gst_println ("Use DXGI_FORMAT_R8G8B8A8_UNORM (RGBA) format");
274 format = DXGI_FORMAT_R8G8B8A8_UNORM;
275 } else if (g_strcmp0 (texture_foramt, "RGB10A2_LE") == 0) {
276 gst_println ("Use DXGI_FORMAT_R10G10B10A2_UNORM (RGB10A2_LE) format");
277 format = DXGI_FORMAT_R10G10B10A2_UNORM;
278 } else {
279 gst_println ("Use DXGI_FORMAT_B8G8R8A8_UNORM format");
280 format = DXGI_FORMAT_B8G8R8A8_UNORM;
281 }
282
283 /* NT handle needs to be used with keyed mutex */
284 if (use_nt_handle) {
285 misc_flags = D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX | D3D11_RESOURCE_MISC_SHARED_NTHANDLE;
286 } else if (use_keyed_mutex) {
287 misc_flags = D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX;
288 } else {
289 misc_flags = D3D11_RESOURCE_MISC_SHARED;
290 }
291
292 gst_println ("Use keyed-mutex: %d, use_nt_handle: %d", use_keyed_mutex,
293 use_nt_handle);
294
295 /* 1) Prepare window */
296 wc.cbSize = sizeof (WNDCLASSEX);
297 wc.style = CS_HREDRAW | CS_VREDRAW;
298 wc.lpfnWndProc = (WNDPROC) window_proc;
299 wc.hInstance = hinstance;
300 wc.hCursor = LoadCursor (NULL, IDC_ARROW);
301 wc.lpszClassName = TEXT ("GstD3D11VideoSinkSharedTextureExExample");
302 RegisterClassEx (&wc);
303
304 AdjustWindowRect (&wr, WS_OVERLAPPEDWINDOW, FALSE);
305
306 hwnd = CreateWindowEx (0, wc.lpszClassName,
307 TEXT ("GstD3D11VideoSinkSharedTextureExExample"),
308 WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW,
309 CW_USEDEFAULT, CW_USEDEFAULT,
310 wr.right - wr.left, wr.bottom - wr.top, (HWND) NULL, (HMENU) NULL,
311 hinstance, NULL);
312
313 /* 2) Prepare D3D11 device */
314 hr = prepare_d3d11_device (&device, &context, &factory);
315 if (FAILED (hr)) {
316 gst_printerrln ("D3D11 device is unavailable");
317 return -1;
318 }
319
320 hr = prepare_shader (device.Get(), context.Get(), &sampler,
321 &ps, &vs, &layout, &vertex, &index);
322 if (FAILED (hr)) {
323 gst_printerrln ("Couldn't setup shader");
324 return -1;
325 }
326
327 /* 3) Prepare SwapChain */
328 DXGI_SWAP_CHAIN_DESC1 desc = { 0, };
329 desc.Width = 0;
330 desc.Height = 0;
331 desc.Format = format;
332 desc.Stereo = FALSE;
333 desc.SampleDesc.Count = 1;
334 desc.SampleDesc.Quality = 0;
335 desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
336 desc.BufferCount = 2;
337 desc.Scaling = DXGI_SCALING_NONE;
338 desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
339 desc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
340
341 hr = factory->CreateSwapChainForHwnd (device.Get(), hwnd, &desc,
342 nullptr, nullptr, &swapchain);
343 if (FAILED (hr)) {
344 gst_printerrln ("IDXGISwapChain1 is unavailable");
345 return -1;
346 }
347
348 /* 4) Create shared texture */
349 /* Texture size doesn't need to be identical to that of backbuffer */
350 hr = prepare_shared_texture (device.Get(), 1280, 720,
351 format, misc_flags, &shared_texture, &srv, &keyed_mutex, &shared_handle);
352 if (FAILED (hr)) {
353 gst_printerrln ("Couldn't create texture to share with d3d11videosink");
354 return -1;
355 }
356
357 context->IASetPrimitiveTopology (D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
358 context->IASetInputLayout (layout.Get());
359 ID3D11Buffer *buf = vertex.Get();
360 UINT offsets = 0;
361 UINT stride = sizeof(VertexData);
362 context->IASetVertexBuffers (0, 1, &buf, &stride, &offsets);
363 context->IASetIndexBuffer (index.Get(), DXGI_FORMAT_R16_UINT, 0);
364
365 ID3D11SamplerState *sampler_state = sampler.Get();
366 context->PSSetSamplers (0, 1, &sampler_state);
367 context->VSSetShader (vs.Get(), nullptr, 0);
368 context->PSSetShader (ps.Get(), nullptr, 0);
369
370 /* Call initial resize to prepare resources */
371 on_resize();
372
373 loop = g_main_loop_new (NULL, FALSE);
374 msg_io_channel = g_io_channel_win32_new_messages ((gsize) hwnd);
375 g_io_add_watch (msg_io_channel, G_IO_IN, msg_cb, NULL);
376
377 /* Enable drawing on our texture and add signal handler */
378 sink = gst_element_factory_make ("d3d11videosink", NULL);
379 g_object_set (sink, "draw-on-shared-texture", TRUE, nullptr);
380 g_signal_connect (sink, "begin-draw", G_CALLBACK (on_begin_draw), sink);
381
382 if (uri) {
383 pipeline = gst_element_factory_make ("playbin", nullptr);
384 g_object_set (pipeline, "uri", uri, "video-sink", sink, nullptr);
385 } else {
386 GstElement *src = gst_element_factory_make ("videotestsrc", nullptr);
387
388 pipeline = gst_pipeline_new ("d3d11videosink-pipeline");
389 gst_bin_add_many (GST_BIN (pipeline), src, sink, NULL);
390 gst_element_link (src, sink);
391 }
392
393 gst_bus_add_watch (GST_ELEMENT_BUS (pipeline), (GstBusFunc) bus_msg,
394 pipeline);
395
396 sret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
397 if (sret == GST_STATE_CHANGE_FAILURE) {
398 g_printerr ("Pipeline doesn't want to pause\n");
399 } else {
400 g_main_loop_run (loop);
401 gst_element_set_state (pipeline, GST_STATE_NULL);
402 }
403
404 gst_bus_remove_watch (GST_ELEMENT_BUS (pipeline));
405
406 if (hwnd)
407 DestroyWindow (hwnd);
408
409 gst_object_unref (pipeline);
410 if (msg_io_channel)
411 g_io_channel_unref (msg_io_channel);
412 g_main_loop_unref (loop);
413
414 gst_deinit ();
415
416 g_free (texture_foramt);
417 g_free (uri);
418
419 /* NT handle should be explicitly closed to avoid leak */
420 if (use_nt_handle)
421 CloseHandle (shared_handle);
422
423 return 0;
424 }
425