• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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