#ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include "glextensions.h" #include static GObjectClass *gst_gl_buffer_parent_class; static void gst_gl_buffer_finalize (GstGLBuffer * buffer) { gst_gl_display_lock (buffer->display); glDeleteTextures (1, &buffer->texture); if (buffer->texture_u) { glDeleteTextures (1, &buffer->texture_u); } if (buffer->texture_v) { glDeleteTextures (1, &buffer->texture_v); } gst_gl_display_unlock (buffer->display); g_object_unref (buffer->display); GST_MINI_OBJECT_CLASS (gst_gl_buffer_parent_class)-> finalize (GST_MINI_OBJECT (buffer)); } static void gst_gl_buffer_init (GstGLBuffer * buffer, gpointer g_class) { } static void gst_gl_buffer_class_init (gpointer g_class, gpointer class_data) { GstMiniObjectClass *mini_object_class = GST_MINI_OBJECT_CLASS (g_class); gst_gl_buffer_parent_class = g_type_class_peek_parent (g_class); mini_object_class->finalize = (GstMiniObjectFinalizeFunction) gst_gl_buffer_finalize; } GType gst_gl_buffer_get_type (void) { static GType _gst_gl_buffer_type; if (G_UNLIKELY (_gst_gl_buffer_type == 0)) { static const GTypeInfo info = { sizeof (GstBufferClass), NULL, NULL, gst_gl_buffer_class_init, NULL, NULL, sizeof (GstGLBuffer), 0, (GInstanceInitFunc) gst_gl_buffer_init, NULL }; _gst_gl_buffer_type = g_type_register_static (GST_TYPE_BUFFER, "GstGLBuffer", &info, 0); } return _gst_gl_buffer_type; } GstGLBuffer * gst_gl_buffer_new (GstGLDisplay * display, int width, int height) { g_return_val_if_fail (display != NULL, NULL); g_return_val_if_fail (width > 0, NULL); g_return_val_if_fail (height > 0, NULL); return gst_gl_buffer_new_with_format (display, GST_GL_BUFFER_FORMAT_RGBA, width, height); } GstGLBuffer * gst_gl_buffer_new_with_format (GstGLDisplay * display, GstGLBufferFormat format, int width, int height) { GstGLBuffer *buffer; g_return_val_if_fail (format != GST_GL_BUFFER_FORMAT_UNKNOWN, NULL); g_return_val_if_fail (display != NULL, NULL); g_return_val_if_fail (width > 0, NULL); g_return_val_if_fail (height > 0, NULL); buffer = (GstGLBuffer *) gst_mini_object_new (GST_TYPE_GL_BUFFER); buffer->display = g_object_ref (display); buffer->width = width; buffer->height = height; buffer->format = format; GST_BUFFER_SIZE (buffer) = gst_gl_buffer_format_get_size (format, width, height); gst_gl_display_lock (buffer->display); glGenTextures (1, &buffer->texture); glBindTexture (GL_TEXTURE_RECTANGLE_ARB, buffer->texture); switch (format) { case GST_GL_BUFFER_FORMAT_RGBA: glTexImage2D (GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA, buffer->width, buffer->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); break; case GST_GL_BUFFER_FORMAT_RGB: glTexImage2D (GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGB, buffer->width, buffer->height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); break; case GST_GL_BUFFER_FORMAT_YUYV: glTexImage2D (GL_TEXTURE_RECTANGLE_ARB, 0, GL_YCBCR_MESA, buffer->width, buffer->height, 0, GL_YCBCR_MESA, GL_UNSIGNED_SHORT_8_8_REV_MESA, NULL); break; case GST_GL_BUFFER_FORMAT_PLANAR444: glTexImage2D (GL_TEXTURE_RECTANGLE_ARB, 0, GL_LUMINANCE, buffer->width, buffer->height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, NULL); glGenTextures (1, &buffer->texture_u); glBindTexture (GL_TEXTURE_RECTANGLE_ARB, buffer->texture_u); glTexImage2D (GL_TEXTURE_RECTANGLE_ARB, 0, GL_LUMINANCE, buffer->width, buffer->height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, NULL); glGenTextures (1, &buffer->texture_v); glBindTexture (GL_TEXTURE_RECTANGLE_ARB, buffer->texture_v); glTexImage2D (GL_TEXTURE_RECTANGLE_ARB, 0, GL_LUMINANCE, buffer->width, buffer->height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, NULL); break; case GST_GL_BUFFER_FORMAT_PLANAR422: glTexImage2D (GL_TEXTURE_RECTANGLE_ARB, 0, GL_LUMINANCE, buffer->width, buffer->height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, NULL); glGenTextures (1, &buffer->texture_u); glBindTexture (GL_TEXTURE_RECTANGLE_ARB, buffer->texture_u); glTexImage2D (GL_TEXTURE_RECTANGLE_ARB, 0, GL_LUMINANCE, GST_ROUND_UP_2 (buffer->width) / 2, buffer->height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, NULL); glGenTextures (1, &buffer->texture_v); glBindTexture (GL_TEXTURE_RECTANGLE_ARB, buffer->texture_v); glTexImage2D (GL_TEXTURE_RECTANGLE_ARB, 0, GL_LUMINANCE, GST_ROUND_UP_2 (buffer->width) / 2, buffer->height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, NULL); break; case GST_GL_BUFFER_FORMAT_PLANAR420: glTexImage2D (GL_TEXTURE_RECTANGLE_ARB, 0, GL_LUMINANCE, buffer->width, buffer->height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, NULL); glGenTextures (1, &buffer->texture_u); glBindTexture (GL_TEXTURE_RECTANGLE_ARB, buffer->texture_u); glTexImage2D (GL_TEXTURE_RECTANGLE_ARB, 0, GL_LUMINANCE, GST_ROUND_UP_2 (buffer->width) / 2, GST_ROUND_UP_2 (buffer->height) / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, NULL); glGenTextures (1, &buffer->texture_v); glBindTexture (GL_TEXTURE_RECTANGLE_ARB, buffer->texture_v); glTexImage2D (GL_TEXTURE_RECTANGLE_ARB, 0, GL_LUMINANCE, GST_ROUND_UP_2 (buffer->width) / 2, GST_ROUND_UP_2 (buffer->height) / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, NULL); break; default: g_warning ("GL buffer format not handled"); } gst_gl_display_unlock (buffer->display); return buffer; } GstGLBuffer * gst_gl_buffer_new_from_video_format (GstGLDisplay * display, GstVideoFormat video_format, int width, int height) { GstGLBuffer *buffer; g_return_val_if_fail (width > 0, NULL); g_return_val_if_fail (height > 0, NULL); buffer = gst_gl_buffer_new_with_format (display, gst_gl_buffer_format_from_video_format (video_format), width, height); switch (video_format) { case GST_VIDEO_FORMAT_RGBx: case GST_VIDEO_FORMAT_BGRx: case GST_VIDEO_FORMAT_xRGB: case GST_VIDEO_FORMAT_xBGR: buffer->is_yuv = FALSE; break; case GST_VIDEO_FORMAT_YUY2: case GST_VIDEO_FORMAT_UYVY: /* counterintuitive: when you use a GL_YCBCR_MESA texture, the * colorspace is automatically converted, so it's referred to * as RGB */ buffer->is_yuv = FALSE; break; case GST_VIDEO_FORMAT_AYUV: case GST_VIDEO_FORMAT_I420: case GST_VIDEO_FORMAT_YV12: buffer->is_yuv = TRUE; break; default: g_assert_not_reached (); } return buffer; } void gst_gl_buffer_upload (GstGLBuffer * buffer, GstVideoFormat video_format, void *data) { int width = buffer->width; int height = buffer->height; GST_DEBUG ("uploading %p %dx%d", data, width, height); g_return_if_fail (buffer->format == gst_gl_buffer_format_from_video_format (video_format)); gst_gl_display_lock (buffer->display); glBindTexture (GL_TEXTURE_RECTANGLE_ARB, buffer->texture); switch (video_format) { case GST_VIDEO_FORMAT_RGBx: glTexSubImage2D (GL_TEXTURE_RECTANGLE_ARB, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data); break; case GST_VIDEO_FORMAT_BGRx: glTexSubImage2D (GL_TEXTURE_RECTANGLE_ARB, 0, 0, 0, width, height, GL_BGRA, GL_UNSIGNED_BYTE, data); break; case GST_VIDEO_FORMAT_AYUV: case GST_VIDEO_FORMAT_xRGB: glTexSubImage2D (GL_TEXTURE_RECTANGLE_ARB, 0, 0, 0, width, height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8, data); break; case GST_VIDEO_FORMAT_xBGR: glTexSubImage2D (GL_TEXTURE_RECTANGLE_ARB, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, data); break; case GST_VIDEO_FORMAT_YUY2: glTexSubImage2D (GL_TEXTURE_RECTANGLE_ARB, 0, 0, 0, width, height, GL_YCBCR_MESA, GL_UNSIGNED_SHORT_8_8_REV_MESA, data); break; case GST_VIDEO_FORMAT_UYVY: glTexSubImage2D (GL_TEXTURE_RECTANGLE_ARB, 0, 0, 0, width, height, GL_YCBCR_MESA, GL_UNSIGNED_SHORT_8_8_MESA, data); break; case GST_VIDEO_FORMAT_I420: case GST_VIDEO_FORMAT_YV12: glTexSubImage2D (GL_TEXTURE_RECTANGLE_ARB, 0, 0, 0, width, height, GL_LUMINANCE, GL_UNSIGNED_BYTE, data); glBindTexture (GL_TEXTURE_RECTANGLE_ARB, buffer->texture_u); glTexSubImage2D (GL_TEXTURE_RECTANGLE_ARB, 0, 0, 0, GST_ROUND_UP_2 (buffer->width) / 2, GST_ROUND_UP_2 (buffer->height) / 2, GL_LUMINANCE, GL_UNSIGNED_BYTE, (guint8 *) data + gst_video_format_get_component_offset (video_format, 1, width, height)); glBindTexture (GL_TEXTURE_RECTANGLE_ARB, buffer->texture_v); glTexSubImage2D (GL_TEXTURE_RECTANGLE_ARB, 0, 0, 0, GST_ROUND_UP_2 (buffer->width) / 2, GST_ROUND_UP_2 (buffer->height) / 2, GL_LUMINANCE, GL_UNSIGNED_BYTE, (guint8 *) data + gst_video_format_get_component_offset (video_format, 2, width, height)); break; default: g_assert_not_reached (); } gst_gl_display_unlock (buffer->display); } void gst_gl_buffer_download (GstGLBuffer * buffer, GstVideoFormat video_format, void *data) { GLuint fbo; g_return_if_fail (buffer->format == gst_gl_buffer_format_from_video_format (video_format)); GST_DEBUG ("downloading"); gst_gl_display_lock (buffer->display); /* we need a reset function */ glMatrixMode (GL_COLOR); glLoadIdentity (); glPixelTransferf (GL_POST_COLOR_MATRIX_RED_BIAS, 0); glPixelTransferf (GL_POST_COLOR_MATRIX_GREEN_BIAS, 0); glPixelTransferf (GL_POST_COLOR_MATRIX_BLUE_BIAS, 0); glGenFramebuffersEXT (1, &fbo); glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, fbo); glFramebufferTexture2DEXT (GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT, GL_TEXTURE_RECTANGLE_ARB, buffer->texture, 0); glDrawBuffer (GL_COLOR_ATTACHMENT1_EXT); glReadBuffer (GL_COLOR_ATTACHMENT1_EXT); g_assert (glCheckFramebufferStatusEXT (GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT); switch (video_format) { case GST_VIDEO_FORMAT_RGBx: glReadPixels (0, 0, buffer->width, buffer->height, GL_RGBA, GL_UNSIGNED_BYTE, data); break; case GST_VIDEO_FORMAT_BGRx: glReadPixels (0, 0, buffer->width, buffer->height, GL_BGRA, GL_UNSIGNED_BYTE, data); break; case GST_VIDEO_FORMAT_xBGR: glReadPixels (0, 0, buffer->width, buffer->height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, data); break; case GST_VIDEO_FORMAT_AYUV: case GST_VIDEO_FORMAT_xRGB: glReadPixels (0, 0, buffer->width, buffer->height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8, data); break; case GST_VIDEO_FORMAT_YUY2: case GST_VIDEO_FORMAT_UYVY: g_warning ("video format not supported for download from GL texture"); break; case GST_VIDEO_FORMAT_I420: case GST_VIDEO_FORMAT_YV12: glReadPixels (0, 0, buffer->width, buffer->height, GL_LUMINANCE, GL_UNSIGNED_BYTE, data); glFramebufferTexture2DEXT (GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT, GL_TEXTURE_RECTANGLE_ARB, buffer->texture_u, 0); glReadPixels (0, 0, GST_ROUND_UP_2 (buffer->width) / 2, GST_ROUND_UP_2 (buffer->height) / 2, GL_LUMINANCE, GL_UNSIGNED_BYTE, (guint8 *) data + gst_video_format_get_component_offset (video_format, 1, buffer->width, buffer->height)); glFramebufferTexture2DEXT (GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT, GL_TEXTURE_RECTANGLE_ARB, buffer->texture_v, 0); glReadPixels (0, 0, GST_ROUND_UP_2 (buffer->width) / 2, GST_ROUND_UP_2 (buffer->height) / 2, GL_LUMINANCE, GL_UNSIGNED_BYTE, (guint8 *) data + gst_video_format_get_component_offset (video_format, 2, buffer->width, buffer->height)); break; default: g_assert_not_reached (); } glDeleteFramebuffersEXT (1, &fbo); gst_gl_display_unlock (buffer->display); } /* buffer format */ GstGLBufferFormat gst_gl_buffer_format_from_video_format (GstVideoFormat format) { switch (format) { case GST_VIDEO_FORMAT_RGBx: case GST_VIDEO_FORMAT_BGRx: case GST_VIDEO_FORMAT_xRGB: case GST_VIDEO_FORMAT_xBGR: case GST_VIDEO_FORMAT_RGBA: case GST_VIDEO_FORMAT_BGRA: case GST_VIDEO_FORMAT_ARGB: case GST_VIDEO_FORMAT_ABGR: case GST_VIDEO_FORMAT_AYUV: return GST_GL_BUFFER_FORMAT_RGBA; case GST_VIDEO_FORMAT_RGB: case GST_VIDEO_FORMAT_BGR: return GST_GL_BUFFER_FORMAT_RGB; case GST_VIDEO_FORMAT_YUY2: case GST_VIDEO_FORMAT_UYVY: return GST_GL_BUFFER_FORMAT_YUYV; case GST_VIDEO_FORMAT_I420: case GST_VIDEO_FORMAT_YV12: return GST_GL_BUFFER_FORMAT_PLANAR420; case GST_VIDEO_FORMAT_UNKNOWN: return GST_GL_BUFFER_FORMAT_UNKNOWN; } g_return_val_if_reached (GST_GL_BUFFER_FORMAT_UNKNOWN); } int gst_gl_buffer_format_get_size (GstGLBufferFormat format, int width, int height) { /* this is not strictly true, but it's used for compatibility with * queue and BaseTransform */ return width * height * 4; } gboolean gst_gl_buffer_format_parse_caps (GstCaps * caps, GstGLBufferFormat * format, int *width, int *height) { GstStructure *structure; int format_as_int; gboolean ret; structure = gst_caps_get_structure (caps, 0); if (!gst_structure_has_name (structure, "video/x-raw-gl")) { return FALSE; } ret = gst_structure_get_int (structure, "format", &format_as_int); *format = format_as_int; ret &= gst_structure_get_int (structure, "width", width); ret &= gst_structure_get_int (structure, "height", height); return ret; }