1 /* GStreamer
2 * Copyright (C) 2019 Aaron Boxer <aaron.boxer@collabora.com>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
18 */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include "d3dvideosink.h"
25 #include "gstd3d9overlay.h"
26
27 #include <stdio.h>
28
29 GST_DEBUG_CATEGORY_EXTERN (gst_d3dvideosink_debug);
30 #define GST_CAT_DEFAULT gst_d3dvideosink_debug
31
32 #define ERROR_CHECK_HR(hr) \
33 if(hr != S_OK) { \
34 const gchar * str_err=NULL, *t1=NULL; \
35 gchar tmp[128]=""; \
36 switch(hr)
37 #define CASE_HR_ERR(hr_err) \
38 case hr_err: str_err = #hr_err; break;
39 #define CASE_HR_DBG_ERR_END(sink, gst_err_msg, level) \
40 default: \
41 t1=gst_err_msg; \
42 sprintf(tmp, "HR-SEV:%u HR-FAC:%u HR-CODE:%u", (guint)HRESULT_SEVERITY(hr), (guint)HRESULT_FACILITY(hr), (guint)HRESULT_CODE(hr)); \
43 str_err = tmp; \
44 } /* end switch */ \
45 GST_CAT_LEVEL_LOG(GST_CAT_DEFAULT, level, sink, "%s HRESULT: %s", t1?t1:"", str_err);
46 #define CASE_HR_ERR_END(sink, gst_err_msg) \
47 CASE_HR_DBG_ERR_END(sink, gst_err_msg, GST_LEVEL_ERROR)
48 #define CASE_HR_DBG_END(sink, gst_err_msg) \
49 CASE_HR_DBG_ERR_END(sink, gst_err_msg, GST_LEVEL_DEBUG)
50 #define D3D9_CHECK(call) hr = call; \
51 ERROR_CHECK_HR (hr) { \
52 CASE_HR_ERR (D3DERR_INVALIDCALL); \
53 CASE_HR_ERR_END (sink, #call); \
54 goto end; \
55 }
56
57 #define CHECK_D3D_DEVICE(klass, sink, goto_label) \
58 if(!klass->d3d.d3d || !klass->d3d.device.d3d_device) { \
59 GST_ERROR_OBJECT(sink, "Direct3D device or object does not exist"); \
60 goto goto_label; \
61 }
62
63 typedef struct _textured_vertex
64 {
65 float x, y, z, rhw; // The transformed(screen space) position for the vertex.
66 float tu, tv; // Texture coordinates
67 } textured_vertex;
68
69 /* Transformed vertex with 1 set of texture coordinates */
70 static DWORD tri_fvf = D3DFVF_XYZRHW | D3DFVF_TEX1;
71
72 static gboolean
73 _is_rectangle_in_overlays (GList * overlays,
74 GstVideoOverlayRectangle * rectangle);
75 static gboolean
76 _is_overlay_in_composition (GstVideoOverlayComposition * composition,
77 GstD3DVideoSinkOverlay * overlay);
78 static HRESULT
79 gst_d3d9_overlay_init_vb (GstD3DVideoSink * sink,
80 GstD3DVideoSinkOverlay * overlay);
81 static gboolean gst_d3d9_overlay_resize (GstD3DVideoSink * sink);
82 static void
83 gst_d3d9_overlay_calc_dest_rect (GstD3DVideoSink * sink, RECT * dest_rect);
84 static void gst_d3d9_overlay_free_overlay (GstD3DVideoSink * sink,
85 GstD3DVideoSinkOverlay * overlay);
86
87 static void
gst_d3d9_overlay_calc_dest_rect(GstD3DVideoSink * sink,RECT * dest_rect)88 gst_d3d9_overlay_calc_dest_rect (GstD3DVideoSink * sink, RECT * dest_rect)
89 {
90 if (sink->force_aspect_ratio) {
91 gint window_width;
92 gint window_height;
93 GstVideoRectangle src;
94 GstVideoRectangle dst;
95 GstVideoRectangle result;
96
97 memset (&dst, 0, sizeof (dst));
98 memset (&src, 0, sizeof (src));
99
100 /* Set via GstXOverlay set_render_rect */
101 if (sink->d3d.render_rect) {
102 memcpy (&dst, sink->d3d.render_rect, sizeof (dst));
103 } else {
104 d3d_get_hwnd_window_size (sink->d3d.window_handle, &window_width,
105 &window_height);
106 dst.w = window_width;
107 dst.h = window_height;
108 }
109
110 src.w = GST_VIDEO_SINK_WIDTH (sink);
111 src.h = GST_VIDEO_SINK_HEIGHT (sink);
112
113 gst_video_sink_center_rect (src, dst, &result, TRUE);
114
115 dest_rect->left = result.x;
116 dest_rect->top = result.y;
117 dest_rect->right = result.x + result.w;
118 dest_rect->bottom = result.y + result.h;
119 } else if (sink->d3d.render_rect) {
120 dest_rect->left = 0;
121 dest_rect->top = 0;
122 dest_rect->right = sink->d3d.render_rect->w;
123 dest_rect->bottom = sink->d3d.render_rect->h;
124 } else {
125 /* get client window size */
126 GetClientRect (sink->d3d.window_handle, dest_rect);
127 }
128 }
129
130 static void
gst_d3d9_overlay_free_overlay(GstD3DVideoSink * sink,GstD3DVideoSinkOverlay * overlay)131 gst_d3d9_overlay_free_overlay (GstD3DVideoSink * sink,
132 GstD3DVideoSinkOverlay * overlay)
133 {
134 if (G_LIKELY (overlay)) {
135 if (overlay->texture) {
136 HRESULT hr = IDirect3DTexture9_Release (overlay->texture);
137 if (hr != D3D_OK) {
138 GST_ERROR_OBJECT (sink, "Failed to release D3D texture");
139 }
140 }
141 if (overlay->g_list_vb) {
142 HRESULT hr = IDirect3DVertexBuffer9_Release (overlay->g_list_vb);
143 if (hr != D3D_OK) {
144 GST_ERROR_OBJECT (sink, "Failed to release D3D vertex buffer");
145 }
146 }
147 gst_video_overlay_rectangle_unref (overlay->rectangle);
148 g_free (overlay);
149 }
150 }
151
152 static gboolean
_is_rectangle_in_overlays(GList * overlays,GstVideoOverlayRectangle * rectangle)153 _is_rectangle_in_overlays (GList * overlays,
154 GstVideoOverlayRectangle * rectangle)
155 {
156 GList *l;
157
158 for (l = overlays; l != NULL; l = l->next) {
159 GstD3DVideoSinkOverlay *overlay = (GstD3DVideoSinkOverlay *) l->data;
160 if (overlay->rectangle == rectangle)
161 return TRUE;
162 }
163 return FALSE;
164 }
165
166 static gboolean
_is_overlay_in_composition(GstVideoOverlayComposition * composition,GstD3DVideoSinkOverlay * overlay)167 _is_overlay_in_composition (GstVideoOverlayComposition * composition,
168 GstD3DVideoSinkOverlay * overlay)
169 {
170 guint i;
171
172 for (i = 0; i < gst_video_overlay_composition_n_rectangles (composition); i++) {
173 GstVideoOverlayRectangle *rectangle =
174 gst_video_overlay_composition_get_rectangle (composition, i);
175 if (overlay->rectangle == rectangle)
176 return TRUE;
177 }
178 return FALSE;
179 }
180
181 GstFlowReturn
gst_d3d9_overlay_prepare(GstD3DVideoSink * sink,GstBuffer * buf)182 gst_d3d9_overlay_prepare (GstD3DVideoSink * sink, GstBuffer * buf)
183 {
184 GstD3DVideoSinkClass *klass = GST_D3DVIDEOSINK_GET_CLASS (sink);
185 GList *l = NULL;
186 GstVideoOverlayComposition *composition = NULL;
187 guint num_overlays, i;
188 GstVideoOverlayCompositionMeta *composition_meta =
189 gst_buffer_get_video_overlay_composition_meta (buf);
190 gboolean found_new_overlay_rectangle = FALSE;
191
192 if (!composition_meta) {
193 gst_d3d9_overlay_free (sink);
194 return GST_FLOW_OK;
195 }
196 l = sink->d3d.overlay;
197 composition = composition_meta->overlay;
198 num_overlays = gst_video_overlay_composition_n_rectangles (composition);
199
200 GST_DEBUG_OBJECT (sink, "GstVideoOverlayCompositionMeta found.");
201
202 /* check for new overlays */
203 for (i = 0; i < num_overlays; i++) {
204 GstVideoOverlayRectangle *rectangle =
205 gst_video_overlay_composition_get_rectangle (composition, i);
206
207 if (!_is_rectangle_in_overlays (sink->d3d.overlay, rectangle)) {
208 found_new_overlay_rectangle = TRUE;
209 break;
210 }
211 }
212
213 /* add new overlays to list */
214 if (found_new_overlay_rectangle) {
215 GST_DEBUG_OBJECT (sink, "New overlay composition rectangles found.");
216 LOCK_CLASS (sink, klass);
217 if (!klass->d3d.refs) {
218 GST_ERROR_OBJECT (sink, "Direct3D object ref count = 0");
219 gst_d3d9_overlay_free (sink);
220 UNLOCK_CLASS (sink, klass);
221 return GST_FLOW_ERROR;
222 }
223 for (i = 0; i < num_overlays; i++) {
224 GstVideoOverlayRectangle *rectangle =
225 gst_video_overlay_composition_get_rectangle (composition, i);
226
227 if (!_is_rectangle_in_overlays (sink->d3d.overlay, rectangle)) {
228 GstVideoOverlayFormatFlags flags;
229 gint x, y;
230 guint width, height;
231 HRESULT hr = 0;
232 GstMapInfo info;
233 GstBuffer *from = NULL;
234 GstD3DVideoSinkOverlay *overlay = g_new0 (GstD3DVideoSinkOverlay, 1);
235 overlay->rectangle = gst_video_overlay_rectangle_ref (rectangle);
236 if (!gst_video_overlay_rectangle_get_render_rectangle
237 (overlay->rectangle, &x, &y, &width, &height)) {
238 GST_ERROR_OBJECT (sink,
239 "Failed to get overlay rectangle of dimension (%d,%d)", width,
240 height);
241 g_free (overlay);
242 continue;
243 }
244 hr = IDirect3DDevice9_CreateTexture (klass->d3d.device.d3d_device,
245 width, height, 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED,
246 &overlay->texture, NULL);
247 if (hr != D3D_OK) {
248 GST_ERROR_OBJECT (sink,
249 "Failed to create D3D texture of dimensions (%d,%d)", width,
250 height);
251 g_free (overlay);
252 continue;
253 }
254 flags = gst_video_overlay_rectangle_get_flags (rectangle);
255 /* FIXME: investigate support for pre-multiplied vs. non-pre-multiplied alpha */
256 from = gst_video_overlay_rectangle_get_pixels_unscaled_argb
257 (rectangle, flags);
258 if (gst_buffer_map (from, &info, GST_MAP_READ)) {
259 /* 1. lock texture */
260 D3DLOCKED_RECT rect;
261 hr = IDirect3DTexture9_LockRect (overlay->texture, 0, &rect, NULL,
262 D3DUSAGE_WRITEONLY);
263 if (hr != D3D_OK) {
264 GST_ERROR_OBJECT (sink, "Failed to lock D3D texture");
265 gst_buffer_unmap (from, &info);
266 gst_d3d9_overlay_free_overlay (sink, overlay);
267 continue;
268 }
269 /* 2. copy */
270 memcpy (rect.pBits, info.data, info.size);
271 /* 3. unlock texture */
272 hr = IDirect3DTexture9_UnlockRect (overlay->texture, 0);
273 if (hr != D3D_OK) {
274 GST_ERROR_OBJECT (sink, "Failed to unlock D3D texture");
275 gst_buffer_unmap (from, &info);
276 gst_d3d9_overlay_free_overlay (sink, overlay);
277 continue;
278 }
279 gst_buffer_unmap (from, &info);
280 hr = gst_d3d9_overlay_init_vb (sink, overlay);
281 if (FAILED (hr)) {
282 gst_d3d9_overlay_free_overlay (sink, overlay);
283 continue;
284 }
285 }
286 sink->d3d.overlay = g_list_append (sink->d3d.overlay, overlay);
287 }
288 }
289 UNLOCK_CLASS (sink, klass);
290 }
291 /* remove old overlays from list */
292 while (l != NULL) {
293 GList *next = l->next;
294 GstD3DVideoSinkOverlay *overlay = (GstD3DVideoSinkOverlay *) l->data;
295
296 if (!_is_overlay_in_composition (composition, overlay)) {
297 gst_d3d9_overlay_free_overlay (sink, overlay);
298 sink->d3d.overlay = g_list_delete_link (sink->d3d.overlay, l);
299 }
300 l = next;
301 }
302
303 return GST_FLOW_OK;
304 }
305
306 gboolean
gst_d3d9_overlay_resize(GstD3DVideoSink * sink)307 gst_d3d9_overlay_resize (GstD3DVideoSink * sink)
308 {
309 GList *l = sink->d3d.overlay;
310
311 while (l != NULL) {
312 GList *next = l->next;
313 GstD3DVideoSinkOverlay *overlay = (GstD3DVideoSinkOverlay *) l->data;
314 HRESULT hr = gst_d3d9_overlay_init_vb (sink, overlay);
315
316 if (FAILED (hr)) {
317 return FALSE;
318 }
319 l = next;
320 }
321
322 return TRUE;
323 }
324
325 void
gst_d3d9_overlay_free(GstD3DVideoSink * sink)326 gst_d3d9_overlay_free (GstD3DVideoSink * sink)
327 {
328 GList *l = sink->d3d.overlay;
329
330 while (l != NULL) {
331 GList *next = l->next;
332 GstD3DVideoSinkOverlay *overlay = (GstD3DVideoSinkOverlay *) l->data;
333
334 gst_d3d9_overlay_free_overlay (sink, overlay);
335 sink->d3d.overlay = g_list_delete_link (sink->d3d.overlay, l);
336 l = next;
337 }
338 g_list_free (sink->d3d.overlay);
339 sink->d3d.overlay = NULL;
340 }
341
342 static HRESULT
gst_d3d9_overlay_init_vb(GstD3DVideoSink * sink,GstD3DVideoSinkOverlay * overlay)343 gst_d3d9_overlay_init_vb (GstD3DVideoSink * sink,
344 GstD3DVideoSinkOverlay * overlay)
345 {
346 GstD3DVideoSinkClass *klass = GST_D3DVIDEOSINK_GET_CLASS (sink);
347 gint x = 0, y = 0;
348 guint width = 0, height = 0;
349 guint sink_width = GST_VIDEO_SINK_WIDTH (sink);
350 guint sink_height = GST_VIDEO_SINK_HEIGHT (sink);
351 float scaleX = 1.0f, scaleY = 1.0f;
352 RECT dest_rect;
353 guint dest_width, dest_height;
354 void *vb_vertices = NULL;
355 HRESULT hr = 0;
356 int vert_count, byte_count;
357
358 if (sink_width < 1 || sink_height < 1) {
359 return D3D_OK;
360 }
361
362 if (!gst_video_overlay_rectangle_get_render_rectangle
363 (overlay->rectangle, &x, &y, &width, &height)) {
364 GST_ERROR_OBJECT (sink, "Failed to get overlay rectangle");
365 return 0;
366 }
367 if (width < 1 || height < 1) {
368 return D3D_OK;
369 }
370 memset (&dest_rect, 0, sizeof (dest_rect));
371 gst_d3d9_overlay_calc_dest_rect (sink, &dest_rect);
372 dest_width = dest_rect.right - dest_rect.left;
373 dest_height = dest_rect.bottom - dest_rect.top;
374 scaleX = (float) dest_width / sink_width;
375 scaleY = (float) dest_height / sink_height;
376 x = dest_rect.left + x * scaleX;
377 y = dest_rect.top + y * scaleY;
378 width *= scaleX;
379 height *= scaleY;
380
381 /* a quad is composed of six vertices */
382 vert_count = 6;
383 byte_count = vert_count * sizeof (textured_vertex);
384 overlay->g_list_count = vert_count / 3;
385
386 /* destroy existing buffer */
387 if (overlay->g_list_vb) {
388 hr = IDirect3DVertexBuffer9_Release (overlay->g_list_vb);
389 if (hr != D3D_OK) {
390 GST_ERROR_OBJECT (sink, "Failed to release D3D vertex buffer");
391 }
392 }
393 CHECK_D3D_DEVICE (klass, sink, error);
394 hr = IDirect3DDevice9_CreateVertexBuffer (klass->d3d.device.d3d_device, byte_count, /* Length */
395 D3DUSAGE_WRITEONLY, /* Usage */
396 tri_fvf, /* FVF */
397 D3DPOOL_MANAGED, /* Pool */
398 &overlay->g_list_vb, /* ppVertexBuffer */
399 NULL); /* Handle */
400 if (FAILED (hr)) {
401 GST_ERROR_OBJECT (sink, "Error Creating vertex buffer");
402 return hr;
403 }
404
405 hr = IDirect3DVertexBuffer9_Lock (overlay->g_list_vb, 0, /* Offset */
406 0, /* SizeToLock */
407 &vb_vertices, /* Vertices */
408 0); /* Flags */
409 if (FAILED (hr)) {
410 GST_ERROR_OBJECT (sink, "Error Locking vertex buffer");
411 return hr;
412 }
413 {
414 textured_vertex data[] = {
415
416 {x, y + height, 1, 1, 0, 1}
417 , {x, y, 1, 1, 0, 0}
418 , {x + width, y, 1, 1, 1, 0}
419 ,
420 {x, y + height, 1, 1, 0, 1}
421 , {x + width, y, 1, 1, 1, 0}
422 , {x + width,
423 y + height, 1, 1, 1, 1}
424
425 };
426 memcpy (vb_vertices, data, byte_count);
427 }
428 hr = IDirect3DVertexBuffer9_Unlock (overlay->g_list_vb);
429 if (FAILED (hr)) {
430 GST_ERROR_OBJECT (sink, "Error Unlocking vertex buffer");
431 return hr;
432 }
433
434 return D3D_OK;
435
436 error:
437 return hr;
438 }
439
440 gboolean
gst_d3d9_overlay_set_render_state(GstD3DVideoSink * sink)441 gst_d3d9_overlay_set_render_state (GstD3DVideoSink * sink)
442 {
443 HRESULT hr = 0;
444 GstD3DVideoSinkClass *klass = GST_D3DVIDEOSINK_GET_CLASS (sink);
445 gboolean ret = FALSE;
446
447 D3D9_CHECK (IDirect3DDevice9_SetRenderState (klass->d3d.device.d3d_device,
448 D3DRS_ALPHABLENDENABLE, TRUE));
449 D3D9_CHECK (IDirect3DDevice9_SetRenderState (klass->d3d.device.d3d_device,
450 D3DRS_SRCBLEND, D3DBLEND_SRCALPHA));
451 D3D9_CHECK (IDirect3DDevice9_SetRenderState (klass->d3d.device.d3d_device,
452 D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA));
453
454 ret = TRUE;
455 end:
456 return ret;
457 }
458
459 gboolean
gst_d3d9_overlay_render(GstD3DVideoSink * sink)460 gst_d3d9_overlay_render (GstD3DVideoSink * sink)
461 {
462 HRESULT hr = 0;
463 GList *iter = NULL;
464 gboolean ret = FALSE;
465 GstD3DVideoSinkClass *klass = GST_D3DVIDEOSINK_GET_CLASS (sink);
466
467 if (!sink->d3d.overlay)
468 return TRUE;
469
470 if (sink->d3d.overlay_needs_resize && !gst_d3d9_overlay_resize (sink))
471 return FALSE;
472 sink->d3d.overlay_needs_resize = FALSE;
473 iter = sink->d3d.overlay;
474 while (iter != NULL) {
475 GList *next = iter->next;
476 GstD3DVideoSinkOverlay *overlay = (GstD3DVideoSinkOverlay *) iter->data;
477
478 if (!overlay->g_list_vb) {
479 GST_ERROR_OBJECT (sink, "Overlay is missing vertex buffer");
480 goto end;
481 }
482 if (!overlay->texture) {
483 GST_ERROR_OBJECT (sink, "Overlay is missing texture");
484 goto end;
485 }
486 D3D9_CHECK (IDirect3DDevice9_SetTexture (klass->d3d.device.d3d_device, 0,
487 (IDirect3DBaseTexture9 *) overlay->texture))
488 /* Bind our Vertex Buffer */
489 D3D9_CHECK (IDirect3DDevice9_SetFVF (klass->d3d.device.d3d_device,
490 tri_fvf))
491 D3D9_CHECK (IDirect3DDevice9_SetStreamSource (klass->d3d.device.d3d_device, 0, /* StreamNumber */
492 overlay->g_list_vb, /* StreamData */
493 0, /* OffsetInBytes */
494 sizeof (textured_vertex)))
495 /* Stride */
496 //Render from our Vertex Buffer
497 D3D9_CHECK (IDirect3DDevice9_DrawPrimitive (klass->d3d.device.d3d_device, D3DPT_TRIANGLELIST, /* PrimitiveType */
498 0, /* StartVertex */
499 overlay->g_list_count)) /* PrimitiveCount */
500 iter = next;
501 }
502 ret = TRUE;
503 end:
504 return ret;
505 }
506