1 /* GStreamer
2 * Copyright (C) 2012 Roland Krikava <info@bluedigits.com>
3 * Copyright (C) 2010-2011 David Hoyt <dhoyt@hoytsoft.org>
4 * Copyright (C) 2010 Andoni Morales <ylatuya@gmail.com>
5 * Copyright (C) 2012 Sebastian Dröge <sebastian.droege@collabora.co.uk>
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Library General Public
18 * License along with this library; if not, write to the
19 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 * Boston, MA 02111-1307, USA.
21 */
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25
26 #include "d3dvideosink.h"
27 #include "d3dhelpers.h"
28 #include "gstd3d9overlay.h"
29
30 #include <stdio.h>
31
32 typedef enum
33 {
34 WINDOW_VISIBILITY_FULL = 1,
35 WINDOW_VISIBILITY_PARTIAL = 2,
36 WINDOW_VISIBILITY_HIDDEN = 3,
37 WINDOW_VISIBILITY_ERROR = 4
38 } WindowHandleVisibility;
39
40 /* FWD DECLS */
41
42 static gboolean d3d_hidden_window_thread (GstD3DVideoSinkClass * klass);
43 static gboolean d3d_window_wndproc_set (GstD3DVideoSink * sink);
44 static void d3d_window_wndproc_unset (GstD3DVideoSink * sink);
45 static gboolean d3d_init_swap_chain (GstD3DVideoSink * sink, HWND hWnd);
46 static gboolean d3d_release_swap_chain (GstD3DVideoSink * sink);
47 static gboolean d3d_resize_swap_chain (GstD3DVideoSink * sink);
48 static gboolean d3d_present_swap_chain (GstD3DVideoSink * sink);
49 static gboolean d3d_copy_buffer (GstD3DVideoSink * sink,
50 GstBuffer * from, GstBuffer * to);
51 static gboolean d3d_stretch_and_copy (GstD3DVideoSink * sink,
52 LPDIRECT3DSURFACE9 back_buffer);
53 static HWND d3d_create_internal_window (GstD3DVideoSink * sink);
54
55 static void d3d_class_notify_device_lost (GstD3DVideoSink * sink);
56
57 static void d3d_class_display_device_destroy (GstD3DVideoSinkClass * klass);
58 static gboolean d3d_class_display_device_create (GstD3DVideoSinkClass * klass,
59 UINT adapter);
60 static void d3d_class_hidden_window_message_queue (gpointer data,
61 gpointer user_data);
62
63 static LRESULT APIENTRY d3d_wnd_proc_internal (HWND hWnd, UINT message,
64 WPARAM wParam, LPARAM lParam);
65 static LRESULT APIENTRY d3d_wnd_proc (HWND hWnd, UINT message, WPARAM wParam,
66 LPARAM lParam);
67
68
69 GST_DEBUG_CATEGORY_EXTERN (gst_d3dvideosink_debug);
70 #define GST_CAT_DEFAULT gst_d3dvideosink_debug
71
72 static gint WM_D3DVIDEO_NOTIFY_DEVICE_LOST = 0;
73 #define IDT_DEVICE_RESET_TIMER 0
74
75 #define WM_QUIT_THREAD WM_USER+0
76
77 typedef struct
78 {
79 gint window_message_id;
80 guint create_count;
81 } GstD3DVideoSinkEvent;
82
83 /* Helpers */
84
85 #define ERROR_CHECK_HR(hr) \
86 if(hr != S_OK) { \
87 const gchar * str_err=NULL, *t1=NULL; \
88 gchar tmp[128]=""; \
89 switch(hr)
90 #define CASE_HR_ERR(hr_err) \
91 case hr_err: str_err = #hr_err; break;
92 #define CASE_HR_DBG_ERR_END(sink, gst_err_msg, level) \
93 default: \
94 t1=gst_err_msg; \
95 sprintf(tmp, "HR-SEV:%u HR-FAC:%u HR-CODE:%u", (guint)HRESULT_SEVERITY(hr), (guint)HRESULT_FACILITY(hr), (guint)HRESULT_CODE(hr)); \
96 str_err = tmp; \
97 } /* end switch */ \
98 GST_CAT_LEVEL_LOG(GST_CAT_DEFAULT, level, sink, "%s HRESULT: %s", t1?t1:"", str_err);
99 #define CASE_HR_ERR_END(sink, gst_err_msg) \
100 CASE_HR_DBG_ERR_END(sink, gst_err_msg, GST_LEVEL_ERROR)
101 #define CASE_HR_DBG_END(sink, gst_err_msg) \
102 CASE_HR_DBG_ERR_END(sink, gst_err_msg, GST_LEVEL_DEBUG)
103
104 #define CHECK_REF_COUNT(klass, sink, goto_label) \
105 if(!klass->d3d.refs) { \
106 GST_ERROR_OBJECT(sink, "Direct3D object ref count = 0"); \
107 goto goto_label; \
108 }
109 #define CHECK_D3D_DEVICE(klass, sink, goto_label) \
110 if(!klass->d3d.d3d || !klass->d3d.device.d3d_device) { \
111 GST_ERROR_OBJECT(sink, "Direct3D device or object does not exist"); \
112 goto goto_label; \
113 }
114 #define CHECK_D3D_SWAPCHAIN(sink, goto_label) \
115 if(!sink->d3d.swapchain) { \
116 GST_ERROR_OBJECT(sink, "Direct3D swap chain does not exist"); \
117 goto goto_label; \
118 }
119 #define CHECK_D3D_SURFACE(sink, goto_label) \
120 if(!sink->d3d.surface) { \
121 GST_ERROR_OBJECT(sink, "NULL D3D offscreen surface"); \
122 goto goto_label; \
123 }
124 #define CHECK_WINDOW_HANDLE(sink, goto_label, is_error) \
125 if(!sink->d3d.window_handle) { \
126 GST_CAT_LEVEL_LOG(GST_CAT_DEFAULT, \
127 (is_error?GST_LEVEL_ERROR:GST_LEVEL_DEBUG), \
128 sink, "No window handle is set"); \
129 goto goto_label; \
130 }
131
132 #ifndef D3DFMT_YV12
133 #define D3DFMT_YV12 MAKEFOURCC ('Y', 'V', '1', '2')
134 #endif
135 #ifndef D3DFMT_NV12
136 #define D3DFMT_NV12 MAKEFOURCC ('N', 'V', '1', '2')
137 #endif
138
139 /* FORMATS */
140
141 #define CASE(x) case x: return #x;
142 static const gchar *
d3d_format_to_string(D3DFORMAT format)143 d3d_format_to_string (D3DFORMAT format)
144 {
145 /* Self defined up above */
146 if (format == D3DFMT_YV12)
147 return "D3DFMT_YV12";
148 else if (format == D3DFMT_NV12)
149 return "D3DFMT_NV12";
150
151 switch (format) {
152 /* From D3D enum */
153 CASE (D3DFMT_UNKNOWN);
154 CASE (D3DFMT_X8R8G8B8);
155 CASE (D3DFMT_YUY2);
156 CASE (D3DFMT_A8R8G8B8);
157 CASE (D3DFMT_UYVY);
158 CASE (D3DFMT_R8G8B8);
159 CASE (D3DFMT_R5G6B5);
160 CASE (D3DFMT_X1R5G5B5);
161 CASE (D3DFMT_A1R5G5B5);
162 CASE (D3DFMT_A4R4G4B4);
163 CASE (D3DFMT_R3G3B2);
164 CASE (D3DFMT_A8);
165 CASE (D3DFMT_A8R3G3B2);
166 CASE (D3DFMT_X4R4G4B4);
167 CASE (D3DFMT_A2B10G10R10);
168 CASE (D3DFMT_A8B8G8R8);
169 CASE (D3DFMT_X8B8G8R8);
170 CASE (D3DFMT_G16R16);
171 CASE (D3DFMT_A2R10G10B10);
172 CASE (D3DFMT_A16B16G16R16);
173 CASE (D3DFMT_A8P8);
174 CASE (D3DFMT_P8);
175 CASE (D3DFMT_L8);
176 CASE (D3DFMT_A8L8);
177 CASE (D3DFMT_A4L4);
178 CASE (D3DFMT_V8U8);
179 CASE (D3DFMT_L6V5U5);
180 CASE (D3DFMT_X8L8V8U8);
181 CASE (D3DFMT_Q8W8V8U8);
182 CASE (D3DFMT_V16U16);
183 CASE (D3DFMT_A2W10V10U10);
184 CASE (D3DFMT_DXT1);
185 CASE (D3DFMT_DXT2);
186 CASE (D3DFMT_DXT3);
187 CASE (D3DFMT_DXT4);
188 CASE (D3DFMT_DXT5);
189 CASE (D3DFMT_MULTI2_ARGB8);
190 CASE (D3DFMT_G8R8_G8B8);
191 CASE (D3DFMT_R8G8_B8G8);
192 CASE (D3DFMT_D16_LOCKABLE);
193 CASE (D3DFMT_D32);
194 CASE (D3DFMT_D15S1);
195 CASE (D3DFMT_D24S8);
196 CASE (D3DFMT_D24X8);
197 CASE (D3DFMT_D24X4S4);
198 CASE (D3DFMT_D16);
199 CASE (D3DFMT_L16);
200 CASE (D3DFMT_D32F_LOCKABLE);
201 CASE (D3DFMT_D24FS8);
202 CASE (D3DFMT_VERTEXDATA);
203 CASE (D3DFMT_INDEX16);
204 CASE (D3DFMT_INDEX32);
205 CASE (D3DFMT_Q16W16V16U16);
206 CASE (D3DFMT_R16F);
207 CASE (D3DFMT_G16R16F);
208 CASE (D3DFMT_A16B16G16R16F);
209 CASE (D3DFMT_R32F);
210 CASE (D3DFMT_G32R32F);
211 CASE (D3DFMT_A32B32G32R32F);
212 CASE (D3DFMT_CxV8U8);
213 CASE (D3DFMT_FORCE_DWORD);
214 default:
215 break;
216 }
217
218 return "UNKNOWN";
219 }
220
221 #undef CASE
222
223 static const struct
224 {
225 GstVideoFormat gst_format;
226 D3DFORMAT d3d_format;
227 } gst_d3d_format_map[] = {
228 {
229 GST_VIDEO_FORMAT_BGRx, D3DFMT_X8R8G8B8}, {
230 GST_VIDEO_FORMAT_RGBx, D3DFMT_X8B8G8R8}, {
231 GST_VIDEO_FORMAT_BGRA, D3DFMT_A8R8G8B8}, {
232 GST_VIDEO_FORMAT_RGBA, D3DFMT_A8B8G8R8}, {
233 GST_VIDEO_FORMAT_BGR, D3DFMT_R8G8B8}, {
234 GST_VIDEO_FORMAT_RGB16, D3DFMT_R5G6B5}, {
235 GST_VIDEO_FORMAT_RGB15, D3DFMT_X1R5G5B5}, {
236 GST_VIDEO_FORMAT_I420, D3DFMT_YV12}, {
237 GST_VIDEO_FORMAT_YV12, D3DFMT_YV12}, {
238 GST_VIDEO_FORMAT_NV12, D3DFMT_NV12}, {
239 GST_VIDEO_FORMAT_YUY2, D3DFMT_YUY2}, {
240 GST_VIDEO_FORMAT_UYVY, D3DFMT_UYVY}
241 };
242
243 static D3DFORMAT
gst_video_format_to_d3d_format(GstVideoFormat format)244 gst_video_format_to_d3d_format (GstVideoFormat format)
245 {
246 gint i;
247
248 for (i = 0; i < G_N_ELEMENTS (gst_d3d_format_map); i++)
249 if (gst_d3d_format_map[i].gst_format == format)
250 return gst_d3d_format_map[i].d3d_format;
251 return D3DFMT_UNKNOWN;
252 }
253
254 static gboolean
gst_video_d3d_format_check(GstD3DVideoSinkClass * klass,D3DFORMAT fmt)255 gst_video_d3d_format_check (GstD3DVideoSinkClass * klass, D3DFORMAT fmt)
256 {
257 HRESULT hr;
258 gboolean ret = FALSE;
259
260 LOCK_CLASS (NULL, klass);
261 CHECK_REF_COUNT (klass, NULL, end);
262 hr = IDirect3D9_CheckDeviceFormat (klass->d3d.d3d,
263 klass->d3d.device.adapter,
264 D3DDEVTYPE_HAL, klass->d3d.device.format, 0, D3DRTYPE_SURFACE, fmt);
265 if (hr == D3D_OK) {
266 /* test whether device can perform color-conversion
267 * from that format to target format
268 */
269 hr = IDirect3D9_CheckDeviceFormatConversion (klass->d3d.d3d,
270 klass->d3d.device.adapter,
271 D3DDEVTYPE_HAL, fmt, klass->d3d.device.format);
272 if (hr == D3D_OK)
273 ret = TRUE;
274 }
275 GST_DEBUG ("Checking: %s - %s", d3d_format_to_string (fmt),
276 ret ? "TRUE" : "FALSE");
277 end:
278 UNLOCK_CLASS (NULL, klass);
279 return ret;
280 }
281
282 static gboolean
gst_video_query_d3d_format(GstD3DVideoSinkClass * klass,D3DFORMAT d3dformat)283 gst_video_query_d3d_format (GstD3DVideoSinkClass * klass, D3DFORMAT d3dformat)
284 {
285 gboolean ret = FALSE;
286
287 LOCK_CLASS (NULL, klass);
288 CHECK_REF_COUNT (klass, NULL, end);
289 /* If it's the display adapter format we don't need to probe */
290 if (d3dformat == klass->d3d.device.format) {
291 ret = TRUE;
292 goto end;
293 }
294 ret = gst_video_d3d_format_check (klass, d3dformat);
295 end:
296 UNLOCK_CLASS (NULL, klass);
297
298 return ret;
299 }
300
301 typedef struct
302 {
303 GstVideoFormat fmt;
304 D3DFORMAT d3d_fmt;
305 gboolean display;
306 } D3DFormatComp;
307
308 static void
d3d_format_comp_free(D3DFormatComp * comp)309 d3d_format_comp_free (D3DFormatComp * comp)
310 {
311 g_slice_free (D3DFormatComp, comp);
312 }
313
314 static gint
d3d_format_comp_rate(const D3DFormatComp * comp)315 d3d_format_comp_rate (const D3DFormatComp * comp)
316 {
317 gint points = 0;
318 const GstVideoFormatInfo *info;
319
320 info = gst_video_format_get_info (comp->fmt);
321
322 if (comp->display)
323 points += 10;
324 if (GST_VIDEO_FORMAT_INFO_IS_YUV (info))
325 points += 5;
326 else if (GST_VIDEO_FORMAT_INFO_IS_RGB (info)) {
327 guint i, bit_depth = 0;
328 for (i = 0; i < GST_VIDEO_FORMAT_INFO_N_COMPONENTS (info); i++)
329 bit_depth += GST_VIDEO_FORMAT_INFO_DEPTH (info, i);
330 if (bit_depth >= 24)
331 points += 1;
332 }
333
334 return points;
335 }
336
337 static gint
d3d_format_comp_compare(gconstpointer a,gconstpointer b)338 d3d_format_comp_compare (gconstpointer a, gconstpointer b)
339 {
340 gint ptsa = 0, ptsb = 0;
341
342 ptsa = d3d_format_comp_rate ((const D3DFormatComp *) a);
343 ptsb = d3d_format_comp_rate ((const D3DFormatComp *) b);
344
345 if (ptsa < ptsb)
346 return -1;
347 else if (ptsa == ptsb)
348 return 0;
349 else
350 return 1;
351 }
352
353 #define GST_D3D_SURFACE_MEMORY_NAME "D3DSurface"
354
355 typedef struct
356 {
357 GstMemory mem;
358
359 GstD3DVideoSink *sink;
360
361 GMutex lock;
362 gint map_count;
363
364 LPDIRECT3DSURFACE9 surface;
365 D3DLOCKED_RECT lr;
366 gint x, y, width, height;
367 } GstD3DSurfaceMemory;
368
369 static GstMemory *
gst_d3d_surface_memory_allocator_alloc(GstAllocator * allocator,gsize size,GstAllocationParams * params)370 gst_d3d_surface_memory_allocator_alloc (GstAllocator * allocator, gsize size,
371 GstAllocationParams * params)
372 {
373 g_assert_not_reached ();
374 return NULL;
375 }
376
377 static void
gst_d3d_surface_memory_allocator_free(GstAllocator * allocator,GstMemory * mem)378 gst_d3d_surface_memory_allocator_free (GstAllocator * allocator,
379 GstMemory * mem)
380 {
381 GstD3DSurfaceMemory *dmem = (GstD3DSurfaceMemory *) mem;
382
383 /* If this is a sub-memory, do nothing */
384 if (mem->parent)
385 return;
386
387 if (dmem->lr.pBits)
388 g_warning ("d3dvideosink: Freeing memory that is still mapped");
389
390 IDirect3DSurface9_Release (dmem->surface);
391 gst_object_unref (dmem->sink);
392 g_mutex_clear (&dmem->lock);
393 g_slice_free (GstD3DSurfaceMemory, dmem);
394 }
395
396 static gpointer
gst_d3d_surface_memory_map(GstMemory * mem,gsize maxsize,GstMapFlags flags)397 gst_d3d_surface_memory_map (GstMemory * mem, gsize maxsize, GstMapFlags flags)
398 {
399 GstD3DSurfaceMemory *parent;
400 gpointer ret = NULL;
401
402 /* find the real parent */
403 if ((parent = (GstD3DSurfaceMemory *) mem->parent) == NULL)
404 parent = (GstD3DSurfaceMemory *) mem;
405
406 g_mutex_lock (&parent->lock);
407 if (!parent->map_count
408 && IDirect3DSurface9_LockRect (parent->surface, &parent->lr, NULL,
409 0) != D3D_OK) {
410 ret = NULL;
411 goto done;
412 }
413
414 ret = parent->lr.pBits;
415 parent->map_count++;
416
417 done:
418 g_mutex_unlock (&parent->lock);
419
420 return ret;
421 }
422
423 static void
gst_d3d_surface_memory_unmap(GstMemory * mem)424 gst_d3d_surface_memory_unmap (GstMemory * mem)
425 {
426 GstD3DSurfaceMemory *parent;
427
428 /* find the real parent */
429 if ((parent = (GstD3DSurfaceMemory *) mem->parent) == NULL)
430 parent = (GstD3DSurfaceMemory *) mem;
431
432 g_mutex_lock (&parent->lock);
433 parent->map_count--;
434 if (parent->map_count == 0) {
435 IDirect3DSurface9_UnlockRect (parent->surface);
436 memset (&parent->lr, 0, sizeof (parent->lr));
437 }
438
439 g_mutex_unlock (&parent->lock);
440 }
441
442 static GstMemory *
gst_d3d_surface_memory_share(GstMemory * mem,gssize offset,gssize size)443 gst_d3d_surface_memory_share (GstMemory * mem, gssize offset, gssize size)
444 {
445 GstD3DSurfaceMemory *sub;
446 GstD3DSurfaceMemory *parent;
447
448 /* find the real parent */
449 if ((parent = (GstD3DSurfaceMemory *) mem->parent) == NULL)
450 parent = (GstD3DSurfaceMemory *) mem;
451
452 if (size == -1)
453 size = mem->size - offset;
454
455 sub = g_slice_new0 (GstD3DSurfaceMemory);
456 /* the shared memory is always readonly */
457 gst_memory_init (GST_MEMORY_CAST (sub), GST_MINI_OBJECT_FLAGS (parent) |
458 GST_MINI_OBJECT_FLAG_LOCK_READONLY, mem->allocator,
459 GST_MEMORY_CAST (parent), mem->maxsize, mem->align, mem->offset + offset,
460 size);
461
462 return GST_MEMORY_CAST (sub);
463 }
464
465 typedef struct
466 {
467 GstAllocator parent;
468 } GstD3DSurfaceMemoryAllocator;
469
470 typedef struct
471 {
472 GstAllocatorClass parent_class;
473 } GstD3DSurfaceMemoryAllocatorClass;
474
475 GType gst_d3d_surface_memory_allocator_get_type (void);
476 G_DEFINE_TYPE (GstD3DSurfaceMemoryAllocator, gst_d3d_surface_memory_allocator,
477 GST_TYPE_ALLOCATOR);
478
479 #define GST_TYPE_D3D_SURFACE_MEMORY_ALLOCATOR (gst_d3d_surface_memory_allocator_get_type())
480 #define GST_IS_D3D_SURFACE_MEMORY_ALLOCATOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_D3D_SURFACE_MEMORY_ALLOCATOR))
481
482 static void
gst_d3d_surface_memory_allocator_class_init(GstD3DSurfaceMemoryAllocatorClass * klass)483 gst_d3d_surface_memory_allocator_class_init (GstD3DSurfaceMemoryAllocatorClass *
484 klass)
485 {
486 GstAllocatorClass *allocator_class;
487
488 allocator_class = (GstAllocatorClass *) klass;
489
490 allocator_class->alloc = gst_d3d_surface_memory_allocator_alloc;
491 allocator_class->free = gst_d3d_surface_memory_allocator_free;
492 }
493
494 static void
gst_d3d_surface_memory_allocator_init(GstD3DSurfaceMemoryAllocator * allocator)495 gst_d3d_surface_memory_allocator_init (GstD3DSurfaceMemoryAllocator * allocator)
496 {
497 GstAllocator *alloc = GST_ALLOCATOR_CAST (allocator);
498
499 alloc->mem_type = GST_D3D_SURFACE_MEMORY_NAME;
500 alloc->mem_map = gst_d3d_surface_memory_map;
501 alloc->mem_unmap = gst_d3d_surface_memory_unmap;
502 alloc->mem_share = gst_d3d_surface_memory_share;
503 /* fallback copy */
504
505 GST_OBJECT_FLAG_SET (allocator, GST_ALLOCATOR_FLAG_CUSTOM_ALLOC);
506 }
507
508 G_DEFINE_TYPE (GstD3DSurfaceBufferPool, gst_d3dsurface_buffer_pool,
509 GST_TYPE_VIDEO_BUFFER_POOL);
510
511 GstBufferPool *
gst_d3dsurface_buffer_pool_new(GstD3DVideoSink * sink)512 gst_d3dsurface_buffer_pool_new (GstD3DVideoSink * sink)
513 {
514 GstD3DSurfaceBufferPool *pool;
515
516 pool = g_object_new (GST_TYPE_D3DSURFACE_BUFFER_POOL, NULL);
517 gst_object_ref_sink (pool);
518 pool->sink = gst_object_ref (sink);
519
520 GST_LOG_OBJECT (pool, "new buffer pool %p", pool);
521
522 return GST_BUFFER_POOL_CAST (pool);
523 }
524
525 static void
gst_d3dsurface_buffer_pool_finalize(GObject * object)526 gst_d3dsurface_buffer_pool_finalize (GObject * object)
527 {
528 GstD3DSurfaceBufferPool *pool = GST_D3DSURFACE_BUFFER_POOL_CAST (object);
529
530 GST_LOG_OBJECT (pool, "finalize buffer pool %p", pool);
531
532 gst_object_unref (pool->sink);
533 if (pool->allocator)
534 gst_object_unref (pool->allocator);
535
536 G_OBJECT_CLASS (gst_d3dsurface_buffer_pool_parent_class)->finalize (object);
537 }
538
539 static const gchar **
gst_d3dsurface_buffer_pool_get_options(GstBufferPool * pool)540 gst_d3dsurface_buffer_pool_get_options (GstBufferPool * pool)
541 {
542 static const gchar *options[] = { GST_BUFFER_POOL_OPTION_VIDEO_META, NULL };
543
544 return options;
545 }
546
547 /* Calculate actual required buffer size from D3DLOCKED_RECT structure.
548 * Note that D3D could require larger Pitch value than minimum required one in theory.
549 * See also
550 * https://docs.microsoft.com/en-us/windows/desktop/direct3d9/width-vs--pitch */
551 static gboolean
d3d_calculate_buffer_size(GstVideoInfo * info,D3DLOCKED_RECT * lr,gsize * offset,gint * stride,gsize * size)552 d3d_calculate_buffer_size (GstVideoInfo * info, D3DLOCKED_RECT * lr,
553 gsize * offset, gint * stride, gsize * size)
554 {
555 switch (GST_VIDEO_INFO_FORMAT (info)) {
556 case GST_VIDEO_FORMAT_BGR:
557 case GST_VIDEO_FORMAT_BGRx:
558 case GST_VIDEO_FORMAT_RGBx:
559 case GST_VIDEO_FORMAT_BGRA:
560 case GST_VIDEO_FORMAT_RGBA:
561 case GST_VIDEO_FORMAT_RGB16:
562 case GST_VIDEO_FORMAT_RGB15:
563 case GST_VIDEO_FORMAT_YUY2:
564 case GST_VIDEO_FORMAT_UYVY:
565 offset[0] = 0;
566 stride[0] = lr->Pitch;
567 *size = lr->Pitch * GST_VIDEO_INFO_HEIGHT (info);
568 break;
569 case GST_VIDEO_FORMAT_I420:
570 case GST_VIDEO_FORMAT_YV12:
571 offset[0] = 0;
572 stride[0] = lr->Pitch;
573 if (GST_VIDEO_INFO_FORMAT (info) == GST_VIDEO_FORMAT_YV12) {
574 offset[1] =
575 offset[0] + stride[0] * GST_VIDEO_INFO_COMP_HEIGHT (info, 0);
576 stride[1] = lr->Pitch / 2;
577 offset[2] =
578 offset[1] + stride[1] * GST_VIDEO_INFO_COMP_HEIGHT (info, 1);
579 stride[2] = lr->Pitch / 2;
580 *size = offset[2] + stride[2] * GST_VIDEO_INFO_COMP_HEIGHT (info, 2);
581 } else {
582 offset[2] =
583 offset[0] + stride[0] * GST_VIDEO_INFO_COMP_HEIGHT (info, 0);
584 stride[2] = lr->Pitch / 2;
585 offset[1] =
586 offset[2] + stride[2] * GST_VIDEO_INFO_COMP_HEIGHT (info, 2);
587 stride[1] = lr->Pitch / 2;
588 *size = offset[1] + stride[1] * GST_VIDEO_INFO_COMP_HEIGHT (info, 1);
589 }
590 break;
591 case GST_VIDEO_FORMAT_NV12:
592 offset[0] = 0;
593 stride[0] = lr->Pitch;
594 offset[1] = offset[0] + stride[0] * GST_VIDEO_INFO_COMP_HEIGHT (info, 0);
595 stride[1] = lr->Pitch;
596 *size = offset[1] + stride[1] * GST_VIDEO_INFO_COMP_HEIGHT (info, 1);
597 break;
598 default:
599 return FALSE;
600 }
601
602 GST_LOG ("Calculated buffer size: %" G_GSIZE_FORMAT
603 " (%s %dx%d, Pitch %d)", *size,
604 gst_video_format_to_string (GST_VIDEO_INFO_FORMAT (info)),
605 GST_VIDEO_INFO_WIDTH (info), GST_VIDEO_INFO_HEIGHT (info), lr->Pitch);
606
607 return TRUE;
608 }
609
610 static gboolean
gst_d3dsurface_buffer_pool_set_config(GstBufferPool * bpool,GstStructure * config)611 gst_d3dsurface_buffer_pool_set_config (GstBufferPool * bpool,
612 GstStructure * config)
613 {
614 GstD3DSurfaceBufferPool *pool = GST_D3DSURFACE_BUFFER_POOL_CAST (bpool);
615 GstD3DVideoSink *sink = pool->sink;
616 GstD3DVideoSinkClass *klass = GST_D3DVIDEOSINK_GET_CLASS (sink);
617 GstCaps *caps;
618 GstVideoInfo info;
619 LPDIRECT3DSURFACE9 surface;
620 D3DFORMAT d3dformat;
621 gint stride[GST_VIDEO_MAX_PLANES] = { 0, };
622 gsize offset[GST_VIDEO_MAX_PLANES] = { 0, };
623 D3DLOCKED_RECT lr;
624 HRESULT hr;
625 gsize size;
626
627 if (!gst_buffer_pool_config_get_params (config, &caps, NULL, NULL, NULL)
628 || !caps) {
629 GST_ERROR_OBJECT (pool, "Buffer pool configuration without caps");
630 return FALSE;
631 }
632
633 /* now parse the caps from the config */
634 if (!gst_video_info_from_caps (&info, caps)) {
635 GST_ERROR_OBJECT (pool, "Failed to parse caps %" GST_PTR_FORMAT, caps);
636 return FALSE;
637 }
638
639 d3dformat = gst_video_format_to_d3d_format (GST_VIDEO_INFO_FORMAT (&info));
640 if (d3dformat == D3DFMT_UNKNOWN) {
641 GST_ERROR_OBJECT (pool, "Unsupported video format in caps %" GST_PTR_FORMAT,
642 caps);
643 return FALSE;
644 }
645
646 GST_LOG_OBJECT (pool, "%dx%d, caps %" GST_PTR_FORMAT, info.width, info.height,
647 caps);
648
649 /* Create a surface to get exact buffer size */
650 LOCK_CLASS (sink, klass);
651 CHECK_REF_COUNT (klass, sink, error);
652 CHECK_D3D_DEVICE (klass, sink, error);
653 hr = IDirect3DDevice9_CreateOffscreenPlainSurface (klass->d3d.
654 device.d3d_device, GST_VIDEO_INFO_WIDTH (&info),
655 GST_VIDEO_INFO_HEIGHT (&info), d3dformat, D3DPOOL_DEFAULT, &surface,
656 NULL);
657 UNLOCK_CLASS (sink, klass);
658 if (hr != D3D_OK) {
659 GST_ERROR_OBJECT (sink, "Failed to create D3D surface");
660 return FALSE;
661 }
662
663 IDirect3DSurface9_LockRect (surface, &lr, NULL, 0);
664 if (!lr.pBits) {
665 GST_ERROR_OBJECT (sink, "Failed to lock D3D surface");
666 IDirect3DSurface9_Release (surface);
667 return FALSE;
668 }
669
670 if (!d3d_calculate_buffer_size (&info, &lr, offset, stride, &size)) {
671 GST_ERROR_OBJECT (sink, "Failed to get buffer size");
672 IDirect3DSurface9_UnlockRect (surface);
673 IDirect3DSurface9_Release (surface);
674 return FALSE;
675 }
676
677 IDirect3DSurface9_UnlockRect (surface);
678 IDirect3DSurface9_Release (surface);
679
680 pool->info = info;
681
682 pool->add_metavideo =
683 gst_buffer_pool_config_has_option (config,
684 GST_BUFFER_POOL_OPTION_VIDEO_META);
685
686 if (pool->add_metavideo) {
687 pool->allocator =
688 g_object_new (GST_TYPE_D3D_SURFACE_MEMORY_ALLOCATOR, NULL);
689 gst_object_ref_sink (pool->allocator);
690 }
691
692 gst_buffer_pool_config_set_params (config, caps, size, 2, 0);
693
694 return GST_BUFFER_POOL_CLASS
695 (gst_d3dsurface_buffer_pool_parent_class)->set_config (bpool, config);
696
697 error:
698 UNLOCK_CLASS (sink, klass);
699 return FALSE;
700 }
701
702 static GstFlowReturn
gst_d3dsurface_buffer_pool_alloc_buffer(GstBufferPool * bpool,GstBuffer ** buffer,GstBufferPoolAcquireParams * params)703 gst_d3dsurface_buffer_pool_alloc_buffer (GstBufferPool * bpool,
704 GstBuffer ** buffer, GstBufferPoolAcquireParams * params)
705 {
706 GstD3DSurfaceBufferPool *pool = GST_D3DSURFACE_BUFFER_POOL_CAST (bpool);
707 GstD3DVideoSink *sink = pool->sink;
708 GstD3DVideoSinkClass *klass = GST_D3DVIDEOSINK_GET_CLASS (sink);
709 GstD3DSurfaceMemory *mem;
710 LPDIRECT3DSURFACE9 surface;
711 D3DFORMAT d3dformat;
712 gint stride[GST_VIDEO_MAX_PLANES] = { 0, };
713 gsize offset[GST_VIDEO_MAX_PLANES] = { 0, };
714 D3DLOCKED_RECT lr;
715 HRESULT hr;
716 gsize size = 0;
717
718 *buffer = NULL;
719 if (!pool->add_metavideo) {
720 GST_DEBUG_OBJECT (pool, "No video meta allowed, fallback alloc");
721 goto fallback;
722 }
723
724 d3dformat =
725 gst_video_format_to_d3d_format (GST_VIDEO_INFO_FORMAT (&pool->info));
726 LOCK_CLASS (sink, klass);
727 CHECK_REF_COUNT (klass, sink, error);
728 CHECK_D3D_DEVICE (klass, sink, error);
729 hr = IDirect3DDevice9_CreateOffscreenPlainSurface (klass->d3d.
730 device.d3d_device, GST_VIDEO_INFO_WIDTH (&pool->info),
731 GST_VIDEO_INFO_HEIGHT (&pool->info), d3dformat, D3DPOOL_DEFAULT, &surface,
732 NULL);
733 UNLOCK_CLASS (sink, klass);
734 if (hr != D3D_OK) {
735 GST_ERROR_OBJECT (sink, "Failed to create D3D surface");
736 goto fallback;
737 }
738
739 IDirect3DSurface9_LockRect (surface, &lr, NULL, 0);
740 if (!lr.pBits) {
741 GST_ERROR_OBJECT (sink, "Failed to lock D3D surface");
742 IDirect3DSurface9_Release (surface);
743 goto fallback;
744 }
745
746 if (!d3d_calculate_buffer_size (&pool->info, &lr, offset, stride, &size)) {
747 GST_ERROR_OBJECT (sink, "Failed to get buffer size");
748 IDirect3DSurface9_UnlockRect (surface);
749 IDirect3DSurface9_Release (surface);
750 return GST_FLOW_ERROR;
751 }
752
753 IDirect3DSurface9_UnlockRect (surface);
754
755 *buffer = gst_buffer_new ();
756
757 gst_buffer_add_video_meta_full (*buffer, GST_VIDEO_FRAME_FLAG_NONE,
758 GST_VIDEO_INFO_FORMAT (&pool->info), GST_VIDEO_INFO_WIDTH (&pool->info),
759 GST_VIDEO_INFO_HEIGHT (&pool->info),
760 GST_VIDEO_INFO_N_PLANES (&pool->info), offset, stride);
761
762 mem = g_slice_new0 (GstD3DSurfaceMemory);
763 gst_memory_init (GST_MEMORY_CAST (mem), 0, pool->allocator, NULL, size, 0, 0,
764 size);
765
766 mem->surface = surface;
767 mem->sink = gst_object_ref (sink);
768 mem->x = mem->y = 0;
769 mem->width = GST_VIDEO_INFO_WIDTH (&pool->info);
770 mem->height = GST_VIDEO_INFO_HEIGHT (&pool->info);
771 g_mutex_init (&mem->lock);
772
773 gst_buffer_append_memory (*buffer, GST_MEMORY_CAST (mem));
774
775 return GST_FLOW_OK;
776
777 fallback:
778 {
779 return
780 GST_BUFFER_POOL_CLASS
781 (gst_d3dsurface_buffer_pool_parent_class)->alloc_buffer (bpool, buffer,
782 params);
783 }
784 error:
785 UNLOCK_CLASS (sink, klass);
786 return GST_FLOW_ERROR;
787 }
788
789 static void
gst_d3dsurface_buffer_pool_class_init(GstD3DSurfaceBufferPoolClass * klass)790 gst_d3dsurface_buffer_pool_class_init (GstD3DSurfaceBufferPoolClass * klass)
791 {
792 GObjectClass *gobject_class = (GObjectClass *) klass;
793 GstBufferPoolClass *gstbufferpool_class = (GstBufferPoolClass *) klass;
794
795 gobject_class->finalize = gst_d3dsurface_buffer_pool_finalize;
796
797 gstbufferpool_class->get_options = gst_d3dsurface_buffer_pool_get_options;
798 gstbufferpool_class->set_config = gst_d3dsurface_buffer_pool_set_config;
799 gstbufferpool_class->alloc_buffer = gst_d3dsurface_buffer_pool_alloc_buffer;
800 }
801
802 static void
gst_d3dsurface_buffer_pool_init(GstD3DSurfaceBufferPool * pool)803 gst_d3dsurface_buffer_pool_init (GstD3DSurfaceBufferPool * pool)
804 {
805 }
806
807 GstCaps *
d3d_supported_caps(GstD3DVideoSink * sink)808 d3d_supported_caps (GstD3DVideoSink * sink)
809 {
810 GstD3DVideoSinkClass *klass = GST_D3DVIDEOSINK_GET_CLASS (sink);
811 GList *l;
812 GstCaps *caps = NULL;
813 GValue va = { 0, };
814 GValue v = { 0, };
815
816 g_return_val_if_fail (GST_IS_D3DVIDEOSINK (sink), NULL);
817 LOCK_SINK (sink);
818
819 if (sink->supported_caps) {
820 caps = gst_caps_ref (sink->supported_caps);
821 goto unlock;
822 }
823
824 LOCK_CLASS (sink, klass);
825 if (klass->d3d.refs == 0) {
826 UNLOCK_CLASS (sink, klass);
827 goto unlock;
828 }
829
830 GST_DEBUG_OBJECT (sink, "Supported Caps:");
831
832 g_value_init (&va, GST_TYPE_LIST);
833 g_value_init (&v, G_TYPE_STRING);
834
835 for (l = klass->d3d.supported_formats; l; l = g_list_next (l)) {
836 D3DFormatComp *comp = (D3DFormatComp *) l->data;
837
838 GST_DEBUG_OBJECT (sink, "%s -> %s %s",
839 gst_video_format_to_string (comp->fmt),
840 d3d_format_to_string (comp->d3d_fmt), comp->display ? "[display]" : "");
841 g_value_set_string (&v, gst_video_format_to_string (comp->fmt));
842 gst_value_list_append_value (&va, &v);
843 }
844 UNLOCK_CLASS (sink, klass);
845
846 caps =
847 gst_caps_make_writable (gst_pad_get_pad_template_caps (GST_VIDEO_SINK_PAD
848 (sink)));
849
850 gst_caps_set_value (caps, "format", &va);
851 g_value_unset (&v);
852 g_value_unset (&va);
853
854 sink->supported_caps = gst_caps_ref (caps);
855
856 #ifndef GST_DISABLE_GST_DEBUG
857 {
858 GST_DEBUG_OBJECT (sink, "Supported caps: %" GST_PTR_FORMAT, caps);
859 }
860 #endif
861
862 unlock:
863 UNLOCK_SINK (sink);
864
865 return caps;
866 }
867
868 gboolean
d3d_set_render_format(GstD3DVideoSink * sink)869 d3d_set_render_format (GstD3DVideoSink * sink)
870 {
871 GstD3DVideoSinkClass *klass = GST_D3DVIDEOSINK_GET_CLASS (sink);
872 D3DFORMAT fmt;
873 gboolean ret = FALSE;
874
875 g_return_val_if_fail (GST_IS_D3DVIDEOSINK (sink), FALSE);
876 LOCK_SINK (sink);
877
878 fmt = gst_video_format_to_d3d_format (sink->format);
879 if (fmt == D3DFMT_UNKNOWN) {
880 GST_ERROR_OBJECT (sink, "Unsupported video format %s",
881 gst_video_format_to_string (sink->format));
882 goto end;
883 }
884
885 if (!gst_video_query_d3d_format (klass, fmt)) {
886 GST_ERROR_OBJECT (sink, "Failed to query a D3D render format for %s",
887 gst_video_format_to_string (sink->format));
888 goto end;
889 }
890
891 GST_DEBUG_OBJECT (sink, "Selected %s -> %s",
892 gst_video_format_to_string (sink->format), d3d_format_to_string (fmt));
893
894 sink->d3d.format = fmt;
895
896 ret = TRUE;
897
898 end:
899 UNLOCK_SINK (sink);
900
901 return ret;
902 }
903
904 gboolean
d3d_get_hwnd_window_size(HWND hwnd,gint * width,gint * height)905 d3d_get_hwnd_window_size (HWND hwnd, gint * width, gint * height)
906 {
907 RECT sz;
908
909 g_return_val_if_fail (width != NULL, FALSE);
910 g_return_val_if_fail (height != NULL, FALSE);
911
912 *width = 0;
913 *height = 0;
914
915 if (!hwnd)
916 return FALSE;
917
918 GetClientRect (hwnd, &sz);
919
920 *width = MAX (1, ABS (sz.right - sz.left));
921 *height = MAX (1, ABS (sz.bottom - sz.top));
922
923 return TRUE;
924 }
925
926 static gboolean
d3d_get_render_rects(GstVideoRectangle * rr,RECT * dst,RECT * src)927 d3d_get_render_rects (GstVideoRectangle * rr, RECT * dst, RECT * src)
928 {
929 if (!rr)
930 return FALSE;
931
932 /* Rect on target */
933 if (dst) {
934 dst->left = rr->x;
935 dst->top = rr->y;
936 dst->right = rr->x + rr->w;
937 dst->bottom = rr->y + rr->h;
938 }
939
940 /* Rect on source */
941 if (src) {
942 src->left = 0;
943 src->top = 0;
944 src->right = rr->w;
945 src->bottom = rr->h;
946 }
947
948 return TRUE;
949 }
950
951 static gboolean
d3d_get_render_coordinates(GstD3DVideoSink * sink,gint in_x,gint in_y,gdouble * out_x,gdouble * out_y)952 d3d_get_render_coordinates (GstD3DVideoSink * sink, gint in_x, gint in_y,
953 gdouble * out_x, gdouble * out_y)
954 {
955 GstVideoRectangle r_area;
956 gdouble tmp;
957 gboolean ret = FALSE;
958
959 g_return_val_if_fail (out_x != NULL, FALSE);
960 g_return_val_if_fail (out_y != NULL, FALSE);
961 g_return_val_if_fail (GST_IS_D3DVIDEOSINK (sink), FALSE);
962
963 LOCK_SINK (sink);
964 CHECK_WINDOW_HANDLE (sink, end, FALSE);
965
966 /* Get renderable area of the window */
967 if (sink->d3d.render_rect) {
968 memcpy (&r_area, sink->d3d.render_rect, sizeof (r_area));
969 } else {
970 memset (&r_area, 0, sizeof (r_area));
971 d3d_get_hwnd_window_size (sink->d3d.window_handle, &r_area.w, &r_area.h);
972 }
973
974 /* If window coords outside render area.. return */
975 if (in_x < r_area.x || in_x > r_area.x + r_area.w ||
976 in_y < r_area.y || in_y > r_area.y + r_area.h)
977 goto end;
978
979 /* Convert window coordinates to source frame pixel coordinates */
980 if (sink->force_aspect_ratio) {
981 GstVideoRectangle tmp = { 0, 0, 0, 0 };
982 GstVideoRectangle dst = { 0, 0, 0, 0 };
983
984 tmp.w = GST_VIDEO_SINK_WIDTH (sink);
985 tmp.h = GST_VIDEO_SINK_HEIGHT (sink);
986 gst_video_sink_center_rect (tmp, r_area, &dst, TRUE);
987
988 r_area.x = r_area.x + dst.x;
989 r_area.y = r_area.y + dst.y;
990 r_area.w = dst.w;
991 r_area.h = dst.h;
992
993 /* If window coords outside render area.. return */
994 if (in_x < r_area.x || in_x > (r_area.x + r_area.w) ||
995 in_y < r_area.y || in_y > (r_area.y + r_area.h))
996 goto end;
997 }
998
999 tmp = in_x - r_area.x;
1000 if (r_area.w == GST_VIDEO_SINK_WIDTH (sink))
1001 *out_x = tmp;
1002 else if (r_area.w > GST_VIDEO_SINK_WIDTH (sink))
1003 *out_x =
1004 ((gdouble) tmp / ((gdouble) r_area.w /
1005 (gdouble) GST_VIDEO_SINK_WIDTH (sink)));
1006 else
1007 *out_x =
1008 ((gdouble) GST_VIDEO_SINK_WIDTH (sink) / (gdouble) r_area.w) *
1009 (gdouble) tmp;
1010
1011 tmp = in_y - r_area.y;
1012 if (r_area.h == GST_VIDEO_SINK_HEIGHT (sink))
1013 *out_y = tmp;
1014 else if (r_area.h > GST_VIDEO_SINK_HEIGHT (sink))
1015 *out_y =
1016 ((gdouble) tmp / ((gdouble) r_area.h /
1017 (gdouble) GST_VIDEO_SINK_HEIGHT (sink)));
1018 else
1019 *out_y =
1020 ((gdouble) GST_VIDEO_SINK_HEIGHT (sink) / (gdouble) r_area.h) *
1021 (gdouble) tmp;
1022
1023 ret = TRUE;
1024 end:
1025 UNLOCK_SINK (sink);
1026 return ret;
1027 }
1028
1029 /* Windows for rendering (User Set or Internal) */
1030
1031 static void
d3d_window_wndproc_unset(GstD3DVideoSink * sink)1032 d3d_window_wndproc_unset (GstD3DVideoSink * sink)
1033 {
1034 WNDPROC cur_wnd_proc = NULL;
1035
1036 g_return_if_fail (GST_IS_D3DVIDEOSINK (sink));
1037 LOCK_SINK (sink);
1038
1039 GST_DEBUG_OBJECT (sink, " ");
1040
1041 if (sink->d3d.window_handle == NULL) {
1042 GST_WARNING_OBJECT (sink, "D3D window_handle is NULL");
1043 goto end;
1044 }
1045
1046 cur_wnd_proc =
1047 (WNDPROC) GetWindowLongPtr (sink->d3d.window_handle, GWLP_WNDPROC);
1048
1049 if (cur_wnd_proc != d3d_wnd_proc) {
1050 GST_WARNING_OBJECT (sink, "D3D window proc is not set on current window");
1051 goto end;
1052 }
1053
1054 if (sink->d3d.orig_wnd_proc == NULL) {
1055 GST_WARNING_OBJECT (sink, "D3D orig window proc is NULL, can not restore");
1056 goto end;
1057 }
1058
1059 /* Restore original WndProc for window_handle */
1060 if (!SetWindowLongPtr (sink->d3d.window_handle, GWLP_WNDPROC,
1061 (LONG_PTR) sink->d3d.orig_wnd_proc)) {
1062 GST_WARNING_OBJECT (sink, "D3D failed to set original WndProc");
1063 goto end;
1064 }
1065
1066 end:
1067 sink->d3d.orig_wnd_proc = NULL;
1068 sink->d3d.window_handle = NULL;
1069
1070 UNLOCK_SINK (sink);
1071 }
1072
1073 static gboolean
d3d_window_wndproc_set(GstD3DVideoSink * sink)1074 d3d_window_wndproc_set (GstD3DVideoSink * sink)
1075 {
1076 WNDPROC cur_wnd_proc;
1077 gboolean ret = FALSE;
1078
1079 g_return_val_if_fail (GST_IS_D3DVIDEOSINK (sink), FALSE);
1080 LOCK_SINK (sink);
1081
1082 cur_wnd_proc =
1083 (WNDPROC) GetWindowLongPtr (sink->d3d.window_handle, GWLP_WNDPROC);
1084
1085 if (cur_wnd_proc != NULL && cur_wnd_proc == d3d_wnd_proc) {
1086 GST_DEBUG_OBJECT (sink,
1087 "D3D window proc func is already set on the current window");
1088 ret = TRUE;
1089 goto end;
1090 }
1091
1092 /* Store the original window proc function */
1093 sink->d3d.orig_wnd_proc =
1094 (WNDPROC) SetWindowLongPtr (sink->d3d.window_handle, GWLP_WNDPROC,
1095 (LONG_PTR) d3d_wnd_proc);
1096
1097 /* Note: If the window belongs to another process this will fail */
1098 if (sink->d3d.orig_wnd_proc == NULL) {
1099 GST_ERROR_OBJECT (sink,
1100 "Failed to set WndProc function on window. Error: %d",
1101 (gint) GetLastError ());
1102 goto end;
1103 }
1104
1105 /* Make sink accessible to d3d_wnd_proc */
1106 SetProp (sink->d3d.window_handle, TEXT ("GstD3DVideoSink"), sink);
1107
1108 ret = TRUE;
1109
1110 end:
1111 UNLOCK_SINK (sink);
1112 return ret;
1113 }
1114
1115 static void
d3d_prepare_render_window(GstD3DVideoSink * sink)1116 d3d_prepare_render_window (GstD3DVideoSink * sink)
1117 {
1118 g_return_if_fail (GST_IS_D3DVIDEOSINK (sink));
1119 LOCK_SINK (sink);
1120
1121 if (sink->d3d.window_handle == NULL) {
1122 GST_DEBUG_OBJECT (sink, "No window handle has been set.");
1123 goto end;
1124 }
1125
1126 if (sink->d3d.device_lost) {
1127 GST_DEBUG_OBJECT (sink, "Device is lost, waiting for reset.");
1128 goto end;
1129 }
1130
1131 if (d3d_init_swap_chain (sink, sink->d3d.window_handle)) {
1132 d3d_window_wndproc_set (sink);
1133 sink->d3d.renderable = TRUE;
1134 GST_DEBUG_OBJECT (sink, "Prepared window for render [HWND:%p]",
1135 sink->d3d.window_handle);
1136 } else {
1137 GST_ERROR_OBJECT (sink, "Failed preparing window for render [HWND:%p]",
1138 sink->d3d.window_handle);
1139 }
1140
1141 end:
1142 UNLOCK_SINK (sink);
1143
1144 }
1145
1146 void
d3d_set_window_handle(GstD3DVideoSink * sink,guintptr window_id,gboolean is_internal)1147 d3d_set_window_handle (GstD3DVideoSink * sink, guintptr window_id,
1148 gboolean is_internal)
1149 {
1150 g_return_if_fail (sink != NULL);
1151 LOCK_SINK (sink);
1152
1153 if (sink->d3d.window_handle == (HWND) window_id) {
1154 if (window_id)
1155 GST_WARNING_OBJECT (sink,
1156 "Window HWND already set to: %" G_GUINTPTR_FORMAT, window_id);
1157 goto end;
1158 }
1159
1160 /* Unset current window */
1161 if (sink->d3d.window_handle != NULL) {
1162 PostMessage (sink->d3d.window_handle, WM_QUIT_THREAD, 0, 0);
1163 GST_DEBUG_OBJECT (sink, "Unsetting window [HWND:%p]",
1164 sink->d3d.window_handle);
1165 d3d_window_wndproc_unset (sink);
1166 d3d_release_swap_chain (sink);
1167 sink->d3d.window_handle = NULL;
1168 sink->d3d.window_is_internal = FALSE;
1169 sink->d3d.renderable = FALSE;
1170 }
1171
1172 /* Set new one */
1173 if (window_id) {
1174 sink->d3d.window_handle = (HWND) window_id;
1175 sink->d3d.window_is_internal = is_internal;
1176 if (!is_internal)
1177 sink->d3d.external_window_handle = sink->d3d.window_handle;
1178 /* If caps have been set.. prepare window */
1179 if (sink->format != 0)
1180 d3d_prepare_render_window (sink);
1181 }
1182
1183 end:
1184 UNLOCK_SINK (sink);
1185 }
1186
1187 void
d3d_set_render_rectangle(GstD3DVideoSink * sink)1188 d3d_set_render_rectangle (GstD3DVideoSink * sink)
1189 {
1190 g_return_if_fail (sink != NULL);
1191 LOCK_SINK (sink);
1192 /* Setting the pointer lets us know render rect is set */
1193 sink->d3d.render_rect = &sink->render_rect;
1194 d3d_resize_swap_chain (sink);
1195 d3d_present_swap_chain (sink);
1196 UNLOCK_SINK (sink);
1197 }
1198
1199 void
d3d_expose_window(GstD3DVideoSink * sink)1200 d3d_expose_window (GstD3DVideoSink * sink)
1201 {
1202 GST_DEBUG_OBJECT (sink, "EXPOSE");
1203 d3d_present_swap_chain (sink);
1204 }
1205
1206 gboolean
d3d_prepare_window(GstD3DVideoSink * sink)1207 d3d_prepare_window (GstD3DVideoSink * sink)
1208 {
1209 HWND hWnd;
1210 gboolean ret = FALSE;
1211
1212 g_return_val_if_fail (GST_IS_D3DVIDEOSINK (sink), FALSE);
1213 LOCK_SINK (sink);
1214
1215 /* if we already had an external window, then use it again */
1216 if (sink->d3d.external_window_handle)
1217 sink->d3d.window_handle = sink->d3d.external_window_handle;
1218
1219 /* Give the app a last chance to set a window id */
1220 if (!sink->d3d.window_handle)
1221 gst_video_overlay_prepare_window_handle (GST_VIDEO_OVERLAY (sink));
1222
1223 /* If the user did not set a window id .. check if we should create one */
1224 if (!sink->d3d.window_handle) {
1225 if (sink->create_internal_window) {
1226 if ((hWnd = d3d_create_internal_window (sink))) {
1227 GST_DEBUG_OBJECT (sink,
1228 "No window id was set.. creating internal window");
1229 d3d_set_window_handle (sink, (guintptr) hWnd, TRUE);
1230 } else {
1231 GST_ERROR_OBJECT (sink, "Failed to create internal window");
1232 goto end;
1233 }
1234 } else {
1235 GST_DEBUG_OBJECT (sink, "No window id is set..");
1236 goto end;
1237 }
1238 } else {
1239 d3d_prepare_render_window (sink);
1240 }
1241
1242 ret = TRUE;
1243
1244 end:
1245 UNLOCK_SINK (sink);
1246
1247 return ret;
1248 }
1249
1250 gboolean
d3d_stop(GstD3DVideoSink * sink)1251 d3d_stop (GstD3DVideoSink * sink)
1252 {
1253 if (sink->pool)
1254 gst_buffer_pool_set_active (sink->pool, FALSE);
1255 if (sink->fallback_pool)
1256 gst_buffer_pool_set_active (sink->fallback_pool, FALSE);
1257 gst_object_replace ((GstObject **) & sink->pool, NULL);
1258 gst_object_replace ((GstObject **) & sink->fallback_pool, NULL);
1259 gst_buffer_replace (&sink->fallback_buffer, NULL);
1260
1261 /* Release D3D resources */
1262 d3d_set_window_handle (sink, 0, FALSE);
1263
1264 if (sink->internal_window_thread) {
1265 g_thread_join (sink->internal_window_thread);
1266 sink->internal_window_thread = NULL;
1267 }
1268
1269 return TRUE;
1270 }
1271
1272 /* D3D Lost and Reset Device */
1273
1274 static void
d3d_notify_device_lost(GstD3DVideoSink * sink)1275 d3d_notify_device_lost (GstD3DVideoSink * sink)
1276 {
1277 gboolean notify = FALSE;
1278
1279 g_return_if_fail (GST_IS_D3DVIDEOSINK (sink));
1280
1281 LOCK_SINK (sink);
1282
1283 if (!sink->d3d.device_lost) {
1284 GST_WARNING_OBJECT (sink, "D3D Device has been lost. Clean up resources.");
1285
1286 /* Stream will continue with GST_FLOW_OK, until device has been reset */
1287 sink->d3d.device_lost = TRUE;
1288
1289 /* First we clean up all resources in this d3dvideo instance */
1290 d3d_release_swap_chain (sink);
1291
1292 /* Notify our hidden thread */
1293 notify = TRUE;
1294 }
1295
1296 UNLOCK_SINK (sink);
1297
1298 if (notify)
1299 d3d_class_notify_device_lost (sink);
1300 }
1301
1302 static void
d3d_notify_device_reset(GstD3DVideoSink * sink)1303 d3d_notify_device_reset (GstD3DVideoSink * sink)
1304 {
1305 g_return_if_fail (GST_IS_D3DVIDEOSINK (sink));
1306 LOCK_SINK (sink);
1307
1308 if (sink->d3d.device_lost) {
1309 GST_DEBUG_OBJECT (sink,
1310 "D3D Device has been reset. Re-init swap chain if still streaming");
1311 /* If we're still streaming.. reset swap chain */
1312 if (sink->d3d.window_handle != NULL)
1313 d3d_init_swap_chain (sink, sink->d3d.window_handle);
1314 sink->d3d.device_lost = FALSE;
1315 }
1316
1317 UNLOCK_SINK (sink);
1318 }
1319
1320 /* Swap Chains */
1321
1322 static gboolean
d3d_init_swap_chain(GstD3DVideoSink * sink,HWND hWnd)1323 d3d_init_swap_chain (GstD3DVideoSink * sink, HWND hWnd)
1324 {
1325 D3DPRESENT_PARAMETERS present_params;
1326 LPDIRECT3DSWAPCHAIN9 d3d_swapchain = NULL;
1327 D3DTEXTUREFILTERTYPE d3d_filtertype;
1328 HRESULT hr;
1329 GstD3DVideoSinkClass *klass;
1330 gboolean ret = FALSE;
1331
1332 g_return_val_if_fail (sink != NULL, FALSE);
1333 klass = GST_D3DVIDEOSINK_GET_CLASS (sink);
1334 g_return_val_if_fail (klass != NULL, FALSE);
1335
1336 LOCK_SINK (sink);
1337 LOCK_CLASS (sink, klass);
1338 CHECK_REF_COUNT (klass, sink, error);
1339
1340 /* We need a display device */
1341 CHECK_D3D_DEVICE (klass, sink, error);
1342
1343 GST_DEBUG ("Initializing Direct3D swap chain");
1344
1345 GST_DEBUG ("Direct3D back buffer size: %dx%d", GST_VIDEO_SINK_WIDTH (sink),
1346 GST_VIDEO_SINK_HEIGHT (sink));
1347
1348 /* When windowed, width and height determined by HWND */
1349 ZeroMemory (&present_params, sizeof (present_params));
1350 present_params.Windowed = TRUE;
1351 present_params.SwapEffect = D3DSWAPEFFECT_DISCARD; /* D3DSWAPEFFECT_COPY */
1352 present_params.hDeviceWindow = hWnd;
1353 present_params.BackBufferFormat = klass->d3d.device.format;
1354
1355 hr = IDirect3DDevice9_CreateAdditionalSwapChain (klass->d3d.device.d3d_device,
1356 &present_params, &d3d_swapchain);
1357 ERROR_CHECK_HR (hr) {
1358 CASE_HR_ERR (D3DERR_NOTAVAILABLE);
1359 CASE_HR_ERR (D3DERR_DEVICELOST);
1360 CASE_HR_ERR (D3DERR_INVALIDCALL);
1361 CASE_HR_ERR (D3DERR_OUTOFVIDEOMEMORY);
1362 CASE_HR_ERR (E_OUTOFMEMORY);
1363 CASE_HR_ERR_END (sink, "Error creating D3D swapchian");
1364 goto error;
1365 }
1366
1367 /* Determine texture filtering support. If it's supported for this format,
1368 * use the filter type determined when we created the dev and checked the
1369 * dev caps.
1370 */
1371 hr = IDirect3D9_CheckDeviceFormat (klass->d3d.d3d,
1372 klass->d3d.device.adapter,
1373 D3DDEVTYPE_HAL,
1374 klass->d3d.device.format,
1375 D3DUSAGE_QUERY_FILTER, D3DRTYPE_TEXTURE, sink->d3d.format);
1376 if (hr == D3D_OK)
1377 d3d_filtertype = klass->d3d.device.filter_type;
1378 else
1379 d3d_filtertype = D3DTEXF_NONE;
1380
1381 GST_DEBUG ("Direct3D stretch rect texture filter: %d", d3d_filtertype);
1382
1383 sink->d3d.filtertype = d3d_filtertype;
1384
1385 if (sink->d3d.swapchain != NULL)
1386 IDirect3DSwapChain9_Release (sink->d3d.swapchain);
1387
1388 sink->d3d.swapchain = d3d_swapchain;
1389
1390 ret = TRUE;
1391
1392 error:
1393 if (!ret) {
1394 if (d3d_swapchain)
1395 IDirect3DSwapChain9_Release (d3d_swapchain);
1396 }
1397
1398 UNLOCK_CLASS (sink, klass);
1399 UNLOCK_SINK (sink);
1400
1401 return ret;
1402 }
1403
1404 static gboolean
d3d_release_swap_chain(GstD3DVideoSink * sink)1405 d3d_release_swap_chain (GstD3DVideoSink * sink)
1406 {
1407 int ref_count;
1408 gboolean ret = FALSE;
1409
1410 g_return_val_if_fail (GST_IS_D3DVIDEOSINK (sink), FALSE);
1411 LOCK_SINK (sink);
1412
1413 GST_DEBUG_OBJECT (sink, "Releasing Direct3D swap chain");
1414
1415
1416 if (!sink->d3d.swapchain) {
1417 ret = TRUE;
1418 goto end;
1419 }
1420
1421 gst_buffer_replace (&sink->fallback_buffer, NULL);
1422 if (sink->fallback_pool)
1423 gst_buffer_pool_set_active (sink->fallback_pool, FALSE);
1424
1425 if (sink->d3d.swapchain) {
1426 ref_count = IDirect3DSwapChain9_Release (sink->d3d.swapchain);
1427 sink->d3d.swapchain = NULL;
1428 GST_DEBUG_OBJECT (sink, "D3D swapchain released. Ref count: %d", ref_count);
1429 }
1430
1431 if (sink->d3d.surface) {
1432 ref_count = IDirect3DSurface9_Release (sink->d3d.surface);
1433 sink->d3d.surface = NULL;
1434 GST_DEBUG_OBJECT (sink, "D3D surface released. Ref count: %d", ref_count);
1435 }
1436
1437 gst_d3d9_overlay_free (sink);
1438 ret = TRUE;
1439
1440 end:
1441 UNLOCK_SINK (sink);
1442
1443 return ret;
1444 }
1445
1446 static gboolean
d3d_resize_swap_chain(GstD3DVideoSink * sink)1447 d3d_resize_swap_chain (GstD3DVideoSink * sink)
1448 {
1449 GstD3DVideoSinkClass *klass;
1450 D3DPRESENT_PARAMETERS d3d_pp;
1451 LPDIRECT3DSWAPCHAIN9 swapchain = NULL;
1452 gint w = 0, h = 0, ref_count = 0;
1453 gboolean ret = FALSE;
1454 HRESULT hr;
1455 gboolean need_new = FALSE;
1456 int clip_ret;
1457 HDC handle_hdc;
1458 RECT clip_rectangle;
1459
1460 g_return_val_if_fail (sink != NULL, FALSE);
1461 klass = GST_D3DVIDEOSINK_GET_CLASS (sink);
1462 g_return_val_if_fail (klass != NULL, FALSE);
1463
1464 LOCK_SINK (sink);
1465
1466 if (!sink->d3d.renderable || sink->d3d.device_lost) {
1467 UNLOCK_SINK (sink);
1468 return FALSE;
1469 }
1470
1471 LOCK_CLASS (sink, klass);
1472
1473 CHECK_REF_COUNT (klass, sink, end);
1474 CHECK_WINDOW_HANDLE (sink, end, FALSE);
1475 CHECK_D3D_DEVICE (klass, sink, end);
1476 CHECK_D3D_SWAPCHAIN (sink, end);
1477
1478 handle_hdc = GetDC (sink->d3d.window_handle);
1479 clip_ret = GetClipBox (handle_hdc, &clip_rectangle);
1480 ReleaseDC (sink->d3d.window_handle, handle_hdc);
1481 if (clip_ret == NULLREGION) {
1482 GST_DEBUG_OBJECT (sink, "Window is hidden, not resizing swapchain");
1483 UNLOCK_CLASS (sink, klass);
1484 UNLOCK_SINK (sink);
1485 return TRUE;
1486 }
1487
1488 d3d_get_hwnd_window_size (sink->d3d.window_handle, &w, &h);
1489 ZeroMemory (&d3d_pp, sizeof (d3d_pp));
1490
1491 /* Get the parameters used to create this swap chain */
1492 hr = IDirect3DSwapChain9_GetPresentParameters (sink->d3d.swapchain, &d3d_pp);
1493 if (hr != D3D_OK) {
1494 GST_ERROR_OBJECT (sink,
1495 "Unable to determine Direct3D present parameters for swap chain");
1496 goto end;
1497 }
1498
1499 /* Reisze needed? */
1500 if (d3d_pp.BackBufferWidth != w || d3d_pp.BackBufferHeight != h)
1501 need_new = TRUE;
1502 #if 0
1503 /* Render rect set or unset? */
1504 if ((d3d_pp.SwapEffect != D3DSWAPEFFECT_COPY && sink->d3d.render_rect) ||
1505 (d3d_pp.SwapEffect != D3DSWAPEFFECT_DISCARD
1506 && sink->d3d.render_rect == NULL)) {
1507 d3d_pp.SwapEffect =
1508 (sink->d3d.render_rect ==
1509 NULL) ? D3DSWAPEFFECT_DISCARD : D3DSWAPEFFECT_COPY;
1510 GST_DEBUG_OBJECT (sink, "Setting SwapEffect: %s",
1511 sink->d3d.render_rect ? "COPY" : "DISCARD");
1512 need_new = TRUE;
1513 }
1514 #endif
1515 if (!need_new) {
1516 ret = TRUE;
1517 goto end;
1518 }
1519
1520 GST_DEBUG_OBJECT (sink, "Resizing swapchain %dx%d to %dx%d",
1521 d3d_pp.BackBufferWidth, d3d_pp.BackBufferHeight, w, h);
1522
1523
1524 /* As long as present params windowed == TRUE, width or height
1525 * of 0 will force use of HWND's size.
1526 */
1527 d3d_pp.BackBufferWidth = 0;
1528 d3d_pp.BackBufferHeight = 0;
1529
1530 /* Release current swapchain */
1531 if (sink->d3d.swapchain != NULL) {
1532 ref_count = IDirect3DSwapChain9_Release (sink->d3d.swapchain);
1533 if (ref_count > 0) {
1534 GST_WARNING_OBJECT (sink, "Release swapchain refcount: %d", ref_count);
1535 }
1536 sink->d3d.swapchain = NULL;
1537 }
1538
1539 hr = IDirect3DDevice9_CreateAdditionalSwapChain (klass->d3d.device.d3d_device,
1540 &d3d_pp, &swapchain);
1541 ERROR_CHECK_HR (hr) {
1542 CASE_HR_ERR (D3DERR_NOTAVAILABLE);
1543 CASE_HR_ERR (D3DERR_DEVICELOST);
1544 CASE_HR_ERR (D3DERR_INVALIDCALL);
1545 CASE_HR_ERR (D3DERR_OUTOFVIDEOMEMORY);
1546 CASE_HR_ERR (E_OUTOFMEMORY);
1547 CASE_HR_ERR_END (sink, "Error creating swapchian");
1548 goto end;
1549 }
1550
1551 sink->d3d.swapchain = swapchain;
1552 sink->d3d.overlay_needs_resize = TRUE;
1553 ret = TRUE;
1554
1555 end:
1556 UNLOCK_CLASS (sink, klass);
1557 UNLOCK_SINK (sink);
1558
1559 return ret;
1560 }
1561
1562 static gboolean
d3d_copy_buffer(GstD3DVideoSink * sink,GstBuffer * from,GstBuffer * to)1563 d3d_copy_buffer (GstD3DVideoSink * sink, GstBuffer * from, GstBuffer * to)
1564 {
1565 gboolean ret = FALSE;
1566 GstVideoFrame from_frame, to_frame;
1567
1568 memset (&from_frame, 0, sizeof (from_frame));
1569 memset (&to_frame, 0, sizeof (to_frame));
1570
1571 g_return_val_if_fail (GST_IS_D3DVIDEOSINK (sink), FALSE);
1572 LOCK_SINK (sink);
1573
1574 if (!sink->d3d.renderable || sink->d3d.device_lost)
1575 goto end;
1576
1577 if (!gst_video_frame_map (&from_frame, &sink->info, from, GST_MAP_READ) ||
1578 !gst_video_frame_map (&to_frame, &sink->info, to, GST_MAP_WRITE)) {
1579 GST_ERROR_OBJECT (sink, "NULL GstBuffer");
1580 goto end;
1581 }
1582
1583 switch (sink->format) {
1584 case GST_VIDEO_FORMAT_YUY2:
1585 case GST_VIDEO_FORMAT_UYVY:{
1586 const guint8 *src;
1587 guint8 *dst;
1588 gint dststride, srcstride;
1589 gint i, h, w;
1590
1591 src = GST_VIDEO_FRAME_PLANE_DATA (&from_frame, 0);
1592 dst = GST_VIDEO_FRAME_PLANE_DATA (&to_frame, 0);
1593 srcstride = GST_VIDEO_FRAME_PLANE_STRIDE (&from_frame, 0);
1594 dststride = GST_VIDEO_FRAME_PLANE_STRIDE (&to_frame, 0);
1595 h = GST_VIDEO_FRAME_HEIGHT (&from_frame);
1596 w = GST_ROUND_UP_4 (GST_VIDEO_FRAME_WIDTH (&from_frame) * 2);
1597
1598 for (i = 0; i < h; i++) {
1599 memcpy (dst, src, w);
1600 dst += dststride;
1601 src += srcstride;
1602 }
1603
1604 break;
1605 }
1606 case GST_VIDEO_FORMAT_I420:
1607 case GST_VIDEO_FORMAT_YV12:{
1608 const guint8 *src;
1609 guint8 *dst;
1610 gint srcstride, dststride;
1611 gint i, j, h_, w_;
1612
1613 for (i = 0; i < 3; i++) {
1614 src = GST_VIDEO_FRAME_COMP_DATA (&from_frame, i);
1615 dst = GST_VIDEO_FRAME_COMP_DATA (&to_frame, i);
1616 srcstride = GST_VIDEO_FRAME_COMP_STRIDE (&from_frame, i);
1617 dststride = GST_VIDEO_FRAME_COMP_STRIDE (&to_frame, i);
1618 h_ = GST_VIDEO_FRAME_COMP_HEIGHT (&from_frame, i);
1619 w_ = GST_VIDEO_FRAME_COMP_WIDTH (&from_frame, i);
1620
1621 for (j = 0; j < h_; j++) {
1622 memcpy (dst, src, w_);
1623 dst += dststride;
1624 src += srcstride;
1625 }
1626 }
1627
1628 break;
1629 }
1630 case GST_VIDEO_FORMAT_NV12:{
1631 const guint8 *src;
1632 guint8 *dst;
1633 gint srcstride, dststride;
1634 gint i, j, h_, w_;
1635
1636 for (i = 0; i < 2; i++) {
1637 src = GST_VIDEO_FRAME_PLANE_DATA (&from_frame, i);
1638 dst = GST_VIDEO_FRAME_PLANE_DATA (&to_frame, i);
1639 srcstride = GST_VIDEO_FRAME_PLANE_STRIDE (&from_frame, i);
1640 dststride = GST_VIDEO_FRAME_PLANE_STRIDE (&to_frame, i);
1641 h_ = GST_VIDEO_FRAME_COMP_HEIGHT (&from_frame, i);
1642 w_ = GST_VIDEO_FRAME_COMP_WIDTH (&from_frame, i);
1643
1644 for (j = 0; j < h_; j++) {
1645 memcpy (dst, src, w_ * 2);
1646 dst += dststride;
1647 src += srcstride;
1648 }
1649 }
1650
1651 break;
1652 }
1653 case GST_VIDEO_FORMAT_BGRA:
1654 case GST_VIDEO_FORMAT_RGBA:
1655 case GST_VIDEO_FORMAT_BGRx:
1656 case GST_VIDEO_FORMAT_RGBx:{
1657 const guint8 *src;
1658 guint8 *dst;
1659 gint srcstride, dststride;
1660 gint i, h, w;
1661
1662 src = GST_VIDEO_FRAME_PLANE_DATA (&from_frame, 0);
1663 dst = GST_VIDEO_FRAME_PLANE_DATA (&to_frame, 0);
1664 srcstride = GST_VIDEO_FRAME_PLANE_STRIDE (&from_frame, 0);
1665 dststride = GST_VIDEO_FRAME_PLANE_STRIDE (&to_frame, 0);
1666 h = GST_VIDEO_FRAME_HEIGHT (&from_frame);
1667 w = GST_VIDEO_FRAME_WIDTH (&from_frame) * 4;
1668
1669 for (i = 0; i < h; i++) {
1670 memcpy (dst, src, w);
1671 dst += dststride;
1672 src += srcstride;
1673 }
1674
1675 break;
1676 }
1677 case GST_VIDEO_FORMAT_BGR:{
1678 const guint8 *src;
1679 guint8 *dst;
1680 gint srcstride, dststride;
1681 gint i, h, w;
1682
1683 src = GST_VIDEO_FRAME_PLANE_DATA (&from_frame, 0);
1684 dst = GST_VIDEO_FRAME_PLANE_DATA (&to_frame, 0);
1685 srcstride = GST_VIDEO_FRAME_PLANE_STRIDE (&from_frame, 0);
1686 dststride = GST_VIDEO_FRAME_PLANE_STRIDE (&to_frame, 0);
1687 h = GST_VIDEO_FRAME_HEIGHT (&from_frame);
1688 w = GST_VIDEO_FRAME_WIDTH (&from_frame) * 3;
1689
1690 for (i = 0; i < h; i++) {
1691 memcpy (dst, src, w);
1692 dst += dststride;
1693 src += srcstride;
1694 }
1695
1696 break;
1697 }
1698 case GST_VIDEO_FORMAT_RGB16:
1699 case GST_VIDEO_FORMAT_RGB15:{
1700 const guint8 *src;
1701 guint8 *dst;
1702 gint srcstride, dststride;
1703 gint i, h, w;
1704
1705 src = GST_VIDEO_FRAME_PLANE_DATA (&from_frame, 0);
1706 dst = GST_VIDEO_FRAME_PLANE_DATA (&to_frame, 0);
1707 srcstride = GST_VIDEO_FRAME_PLANE_STRIDE (&from_frame, 0);
1708 dststride = GST_VIDEO_FRAME_PLANE_STRIDE (&to_frame, 0);
1709 h = GST_VIDEO_FRAME_HEIGHT (&from_frame);
1710 w = GST_VIDEO_FRAME_WIDTH (&from_frame) * 2;
1711
1712 for (i = 0; i < h; i++) {
1713 memcpy (dst, src, w);
1714 dst += dststride;
1715 src += srcstride;
1716 }
1717
1718 break;
1719 }
1720 default:
1721 goto unhandled_format;
1722 }
1723
1724 ret = TRUE;
1725
1726 end:
1727 if (from_frame.buffer)
1728 gst_video_frame_unmap (&from_frame);
1729 if (to_frame.buffer)
1730 gst_video_frame_unmap (&to_frame);
1731
1732 UNLOCK_SINK (sink);
1733 return ret;
1734
1735 unhandled_format:
1736 GST_ERROR_OBJECT (sink,
1737 "Unhandled format '%s' -> '%s' (should not get here)",
1738 gst_video_format_to_string (sink->format),
1739 d3d_format_to_string (sink->d3d.format));
1740 ret = FALSE;
1741 goto end;
1742 }
1743
1744 static gboolean
d3d_present_swap_chain(GstD3DVideoSink * sink)1745 d3d_present_swap_chain (GstD3DVideoSink * sink)
1746 {
1747 GstD3DVideoSinkClass *klass = GST_D3DVIDEOSINK_GET_CLASS (sink);
1748 LPDIRECT3DSURFACE9 back_buffer = NULL;
1749 gboolean ret = FALSE;
1750 HRESULT hr;
1751 RECT dstr, srcr, *pDestRect = NULL, *pSrcRect = NULL;
1752
1753 g_return_val_if_fail (GST_IS_D3DVIDEOSINK (sink), FALSE);
1754 LOCK_SINK (sink);
1755
1756 if (!sink->d3d.renderable || sink->d3d.device_lost) {
1757 UNLOCK_SINK (sink);
1758 return FALSE;
1759 }
1760
1761 LOCK_CLASS (sink, klass);
1762
1763 CHECK_REF_COUNT (klass, sink, end);
1764 CHECK_WINDOW_HANDLE (sink, end, FALSE);
1765 CHECK_D3D_DEVICE (klass, sink, end);
1766 CHECK_D3D_SWAPCHAIN (sink, end);
1767
1768 /* Set the render target to our swap chain */
1769 hr = IDirect3DSwapChain9_GetBackBuffer (sink->d3d.swapchain, 0,
1770 D3DBACKBUFFER_TYPE_MONO, &back_buffer);
1771 ERROR_CHECK_HR (hr) {
1772 CASE_HR_ERR (D3DERR_INVALIDCALL);
1773 CASE_HR_ERR_END (sink, "IDirect3DSwapChain9_GetBackBuffer");
1774 goto end;
1775 }
1776 hr = IDirect3DDevice9_SetRenderTarget (klass->d3d.device.d3d_device, 0,
1777 back_buffer);
1778 ERROR_CHECK_HR (hr) {
1779 CASE_HR_ERR (D3DERR_INVALIDCALL);
1780 CASE_HR_ERR_END (sink, "IDirect3DDevice9_SetRenderTarget");
1781 goto end;
1782 }
1783 hr = IDirect3DSurface9_Release (back_buffer);
1784 ERROR_CHECK_HR (hr) {
1785 CASE_HR_ERR (D3DERR_INVALIDCALL);
1786 CASE_HR_ERR_END (sink, "IDirect3DSurface9_Release");
1787 goto end;
1788 }
1789
1790 /* Clear the target */
1791 hr = IDirect3DDevice9_Clear (klass->d3d.device.d3d_device, 0, NULL,
1792 D3DCLEAR_TARGET, D3DCOLOR_XRGB (0, 0, 0), 1.0f, 0);
1793 ERROR_CHECK_HR (hr) {
1794 CASE_HR_ERR (D3DERR_INVALIDCALL);
1795 CASE_HR_ERR_END (sink, "IDirect3DDevice9_Clear");
1796 goto end;
1797 }
1798
1799 hr = IDirect3DDevice9_BeginScene (klass->d3d.device.d3d_device);
1800 ERROR_CHECK_HR (hr) {
1801 CASE_HR_ERR (D3DERR_INVALIDCALL);
1802 CASE_HR_ERR_END (sink, "IDirect3DDevice9_BeginScene");
1803 goto end;
1804 }
1805
1806 if (!gst_d3d9_overlay_set_render_state (sink)) {
1807 IDirect3DDevice9_EndScene (klass->d3d.device.d3d_device);
1808 goto end;
1809 }
1810
1811 /* Stretch and blit ops, to copy offscreen surface buffer
1812 * to Display back buffer.
1813 */
1814 if (!d3d_stretch_and_copy (sink, back_buffer) ||
1815 !gst_d3d9_overlay_render (sink)) {
1816 IDirect3DDevice9_EndScene (klass->d3d.device.d3d_device);
1817 goto end;
1818 }
1819
1820 hr = IDirect3DDevice9_EndScene (klass->d3d.device.d3d_device);
1821 ERROR_CHECK_HR (hr) {
1822 CASE_HR_ERR (D3DERR_INVALIDCALL);
1823 CASE_HR_ERR_END (sink, "IDirect3DDevice9_EndScene");
1824 goto end;
1825 }
1826
1827 if (d3d_get_render_rects (sink->d3d.render_rect, &dstr, &srcr)) {
1828 pDestRect = &dstr;
1829 pSrcRect = &srcr;
1830 }
1831
1832 /*
1833 * Swap back and front buffers on video card and present to the user
1834 */
1835 hr = IDirect3DSwapChain9_Present (sink->d3d.swapchain, pSrcRect, pDestRect,
1836 NULL, NULL, 0);
1837 if (hr == D3DERR_DEVICELOST) {
1838 d3d_notify_device_lost (sink);
1839 ret = TRUE;
1840 goto end;
1841 }
1842 ERROR_CHECK_HR (hr) {
1843 CASE_HR_ERR (D3DERR_DEVICELOST);
1844 CASE_HR_ERR (D3DERR_DRIVERINTERNALERROR);
1845 CASE_HR_ERR (D3DERR_INVALIDCALL);
1846 CASE_HR_ERR (D3DERR_OUTOFVIDEOMEMORY);
1847 CASE_HR_ERR (E_OUTOFMEMORY);
1848 CASE_HR_DBG_END (sink, "IDirect3DSwapChain9_Present failure");
1849 goto end;
1850 }
1851
1852 ret = TRUE;
1853
1854 end:
1855 UNLOCK_SINK (sink);
1856 UNLOCK_CLASS (sink, klass);
1857 return ret;
1858 }
1859
1860 static gboolean
d3d_stretch_and_copy(GstD3DVideoSink * sink,LPDIRECT3DSURFACE9 back_buffer)1861 d3d_stretch_and_copy (GstD3DVideoSink * sink, LPDIRECT3DSURFACE9 back_buffer)
1862 {
1863 GstD3DVideoSinkClass *klass = GST_D3DVIDEOSINK_GET_CLASS (sink);
1864 GstVideoRectangle *render_rect = NULL;
1865 RECT r, s;
1866 RECT *r_p = NULL;
1867 HRESULT hr;
1868 gboolean ret = FALSE;
1869
1870 g_return_val_if_fail (GST_IS_D3DVIDEOSINK (sink), FALSE);
1871 LOCK_SINK (sink);
1872
1873 CHECK_WINDOW_HANDLE (sink, end, FALSE);
1874 CHECK_D3D_DEVICE (klass, sink, end);
1875 CHECK_D3D_SURFACE (sink, end);
1876
1877 render_rect = sink->d3d.render_rect;
1878
1879 if (sink->force_aspect_ratio) {
1880 gint window_width;
1881 gint window_height;
1882 GstVideoRectangle src;
1883 GstVideoRectangle dst;
1884 GstVideoRectangle result;
1885
1886 memset (&dst, 0, sizeof (dst));
1887 memset (&src, 0, sizeof (src));
1888
1889 /* Set via GstXOverlay set_render_rect */
1890 if (render_rect) {
1891 memcpy (&dst, render_rect, sizeof (dst));
1892 } else {
1893 d3d_get_hwnd_window_size (sink->d3d.window_handle, &window_width,
1894 &window_height);
1895 dst.w = window_width;
1896 dst.h = window_height;
1897 }
1898
1899 src.w = GST_VIDEO_SINK_WIDTH (sink);
1900 src.h = GST_VIDEO_SINK_HEIGHT (sink);
1901
1902 gst_video_sink_center_rect (src, dst, &result, TRUE);
1903
1904 r.left = result.x;
1905 r.top = result.y;
1906 r.right = result.x + result.w;
1907 r.bottom = result.y + result.h;
1908 r_p = &r;
1909 } else if (render_rect) {
1910 r.left = 0;
1911 r.top = 0;
1912 r.right = render_rect->w;
1913 r.bottom = render_rect->h;
1914 r_p = &r;
1915 }
1916
1917 s.left = sink->crop_rect.x;
1918 s.top = sink->crop_rect.y;
1919 s.right = sink->crop_rect.x + sink->crop_rect.w;
1920 s.bottom = sink->crop_rect.y + sink->crop_rect.h;
1921
1922 /* TODO: StretchRect returns error if the dest rect is outside
1923 * the backbuffer area. So we need to calc how much of the src
1924 * surface is being scaled / copied to the render rect..
1925 */
1926
1927 hr = IDirect3DDevice9_StretchRect (klass->d3d.device.d3d_device, sink->d3d.surface, /* Source Surface */
1928 &s, /* Source Surface Rect (NULL: Whole) */
1929 back_buffer, /* Dest Surface */
1930 r_p, /* Dest Surface Rect (NULL: Whole) */
1931 klass->d3d.device.filter_type);
1932
1933 if (hr == D3D_OK) {
1934 ret = TRUE;
1935 } else {
1936 GST_ERROR_OBJECT (sink, "Failure calling Direct3DDevice9_StretchRect");
1937 }
1938
1939 end:
1940 UNLOCK_SINK (sink);
1941
1942 return ret;
1943 }
1944
1945 GstFlowReturn
d3d_render_buffer(GstD3DVideoSink * sink,GstBuffer * buf)1946 d3d_render_buffer (GstD3DVideoSink * sink, GstBuffer * buf)
1947 {
1948 WindowHandleVisibility handle_visibility = WINDOW_VISIBILITY_ERROR;
1949 int clip_ret;
1950 HDC handle_hdc;
1951 RECT handle_rectangle;
1952 RECT clip_rectangle;
1953
1954 GstFlowReturn ret = GST_FLOW_OK;
1955 GstMemory *mem;
1956 LPDIRECT3DSURFACE9 surface = NULL;
1957 GstVideoCropMeta *crop = NULL;
1958
1959 g_return_val_if_fail (GST_IS_D3DVIDEOSINK (sink), GST_FLOW_ERROR);
1960 LOCK_SINK (sink);
1961
1962 if (!sink->d3d.window_handle) {
1963 if (sink->stream_stop_on_close) {
1964 /* Handle window deletion by posting an error on the bus */
1965 GST_ELEMENT_ERROR (sink, RESOURCE, NOT_FOUND,
1966 ("Output window was closed"), (NULL));
1967 ret = GST_FLOW_ERROR;
1968 }
1969 goto end;
1970 }
1971
1972 if (sink->d3d.device_lost) {
1973 GST_LOG_OBJECT (sink, "Device lost, waiting for reset..");
1974 goto end;
1975 }
1976
1977 /* check for window handle visibility, if hidden skip frame rendering */
1978
1979 handle_hdc = GetDC (sink->d3d.window_handle);
1980 GetClientRect (sink->d3d.window_handle, &handle_rectangle);
1981 clip_ret = GetClipBox (handle_hdc, &clip_rectangle);
1982 ReleaseDC (sink->d3d.window_handle, handle_hdc);
1983
1984 switch (clip_ret) {
1985 case NULLREGION:
1986 handle_visibility = WINDOW_VISIBILITY_HIDDEN;
1987 break;
1988 case SIMPLEREGION:
1989 if (EqualRect (&clip_rectangle, &handle_rectangle))
1990 handle_visibility = WINDOW_VISIBILITY_FULL;
1991 else
1992 handle_visibility = WINDOW_VISIBILITY_PARTIAL;
1993 break;
1994 case COMPLEXREGION:
1995 handle_visibility = WINDOW_VISIBILITY_PARTIAL;
1996 break;
1997 default:
1998 handle_visibility = WINDOW_VISIBILITY_ERROR;
1999 break;
2000 }
2001
2002 if (handle_visibility == WINDOW_VISIBILITY_HIDDEN) {
2003 GST_DEBUG_OBJECT (sink, "Hidden hwnd, skipping frame rendering...");
2004 goto end;
2005 }
2006
2007 GST_INFO_OBJECT (sink, "%s %" GST_TIME_FORMAT,
2008 (sink->d3d.window_handle != NULL) ? "Render" : "No Win",
2009 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
2010
2011 crop = gst_buffer_get_video_crop_meta (buf);
2012 if (crop) {
2013 sink->crop_rect.x = crop->x;
2014 sink->crop_rect.y = crop->y;
2015 sink->crop_rect.w = crop->width;
2016 sink->crop_rect.h = crop->height;
2017 } else {
2018 sink->crop_rect.x = 0;
2019 sink->crop_rect.y = 0;
2020 sink->crop_rect.w = sink->info.width;
2021 sink->crop_rect.h = sink->info.height;
2022 }
2023
2024 /* Resize swapchain if needed */
2025 if (!d3d_resize_swap_chain (sink)) {
2026 ret = GST_FLOW_ERROR;
2027 goto end;
2028 }
2029
2030 if (gst_buffer_n_memory (buf) != 1 ||
2031 (mem = gst_buffer_peek_memory (buf, 0)) == 0 ||
2032 !gst_memory_is_type (mem, GST_D3D_SURFACE_MEMORY_NAME)) {
2033 GstBuffer *tmp;
2034 GstBufferPoolAcquireParams params = { 0, };
2035
2036 if (!sink->fallback_pool
2037 || !gst_buffer_pool_set_active (sink->fallback_pool, TRUE)) {
2038 ret = GST_FLOW_NOT_NEGOTIATED;
2039 goto end;
2040 }
2041
2042 /* take a buffer from our pool, if there is no buffer in the pool something
2043 * is seriously wrong, waiting for the pool here might deadlock when we try
2044 * to go to PAUSED because we never flush the pool. */
2045 params.flags = GST_BUFFER_POOL_ACQUIRE_FLAG_DONTWAIT;
2046 ret = gst_buffer_pool_acquire_buffer (sink->fallback_pool, &tmp, ¶ms);
2047 if (ret != GST_FLOW_OK)
2048 goto end;
2049
2050 if (sink->fallback_buffer) {
2051 gst_buffer_unref (sink->fallback_buffer);
2052 sink->fallback_buffer = NULL;
2053 }
2054
2055 mem = gst_buffer_peek_memory (tmp, 0);
2056 if (!mem || !gst_memory_is_type (mem, GST_D3D_SURFACE_MEMORY_NAME)) {
2057 ret = GST_FLOW_ERROR;
2058 gst_buffer_unref (tmp);
2059 goto end;
2060 }
2061 d3d_copy_buffer (sink, buf, tmp);
2062 buf = tmp;
2063
2064 surface = ((GstD3DSurfaceMemory *) mem)->surface;
2065
2066 /* Need to keep an additional ref until the next buffer
2067 * to make sure it isn't reused until then */
2068 sink->fallback_buffer = buf;
2069 } else {
2070 mem = gst_buffer_peek_memory (buf, 0);
2071 surface = ((GstD3DSurfaceMemory *) mem)->surface;
2072
2073 if (sink->fallback_buffer) {
2074 gst_buffer_unref (sink->fallback_buffer);
2075 sink->fallback_buffer = NULL;
2076 }
2077 }
2078
2079 if (sink->d3d.surface)
2080 IDirect3DSurface9_Release (sink->d3d.surface);
2081 IDirect3DSurface9_AddRef (surface);
2082 sink->d3d.surface = surface;
2083
2084 if (!d3d_present_swap_chain (sink)) {
2085 ret = GST_FLOW_ERROR;
2086 goto end;
2087 }
2088
2089 end:
2090 UNLOCK_SINK (sink);
2091 return ret;
2092 }
2093
2094
2095 /* D3D Window Proc Functions */
2096
2097 static LRESULT APIENTRY
d3d_wnd_proc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)2098 d3d_wnd_proc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
2099 {
2100 GstD3DVideoSink *sink =
2101 (GstD3DVideoSink *) GetProp (hWnd, TEXT ("GstD3DVideoSink"));
2102 WNDPROC proc;
2103 LRESULT ret = 0;
2104
2105 /* d3dvideosink object might not available yet.
2106 * The thread for message queue starts earlier than SetProp... */
2107 if (!sink)
2108 return DefWindowProc (hWnd, message, wParam, lParam);
2109
2110 LOCK_SINK (sink);
2111 proc = sink->d3d.orig_wnd_proc;
2112 UNLOCK_SINK (sink);
2113
2114 switch (message) {
2115 case WM_ERASEBKGND:
2116 return TRUE;
2117 case WM_PAINT:{
2118 if (proc)
2119 ret = CallWindowProc (proc, hWnd, message, wParam, lParam);
2120 /* Call this afterwards to ensure that our paint happens last */
2121 d3d_present_swap_chain (sink);
2122 goto end;
2123 }
2124 case WM_SIZE:{
2125 if (proc)
2126 ret = CallWindowProc (proc, hWnd, message, wParam, lParam);
2127
2128 /* Don't resize if the window is being minimized. Recreating the
2129 * swap chain will fail if the window is minimized
2130 */
2131 if (wParam != SIZE_MINIMIZED)
2132 d3d_resize_swap_chain (sink);
2133 goto end;
2134 }
2135 case WM_KEYDOWN:
2136 case WM_KEYUP:
2137 if (sink->enable_navigation_events) {
2138 gunichar2 wcrep[128];
2139 if (GetKeyNameTextW (lParam, (LPWSTR) wcrep, 128)) {
2140 gchar *utfrep = g_utf16_to_utf8 (wcrep, 128, NULL, NULL, NULL);
2141 if (utfrep) {
2142 if (message == WM_KEYDOWN)
2143 gst_navigation_send_key_event (GST_NAVIGATION (sink), "key-press",
2144 utfrep);
2145 else if (message == WM_KEYUP)
2146 gst_navigation_send_key_event (GST_NAVIGATION (sink),
2147 "key-release", utfrep);
2148 g_free (utfrep);
2149 }
2150 }
2151 }
2152 break;
2153 case WM_LBUTTONDOWN:
2154 case WM_LBUTTONUP:
2155 case WM_RBUTTONDOWN:
2156 case WM_RBUTTONUP:
2157 case WM_MBUTTONDOWN:
2158 case WM_MBUTTONUP:
2159 case WM_MOUSEMOVE:{
2160 gdouble x = 0, y = 0;
2161 if (sink->enable_navigation_events
2162 && d3d_get_render_coordinates (sink, LOWORD (lParam), HIWORD (lParam),
2163 &x, &y)) {
2164 gint button;
2165 const gchar *action = NULL;
2166 switch (message) {
2167 case WM_MOUSEMOVE:
2168 button = 0;
2169 action = "mouse-move";
2170 break;
2171 case WM_LBUTTONDOWN:
2172 button = 1;
2173 action = "mouse-button-press";
2174 break;
2175 case WM_LBUTTONUP:
2176 button = 1;
2177 action = "mouse-button-release";
2178 break;
2179 case WM_RBUTTONDOWN:
2180 button = 2;
2181 action = "mouse-button-press";
2182 break;
2183 case WM_RBUTTONUP:
2184 button = 2;
2185 action = "mouse-button-release";
2186 break;
2187 case WM_MBUTTONDOWN:
2188 button = 3;
2189 action = "mouse-button-press";
2190 break;
2191 case WM_MBUTTONUP:
2192 button = 3;
2193 action = "mouse-button-release";
2194 break;
2195 default:
2196 break;
2197 }
2198 if (action) {
2199 /* GST_DEBUG_OBJECT(sink, "%s: %lfx%lf", action, x, y); */
2200 gst_navigation_send_mouse_event (GST_NAVIGATION (sink), action,
2201 button, x, y);
2202 }
2203 }
2204 break;
2205 }
2206 case WM_CLOSE:
2207 d3d_set_window_handle (sink, 0, FALSE);
2208 break;
2209 default:
2210 break;
2211 }
2212
2213 if (proc)
2214 ret = CallWindowProc (proc, hWnd, message, wParam, lParam);
2215 else
2216 ret = DefWindowProc (hWnd, message, wParam, lParam);
2217
2218 end:
2219 return ret;
2220 }
2221
2222 /* Internal Window */
2223
2224 static LRESULT APIENTRY
d3d_wnd_proc_internal(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)2225 d3d_wnd_proc_internal (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
2226 {
2227 switch (message) {
2228 case WM_DESTROY:
2229 GST_DEBUG ("Internal window: WM_DESTROY");
2230 /* Tell the internal window thread to shut down */
2231 PostQuitMessage (0);
2232 GST_DEBUG ("Posted quit..");
2233 break;
2234 }
2235
2236 return DefWindowProc (hWnd, message, wParam, lParam);
2237 }
2238
2239 static HWND
_d3d_create_internal_window(GstD3DVideoSink * sink)2240 _d3d_create_internal_window (GstD3DVideoSink * sink)
2241 {
2242 GstD3DVideoSinkClass *klass = GST_D3DVIDEOSINK_GET_CLASS (sink);
2243 int width, height;
2244 int offx, offy;
2245 DWORD exstyle, style;
2246 HWND video_window;
2247 RECT rect;
2248 int screenwidth;
2249 int screenheight;
2250
2251 /*
2252 * GST_VIDEO_SINK_WIDTH() is the aspect-ratio-corrected size of the video.
2253 * GetSystemMetrics() returns the width of the dialog's border (doubled
2254 * b/c of left and right borders).
2255 */
2256 width = GST_VIDEO_SINK_WIDTH (sink) + GetSystemMetrics (SM_CXSIZEFRAME) * 2;
2257 height =
2258 GST_VIDEO_SINK_HEIGHT (sink) + GetSystemMetrics (SM_CYCAPTION) +
2259 (GetSystemMetrics (SM_CYSIZEFRAME) * 2);
2260
2261 SystemParametersInfo (SPI_GETWORKAREA, 0, &rect, 0);
2262 screenwidth = rect.right - rect.left;
2263 screenheight = rect.bottom - rect.top;
2264 offx = rect.left;
2265 offy = rect.top;
2266
2267 /* Make it fit into the screen without changing the aspect ratio. */
2268 if (width > screenwidth) {
2269 double ratio = (double) screenwidth / (double) width;
2270 width = screenwidth;
2271 height = (int) (height * ratio);
2272 }
2273
2274 if (height > screenheight) {
2275 double ratio = (double) screenheight / (double) height;
2276 height = screenheight;
2277 width = (int) (width * ratio);
2278 }
2279
2280 style = WS_OVERLAPPEDWINDOW; /* Normal top-level window */
2281 exstyle = 0;
2282 video_window = CreateWindowEx (exstyle,
2283 klass->d3d.wnd_class.lpszClassName,
2284 TEXT ("GStreamer D3D video sink (internal window)"),
2285 style, offx, offy, width, height,
2286 NULL, NULL, klass->d3d.wnd_class.hInstance, sink);
2287
2288 if (video_window == NULL) {
2289 GST_ERROR_OBJECT (sink, "Failed to create internal window: %lu",
2290 GetLastError ());
2291 return NULL;
2292 }
2293
2294 /* Now show the window, as appropriate */
2295 ShowWindow (video_window, SW_SHOWNORMAL);
2296
2297 /* Trigger the initial paint of the window */
2298 UpdateWindow (video_window);
2299
2300 return video_window;
2301 }
2302
2303 typedef struct
2304 {
2305 GstD3DVideoSink *sink;
2306 gboolean error;
2307 HWND hWnd;
2308 GMutex lock;
2309 GCond cond;
2310 } D3DInternalWindowDat;
2311
2312 static gpointer
d3d_internal_window_thread(D3DInternalWindowDat * dat)2313 d3d_internal_window_thread (D3DInternalWindowDat * dat)
2314 {
2315 GstD3DVideoSink *sink;
2316 HWND hWnd;
2317 MSG msg;
2318
2319 g_return_val_if_fail (dat != NULL, NULL);
2320
2321 sink = dat->sink;
2322 GST_DEBUG_OBJECT (sink, "Entering internal window thread: %p",
2323 g_thread_self ());
2324
2325 /* Create internal window */
2326 hWnd = _d3d_create_internal_window (sink);
2327
2328 g_mutex_lock (&dat->lock);
2329 if (!hWnd) {
2330 GST_ERROR_OBJECT (sink, "Failed to create internal window");
2331 dat->error = TRUE;
2332 } else {
2333 dat->hWnd = hWnd;
2334 }
2335 g_cond_signal (&dat->cond);
2336 g_mutex_unlock (&dat->lock);
2337
2338 if (dat->error)
2339 goto end;
2340
2341 /*
2342 * Internal window message loop
2343 */
2344
2345 while (GetMessage (&msg, NULL, 0, 0)) {
2346 if (msg.message == WM_QUIT_THREAD)
2347 break;
2348 TranslateMessage (&msg);
2349 DispatchMessage (&msg);
2350 }
2351
2352 end:
2353 GST_DEBUG_OBJECT (sink, "Exiting internal window thread: %p",
2354 g_thread_self ());
2355 return NULL;
2356 }
2357
2358 static HWND
d3d_create_internal_window(GstD3DVideoSink * sink)2359 d3d_create_internal_window (GstD3DVideoSink * sink)
2360 {
2361 GThread *thread;
2362 D3DInternalWindowDat dat;
2363
2364 dat.sink = sink;
2365 dat.error = FALSE;
2366 dat.hWnd = 0;
2367 g_mutex_init (&dat.lock);
2368 g_cond_init (&dat.cond);
2369
2370 thread =
2371 g_thread_new ("d3dvideosink-window-thread",
2372 (GThreadFunc) d3d_internal_window_thread, &dat);
2373 if (!thread) {
2374 GST_ERROR ("Failed to created internal window thread");
2375 goto clear;
2376 }
2377
2378 sink->internal_window_thread = thread;
2379
2380 /* Wait for window proc loop to start up */
2381 g_mutex_lock (&dat.lock);
2382 while (!dat.error && !dat.hWnd) {
2383 g_cond_wait (&dat.cond, &dat.lock);
2384 }
2385 g_mutex_unlock (&dat.lock);
2386
2387 GST_DEBUG_OBJECT (sink, "Created window: %p (error: %d)",
2388 dat.hWnd, dat.error);
2389
2390 clear:
2391 {
2392 g_mutex_clear (&dat.lock);
2393 g_cond_clear (&dat.cond);
2394 }
2395
2396 return dat.hWnd;
2397 }
2398
2399 /* D3D Video Class Methods */
2400
2401 gboolean
d3d_class_init(GstD3DVideoSink * sink)2402 d3d_class_init (GstD3DVideoSink * sink)
2403 {
2404 GstD3DVideoSinkClass *klass = GST_D3DVIDEOSINK_GET_CLASS (sink);
2405 gboolean ret = FALSE;
2406 gboolean initialized_mutex = FALSE;
2407
2408 g_return_val_if_fail (klass != NULL, FALSE);
2409
2410 LOCK_CLASS (sink, klass);
2411
2412 klass->d3d.refs += 1;
2413 GST_DEBUG ("D3D class init [refs:%u]", klass->d3d.refs);
2414 klass->d3d.sink_list = g_list_append (klass->d3d.sink_list, sink);
2415
2416 if (klass->d3d.refs > 1)
2417 goto end;
2418
2419 WM_D3DVIDEO_NOTIFY_DEVICE_LOST =
2420 RegisterWindowMessage ("WM_D3DVIDEO_NOTIFY_DEVICE_LOST");
2421
2422 klass->d3d.d3d = Direct3DCreate9 (D3D_SDK_VERSION);
2423 if (!klass->d3d.d3d) {
2424 GST_ERROR ("Unable to create Direct3D interface");
2425 goto error;
2426 }
2427
2428 /* Register Window Class for internal Windows */
2429 memset (&klass->d3d.wnd_class, 0, sizeof (WNDCLASS));
2430 klass->d3d.wnd_class.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
2431 klass->d3d.wnd_class.hInstance = GetModuleHandle (NULL);
2432 klass->d3d.wnd_class.lpszClassName = TEXT ("GstD3DVideoSinkInternalWindow");
2433 klass->d3d.wnd_class.hbrBackground = (HBRUSH) GetStockObject (BLACK_BRUSH);
2434 klass->d3d.wnd_class.hCursor = LoadCursor (NULL, IDC_ARROW);
2435 klass->d3d.wnd_class.hIcon = LoadIcon (NULL, IDI_APPLICATION);
2436 klass->d3d.wnd_class.cbClsExtra = 0;
2437 klass->d3d.wnd_class.cbWndExtra = 0;
2438 klass->d3d.wnd_class.lpfnWndProc = d3d_wnd_proc_internal;
2439
2440 if (RegisterClass (&klass->d3d.wnd_class) == 0) {
2441 GST_ERROR ("Failed to register window class: %lu", GetLastError ());
2442 goto error;
2443 }
2444
2445 klass->d3d.thread_started = FALSE;
2446 klass->d3d.thread_error_exit = FALSE;
2447 /* TODO: Multi-monitor setup? */
2448 if (!d3d_class_display_device_create (klass, D3DADAPTER_DEFAULT)) {
2449 GST_ERROR ("Failed to initialize adapter: %u", D3DADAPTER_DEFAULT);
2450 goto error;
2451 }
2452
2453 g_mutex_init (&klass->d3d.thread_start_mutex);
2454 g_cond_init (&klass->d3d.thread_start_cond);
2455 initialized_mutex = TRUE;
2456
2457 klass->d3d.thread =
2458 g_thread_new ("d3dvideosink-window-thread",
2459 (GThreadFunc) d3d_hidden_window_thread, klass);
2460
2461 if (!klass->d3d.thread) {
2462 GST_ERROR ("Failed to created hidden window thread");
2463 goto error;
2464 }
2465
2466 g_mutex_lock (&klass->d3d.thread_start_mutex);
2467 while (!klass->d3d.thread_started && !klass->d3d.thread_error_exit)
2468 g_cond_wait (&klass->d3d.thread_start_cond, &klass->d3d.thread_start_mutex);
2469 g_mutex_unlock (&klass->d3d.thread_start_mutex);
2470
2471 if (klass->d3d.thread_error_exit)
2472 goto error;
2473
2474 GST_DEBUG ("Hidden window message loop is running..");
2475
2476 end:
2477 ret = TRUE;
2478 error:
2479
2480 if (!ret)
2481 d3d_class_destroy (sink);
2482 if (initialized_mutex) {
2483 g_mutex_clear (&klass->d3d.thread_start_mutex);
2484 g_cond_clear (&klass->d3d.thread_start_cond);
2485 }
2486 UNLOCK_CLASS (sink, klass);
2487
2488 return ret;
2489 }
2490
2491 void
d3d_class_destroy(GstD3DVideoSink * sink)2492 d3d_class_destroy (GstD3DVideoSink * sink)
2493 {
2494 GstD3DVideoSinkClass *klass = GST_D3DVIDEOSINK_GET_CLASS (sink);
2495
2496 g_return_if_fail (klass != NULL);
2497
2498 LOCK_CLASS (sink, klass);
2499
2500 klass->d3d.refs -= 1;
2501
2502 GST_DEBUG ("D3D class destroy [refs:%u]", klass->d3d.refs);
2503
2504 klass->d3d.sink_list = g_list_remove (klass->d3d.sink_list, sink);
2505
2506 if (klass->d3d.refs >= 1)
2507 goto end;
2508
2509 if (klass->d3d.thread) {
2510 GST_DEBUG ("Shutting down window proc thread, waiting to join..");
2511 PostMessage (klass->d3d.hidden_window, WM_QUIT, 0, 0);
2512 g_thread_join (klass->d3d.thread);
2513 GST_DEBUG ("Joined..");
2514 }
2515
2516 d3d_class_display_device_destroy (klass);
2517 if (klass->d3d.d3d) {
2518 int ref_count = IDirect3D9_Release (klass->d3d.d3d);
2519 GST_DEBUG ("Direct3D object released. Reference count: %d", ref_count);
2520 }
2521
2522 UnregisterClass (klass->d3d.wnd_class.lpszClassName,
2523 klass->d3d.wnd_class.hInstance);
2524
2525 memset (&klass->d3d, 0, sizeof (GstD3DDataClass));
2526
2527 end:
2528 UNLOCK_CLASS (sink, klass);
2529 }
2530
2531 static gboolean
d3d_class_display_device_create(GstD3DVideoSinkClass * klass,UINT adapter)2532 d3d_class_display_device_create (GstD3DVideoSinkClass * klass, UINT adapter)
2533 {
2534 LPDIRECT3D9 d3d;
2535 GstD3DDisplayDevice *device;
2536 HWND hwnd;
2537 D3DCAPS9 caps;
2538 D3DDISPLAYMODE disp_mode;
2539 DWORD create_mask = 0;
2540 HRESULT hr;
2541 guint i;
2542 gboolean ret = FALSE;
2543
2544 g_return_val_if_fail (klass != NULL, FALSE);
2545
2546 GST_DEBUG (" ");
2547
2548 LOCK_CLASS (NULL, klass);
2549
2550 d3d = klass->d3d.d3d;
2551 device = &klass->d3d.device;
2552 hwnd = klass->d3d.hidden_window;
2553 CHECK_REF_COUNT (klass, NULL, error);
2554
2555 memset (&caps, 0, sizeof (caps));
2556 memset (&disp_mode, 0, sizeof (disp_mode));
2557 memset (&device->present_params, 0, sizeof (device->present_params));
2558
2559 device->adapter = adapter;
2560
2561 if (IDirect3D9_GetAdapterDisplayMode (d3d, adapter, &disp_mode) != D3D_OK) {
2562 GST_ERROR ("Unable to request adapter[%u] display mode", adapter);
2563 goto error;
2564 }
2565
2566 if (IDirect3D9_GetDeviceCaps (d3d, adapter, D3DDEVTYPE_HAL, &caps) != D3D_OK) {
2567 GST_ERROR ("Unable to request adapter[%u] device caps", adapter);
2568 goto error;
2569 }
2570
2571 /* Ask DirectX to please not clobber the FPU state when making DirectX
2572 * API calls. This can cause libraries such as cairo to misbehave in
2573 * certain scenarios.
2574 */
2575 create_mask = 0 | D3DCREATE_FPU_PRESERVE;
2576
2577 /* Make sure that device access is threadsafe */
2578 create_mask |= D3DCREATE_MULTITHREADED;
2579
2580 /* Determine vertex processing capabilities. Some cards have issues
2581 * using software vertex processing. Courtesy:
2582 * http://www.chadvernon.com/blog/resources/directx9/improved-direct3d-initialization/
2583 */
2584 if ((caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) ==
2585 D3DDEVCAPS_HWTRANSFORMANDLIGHT) {
2586 create_mask |= D3DCREATE_HARDWARE_VERTEXPROCESSING;
2587 /* if ((d3dcaps.DevCaps & D3DDEVCAPS_PUREDEVICE) == D3DDEVCAPS_PUREDEVICE) */
2588 /* d3dcreate |= D3DCREATE_PUREDEVICE; */
2589 } else {
2590 create_mask |= D3DCREATE_SOFTWARE_VERTEXPROCESSING;
2591 }
2592
2593 /* Check the filter type. */
2594 if ((caps.StretchRectFilterCaps & D3DPTFILTERCAPS_MINFLINEAR) ==
2595 D3DPTFILTERCAPS_MINFLINEAR
2596 || (caps.StretchRectFilterCaps & D3DPTFILTERCAPS_MAGFLINEAR) ==
2597 D3DPTFILTERCAPS_MAGFLINEAR) {
2598 device->filter_type = D3DTEXF_LINEAR;
2599 } else {
2600 device->filter_type = D3DTEXF_NONE;
2601 }
2602
2603 /* Setup the display mode format. */
2604 device->format = disp_mode.Format;
2605
2606 /* present_params.Flags = D3DPRESENTFLAG_VIDEO; */
2607 device->present_params.Windowed = TRUE;
2608 device->present_params.SwapEffect = D3DSWAPEFFECT_DISCARD;
2609 device->present_params.BackBufferCount = 1;
2610 device->present_params.BackBufferFormat = device->format;
2611 device->present_params.BackBufferWidth = 1;
2612 device->present_params.BackBufferHeight = 1;
2613 device->present_params.MultiSampleType = D3DMULTISAMPLE_NONE;
2614 device->present_params.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT; /* D3DPRESENT_INTERVAL_IMMEDIATE; */
2615
2616 GST_DEBUG ("Creating Direct3D device for hidden window %p", hwnd);
2617
2618 if ((hr = IDirect3D9_CreateDevice (d3d, adapter, D3DDEVTYPE_HAL, hwnd,
2619 create_mask, &device->present_params,
2620 &device->d3d_device)) != D3D_OK) {
2621 GST_ERROR ("Unable to create Direct3D device. Result: %ld (0x%lx)", hr, hr);
2622 goto error;
2623 }
2624 /* cache d3d formats */
2625 for (i = 0; i < G_N_ELEMENTS (gst_d3d_format_map); i++) {
2626 D3DFormatComp *fmt;
2627 if (!gst_video_query_d3d_format (klass, gst_d3d_format_map[i].d3d_format))
2628 continue;
2629 fmt = g_slice_new0 (D3DFormatComp);
2630 fmt->fmt = gst_d3d_format_map[i].gst_format;
2631 fmt->d3d_fmt = gst_d3d_format_map[i].d3d_format;
2632 fmt->display = (fmt->d3d_fmt == klass->d3d.device.format);
2633 klass->d3d.supported_formats =
2634 g_list_insert_sorted (klass->d3d.supported_formats, fmt,
2635 d3d_format_comp_compare);
2636 }
2637
2638
2639 GST_DEBUG ("Display Device format: %s",
2640 d3d_format_to_string (disp_mode.Format));
2641
2642 ret = TRUE;
2643 goto end;
2644 error:
2645 memset (device, 0, sizeof (GstD3DDisplayDevice));
2646 end:
2647 UNLOCK_CLASS (NULL, klass);
2648
2649 return ret;
2650 }
2651
2652 static void
d3d_class_display_device_destroy(GstD3DVideoSinkClass * klass)2653 d3d_class_display_device_destroy (GstD3DVideoSinkClass * klass)
2654 {
2655 g_return_if_fail (klass != NULL);
2656
2657 LOCK_CLASS (NULL, klass);
2658 if (klass->d3d.device.d3d_device) {
2659 int ref_count;
2660 ref_count = IDirect3DDevice9_Release (klass->d3d.device.d3d_device);
2661 GST_DEBUG ("Direct3D device [adapter:%u] released. Reference count: %d",
2662 klass->d3d.device.adapter, ref_count);
2663 }
2664 g_list_free_full (klass->d3d.supported_formats,
2665 (GDestroyNotify) d3d_format_comp_free);
2666 memset (&klass->d3d.device, 0, sizeof (GstD3DDisplayDevice));
2667 UNLOCK_CLASS (NULL, klass);
2668 }
2669
2670 static void
d3d_class_notify_device_lost(GstD3DVideoSink * sink)2671 d3d_class_notify_device_lost (GstD3DVideoSink * sink)
2672 {
2673 GstD3DVideoSinkClass *klass = GST_D3DVIDEOSINK_GET_CLASS (sink);
2674 GstD3DVideoSinkEvent *evt = g_new0 (GstD3DVideoSinkEvent, 1);
2675
2676 evt->window_message_id = IDT_DEVICE_RESET_TIMER;
2677 evt->create_count = klass->create_count;
2678 gst_element_call_async (GST_ELEMENT (klass),
2679 (GstElementCallAsyncFunc) d3d_class_hidden_window_message_queue, evt,
2680 g_free);
2681 }
2682
2683 static void
d3d_class_notify_device_lost_all(GstD3DVideoSinkClass * klass)2684 d3d_class_notify_device_lost_all (GstD3DVideoSinkClass * klass)
2685 {
2686 g_return_if_fail (klass != NULL);
2687
2688 LOCK_CLASS (NULL, klass);
2689 CHECK_REF_COUNT (klass, NULL, end);
2690 if (!klass->d3d.device_lost) {
2691 GList *lst, *clst;
2692 klass->d3d.device_lost = TRUE;
2693
2694 GST_DEBUG ("Notifying all instances of device loss");
2695
2696 clst = g_list_copy (klass->d3d.sink_list);
2697
2698 for (lst = clst; lst != NULL; lst = lst->next) {
2699 GstD3DVideoSink *sink = (GstD3DVideoSink *) lst->data;
2700 if (!sink)
2701 continue;
2702 d3d_notify_device_lost (sink);
2703 }
2704 g_list_free (clst);
2705
2706 /* Set timer to try reset at given interval */
2707 SetTimer (klass->d3d.hidden_window, IDT_DEVICE_RESET_TIMER, 500, NULL);
2708 }
2709 end:
2710 UNLOCK_CLASS (NULL, klass);
2711 }
2712
2713 static void
d3d_class_reset_display_device(GstD3DVideoSinkClass * klass)2714 d3d_class_reset_display_device (GstD3DVideoSinkClass * klass)
2715 {
2716 HRESULT hr;
2717
2718 g_return_if_fail (klass != NULL);
2719
2720 LOCK_CLASS (NULL, klass);
2721 CHECK_REF_COUNT (klass, NULL, end);
2722 CHECK_D3D_DEVICE (klass, NULL, end)
2723 hr = IDirect3DDevice9_Reset (klass->d3d.device.d3d_device,
2724 &klass->d3d.device.present_params);
2725 ERROR_CHECK_HR (hr) {
2726 CASE_HR_ERR (D3DERR_DEVICELOST);
2727 CASE_HR_ERR (D3DERR_DEVICEREMOVED);
2728 CASE_HR_ERR (D3DERR_DRIVERINTERNALERROR);
2729 CASE_HR_ERR (D3DERR_OUTOFVIDEOMEMORY);
2730 CASE_HR_DBG_END (NULL, "Attempt device reset.. failed");
2731 goto end;
2732 }
2733
2734 GST_INFO ("Attempt device reset.. success");
2735
2736 klass->d3d.device_lost = FALSE;
2737 KillTimer (klass->d3d.hidden_window, IDT_DEVICE_RESET_TIMER);
2738
2739 g_list_foreach (klass->d3d.sink_list, (GFunc) d3d_notify_device_reset, NULL);
2740 end:
2741 UNLOCK_CLASS (NULL, klass);
2742 }
2743
2744 /* Hidden Window Loop Thread */
2745
2746 static void
d3d_class_hidden_window_message_queue(gpointer data,gpointer user_data)2747 d3d_class_hidden_window_message_queue (gpointer data, gpointer user_data)
2748 {
2749 guint id = 0;
2750 GstD3DVideoSinkClass *klass = (GstD3DVideoSinkClass *) data;
2751 GstD3DVideoSinkEvent *evt = (GstD3DVideoSinkEvent *) user_data;
2752
2753 if (!klass || !evt)
2754 return;
2755
2756 switch (evt->window_message_id) {
2757 case IDT_DEVICE_RESET_TIMER:
2758 LOCK_CLASS (NULL, klass);
2759 /* make sure this event does not originate from old class */
2760 if (evt->create_count == klass->create_count)
2761 d3d_class_reset_display_device (klass);
2762 UNLOCK_CLASS (NULL, klass);
2763 break;
2764 default:
2765 if (id == WM_D3DVIDEO_NOTIFY_DEVICE_LOST) {
2766 LOCK_CLASS (NULL, klass);
2767 /* make sure this event does not originate from old class */
2768 if (evt->create_count == klass->create_count)
2769 d3d_class_notify_device_lost_all (klass);
2770 UNLOCK_CLASS (NULL, klass);
2771 }
2772 break;
2773 }
2774 }
2775
2776 static LRESULT APIENTRY
D3DHiddenWndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)2777 D3DHiddenWndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
2778 {
2779 GstD3DVideoSinkClass *klass =
2780 (GstD3DVideoSinkClass *) GetWindowLongPtr (hWnd, GWLP_USERDATA);
2781 GstD3DVideoSinkEvent *evt;
2782
2783 switch (message) {
2784 case WM_TIMER:
2785 switch (wParam) {
2786 case IDT_DEVICE_RESET_TIMER:
2787 evt = g_new0 (GstD3DVideoSinkEvent, 1);
2788 evt->window_message_id = IDT_DEVICE_RESET_TIMER;
2789 evt->create_count = klass->create_count;
2790 gst_element_call_async (GST_ELEMENT (klass),
2791 (GstElementCallAsyncFunc) d3d_class_hidden_window_message_queue,
2792 evt, g_free);
2793 break;
2794 }
2795 return 0;
2796 case WM_DESTROY:
2797 PostQuitMessage (0);
2798 return 0;
2799 default:
2800 break;
2801 }
2802
2803 return DefWindowProc (hWnd, message, wParam, lParam);
2804 }
2805
2806 static gboolean
d3d_hidden_window_thread(GstD3DVideoSinkClass * klass)2807 d3d_hidden_window_thread (GstD3DVideoSinkClass * klass)
2808 {
2809 WNDCLASS WndClass;
2810 gboolean reged = FALSE;
2811 HWND hWnd = 0;
2812 gboolean ret = FALSE;
2813
2814 g_return_val_if_fail (klass != NULL, FALSE);
2815
2816 memset (&WndClass, 0, sizeof (WNDCLASS));
2817 WndClass.hInstance = GetModuleHandle (NULL);
2818 WndClass.lpszClassName = TEXT ("gstd3dvideo-hidden-window-class");
2819 WndClass.lpfnWndProc = D3DHiddenWndProc;
2820
2821 if (!RegisterClass (&WndClass)) {
2822 GST_ERROR ("Unable to register Direct3D hidden window class");
2823 goto error;
2824 }
2825 reged = TRUE;
2826
2827 hWnd = CreateWindowEx (0,
2828 WndClass.lpszClassName,
2829 TEXT ("GStreamer Direct3D hidden window"),
2830 WS_POPUP, 0, 0, 1, 1, HWND_MESSAGE, NULL, WndClass.hInstance, klass);
2831
2832 if (hWnd == NULL) {
2833 GST_ERROR ("Failed to create Direct3D hidden window");
2834 goto error;
2835 }
2836
2837 GST_DEBUG ("Direct3D hidden window handle: %p", hWnd);
2838
2839 klass->d3d.hidden_window = hWnd;
2840
2841 /* Attach data to window */
2842 SetWindowLongPtr (hWnd, GWLP_USERDATA, (LONG_PTR) klass);
2843
2844 GST_DEBUG ("Entering Direct3D hidden window message loop");
2845
2846 /* set running flag and signal calling thread */
2847 g_mutex_lock (&klass->d3d.thread_start_mutex);
2848 klass->d3d.thread_started = TRUE;
2849 g_cond_signal (&klass->d3d.thread_start_cond);
2850 g_mutex_unlock (&klass->d3d.thread_start_mutex);
2851
2852 /* Hidden Window Message Loop */
2853 while (1) {
2854 MSG msg;
2855 while (GetMessage (&msg, NULL, 0, 0)) {
2856 TranslateMessage (&msg);
2857 DispatchMessage (&msg);
2858 }
2859 if (msg.message == WM_QUIT || msg.message == WM_CLOSE)
2860 break;
2861 }
2862
2863
2864 GST_DEBUG ("Leaving Direct3D hidden window message loop");
2865
2866 ret = TRUE;
2867
2868 error:
2869 if (hWnd) {
2870 PostMessage (hWnd, WM_DESTROY, 0, 0);
2871 DestroyWindow (hWnd);
2872 klass->d3d.hidden_window = 0;
2873 }
2874 if (reged)
2875 UnregisterClass (WndClass.lpszClassName, WndClass.hInstance);
2876
2877 /* if failed, set error flag and signal calling thread */
2878 if (!ret) {
2879 g_mutex_lock (&klass->d3d.thread_start_mutex);
2880 klass->d3d.thread_error_exit = TRUE;
2881 g_cond_signal (&klass->d3d.thread_start_cond);
2882 g_mutex_unlock (&klass->d3d.thread_start_mutex);
2883 }
2884
2885 return ret;
2886 }
2887