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