1 | /* |
2 | * GStreamer |
3 | * Copyright (C) 2015 Matthew Waters <matthew@centricular.com> |
4 | * |
5 | * This library is free software; you can redistribute it and/or |
6 | * modify it under the terms of the GNU Library General Public |
7 | * License as published by the Free Software Foundation; either |
8 | * version 2 of the License, or (at your option) any later version. |
9 | * |
10 | * This library is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | * Library General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU Library General Public |
16 | * License along with this library; if not, write to the |
17 | * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, |
18 | * Boston, MA 02110-1301, USA. |
19 | */ |
20 | |
21 | #include "config.h" |
22 | |
23 | #include "gtkgstsinkprivate.h" |
24 | |
25 | #include "gtkgstpaintableprivate.h" |
26 | #include "gtkintl.h" |
27 | |
28 | #if GST_GL_HAVE_WINDOW_X11 && (GST_GL_HAVE_PLATFORM_GLX || GST_GL_HAVE_PLATFORM_EGL) && defined (GDK_WINDOWING_X11) |
29 | #define HAVE_GST_X11_SUPPORT |
30 | #include <gdk/x11/gdkx.h> |
31 | #if GST_GL_HAVE_PLATFORM_GLX |
32 | #include <gst/gl/x11/gstgldisplay_x11.h> |
33 | #endif |
34 | #endif |
35 | |
36 | #if GST_GL_HAVE_WINDOW_WAYLAND && GST_GL_HAVE_PLATFORM_EGL && defined (GDK_WINDOWING_WAYLAND) |
37 | #define HAVE_GST_WAYLAND_SUPPORT |
38 | #include <gdk/wayland/gdkwayland.h> |
39 | #include <gst/gl/wayland/gstgldisplay_wayland.h> |
40 | #endif |
41 | |
42 | #if GST_GL_HAVE_WINDOW_WIN32 && (GST_GL_HAVE_PLATFORM_WGL || GST_GL_HAVE_PLATFORM_EGL) && defined (GDK_WINDOWING_WIN32) |
43 | #include <gdk/win32/gdkwin32.h> |
44 | #include <epoxy/wgl.h> |
45 | #endif |
46 | |
47 | #if GST_GL_HAVE_PLATFORM_EGL && (GST_GL_HAVE_WINDOW_WIN32 || GST_GL_HAVE_WINDOW_X11) |
48 | #include <gst/gl/egl/gstgldisplay_egl.h> |
49 | #endif |
50 | |
51 | #ifdef GDK_WINDOWING_MACOS |
52 | #include <gdk/macos/gdkmacos.h> |
53 | #endif |
54 | |
55 | #include <gst/gl/gstglfuncs.h> |
56 | |
57 | enum { |
58 | PROP_0, |
59 | PROP_PAINTABLE, |
60 | PROP_GL_CONTEXT, |
61 | |
62 | N_PROPS, |
63 | }; |
64 | |
65 | GST_DEBUG_CATEGORY (gtk_debug_gst_sink); |
66 | #define GST_CAT_DEFAULT gtk_debug_gst_sink |
67 | |
68 | #define FORMATS "{ BGRA, ARGB, RGBA, ABGR, RGB, BGR }" |
69 | |
70 | #define NOGL_CAPS GST_VIDEO_CAPS_MAKE (FORMATS) |
71 | |
72 | static GstStaticPadTemplate gtk_gst_sink_template = |
73 | GST_STATIC_PAD_TEMPLATE ("sink" , |
74 | GST_PAD_SINK, |
75 | GST_PAD_ALWAYS, |
76 | GST_STATIC_CAPS ("video/x-raw(" GST_CAPS_FEATURE_MEMORY_GL_MEMORY "), " |
77 | "format = (string) RGBA, " |
78 | "width = " GST_VIDEO_SIZE_RANGE ", " |
79 | "height = " GST_VIDEO_SIZE_RANGE ", " |
80 | "framerate = " GST_VIDEO_FPS_RANGE ", " |
81 | "texture-target = (string) 2D" |
82 | "; " NOGL_CAPS) |
83 | ); |
84 | |
85 | G_DEFINE_TYPE_WITH_CODE (GtkGstSink, gtk_gst_sink, |
86 | GST_TYPE_VIDEO_SINK, |
87 | GST_DEBUG_CATEGORY_INIT (gtk_debug_gst_sink, |
88 | "gtkgstsink" , 0, "GtkGstMediaFile Video Sink" )); |
89 | |
90 | static GParamSpec *properties[N_PROPS] = { NULL, }; |
91 | |
92 | |
93 | static void |
94 | gtk_gst_sink_get_times (GstBaseSink *bsink, |
95 | GstBuffer *buf, |
96 | GstClockTime *start, |
97 | GstClockTime *end) |
98 | { |
99 | GtkGstSink *gtk_sink; |
100 | |
101 | gtk_sink = GTK_GST_SINK (bsink); |
102 | |
103 | if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) |
104 | { |
105 | *start = GST_BUFFER_TIMESTAMP (buf); |
106 | if (GST_BUFFER_DURATION_IS_VALID (buf)) |
107 | *end = *start + GST_BUFFER_DURATION (buf); |
108 | else |
109 | { |
110 | if (GST_VIDEO_INFO_FPS_N (>k_sink->v_info) > 0) |
111 | { |
112 | *end = *start + |
113 | gst_util_uint64_scale_int (GST_SECOND, |
114 | GST_VIDEO_INFO_FPS_D (>k_sink->v_info), |
115 | GST_VIDEO_INFO_FPS_N (>k_sink->v_info)); |
116 | } |
117 | } |
118 | } |
119 | } |
120 | |
121 | static GstCaps * |
122 | gtk_gst_sink_get_caps (GstBaseSink *bsink, |
123 | GstCaps *filter) |
124 | { |
125 | GtkGstSink *self = GTK_GST_SINK (bsink); |
126 | GstCaps *tmp; |
127 | GstCaps *result; |
128 | |
129 | if (self->gst_context) |
130 | { |
131 | tmp = gst_pad_get_pad_template_caps (GST_BASE_SINK_PAD (bsink)); |
132 | } |
133 | else |
134 | { |
135 | tmp = gst_caps_from_string (NOGL_CAPS); |
136 | } |
137 | GST_DEBUG_OBJECT (self, "advertising own caps %" GST_PTR_FORMAT, tmp); |
138 | |
139 | if (filter) |
140 | { |
141 | GST_DEBUG_OBJECT (self, "intersecting with filter caps %" GST_PTR_FORMAT, filter); |
142 | |
143 | result = gst_caps_intersect_full (caps1: filter, caps2: tmp, mode: GST_CAPS_INTERSECT_FIRST); |
144 | gst_caps_unref (caps: tmp); |
145 | } |
146 | else |
147 | { |
148 | result = tmp; |
149 | } |
150 | |
151 | GST_DEBUG_OBJECT (self, "returning caps: %" GST_PTR_FORMAT, result); |
152 | |
153 | return result; |
154 | } |
155 | |
156 | static gboolean |
157 | gtk_gst_sink_set_caps (GstBaseSink *bsink, |
158 | GstCaps *caps) |
159 | { |
160 | GtkGstSink *self = GTK_GST_SINK (bsink); |
161 | |
162 | GST_DEBUG_OBJECT (self, "set caps with %" GST_PTR_FORMAT, caps); |
163 | |
164 | if (!gst_video_info_from_caps (info: &self->v_info, caps)) |
165 | return FALSE; |
166 | |
167 | return TRUE; |
168 | } |
169 | |
170 | static gboolean |
171 | gtk_gst_sink_query (GstBaseSink *bsink, |
172 | GstQuery *query) |
173 | { |
174 | GtkGstSink *self = GTK_GST_SINK (bsink); |
175 | |
176 | if (GST_QUERY_TYPE (query) == GST_QUERY_CONTEXT && |
177 | self->gst_display != NULL && |
178 | gst_gl_handle_context_query (GST_ELEMENT (self), query, display: self->gst_display, context: self->gst_context, other_context: self->gst_app_context)) |
179 | return TRUE; |
180 | |
181 | return GST_BASE_SINK_CLASS (gtk_gst_sink_parent_class)->query (bsink, query); |
182 | } |
183 | |
184 | static gboolean |
185 | gtk_gst_sink_propose_allocation (GstBaseSink *bsink, |
186 | GstQuery *query) |
187 | { |
188 | GtkGstSink *self = GTK_GST_SINK (bsink); |
189 | GstBufferPool *pool = NULL; |
190 | GstStructure *config; |
191 | GstCaps *caps; |
192 | guint size; |
193 | gboolean need_pool; |
194 | GstVideoInfo info; |
195 | |
196 | if (!self->gst_context) |
197 | return FALSE; |
198 | |
199 | gst_query_parse_allocation (query, caps: &caps, need_pool: &need_pool); |
200 | |
201 | if (caps == NULL) |
202 | { |
203 | GST_DEBUG_OBJECT (bsink, "no caps specified" ); |
204 | return FALSE; |
205 | } |
206 | |
207 | if (!gst_caps_features_contains (features: gst_caps_get_features (caps, index: 0), GST_CAPS_FEATURE_MEMORY_GL_MEMORY)) |
208 | return FALSE; |
209 | |
210 | if (!gst_video_info_from_caps (info: &info, caps)) |
211 | { |
212 | GST_DEBUG_OBJECT (self, "invalid caps specified" ); |
213 | return FALSE; |
214 | } |
215 | |
216 | /* the normal size of a frame */ |
217 | size = info.size; |
218 | |
219 | if (need_pool) |
220 | { |
221 | GST_DEBUG_OBJECT (self, "create new pool" ); |
222 | pool = gst_gl_buffer_pool_new (context: self->gst_context); |
223 | |
224 | config = gst_buffer_pool_get_config (pool); |
225 | gst_buffer_pool_config_set_params (config, caps, size, min_buffers: 0, max_buffers: 0); |
226 | gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_GL_SYNC_META); |
227 | |
228 | if (!gst_buffer_pool_set_config (pool, config)) |
229 | { |
230 | GST_DEBUG_OBJECT (bsink, "failed setting config" ); |
231 | gst_object_unref (object: pool); |
232 | return FALSE; |
233 | } |
234 | } |
235 | |
236 | /* we need at least 2 buffer because we hold on to the last one */ |
237 | gst_query_add_allocation_pool (query, pool, size, min_buffers: 2, max_buffers: 0); |
238 | if (pool) |
239 | gst_object_unref (object: pool); |
240 | |
241 | /* we also support various metadata */ |
242 | gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, params: 0); |
243 | |
244 | if (self->gst_context->gl_vtable->FenceSync) |
245 | gst_query_add_allocation_meta (query, GST_GL_SYNC_META_API_TYPE, params: 0); |
246 | |
247 | return TRUE; |
248 | } |
249 | |
250 | static GdkMemoryFormat |
251 | gtk_gst_memory_format_from_video (GstVideoFormat format) |
252 | { |
253 | switch ((guint) format) |
254 | { |
255 | case GST_VIDEO_FORMAT_BGRA: |
256 | return GDK_MEMORY_B8G8R8A8; |
257 | case GST_VIDEO_FORMAT_ARGB: |
258 | return GDK_MEMORY_A8R8G8B8; |
259 | case GST_VIDEO_FORMAT_RGBA: |
260 | return GDK_MEMORY_R8G8B8A8; |
261 | case GST_VIDEO_FORMAT_ABGR: |
262 | return GDK_MEMORY_A8B8G8R8; |
263 | case GST_VIDEO_FORMAT_RGB: |
264 | return GDK_MEMORY_R8G8B8; |
265 | case GST_VIDEO_FORMAT_BGR: |
266 | return GDK_MEMORY_B8G8R8; |
267 | default: |
268 | g_assert_not_reached (); |
269 | return GDK_MEMORY_A8R8G8B8; |
270 | } |
271 | } |
272 | |
273 | static void |
274 | video_frame_free (GstVideoFrame *frame) |
275 | { |
276 | gst_video_frame_unmap (frame); |
277 | g_free (mem: frame); |
278 | } |
279 | |
280 | static GdkTexture * |
281 | gtk_gst_sink_texture_from_buffer (GtkGstSink *self, |
282 | GstBuffer *buffer, |
283 | double *pixel_aspect_ratio) |
284 | { |
285 | GstVideoFrame *frame = g_new (GstVideoFrame, 1); |
286 | GdkTexture *texture; |
287 | |
288 | if (self->gdk_context && |
289 | gst_video_frame_map (frame, info: &self->v_info, buffer, flags: GST_MAP_READ | GST_MAP_GL)) |
290 | { |
291 | GstGLSyncMeta *sync_meta; |
292 | |
293 | sync_meta = gst_buffer_get_gl_sync_meta (buffer); |
294 | if (sync_meta) { |
295 | gst_gl_sync_meta_set_sync_point (sync_meta, context: self->gst_context); |
296 | gst_gl_sync_meta_wait (sync_meta, context: self->gst_context); |
297 | } |
298 | |
299 | texture = gdk_gl_texture_new (context: self->gdk_context, |
300 | id: *(guint *) frame->data[0], |
301 | width: frame->info.width, |
302 | height: frame->info.height, |
303 | destroy: (GDestroyNotify) video_frame_free, |
304 | data: frame); |
305 | |
306 | *pixel_aspect_ratio = ((double) frame->info.par_n) / ((double) frame->info.par_d); |
307 | } |
308 | else if (gst_video_frame_map (frame, info: &self->v_info, buffer, flags: GST_MAP_READ)) |
309 | { |
310 | GBytes *bytes; |
311 | |
312 | bytes = g_bytes_new_with_free_func (data: frame->data[0], |
313 | size: frame->info.height * frame->info.stride[0], |
314 | free_func: (GDestroyNotify) video_frame_free, |
315 | user_data: frame); |
316 | texture = gdk_memory_texture_new (width: frame->info.width, |
317 | height: frame->info.height, |
318 | format: gtk_gst_memory_format_from_video (GST_VIDEO_FRAME_FORMAT (frame)), |
319 | bytes, |
320 | stride: frame->info.stride[0]); |
321 | g_bytes_unref (bytes); |
322 | |
323 | *pixel_aspect_ratio = ((double) frame->info.par_n) / ((double) frame->info.par_d); |
324 | } |
325 | else |
326 | { |
327 | GST_ERROR_OBJECT (self, "Could not convert buffer to texture." ); |
328 | texture = NULL; |
329 | g_free (mem: frame); |
330 | } |
331 | |
332 | return texture; |
333 | } |
334 | |
335 | static GstFlowReturn |
336 | gtk_gst_sink_show_frame (GstVideoSink *vsink, |
337 | GstBuffer *buf) |
338 | { |
339 | GtkGstSink *self; |
340 | GdkTexture *texture; |
341 | double pixel_aspect_ratio; |
342 | |
343 | GST_TRACE ("rendering buffer:%p" , buf); |
344 | |
345 | self = GTK_GST_SINK (vsink); |
346 | |
347 | GST_OBJECT_LOCK (self); |
348 | |
349 | texture = gtk_gst_sink_texture_from_buffer (self, buffer: buf, pixel_aspect_ratio: &pixel_aspect_ratio); |
350 | if (texture) |
351 | { |
352 | gtk_gst_paintable_queue_set_texture (self: self->paintable, texture, pixel_aspect_ratio); |
353 | g_object_unref (object: texture); |
354 | } |
355 | |
356 | GST_OBJECT_UNLOCK (self); |
357 | |
358 | return GST_FLOW_OK; |
359 | } |
360 | |
361 | #if GST_GL_HAVE_WINDOW_WIN32 && (GST_GL_HAVE_PLATFORM_WGL || GST_GL_HAVE_PLATFORM_EGL) && defined (GDK_WINDOWING_WIN32) |
362 | #define HANDLE_EXTERNAL_WGL_MAKE_CURRENT(ctx) handle_wgl_makecurrent(ctx) |
363 | #define DEACTIVATE_WGL_CONTEXT(ctx) deactivate_gdk_wgl_context(ctx) |
364 | #define REACTIVATE_WGL_CONTEXT(ctx) reactivate_gdk_wgl_context(ctx) |
365 | |
366 | static void |
367 | handle_wgl_makecurrent (GdkGLContext *ctx) |
368 | { |
369 | if (!gdk_gl_context_get_use_es (ctx)) |
370 | epoxy_handle_external_wglMakeCurrent(); |
371 | } |
372 | |
373 | static void |
374 | deactivate_gdk_wgl_context (GdkGLContext *ctx) |
375 | { |
376 | if (!gdk_gl_context_get_use_es (ctx)) |
377 | { |
378 | HDC hdc = GetDC (GDK_SURFACE_HWND (gdk_gl_context_get_surface (ctx))); |
379 | wglMakeCurrent (hdc, NULL); |
380 | } |
381 | } |
382 | |
383 | static void |
384 | reactivate_gdk_wgl_context (GdkGLContext *ctx) |
385 | { |
386 | if (!gdk_gl_context_get_use_es (ctx)) |
387 | gdk_gl_context_make_current (ctx); |
388 | } |
389 | |
390 | /* |
391 | * Unfortunately, libepoxy does not offer a way to allow us to safely call |
392 | * gst_gl_context_get_current_gl_api() on a WGL context that underlies a |
393 | * GdkGLContext after we notify libepoxy an external wglMakeCurrent() has |
394 | * been called (which is required for the first gdk_gl_context_make_current() |
395 | * call in gtk_gst_sink_initialize_gl(), for instance), so we can't do |
396 | * gst_gl_context_get_current_gl_api() directly on WGL contexts that underlies |
397 | * GdkGLContext's. So, we just ask GDK about our WGL context, since it already |
398 | * knows what kind of WGL context we have there... |
399 | */ |
400 | static gboolean |
401 | check_win32_gst_gl_api (GdkGLContext *ctx, |
402 | GstGLPlatform *platform, |
403 | GstGLAPI *gl_api) |
404 | { |
405 | gboolean is_gles = gdk_gl_context_get_use_es (ctx); |
406 | |
407 | g_return_val_if_fail (*gl_api == GST_GL_API_NONE, FALSE); |
408 | |
409 | *platform = is_gles ? GST_GL_PLATFORM_EGL : GST_GL_PLATFORM_WGL; |
410 | |
411 | if (is_gles) |
412 | *gl_api = gst_gl_context_get_current_gl_api (*platform, NULL, NULL); |
413 | else |
414 | *gl_api = gdk_gl_context_is_legacy (ctx) ? GST_GL_API_OPENGL : GST_GL_API_OPENGL3; |
415 | |
416 | return is_gles; |
417 | } |
418 | #else |
419 | #define HANDLE_EXTERNAL_WGL_MAKE_CURRENT(ctx) |
420 | #define DEACTIVATE_WGL_CONTEXT(ctx) |
421 | #define REACTIVATE_WGL_CONTEXT(ctx) |
422 | #endif |
423 | |
424 | static gboolean |
425 | gtk_gst_sink_initialize_gl (GtkGstSink *self) |
426 | { |
427 | GdkDisplay *display; |
428 | GError *error = NULL; |
429 | GstGLPlatform platform = GST_GL_PLATFORM_NONE; |
430 | GstGLAPI gl_api = GST_GL_API_NONE; |
431 | guintptr gl_handle = 0; |
432 | gboolean succeeded = FALSE; |
433 | |
434 | display = gdk_gl_context_get_display (context: self->gdk_context); |
435 | |
436 | HANDLE_EXTERNAL_WGL_MAKE_CURRENT (self->gdk_context); |
437 | gdk_gl_context_make_current (context: self->gdk_context); |
438 | |
439 | #ifdef HAVE_GST_X11_SUPPORT |
440 | if (GDK_IS_X11_DISPLAY (display)) |
441 | { |
442 | gpointer display_ptr; |
443 | |
444 | #if GST_GL_HAVE_PLATFORM_EGL |
445 | display_ptr = gdk_x11_display_get_egl_display (display); |
446 | if (display_ptr) |
447 | { |
448 | GST_DEBUG_OBJECT (self, "got EGL on X11!" ); |
449 | platform = GST_GL_PLATFORM_EGL; |
450 | self->gst_display = GST_GL_DISPLAY (gst_gl_display_egl_new_with_egl_display (display_ptr)); |
451 | } |
452 | #endif |
453 | #if GST_GL_HAVE_PLATFORM_GLX |
454 | if (!self->gst_display) |
455 | { |
456 | GST_DEBUG_OBJECT (self, "got GLX on X11!" ); |
457 | platform = GST_GL_PLATFORM_GLX; |
458 | display_ptr = gdk_x11_display_get_xdisplay (display); |
459 | self->gst_display = GST_GL_DISPLAY (gst_gl_display_x11_new_with_display (display_ptr)); |
460 | } |
461 | #endif |
462 | |
463 | gl_api = gst_gl_context_get_current_gl_api (platform, NULL, NULL); |
464 | gl_handle = gst_gl_context_get_current_gl_context (context_type: platform); |
465 | |
466 | if (gl_handle) |
467 | { |
468 | self->gst_app_context = gst_gl_context_new_wrapped (display: self->gst_display, handle: gl_handle, context_type: platform, available_apis: gl_api); |
469 | } |
470 | else |
471 | { |
472 | GST_ERROR_OBJECT (self, "Failed to get handle from GdkGLContext" ); |
473 | return FALSE; |
474 | } |
475 | } |
476 | else |
477 | #endif |
478 | #ifdef HAVE_GST_WAYLAND_SUPPORT |
479 | if (GDK_IS_WAYLAND_DISPLAY (display)) |
480 | { |
481 | platform = GST_GL_PLATFORM_EGL; |
482 | |
483 | GST_DEBUG_OBJECT (self, "got EGL on Wayland!" ); |
484 | |
485 | gl_api = gst_gl_context_get_current_gl_api (platform, NULL, NULL); |
486 | gl_handle = gst_gl_context_get_current_gl_context (context_type: platform); |
487 | |
488 | if (gl_handle) |
489 | { |
490 | struct wl_display *wayland_display; |
491 | |
492 | wayland_display = gdk_wayland_display_get_wl_display (display); |
493 | self->gst_display = GST_GL_DISPLAY (gst_gl_display_wayland_new_with_display (wayland_display)); |
494 | self->gst_app_context = gst_gl_context_new_wrapped (display: self->gst_display, handle: gl_handle, context_type: platform, available_apis: gl_api); |
495 | } |
496 | else |
497 | { |
498 | GST_ERROR_OBJECT (self, "Failed to get handle from GdkGLContext, not using Wayland EGL" ); |
499 | return FALSE; |
500 | } |
501 | } |
502 | else |
503 | #endif |
504 | #if defined(GST_GL_HAVE_PLATFORM_CGL) && defined(GDK_WINDOWING_MACOS) |
505 | if (GDK_IS_MACOS_DISPLAY (display)) |
506 | { |
507 | platform = GST_GL_PLATFORM_CGL; |
508 | |
509 | GST_DEBUG_OBJECT (self, "got CGL on macOS!" ); |
510 | |
511 | gl_api = gst_gl_context_get_current_gl_api (platform, NULL, NULL); |
512 | gl_handle = gst_gl_context_get_current_gl_context (platform); |
513 | |
514 | if (gl_handle) |
515 | { |
516 | self->gst_display = gst_gl_display_new (); |
517 | self->gst_app_context = gst_gl_context_new_wrapped (self->gst_display, gl_handle, platform, gl_api); |
518 | } |
519 | else |
520 | { |
521 | GST_ERROR_OBJECT (self, "Failed to get handle from GdkGLContext, not using macOS CGL" ); |
522 | return FALSE; |
523 | } |
524 | } |
525 | else |
526 | #endif |
527 | #if GST_GL_HAVE_WINDOW_WIN32 && (GST_GL_HAVE_PLATFORM_WGL || GST_GL_HAVE_PLATFORM_EGL) && defined (GDK_WINDOWING_WIN32) |
528 | if (GDK_IS_WIN32_DISPLAY (display)) |
529 | { |
530 | gboolean is_gles = check_win32_gst_gl_api (self->gdk_context, &platform, &gl_api); |
531 | const gchar *gl_type = is_gles ? "EGL" : "WGL" ; |
532 | |
533 | GST_DEBUG_OBJECT (self, "got %s on Win32!" , gl_type); |
534 | |
535 | gl_handle = gst_gl_context_get_current_gl_context (platform); |
536 | |
537 | if (gl_handle) |
538 | { |
539 | /* |
540 | * We must force a win32 GstGL display type and if using desktop GL, the GL_Platform to be WGL |
541 | * and an appropriate GstGL API depending on the gl_api we receive. We also ensure that we use |
542 | * an EGL GstGL API if we are using EGL in GDK. Envvars are required, unless |
543 | * gst_gl_display_new_with_type() is available, unfortunately, so that gst_gl_display_new() does |
544 | * things correctly if we have GstGL built with both EGL and WGL support for the WGL case, |
545 | * otherwise gst_gl_display_new() will assume an EGL display, which won't work for us |
546 | */ |
547 | |
548 | if (gl_api & (GST_GL_API_OPENGL3 | GST_GL_API_OPENGL)) |
549 | { |
550 | #ifdef HAVE_GST_GL_DISPLAY_NEW_WITH_TYPE |
551 | self->gst_display = gst_gl_display_new_with_type (GST_GL_DISPLAY_TYPE_WIN32); |
552 | #else |
553 | #if GST_GL_HAVE_PLATFORM_EGL |
554 | g_message ("If media fails to play, set the envvar `GST_DEBUG=1`, and if GstGL context creation fails" ); |
555 | g_message ("due to \"Couldn't create GL context: Cannot share context with non-EGL context\"," ); |
556 | g_message ("set in the environment `GST_GL_PLATFORM=wgl` and `GST_GL_WINDOW=win32`," ); |
557 | g_message ("and restart the GTK application" ); |
558 | #endif |
559 | |
560 | self->gst_display = gst_gl_display_new (); |
561 | #endif |
562 | } |
563 | |
564 | #if GST_GL_HAVE_PLATFORM_EGL |
565 | else |
566 | { |
567 | gpointer display_ptr = gdk_win32_display_get_egl_display (display); |
568 | self->gst_display = GST_GL_DISPLAY (gst_gl_display_egl_new_with_egl_display (display_ptr)); |
569 | } |
570 | #endif |
571 | |
572 | gst_gl_display_filter_gl_api (self->gst_display, gl_api); |
573 | self->gst_app_context = gst_gl_context_new_wrapped (self->gst_display, gl_handle, platform, gl_api); |
574 | } |
575 | else |
576 | { |
577 | GST_ERROR_OBJECT (self, "Failed to get handle from GdkGLContext, not using %s" , gl_type); |
578 | return FALSE; |
579 | } |
580 | } |
581 | else |
582 | #endif |
583 | { |
584 | GST_INFO_OBJECT (self, "Unsupported GDK display %s for GL" , G_OBJECT_TYPE_NAME (display)); |
585 | return FALSE; |
586 | } |
587 | |
588 | g_assert (self->gst_app_context != NULL); |
589 | |
590 | gst_gl_context_activate (context: self->gst_app_context, TRUE); |
591 | |
592 | if (!gst_gl_context_fill_info (context: self->gst_app_context, error: &error)) |
593 | { |
594 | GST_ERROR_OBJECT (self, "failed to retrieve GDK context info: %s" , error->message); |
595 | g_clear_error (err: &error); |
596 | g_clear_object (&self->gst_app_context); |
597 | g_clear_object (&self->gst_display); |
598 | HANDLE_EXTERNAL_WGL_MAKE_CURRENT (self->gdk_context); |
599 | return FALSE; |
600 | } |
601 | else |
602 | { |
603 | DEACTIVATE_WGL_CONTEXT (self->gdk_context); |
604 | gst_gl_context_activate (context: self->gst_app_context, FALSE); |
605 | } |
606 | |
607 | succeeded = gst_gl_display_create_context (display: self->gst_display, other_context: self->gst_app_context, p_context: &self->gst_context, error: &error); |
608 | |
609 | if (!succeeded) |
610 | { |
611 | GST_ERROR_OBJECT (self, "Couldn't create GL context: %s" , error->message); |
612 | g_error_free (error); |
613 | g_clear_object (&self->gst_app_context); |
614 | g_clear_object (&self->gst_display); |
615 | } |
616 | |
617 | HANDLE_EXTERNAL_WGL_MAKE_CURRENT (self->gdk_context); |
618 | REACTIVATE_WGL_CONTEXT (self->gdk_context); |
619 | return succeeded; |
620 | } |
621 | |
622 | static void |
623 | gtk_gst_sink_set_property (GObject *object, |
624 | guint prop_id, |
625 | const GValue *value, |
626 | GParamSpec *pspec) |
627 | |
628 | { |
629 | GtkGstSink *self = GTK_GST_SINK (object); |
630 | |
631 | switch (prop_id) |
632 | { |
633 | case PROP_PAINTABLE: |
634 | self->paintable = g_value_dup_object (value); |
635 | if (self->paintable == NULL) |
636 | self->paintable = GTK_GST_PAINTABLE (ptr: gtk_gst_paintable_new ()); |
637 | break; |
638 | |
639 | case PROP_GL_CONTEXT: |
640 | self->gdk_context = g_value_dup_object (value); |
641 | if (self->gdk_context != NULL && !gtk_gst_sink_initialize_gl (self)) |
642 | g_clear_object (&self->gdk_context); |
643 | break; |
644 | |
645 | default: |
646 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
647 | break; |
648 | } |
649 | } |
650 | |
651 | static void |
652 | gtk_gst_sink_get_property (GObject *object, |
653 | guint prop_id, |
654 | GValue *value, |
655 | GParamSpec *pspec) |
656 | { |
657 | GtkGstSink *self = GTK_GST_SINK (object); |
658 | |
659 | switch (prop_id) |
660 | { |
661 | case PROP_PAINTABLE: |
662 | g_value_set_object (value, v_object: self->paintable); |
663 | break; |
664 | case PROP_GL_CONTEXT: |
665 | g_value_set_object (value, v_object: self->gdk_context); |
666 | break; |
667 | |
668 | default: |
669 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
670 | break; |
671 | } |
672 | } |
673 | |
674 | static void |
675 | gtk_gst_sink_dispose (GObject *object) |
676 | { |
677 | GtkGstSink *self = GTK_GST_SINK (object); |
678 | |
679 | g_clear_object (&self->paintable); |
680 | g_clear_object (&self->gst_app_context); |
681 | g_clear_object (&self->gst_display); |
682 | g_clear_object (&self->gdk_context); |
683 | |
684 | G_OBJECT_CLASS (gtk_gst_sink_parent_class)->dispose (object); |
685 | } |
686 | |
687 | static void |
688 | gtk_gst_sink_class_init (GtkGstSinkClass * klass) |
689 | { |
690 | GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
691 | GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); |
692 | GstBaseSinkClass *gstbasesink_class = GST_BASE_SINK_CLASS (klass); |
693 | GstVideoSinkClass *gstvideosink_class = GST_VIDEO_SINK_CLASS (klass); |
694 | |
695 | gobject_class->set_property = gtk_gst_sink_set_property; |
696 | gobject_class->get_property = gtk_gst_sink_get_property; |
697 | gobject_class->dispose = gtk_gst_sink_dispose; |
698 | |
699 | gstbasesink_class->set_caps = gtk_gst_sink_set_caps; |
700 | gstbasesink_class->get_times = gtk_gst_sink_get_times; |
701 | gstbasesink_class->query = gtk_gst_sink_query; |
702 | gstbasesink_class->propose_allocation = gtk_gst_sink_propose_allocation; |
703 | gstbasesink_class->get_caps = gtk_gst_sink_get_caps; |
704 | |
705 | gstvideosink_class->show_frame = gtk_gst_sink_show_frame; |
706 | |
707 | /** |
708 | * GtkGstSink:paintable: |
709 | * |
710 | * The paintable that provides the picture for this sink. |
711 | */ |
712 | properties[PROP_PAINTABLE] = |
713 | g_param_spec_object (name: "paintable" , |
714 | P_("paintable" ), |
715 | P_("Paintable providing the picture" ), |
716 | GTK_TYPE_GST_PAINTABLE, |
717 | flags: G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); |
718 | |
719 | /** |
720 | * GtkGstSink:gl-context: |
721 | * |
722 | * The #GdkGLContext to use for GL rendering. |
723 | */ |
724 | properties[PROP_GL_CONTEXT] = |
725 | g_param_spec_object (name: "gl-context" , |
726 | P_("GL context" ), |
727 | P_("GL context to use for rendering" ), |
728 | GDK_TYPE_GL_CONTEXT, |
729 | flags: G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); |
730 | |
731 | g_object_class_install_properties (oclass: gobject_class, n_pspecs: N_PROPS, pspecs: properties); |
732 | |
733 | gst_element_class_set_metadata (klass: gstelement_class, |
734 | longname: "GtkMediaStream Video Sink" , |
735 | classification: "Sink/Video" , description: "The video sink used by GtkMediaStream" , |
736 | author: "Matthew Waters <matthew@centricular.com>, " |
737 | "Benjamin Otte <otte@gnome.org>" ); |
738 | |
739 | gst_element_class_add_static_pad_template (klass: gstelement_class, |
740 | static_templ: >k_gst_sink_template); |
741 | } |
742 | |
743 | static void |
744 | gtk_gst_sink_init (GtkGstSink * gtk_sink) |
745 | { |
746 | } |
747 | |
748 | |