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 #include <d3d9.h>
32
33 using namespace Microsoft::WRL;
34
35 static gchar *uri = nullptr;
36
37 static GMainLoop *loop = nullptr;
38 static gboolean visible = FALSE;
39 static HWND hwnd = nullptr;
40 std::mutex lock;
41
42 /* Device handles */
43 ComPtr<ID3D11Device> device;
44 ComPtr<ID3D11DeviceContext> context;
45 ComPtr<IDXGIFactory2> factory;
46
47 ComPtr<IDirect3D9Ex> d3d9_handle;
48 ComPtr<IDirect3DDevice9Ex> d3d9_device;
49
50 /* SwapChain resources */
51 ComPtr<IDirect3DSwapChain9> swapchain;
52
53 /* Shard texture resources */
54 ComPtr<ID3D11Texture2D> shared_texture;
55 ComPtr<IDirect3DTexture9> shared_d3d9_texture;
56 ComPtr<IDirect3DSurface9> d3d9_surface;
57 HANDLE shared_handle = nullptr;
58
59 static void
on_begin_draw(GstElement * videosink,gpointer user_data)60 on_begin_draw (GstElement * videosink, gpointer user_data)
61 {
62 std::lock_guard<std::mutex> lk (lock);
63 GstElement *sink = GST_ELEMENT (user_data);
64 gboolean ret = TRUE;
65 HRESULT hr;
66 ComPtr<IDirect3DSurface9> backbuffer;
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 if (!swapchain) {
78 gst_printerrln ("SwapChain wasn't configured");
79 exit (-1);
80 }
81
82 g_signal_emit_by_name (sink,
83 "draw", shared_handle, D3D11_RESOURCE_MISC_SHARED, 0, 0, &ret);
84
85 if (!ret) {
86 gst_printerrln ("Failed to draw on shared handle");
87 if (loop)
88 g_main_loop_quit (loop);
89
90 return;
91 }
92
93 /* Get swapchain's backbuffer */
94 hr = swapchain->GetBackBuffer (0, D3DBACKBUFFER_TYPE_MONO, &backbuffer);
95 if (FAILED (hr)) {
96 gst_printerrln ("Couldn't get backbuffer");
97 exit (-1);
98 }
99
100 /* Copy shared texture to backbuffer */
101 hr = d3d9_device->StretchRect (d3d9_surface.Get(), nullptr,
102 backbuffer.Get(), nullptr, D3DTEXF_LINEAR);
103 if (FAILED (hr)) {
104 gst_printerrln ("StretchRect failed");
105 exit (-1);
106 }
107
108 hr = d3d9_device->BeginScene ();
109 if (FAILED (hr)) {
110 gst_printerrln ("BeginScene failed");
111 exit (-1);
112 }
113
114 hr = swapchain->Present (nullptr, nullptr, nullptr, nullptr, 0);
115 if (FAILED (hr)) {
116 gst_printerrln ("Present failed");
117 exit (-1);
118 }
119
120 hr = d3d9_device->EndScene ();
121 if (FAILED (hr)) {
122 gst_printerrln ("BeginScene failed");
123 exit (-1);
124 }
125 }
126
127 static void
on_resize(void)128 on_resize (void)
129 {
130 std::lock_guard<std::mutex> lk (lock);
131 RECT client_rect;
132 guint width, height;
133 HRESULT hr;
134
135 GetClientRect (hwnd, &client_rect);
136
137 width = MAX (1, (client_rect.right - client_rect.left));
138 height = MAX (1, (client_rect.bottom - client_rect.top));
139
140 D3DPRESENT_PARAMETERS params = { 0, };
141
142 if (!swapchain) {
143 params.Windowed = TRUE;
144 params.SwapEffect = D3DSWAPEFFECT_DISCARD;
145 params.hDeviceWindow = hwnd;
146 /* GST_VIDEO_FORMAT_BGRA */
147 params.BackBufferFormat = D3DFMT_A8R8G8B8;
148
149 hr = d3d9_device->CreateAdditionalSwapChain (¶ms, &swapchain);
150 if (FAILED (hr)) {
151 gst_printerrln ("Couldn't create swapchain");
152 exit (-1);
153 }
154 } else {
155 /* Check whether we need to re-create swapchain */
156 hr = swapchain->GetPresentParameters (¶ms);
157 if (FAILED (hr)) {
158 gst_printerrln ("Couldn't get swapchain parameters");
159 exit (-1);
160 }
161
162 if (params.BackBufferWidth != width || params.BackBufferHeight != height) {
163 /* Backbuffer will have client area size */
164 params.BackBufferWidth = 0;
165 params.BackBufferHeight = 0;
166
167 swapchain = nullptr;
168 hr = d3d9_device->CreateAdditionalSwapChain (¶ms, &swapchain);
169 if (FAILED (hr)) {
170 gst_printerrln ("Couldn't re-create swapchain");
171 exit (-1);
172 }
173 }
174 }
175 }
176
177 static LRESULT CALLBACK
window_proc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)178 window_proc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
179 {
180 switch (message) {
181 case WM_DESTROY:
182 {
183 std::lock_guard<std::mutex> lk (lock);
184 hwnd = NULL;
185 if (loop)
186 g_main_loop_quit (loop);
187 break;
188 }
189 case WM_SIZE:
190 on_resize ();
191 break;
192 default:
193 break;
194 }
195
196 return DefWindowProc (hWnd, message, wParam, lParam);
197 }
198
199 static gboolean
bus_msg(GstBus * bus,GstMessage * msg,GstElement * pipeline)200 bus_msg (GstBus * bus, GstMessage * msg, GstElement * pipeline)
201 {
202 switch (GST_MESSAGE_TYPE (msg)) {
203 case GST_MESSAGE_ASYNC_DONE:
204 /* make window visible when we have something to show */
205 if (!visible && hwnd) {
206 ShowWindow (hwnd, SW_SHOW);
207 visible = TRUE;
208 }
209 gst_element_set_state (pipeline, GST_STATE_PLAYING);
210 break;
211 case GST_MESSAGE_ERROR:{
212 GError *err;
213 gchar *dbg;
214
215 gst_message_parse_error (msg, &err, &dbg);
216 g_printerr ("ERROR %s \n", err->message);
217 if (dbg != NULL)
218 g_printerr ("ERROR debug information: %s\n", dbg);
219 g_clear_error (&err);
220 g_free (dbg);
221
222 g_main_loop_quit (loop);
223 break;
224 }
225 default:
226 break;
227 }
228
229 return TRUE;
230 }
231
232 static gboolean
msg_cb(GIOChannel * source,GIOCondition condition,gpointer data)233 msg_cb (GIOChannel * source, GIOCondition condition, gpointer data)
234 {
235 MSG msg;
236
237 if (!PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
238 return G_SOURCE_CONTINUE;
239
240 TranslateMessage (&msg);
241 DispatchMessage (&msg);
242
243 return G_SOURCE_CONTINUE;
244 }
245
246 gint
main(gint argc,gchar ** argv)247 main (gint argc, gchar ** argv)
248 {
249 GstElement *pipeline, *sink;
250 GstStateChangeReturn sret;
251 WNDCLASSEX wc = { 0, };
252 HINSTANCE hinstance = GetModuleHandle (NULL);
253 GIOChannel *msg_io_channel = NULL;
254 RECT wr = { 0, 0, 320, 240 };
255 HRESULT hr;
256 GOptionEntry options[] = {
257 {"uri", 0, 0, G_OPTION_ARG_STRING, &uri,
258 "URI to test (if unspecified, videotestsrc will be used)",
259 NULL},
260 {NULL}
261 };
262 GOptionContext *option_ctx;
263 gboolean ret;
264 GError *error = nullptr;
265
266 option_ctx = g_option_context_new ("d3d11videosink shard-texture with d3d9 interop example");
267 g_option_context_add_main_entries (option_ctx, options, NULL);
268 g_option_context_add_group (option_ctx, gst_init_get_option_group ());
269 ret = g_option_context_parse (option_ctx, &argc, &argv, &error);
270 g_option_context_free (option_ctx);
271
272 if (!ret) {
273 g_printerr ("option parsing failed: %s\n", error->message);
274 g_clear_error (&error);
275 exit (1);
276 }
277
278 /* 1) Prepare window */
279 wc.cbSize = sizeof (WNDCLASSEX);
280 wc.style = CS_HREDRAW | CS_VREDRAW;
281 wc.lpfnWndProc = (WNDPROC) window_proc;
282 wc.hInstance = hinstance;
283 wc.hCursor = LoadCursor (NULL, IDC_ARROW);
284 wc.lpszClassName = TEXT ("GstD3D11VideoSinkSharedTextureD3D9ExExample");
285 RegisterClassEx (&wc);
286
287 AdjustWindowRect (&wr, WS_OVERLAPPEDWINDOW, FALSE);
288
289 hwnd = CreateWindowEx (0, wc.lpszClassName,
290 TEXT ("GstD3D11VideoSinkSharedTextureD3D9ExExample"),
291 WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW,
292 CW_USEDEFAULT, CW_USEDEFAULT,
293 wr.right - wr.left, wr.bottom - wr.top, (HWND) NULL, (HMENU) NULL,
294 hinstance, NULL);
295
296 /* 2) Prepare D3D11 device */
297 hr = prepare_d3d11_device (&device, &context, &factory);
298 if (FAILED (hr)) {
299 gst_printerrln ("D3D11 device is unavailable");
300 return -1;
301 }
302
303 /* 3) Prepare D3D9EX device */
304 Direct3DCreate9Ex (D3D_SDK_VERSION, &d3d9_handle);
305 if (!d3d9_handle) {
306 gst_printerrln ("D3D9 handle is unavailable");
307 return -1;
308 }
309
310 D3DPRESENT_PARAMETERS params = { 0,};
311 params.Windowed = TRUE;
312 params.SwapEffect = D3DSWAPEFFECT_DISCARD;
313 params.hDeviceWindow = hwnd;
314 params.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
315
316 hr = d3d9_handle->CreateDeviceEx (D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd,
317 D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_MULTITHREADED | D3DCREATE_FPU_PRESERVE,
318 ¶ms, nullptr, &d3d9_device);
319 if (FAILED (hr)) {
320 gst_printerrln ("D3d9 deice is unavailable");
321 return -1;
322 }
323
324 /* 4) Create shared texture */
325 /* Texture size doesn't need to be identical to that of backbuffer */
326 hr = prepare_shared_texture (device.Get(), 1280, 720,
327 DXGI_FORMAT_B8G8R8A8_UNORM,
328 /* NOTE: D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX is incompatible with
329 * d3d9. User should use D3D11_RESOURCE_MISC_SHARED in case of d3d9 */
330 D3D11_RESOURCE_MISC_SHARED,
331 &shared_texture, nullptr, nullptr, &shared_handle);
332 if (FAILED (hr)) {
333 gst_printerrln ("Couldn't create texture to share with d3d11videosink");
334 return -1;
335 }
336
337 hr = d3d9_device->CreateTexture (1280, 720, 1, D3DUSAGE_RENDERTARGET,
338 D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &shared_d3d9_texture, &shared_handle);
339 if (FAILED (hr)) {
340 gst_printerrln ("Couldn't create shared d3d9 texture");
341 return -1;
342 }
343
344 hr = shared_d3d9_texture->GetSurfaceLevel (0, &d3d9_surface);
345 if (FAILED (hr)) {
346 gst_printerrln ("Couldn't get surface from shared d3d9 texture");
347 return -1;
348 }
349
350 /* Call initial resize to prepare swapchain */
351 on_resize();
352
353 loop = g_main_loop_new (NULL, FALSE);
354 msg_io_channel = g_io_channel_win32_new_messages ((gsize) hwnd);
355 g_io_add_watch (msg_io_channel, G_IO_IN, msg_cb, NULL);
356
357 /* Enable drawing on our texture and add signal handler */
358 sink = gst_element_factory_make ("d3d11videosink", NULL);
359 g_object_set (sink, "draw-on-shared-texture", TRUE, nullptr);
360 g_signal_connect (sink, "begin-draw", G_CALLBACK (on_begin_draw), sink);
361
362 if (uri) {
363 pipeline = gst_element_factory_make ("playbin", nullptr);
364 g_object_set (pipeline, "uri", uri, "video-sink", sink, nullptr);
365 } else {
366 GstElement *src = gst_element_factory_make ("videotestsrc", nullptr);
367
368 pipeline = gst_pipeline_new ("d3d11videosink-pipeline");
369 gst_bin_add_many (GST_BIN (pipeline), src, sink, NULL);
370 gst_element_link (src, sink);
371 }
372
373 gst_bus_add_watch (GST_ELEMENT_BUS (pipeline), (GstBusFunc) bus_msg,
374 pipeline);
375
376 sret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
377 if (sret == GST_STATE_CHANGE_FAILURE) {
378 g_printerr ("Pipeline doesn't want to pause\n");
379 } else {
380 g_main_loop_run (loop);
381 gst_element_set_state (pipeline, GST_STATE_NULL);
382 }
383
384 gst_bus_remove_watch (GST_ELEMENT_BUS (pipeline));
385
386 if (hwnd)
387 DestroyWindow (hwnd);
388
389 gst_object_unref (pipeline);
390 if (msg_io_channel)
391 g_io_channel_unref (msg_io_channel);
392 g_main_loop_unref (loop);
393
394 gst_deinit ();
395 g_free (uri);
396
397 return 0;
398 }
399