• 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 #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 (&params, &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 (&params);
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 (&params, &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       &params, 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