1 /*
2 * GStreamer
3 * Copyright (C) 2013 Matthew Waters <ystreet00@gmail.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 /**
22 * SECTION:gstglframebuffer
23 * @short_description: OpenGL framebuffer abstraction
24 * @title: GstGLFramebuffer
25 * @see_also: #GstGLBaseMemory, #GstGLMemory, #GstGLContext
26 *
27 * A #GstGLFramebuffer represents and holds an OpenGL framebuffer object with
28 * it's associated attachments.
29 *
30 * A #GstGLFramebuffer can be created with gst_gl_framebuffer_new() or
31 * gst_gl_framebuffer_new_with_default_depth() and bound with
32 * gst_gl_framebuffer_bind(). Other resources can be bound with
33 * gst_gl_framebuffer_attach()
34 *
35 * Note: OpenGL framebuffers are not shareable resources so cannot be used
36 * between multiple OpenGL contexts.
37 *
38 * Since: 1.10
39 */
40
41 #ifdef HAVE_CONFIG_H
42 #include "config.h"
43 #endif
44
45 #include "gstglframebuffer.h"
46
47 #include "gstglcontext.h"
48 #include "gstglcontext_private.h"
49 #include "gstglfuncs.h"
50 #include "gstglmemory.h"
51 #include "gstglrenderbuffer.h"
52
53 #ifndef GL_FRAMEBUFFER_UNDEFINED
54 #define GL_FRAMEBUFFER_UNDEFINED 0x8219
55 #endif
56 #ifndef GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT
57 #define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT 0x8CD6
58 #endif
59 #ifndef GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT
60 #define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7
61 #endif
62 #ifndef GL_FRAMEBUFFER_UNSUPPORTED
63 #define GL_FRAMEBUFFER_UNSUPPORTED 0x8CDD
64 #endif
65 #ifndef GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS
66 #define GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS 0x8CD9
67 #endif
68
69 #ifndef GL_DEPTH_STENCIL_ATTACHMENT
70 #define GL_DEPTH_STENCIL_ATTACHMENT 0x821A
71 #endif
72
73 #ifndef GL_READ_FRAMEBUFFER
74 #define GL_READ_FRAMEBUFFER 0x8CA8
75 #endif
76 #ifndef GL_DRAW_FRAMEBUFFER
77 #define GL_DRAW_FRAMEBUFFER 0x8CA9
78 #endif
79
80 GST_DEBUG_CATEGORY_STATIC (gst_gl_framebuffer_debug);
81 #define GST_CAT_DEFAULT gst_gl_framebuffer_debug
82
83 static void gst_gl_framebuffer_finalize (GObject * object);
84
85 struct _GstGLFramebufferPrivate
86 {
87 guint effective_width;
88 guint effective_height;
89 };
90
91 #define DEBUG_INIT \
92 GST_DEBUG_CATEGORY_INIT (gst_gl_framebuffer_debug, "glframebuffer", 0, "GL Framebuffer");
93
94 G_DEFINE_TYPE_WITH_CODE (GstGLFramebuffer, gst_gl_framebuffer, GST_TYPE_OBJECT,
95 G_ADD_PRIVATE (GstGLFramebuffer) DEBUG_INIT);
96
97 struct fbo_attachment
98 {
99 guint attachment_point;
100 GstGLBaseMemory *mem;
101 };
102
103 static void
_fbo_attachment_init(struct fbo_attachment * attach,guint point,GstGLBaseMemory * mem)104 _fbo_attachment_init (struct fbo_attachment *attach, guint point,
105 GstGLBaseMemory * mem)
106 {
107 attach->attachment_point = point;
108 attach->mem = (GstGLBaseMemory *) gst_memory_ref (GST_MEMORY_CAST (mem));
109 }
110
111 static void
_fbo_attachment_unset(struct fbo_attachment * attach)112 _fbo_attachment_unset (struct fbo_attachment *attach)
113 {
114 if (!attach)
115 return;
116
117 if (attach->mem)
118 gst_memory_unref (GST_MEMORY_CAST (attach->mem));
119 attach->mem = NULL;
120 }
121
122 static void
gst_gl_framebuffer_class_init(GstGLFramebufferClass * klass)123 gst_gl_framebuffer_class_init (GstGLFramebufferClass * klass)
124 {
125 G_OBJECT_CLASS (klass)->finalize = gst_gl_framebuffer_finalize;
126 }
127
128 static void
gst_gl_framebuffer_init(GstGLFramebuffer * fb)129 gst_gl_framebuffer_init (GstGLFramebuffer * fb)
130 {
131 fb->priv = gst_gl_framebuffer_get_instance_private (fb);
132
133 fb->attachments =
134 g_array_new (FALSE, FALSE, (sizeof (struct fbo_attachment)));
135 g_array_set_clear_func (fb->attachments,
136 (GDestroyNotify) _fbo_attachment_unset);
137 }
138
139 static void
_delete_fbo_gl(GstGLContext * context,GstGLFramebuffer * fb)140 _delete_fbo_gl (GstGLContext * context, GstGLFramebuffer * fb)
141 {
142 const GstGLFuncs *gl = context->gl_vtable;
143
144 if (fb->fbo_id)
145 gl->DeleteFramebuffers (1, &fb->fbo_id);
146 fb->fbo_id = 0;
147 }
148
149 static void
gst_gl_framebuffer_finalize(GObject * object)150 gst_gl_framebuffer_finalize (GObject * object)
151 {
152 GstGLFramebuffer *fb = GST_GL_FRAMEBUFFER (object);
153
154 if (fb->context) {
155 if (fb->fbo_id)
156 gst_gl_context_thread_add (fb->context,
157 (GstGLContextThreadFunc) _delete_fbo_gl, fb);
158
159 gst_object_unref (fb->context);
160 fb->context = NULL;
161 }
162
163 if (fb->attachments)
164 g_array_free (fb->attachments, TRUE);
165 fb->attachments = NULL;
166
167 G_OBJECT_CLASS (gst_gl_framebuffer_parent_class)->finalize (object);
168 }
169
170 /**
171 * gst_gl_framebuffer_new:
172 * @context: a #GstGLContext
173 *
174 * This function will internally create an OpenGL framebuffer object and must
175 * be called on @context's OpenGL thread.
176 *
177 * Returns: (transfer full): a new #GstGLFramebuffer
178 *
179 * Since: 1.10
180 */
181 GstGLFramebuffer *
gst_gl_framebuffer_new(GstGLContext * context)182 gst_gl_framebuffer_new (GstGLContext * context)
183 {
184 GstGLFramebuffer *fb;
185 const GstGLFuncs *gl;
186
187 g_return_val_if_fail (GST_IS_GL_CONTEXT (context), NULL);
188 g_return_val_if_fail (gst_gl_context_get_current () == context, NULL);
189
190 gl = context->gl_vtable;
191
192 if (!gl->GenFramebuffers) {
193 GST_ERROR_OBJECT (context, "Framebuffers are not supported!");
194 return NULL;
195 }
196
197 fb = g_object_new (GST_TYPE_GL_FRAMEBUFFER, NULL);
198 fb->context = gst_object_ref (context);
199 gl->GenFramebuffers (1, &fb->fbo_id);
200 gst_object_ref_sink (fb);
201
202 return fb;
203 }
204
205 /**
206 * gst_gl_framebuffer_new_with_default_depth:
207 * @context: a #GstGLContext
208 * @width: width for the depth buffer
209 * @height: for the depth buffer
210 *
211 * This function will internally create an OpenGL framebuffer object and must
212 * be called on @context's OpenGL thread.
213 *
214 * Returns: a new #GstGLFramebuffer with a depth buffer of @width and @height
215 *
216 * Since: 1.10
217 */
218 GstGLFramebuffer *
gst_gl_framebuffer_new_with_default_depth(GstGLContext * context,guint width,guint height)219 gst_gl_framebuffer_new_with_default_depth (GstGLContext * context, guint width,
220 guint height)
221 {
222 GstGLFramebuffer *fb = gst_gl_framebuffer_new (context);
223 GstGLBaseMemoryAllocator *render_alloc;
224 GstGLAllocationParams *params;
225 GstGLBaseMemory *renderbuffer;
226 guint attach_point, attach_type;
227
228 if (!fb)
229 return NULL;
230
231 if (gst_gl_context_get_gl_api (fb->context) & (GST_GL_API_OPENGL |
232 GST_GL_API_OPENGL3)) {
233 attach_point = GL_DEPTH_STENCIL_ATTACHMENT;
234 attach_type = GST_GL_DEPTH24_STENCIL8;
235 } else if (gst_gl_context_get_gl_api (fb->context) & GST_GL_API_GLES2) {
236 attach_point = GL_DEPTH_ATTACHMENT;
237 attach_type = GST_GL_DEPTH_COMPONENT16;
238 } else {
239 g_assert_not_reached ();
240 return NULL;
241 }
242
243 render_alloc = (GstGLBaseMemoryAllocator *)
244 gst_allocator_find (GST_GL_RENDERBUFFER_ALLOCATOR_NAME);
245 params = (GstGLAllocationParams *)
246 gst_gl_renderbuffer_allocation_params_new (context, NULL, attach_type,
247 width, height);
248
249 renderbuffer = gst_gl_base_memory_alloc (render_alloc, params);
250 gst_gl_allocation_params_free (params);
251 gst_object_unref (render_alloc);
252
253 gst_gl_framebuffer_bind (fb);
254 gst_gl_framebuffer_attach (fb, attach_point, renderbuffer);
255 gst_gl_context_clear_framebuffer (fb->context);
256 gst_memory_unref (GST_MEMORY_CAST (renderbuffer));
257
258 return fb;
259 }
260
261 /**
262 * gst_gl_framebuffer_draw_to_texture:
263 * @fb: a #GstGLFramebuffer
264 * @mem: the #GstGLMemory to draw to
265 * @func: (scope call): the function to run
266 * @user_data: data to pass to @func
267 *
268 * Perform the steps necessary to have the output of a glDraw* command in
269 * @func update the contents of @mem.
270 *
271 * Note: this function does not map @mem for writing with OpenGL and that must
272 * be done manually by the caller using any of the mapping functions such as
273 * gst_memory_map() with the map flags %GST_MAP_WRITE | %GST_MAP_GL.
274 *
275 * Must be called with the same OpenGL context current that @fb was created
276 * with.
277 *
278 * Returns: the result of executing @func
279 *
280 * Since: 1.10
281 */
282 gboolean
gst_gl_framebuffer_draw_to_texture(GstGLFramebuffer * fb,GstGLMemory * mem,GstGLFramebufferFunc func,gpointer user_data)283 gst_gl_framebuffer_draw_to_texture (GstGLFramebuffer * fb, GstGLMemory * mem,
284 GstGLFramebufferFunc func, gpointer user_data)
285 {
286 const GstGLFuncs *gl;
287 gboolean ret;
288
289 g_return_val_if_fail (GST_IS_GL_FRAMEBUFFER (fb), FALSE);
290 g_return_val_if_fail (gst_is_gl_memory (GST_MEMORY_CAST (mem)), FALSE);
291 g_return_val_if_fail (gst_gl_context_get_current () == fb->context, FALSE);
292
293 gl = fb->context->gl_vtable;
294
295 GST_TRACE_OBJECT (fb, "drawing to texture %u, dimensions %ix%i", mem->tex_id,
296 gst_gl_memory_get_texture_width (mem),
297 gst_gl_memory_get_texture_height (mem));
298
299 gst_gl_framebuffer_bind (fb);
300 gst_gl_framebuffer_attach (fb, GL_COLOR_ATTACHMENT0, (GstGLBaseMemory *) mem);
301
302 gl->Viewport (0, 0, fb->priv->effective_width, fb->priv->effective_height);
303 if (gst_gl_context_get_gl_api (fb->context) & (GST_GL_API_OPENGL |
304 GST_GL_API_OPENGL3))
305 gl->DrawBuffer (GL_COLOR_ATTACHMENT0);
306
307 ret = func (user_data);
308
309 if (gst_gl_context_get_gl_api (fb->context) & (GST_GL_API_OPENGL |
310 GST_GL_API_OPENGL3))
311 gl->DrawBuffer (GL_COLOR_ATTACHMENT0);
312 gst_gl_context_clear_framebuffer (fb->context);
313
314 return ret;
315 }
316
317 /**
318 * gst_gl_framebuffer_bind:
319 * @fb: a #GstGLFramebuffer
320 *
321 * Bind @fb into the current thread
322 *
323 * Must be called with the same OpenGL context current that @fb was created
324 * with.
325 *
326 * Since: 1.10
327 */
328 void
gst_gl_framebuffer_bind(GstGLFramebuffer * fb)329 gst_gl_framebuffer_bind (GstGLFramebuffer * fb)
330 {
331 const GstGLFuncs *gl;
332
333 g_return_if_fail (GST_IS_GL_FRAMEBUFFER (fb));
334 g_return_if_fail (gst_gl_context_get_current () == fb->context);
335 g_return_if_fail (fb->fbo_id != 0);
336
337 gl = fb->context->gl_vtable;
338
339 gl->BindFramebuffer (GL_FRAMEBUFFER, fb->fbo_id);
340 }
341
342 /**
343 * gst_gl_context_clear_framebuffer:
344 * @context: a #GstGLContext
345 *
346 * Unbind the current framebuffer
347 *
348 * Since: 1.10
349 */
350 void
gst_gl_context_clear_framebuffer(GstGLContext * context)351 gst_gl_context_clear_framebuffer (GstGLContext * context)
352 {
353 const GstGLFuncs *gl;
354
355 g_return_if_fail (GST_IS_GL_CONTEXT (context));
356
357 gl = context->gl_vtable;
358
359 gl->BindFramebuffer (GL_FRAMEBUFFER, 0);
360 }
361
362 static void
_update_effective_dimensions(GstGLFramebuffer * fb)363 _update_effective_dimensions (GstGLFramebuffer * fb)
364 {
365 int i;
366 guint min_width = -1, min_height = -1;
367
368 /* remove the previous attachment */
369 for (i = 0; i < fb->attachments->len; i++) {
370 struct fbo_attachment *attach;
371 int width, height;
372
373 attach = &g_array_index (fb->attachments, struct fbo_attachment, i);
374
375 if (gst_is_gl_memory (GST_MEMORY_CAST (attach->mem))) {
376 GstGLMemory *mem = (GstGLMemory *) attach->mem;
377
378 width = gst_gl_memory_get_texture_width (mem);
379 height = gst_gl_memory_get_texture_height (mem);
380 } else if (gst_is_gl_renderbuffer (GST_MEMORY_CAST (attach->mem))) {
381 GstGLRenderbuffer *mem = (GstGLRenderbuffer *) attach->mem;
382
383 width = mem->width;
384 height = mem->height;
385 } else {
386 g_assert_not_reached ();
387 }
388
389 if (width < min_width)
390 min_width = width;
391 if (height < min_height)
392 min_height = height;
393 }
394
395 fb->priv->effective_width = min_width;
396 fb->priv->effective_height = min_height;
397 }
398
399 static gboolean
_is_valid_attachment_point(guint attachment_point)400 _is_valid_attachment_point (guint attachment_point)
401 {
402 /* all 31 possible color attachments */
403 if (attachment_point >= 0x8CE0 && attachment_point <= 0x8CFF)
404 return TRUE;
405
406 /* depth-stencil attachment */
407 if (attachment_point == 0x821A)
408 return TRUE;
409
410 /* depth attachment */
411 if (attachment_point == 0x8D00)
412 return TRUE;
413
414 /* stencil attachment */
415 if (attachment_point == 0x8D20)
416 return TRUE;
417
418 return FALSE;
419 }
420
421 static void
_attach_gl_memory(GstGLFramebuffer * fb,guint attachment_point,GstGLMemory * mem)422 _attach_gl_memory (GstGLFramebuffer * fb, guint attachment_point,
423 GstGLMemory * mem)
424 {
425 struct fbo_attachment attach;
426 const GstGLFuncs *gl = fb->context->gl_vtable;
427 guint gl_target = gst_gl_texture_target_to_gl (mem->tex_target);
428
429 gst_gl_framebuffer_bind (fb);
430
431 gl->FramebufferTexture2D (GL_FRAMEBUFFER, attachment_point, gl_target,
432 mem->tex_id, 0);
433
434 _fbo_attachment_init (&attach, attachment_point, (GstGLBaseMemory *) mem);
435 fb->attachments = g_array_append_val (fb->attachments, attach);
436 }
437
438 static void
_attach_renderbuffer(GstGLFramebuffer * fb,guint attachment_point,GstGLRenderbuffer * rb)439 _attach_renderbuffer (GstGLFramebuffer * fb, guint attachment_point,
440 GstGLRenderbuffer * rb)
441 {
442 struct fbo_attachment attach;
443 const GstGLFuncs *gl = fb->context->gl_vtable;
444
445 gst_gl_framebuffer_bind (fb);
446 gl->BindRenderbuffer (GL_RENDERBUFFER, rb->renderbuffer_id);
447
448 gl->FramebufferRenderbuffer (GL_FRAMEBUFFER, attachment_point,
449 GL_RENDERBUFFER, rb->renderbuffer_id);
450
451 _fbo_attachment_init (&attach, attachment_point, (GstGLBaseMemory *) rb);
452 fb->attachments = g_array_append_val (fb->attachments, attach);
453 }
454
455 /**
456 * gst_gl_framebuffer_attach:
457 * @fb: a #GstGLFramebuffer
458 * @attachment_point: the OpenGL attachment point to bind @mem to
459 * @mem: the memory object to bind to @attachment_point
460 *
461 * attach @mem to @attachment_point
462 *
463 * Must be called with the same OpenGL context current that @fb was created
464 * with.
465 *
466 * Since: 1.10
467 */
468 void
gst_gl_framebuffer_attach(GstGLFramebuffer * fb,guint attachment_point,GstGLBaseMemory * mem)469 gst_gl_framebuffer_attach (GstGLFramebuffer * fb, guint attachment_point,
470 GstGLBaseMemory * mem)
471 {
472 int i;
473
474 g_return_if_fail (GST_IS_GL_FRAMEBUFFER (fb));
475 g_return_if_fail (gst_gl_context_get_current () == fb->context);
476 g_return_if_fail (_is_valid_attachment_point (attachment_point));
477
478 /* remove the previous attachment */
479 for (i = 0; i < fb->attachments->len; i++) {
480 struct fbo_attachment *attach;
481
482 attach = &g_array_index (fb->attachments, struct fbo_attachment, i);
483
484 if (attach->attachment_point == attachment_point) {
485 g_array_remove_index_fast (fb->attachments, i);
486 break;
487 }
488 }
489
490 if (gst_is_gl_memory (GST_MEMORY_CAST (mem))) {
491 _attach_gl_memory (fb, attachment_point, (GstGLMemory *) mem);
492 } else if (gst_is_gl_renderbuffer (GST_MEMORY_CAST (mem))) {
493 _attach_renderbuffer (fb, attachment_point, (GstGLRenderbuffer *) mem);
494 } else {
495 g_assert_not_reached ();
496 return;
497 }
498
499 _update_effective_dimensions (fb);
500 }
501
502 /**
503 * gst_gl_framebuffer_get_effective_dimensions:
504 * @fb: a #GstGLFramebuffer
505 * @width: (out) (allow-none): output width
506 * @height: (out) (allow-none): output height
507 *
508 * Retrieve the effective dimensions from the current attachments attached to
509 * @fb.
510 *
511 * Since: 1.10
512 */
513 void
gst_gl_framebuffer_get_effective_dimensions(GstGLFramebuffer * fb,guint * width,guint * height)514 gst_gl_framebuffer_get_effective_dimensions (GstGLFramebuffer * fb,
515 guint * width, guint * height)
516 {
517 g_return_if_fail (GST_IS_GL_FRAMEBUFFER (fb));
518
519 if (width)
520 *width = fb->priv->effective_width;
521 if (height)
522 *height = fb->priv->effective_height;
523 }
524
525 /**
526 * gst_gl_context_check_framebuffer_status:
527 * @context: a #GstGLContext
528 * @fbo_target: the GL value of the framebuffer target, GL_FRAMEBUFFER,
529 * GL_READ_FRAMEBUFFER, GL_DRAW_FRAMEBUFFER
530 *
531 * Must be called with @context current.
532 *
533 * Returns: whether whether the current framebuffer is complete
534 *
535 * Since: 1.10
536 */
537 gboolean
gst_gl_context_check_framebuffer_status(GstGLContext * context,guint fbo_target)538 gst_gl_context_check_framebuffer_status (GstGLContext * context,
539 guint fbo_target)
540 {
541 GLenum ret;
542
543 g_return_val_if_fail (GST_IS_GL_CONTEXT (context), FALSE);
544 g_return_val_if_fail (gst_gl_context_get_current () == context, FALSE);
545
546 if (fbo_target != GL_FRAMEBUFFER && fbo_target != GL_READ_FRAMEBUFFER
547 && fbo_target != GL_DRAW_FRAMEBUFFER) {
548 GST_ERROR_OBJECT (context, "fbo target is invalid");
549 return FALSE;
550 }
551
552 /* Don't do expensive framebuffer checks when debugging is disabled */
553 if (!_gst_gl_context_debug_is_enabled (context))
554 return TRUE;
555
556 ret = context->gl_vtable->CheckFramebufferStatus (fbo_target);
557 switch (ret) {
558 case GL_FRAMEBUFFER_COMPLETE:
559 return TRUE;
560 break;
561 case GL_FRAMEBUFFER_UNSUPPORTED:
562 GST_WARNING_OBJECT (context, "GL_FRAMEBUFFER_UNSUPPORTED");
563 break;
564 case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
565 GST_WARNING_OBJECT (context, "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT");
566 break;
567 case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
568 GST_WARNING_OBJECT (context,
569 "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT");
570 break;
571 case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
572 GST_WARNING_OBJECT (context, "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS");
573 break;
574 #if GST_GL_HAVE_OPENGL
575 case GL_FRAMEBUFFER_UNDEFINED:
576 GST_WARNING_OBJECT (context, "GL_FRAMEBUFFER_UNDEFINED");
577 break;
578 #endif
579 default:
580 GST_WARNING_OBJECT (context, "Unknown FBO error: %d (0x%x)", ret, ret);
581 break;
582 }
583
584 return FALSE;
585 }
586
587 /**
588 * gst_gl_framebuffer_get_id:
589 * @fb: a #GstGLFramebuffer
590 *
591 * Returns: the OpenGL id for @fb
592 *
593 * Since: 1.10
594 */
595 guint
gst_gl_framebuffer_get_id(GstGLFramebuffer * fb)596 gst_gl_framebuffer_get_id (GstGLFramebuffer * fb)
597 {
598 g_return_val_if_fail (GST_IS_GL_FRAMEBUFFER (fb), 0);
599
600 return fb->fbo_id;
601 }
602