You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
848 lines
26 KiB
848 lines
26 KiB
/*****************************************************************************
|
|
* converter_sw.c: OpenGL converters for software video formats
|
|
*****************************************************************************
|
|
* Copyright (C) 2016,2017 VLC authors and VideoLAN
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation; either version 2.1 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
|
|
*****************************************************************************/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <assert.h>
|
|
#include <limits.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <vlc_common.h>
|
|
#include <vlc_arrays.h>
|
|
#include <vlc_plugin.h>
|
|
#include <vlc_opengl.h>
|
|
#include <vlc_opengl_interop.h>
|
|
|
|
#include "gl_util.h"
|
|
#include "interop.h"
|
|
|
|
#define PBO_DISPLAY_COUNT 2 /* Double buffering */
|
|
typedef struct
|
|
{
|
|
PFNGLDELETEBUFFERSPROC DeleteBuffers;
|
|
GLuint buffers[PICTURE_PLANE_MAX];
|
|
size_t bytes[PICTURE_PLANE_MAX];
|
|
} picture_sys_t;
|
|
|
|
struct priv
|
|
{
|
|
bool has_gl_3;
|
|
bool has_texture_rg;
|
|
bool has_unpack_subimage;
|
|
void * texture_temp_buf;
|
|
size_t texture_temp_buf_size;
|
|
struct {
|
|
picture_t *display_pics[PBO_DISPLAY_COUNT];
|
|
size_t display_idx;
|
|
} pbo;
|
|
|
|
#define OPENGL_VTABLE_F(X) \
|
|
X(PFNGLGETERRORPROC, GetError) \
|
|
X(PFNGLGETINTEGERVPROC, GetIntegerv) \
|
|
X(PFNGLGETSTRINGPROC, GetString) \
|
|
\
|
|
X(PFNGLACTIVETEXTUREPROC, ActiveTexture) \
|
|
X(PFNGLBINDTEXTUREPROC, BindTexture) \
|
|
X(PFNGLTEXIMAGE2DPROC, TexImage2D) \
|
|
X(PFNGLTEXSUBIMAGE2DPROC, TexSubImage2D) \
|
|
\
|
|
X(PFNGLBINDBUFFERPROC, BindBuffer) \
|
|
X(PFNGLBUFFERDATAPROC, BufferData) \
|
|
X(PFNGLBUFFERSUBDATAPROC, BufferSubData) \
|
|
X(PFNGLDELETEBUFFERSPROC, DeleteBuffers) \
|
|
X(PFNGLGENBUFFERSPROC, GenBuffers) \
|
|
X(PFNGLPIXELSTOREIPROC, PixelStorei)
|
|
struct {
|
|
#define DECLARE_SYMBOL(type, name) type name;
|
|
OPENGL_VTABLE_F(DECLARE_SYMBOL)
|
|
} gl;
|
|
};
|
|
|
|
static void
|
|
pbo_picture_destroy(picture_t *pic)
|
|
{
|
|
picture_sys_t *picsys = pic->p_sys;
|
|
|
|
picsys->DeleteBuffers(pic->i_planes, picsys->buffers);
|
|
|
|
free(picsys);
|
|
}
|
|
|
|
static picture_t *
|
|
pbo_picture_create(const struct vlc_gl_interop *interop)
|
|
{
|
|
const struct priv *priv = interop->priv;
|
|
picture_sys_t *picsys = calloc(1, sizeof(*picsys));
|
|
if (unlikely(picsys == NULL))
|
|
return NULL;
|
|
|
|
picture_resource_t rsc = {
|
|
.p_sys = picsys,
|
|
.pf_destroy = pbo_picture_destroy,
|
|
};
|
|
picture_t *pic = picture_NewFromResource(&interop->fmt_out, &rsc);
|
|
if (pic == NULL)
|
|
{
|
|
free(picsys);
|
|
return NULL;
|
|
}
|
|
|
|
priv->gl.GenBuffers(pic->i_planes, picsys->buffers);
|
|
picsys->DeleteBuffers = priv->gl.DeleteBuffers;
|
|
|
|
/* XXX: needed since picture_NewFromResource override pic planes */
|
|
if (picture_Setup(pic, &interop->fmt_out))
|
|
{
|
|
picture_Release(pic);
|
|
return NULL;
|
|
}
|
|
|
|
assert(pic->i_planes > 0
|
|
&& (unsigned) pic->i_planes <= interop->tex_count);
|
|
|
|
for (int i = 0; i < pic->i_planes; ++i)
|
|
{
|
|
const plane_t *p = &pic->p[i];
|
|
|
|
if( p->i_pitch < 0 || p->i_lines <= 0 ||
|
|
(size_t)p->i_pitch > SIZE_MAX/p->i_lines )
|
|
{
|
|
picture_Release(pic);
|
|
return NULL;
|
|
}
|
|
picsys->bytes[i] = p->i_pitch * p->i_lines;
|
|
}
|
|
return pic;
|
|
}
|
|
|
|
static int
|
|
pbo_data_alloc(const struct vlc_gl_interop *interop, picture_t *pic)
|
|
{
|
|
const struct priv *priv = interop->priv;
|
|
picture_sys_t *picsys = pic->p_sys;
|
|
|
|
priv->gl.GetError();
|
|
|
|
for (int i = 0; i < pic->i_planes; ++i)
|
|
{
|
|
priv->gl.BindBuffer(GL_PIXEL_UNPACK_BUFFER, picsys->buffers[i]);
|
|
priv->gl.BufferData(GL_PIXEL_UNPACK_BUFFER, picsys->bytes[i], NULL,
|
|
GL_DYNAMIC_DRAW);
|
|
|
|
if (priv->gl.GetError() != GL_NO_ERROR)
|
|
{
|
|
msg_Err(interop->gl, "could not alloc PBO buffers");
|
|
priv->gl.DeleteBuffers(i, picsys->buffers);
|
|
return VLC_EGENERIC;
|
|
}
|
|
}
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
static int
|
|
pbo_pics_alloc(const struct vlc_gl_interop *interop)
|
|
{
|
|
struct priv *priv = interop->priv;
|
|
for (size_t i = 0; i < PBO_DISPLAY_COUNT; ++i)
|
|
{
|
|
picture_t *pic = priv->pbo.display_pics[i] =
|
|
pbo_picture_create(interop);
|
|
if (pic == NULL)
|
|
goto error;
|
|
|
|
if (pbo_data_alloc(interop, pic) != VLC_SUCCESS)
|
|
goto error;
|
|
}
|
|
|
|
/* turn off pbo */
|
|
priv->gl.BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
|
|
|
return VLC_SUCCESS;
|
|
error:
|
|
for (size_t i = 0; i < PBO_DISPLAY_COUNT && priv->pbo.display_pics[i]; ++i)
|
|
picture_Release(priv->pbo.display_pics[i]);
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
static int
|
|
tc_pbo_update(const struct vlc_gl_interop *interop, uint32_t textures[],
|
|
const int32_t tex_width[], const int32_t tex_height[],
|
|
picture_t *pic, const size_t *plane_offset)
|
|
{
|
|
(void) plane_offset; assert(plane_offset == NULL);
|
|
struct priv *priv = interop->priv;
|
|
|
|
picture_t *display_pic = priv->pbo.display_pics[priv->pbo.display_idx];
|
|
picture_sys_t *p_sys = display_pic->p_sys;
|
|
priv->pbo.display_idx = (priv->pbo.display_idx + 1) % PBO_DISPLAY_COUNT;
|
|
|
|
for (int i = 0; i < pic->i_planes; i++)
|
|
{
|
|
GLsizeiptr size = pic->p[i].i_lines * pic->p[i].i_pitch;
|
|
const GLvoid *data = pic->p[i].p_pixels;
|
|
priv->gl.BindBuffer(GL_PIXEL_UNPACK_BUFFER,
|
|
p_sys->buffers[i]);
|
|
priv->gl.BufferSubData(GL_PIXEL_UNPACK_BUFFER, 0, size, data);
|
|
|
|
priv->gl.ActiveTexture(GL_TEXTURE0 + i);
|
|
priv->gl.BindTexture(interop->tex_target, textures[i]);
|
|
|
|
//for YUV with interleaved UV, pixel pitch is reported as 1
|
|
if (pic->i_planes == 2 && interop->tex_count == 2 && i == 1)
|
|
priv->gl.PixelStorei(GL_UNPACK_ROW_LENGTH, pic->p[i].i_pitch / (pic->p[i].i_pixel_pitch * 2));
|
|
else
|
|
priv->gl.PixelStorei(GL_UNPACK_ROW_LENGTH, pic->p[i].i_pitch / pic->p[i].i_pixel_pitch);
|
|
|
|
priv->gl.TexSubImage2D(interop->tex_target, 0, 0, 0, tex_width[i], tex_height[i],
|
|
interop->texs[i].format, interop->texs[i].type, NULL);
|
|
priv->gl.PixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
|
}
|
|
|
|
if (pic->i_planes == 1 && interop->tex_count == 2)
|
|
{
|
|
/* For YUV 4:2:2 formats, a single plane is uploaded into 2 textures */
|
|
priv->gl.ActiveTexture(GL_TEXTURE1);
|
|
priv->gl.BindTexture(interop->tex_target, textures[1]);
|
|
priv->gl.PixelStorei(GL_UNPACK_ROW_LENGTH, (pic->p[0].i_pitch / pic->p[0].i_pixel_pitch) >> 1); /* yuv[Y] */
|
|
priv->gl.TexSubImage2D(interop->tex_target, 0, 0, 0, tex_width[1], tex_height[1],
|
|
interop->texs[1].format, interop->texs[1].type, NULL);
|
|
priv->gl.PixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
|
}
|
|
GL_ASSERT_NOERROR(&priv->gl);
|
|
|
|
/* turn off pbo */
|
|
priv->gl.BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
|
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
static int
|
|
tc_common_allocate_textures(const struct vlc_gl_interop *interop, uint32_t textures[],
|
|
const int32_t tex_width[], const int32_t tex_height[])
|
|
{
|
|
const struct priv *priv = interop->priv;
|
|
for (unsigned i = 0; i < interop->tex_count; i++)
|
|
{
|
|
priv->gl.BindTexture(interop->tex_target, textures[i]);
|
|
priv->gl.TexImage2D(interop->tex_target, 0, interop->texs[i].internal,
|
|
tex_width[i], tex_height[i], 0, interop->texs[i].format,
|
|
interop->texs[i].type, NULL);
|
|
GL_ASSERT_NOERROR(&priv->gl);
|
|
}
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
static int
|
|
upload_plane(const struct vlc_gl_interop *interop, unsigned tex_idx,
|
|
int32_t width, int32_t height, size_t pitch, size_t pixel_size,
|
|
const void *pixels, unsigned pixel_pack)
|
|
{
|
|
struct priv *priv = interop->priv;
|
|
GLenum tex_format = interop->texs[tex_idx].format;
|
|
GLenum tex_type = interop->texs[tex_idx].type;
|
|
|
|
/* This unpack alignment is the default, but setting it just in case. */
|
|
priv->gl.PixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
|
|
|
assert(height > 0);
|
|
assert(width > 0);
|
|
assert(pixel_size);
|
|
assert(pitch % pixel_size == 0);
|
|
assert((size_t) width * pixel_size <= pitch);
|
|
|
|
size_t width_bytes = width * pixel_size * pixel_pack;
|
|
|
|
if (!priv->has_unpack_subimage)
|
|
{
|
|
if (pitch != width_bytes)
|
|
{
|
|
size_t aligned_row_len = vlc_align(width_bytes, 4);
|
|
size_t buf_size = aligned_row_len * height;
|
|
const uint8_t *source = pixels;
|
|
uint8_t *destination;
|
|
if (priv->texture_temp_buf_size < buf_size)
|
|
{
|
|
priv->texture_temp_buf =
|
|
realloc_or_free(priv->texture_temp_buf, buf_size);
|
|
if (priv->texture_temp_buf == NULL)
|
|
{
|
|
priv->texture_temp_buf_size = 0;
|
|
return VLC_ENOMEM;
|
|
}
|
|
priv->texture_temp_buf_size = buf_size;
|
|
}
|
|
destination = priv->texture_temp_buf;
|
|
|
|
for (GLsizei h = 0; h < height ; h++)
|
|
{
|
|
memcpy(destination, source, width_bytes);
|
|
source += pitch;
|
|
destination += aligned_row_len;
|
|
}
|
|
priv->gl.TexSubImage2D(interop->tex_target, 0, 0, 0, width, height,
|
|
tex_format, tex_type, priv->texture_temp_buf);
|
|
}
|
|
else
|
|
{
|
|
priv->gl.TexSubImage2D(interop->tex_target, 0, 0, 0, width, height,
|
|
tex_format, tex_type, pixels);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
priv->gl.PixelStorei(GL_UNPACK_ROW_LENGTH, pitch / (pixel_size * pixel_pack ));
|
|
|
|
priv->gl.TexSubImage2D(interop->tex_target, 0, 0, 0, width, height,
|
|
tex_format, tex_type, pixels);
|
|
priv->gl.PixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
|
}
|
|
|
|
GL_ASSERT_NOERROR(&priv->gl);
|
|
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
static int
|
|
tc_common_update(const struct vlc_gl_interop *interop, uint32_t textures[],
|
|
const int32_t tex_width[], const int32_t tex_height[],
|
|
picture_t *pic, const size_t *plane_offset)
|
|
{
|
|
const struct priv *priv = interop->priv;
|
|
int ret = VLC_SUCCESS;
|
|
for (int i = 0; i < pic->i_planes && ret == VLC_SUCCESS; i++)
|
|
{
|
|
assert(textures[i] != 0);
|
|
priv->gl.ActiveTexture(GL_TEXTURE0 + i);
|
|
priv->gl.BindTexture(interop->tex_target, textures[i]);
|
|
const void *pixels = plane_offset != NULL ?
|
|
&pic->p[i].p_pixels[plane_offset[i]] :
|
|
pic->p[i].p_pixels;
|
|
|
|
//are we uploading packed UV plane
|
|
unsigned pixel_pack = (pic->i_planes == 2 && i == 1) ? 2 : 1;
|
|
ret = upload_plane(interop, i, tex_width[i], tex_height[i],
|
|
pic->p[i].i_pitch, pic->p[i].i_pixel_pitch, pixels, pixel_pack);
|
|
}
|
|
|
|
if (pic->i_planes == 1 && interop->tex_count == 2)
|
|
{
|
|
/* For YUV 4:2:2 formats, a single plane is uploaded into 2 textures */
|
|
assert(textures[1] != 0);
|
|
priv->gl.ActiveTexture(GL_TEXTURE1);
|
|
priv->gl.BindTexture(interop->tex_target, textures[1]);
|
|
const void *pixels = plane_offset != NULL ?
|
|
&pic->p[0].p_pixels[plane_offset[0]] :
|
|
pic->p[0].p_pixels;
|
|
|
|
ret = upload_plane(interop, 1, tex_width[1], tex_height[1],
|
|
pic->p[0].i_pitch, pic->p[0].i_pixel_pitch, pixels, 2);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static inline void
|
|
DivideRationalByTwo(vlc_rational_t *r) {
|
|
if (r->num % 2 == 0)
|
|
r->num /= 2;
|
|
else
|
|
r->den *= 2;
|
|
}
|
|
|
|
static bool fixGLFormat(struct vlc_gl_interop *interop, GLint* intfmt, GLint* fmt)
|
|
{
|
|
struct priv *priv = interop->priv;
|
|
if (*intfmt == 0)
|
|
return true;
|
|
|
|
//GLES 3.0, OpenGL 3.0 and OpenGL with GL_ARB_texture_rg
|
|
//don't need transformations
|
|
if (priv->has_gl_3
|
|
|| (priv->has_texture_rg && interop->gl->api_type == VLC_OPENGL))
|
|
return true;
|
|
|
|
//for GLES2 GL_EXT_texture_rg we need to use GL_RED/GL_RG as internal format
|
|
if (priv->has_texture_rg)
|
|
{
|
|
switch (*intfmt) {
|
|
case GL_R8:
|
|
*intfmt = GL_RED;
|
|
*fmt = GL_RED;
|
|
break;
|
|
case GL_RG8:
|
|
*intfmt = GL_RG;
|
|
*fmt = GL_RG;
|
|
break;
|
|
case GL_R16UI:
|
|
case GL_RG16UI:
|
|
return false;
|
|
default:
|
|
vlc_assert_unreachable();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//fallback to GL_LUMINANCE / GL_LUMINANCE_ALPHA
|
|
switch (*intfmt) {
|
|
case GL_R8:
|
|
*intfmt = GL_LUMINANCE;
|
|
*fmt = GL_LUMINANCE;
|
|
break;
|
|
case GL_R16UI:
|
|
if (interop->gl->api_type == VLC_OPENGL_ES2)
|
|
return false;
|
|
|
|
*intfmt = GL_LUMINANCE16;
|
|
*fmt = GL_LUMINANCE;
|
|
break;
|
|
case GL_RG8:
|
|
*intfmt = GL_LUMINANCE_ALPHA;
|
|
*fmt = GL_LUMINANCE_ALPHA;
|
|
break;
|
|
case GL_RG16UI:
|
|
if (interop->gl->api_type == VLC_OPENGL_ES2)
|
|
return false;
|
|
|
|
*intfmt = GL_LUMINANCE16_ALPHA16;
|
|
*fmt = GL_LUMINANCE_ALPHA;
|
|
break;
|
|
default:
|
|
vlc_assert_unreachable();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static int
|
|
interop_yuv_base_init(struct vlc_gl_interop *interop,
|
|
vlc_fourcc_t chroma, const vlc_chroma_description_t *desc)
|
|
{
|
|
struct interop_formats
|
|
{
|
|
GLint intfmt;
|
|
GLint fmt;
|
|
GLint plane2_intfmt;
|
|
GLint plane2_fmt;
|
|
GLint type;
|
|
GLint plane2_type;
|
|
} formats[] = {
|
|
// 3 and 4 planes
|
|
// 8 bits pixels
|
|
{ GL_R8, GL_RED, 0, 0, GL_UNSIGNED_BYTE, 0 },
|
|
// 16 bits pixels
|
|
{ GL_R16UI, GL_RED, 0, 0, GL_UNSIGNED_SHORT, 0 },
|
|
// 2 planes
|
|
// 8 bits pixels
|
|
{ GL_R8, GL_RED, GL_RG8, GL_RG, GL_UNSIGNED_BYTE, GL_UNSIGNED_BYTE },
|
|
// 16 bits pixels
|
|
{ GL_R16UI, GL_RED, GL_RG16UI, GL_RG_INTEGER, GL_UNSIGNED_SHORT, GL_UNSIGNED_SHORT },
|
|
// 1 plane is a special case that will be handled explicitly
|
|
};
|
|
|
|
if (desc->plane_count == 1)
|
|
{
|
|
if (chroma == VLC_CODEC_VUYA)
|
|
{
|
|
interop->tex_count = 2;
|
|
interop->texs[0] = (struct vlc_gl_tex_cfg) {
|
|
{ 1, 1 }, { 1, 1 },
|
|
GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE
|
|
};
|
|
return VLC_SUCCESS;
|
|
}
|
|
else if (desc->pixel_size != 2)
|
|
{
|
|
msg_Warn(interop->gl, "unsupported chroma %.4s", (char*)&chroma);
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
/* Only YUV 4:2:2 formats */
|
|
/* The pictures have only 1 plane, but it is uploaded twice, once to
|
|
* access the Y components, once to access the UV components. See
|
|
* #26712. */
|
|
GLint intfmt = GL_RG8;
|
|
GLint fmt = GL_RG;
|
|
if (!fixGLFormat(interop, &intfmt, &fmt))
|
|
return VLC_EGENERIC;
|
|
|
|
interop->tex_count = 2;
|
|
interop->texs[0] = (struct vlc_gl_tex_cfg) {
|
|
{ 1, 1 }, { 1, 1 },
|
|
intfmt, fmt, GL_UNSIGNED_BYTE
|
|
};
|
|
interop->texs[1] = (struct vlc_gl_tex_cfg) {
|
|
{ 1, 2 }, { 1, 1 },
|
|
GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE
|
|
};
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
uint8_t format_index = 0;
|
|
switch (desc->plane_count)
|
|
{
|
|
case 4:
|
|
case 3:
|
|
break;
|
|
case 2:
|
|
format_index += 2;
|
|
break;
|
|
default:
|
|
vlc_assert_unreachable();
|
|
break;
|
|
}
|
|
|
|
switch (desc->pixel_size)
|
|
{
|
|
case 1:
|
|
break;
|
|
case 2:
|
|
format_index += 1;
|
|
break;
|
|
default:
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
assert(format_index < ARRAY_SIZE(formats));
|
|
|
|
struct interop_formats* format = &formats[format_index];
|
|
|
|
GLint plane1_intfmt = format->intfmt;
|
|
GLint plane1_fmt = format->fmt;
|
|
if (!fixGLFormat(interop, &plane1_intfmt, &plane1_fmt))
|
|
return VLC_EGENERIC;
|
|
|
|
GLint plane2_intfmt = format->plane2_intfmt;
|
|
GLint plane2_fmt = format->plane2_fmt;
|
|
if (!fixGLFormat(interop, &plane2_intfmt, &plane2_fmt))
|
|
return VLC_EGENERIC;
|
|
|
|
msg_Dbg(interop, "Using format at index %u", format_index);
|
|
msg_Dbg(interop, "Plane1: fmt=%#x intfmt=%#x type=%#x", plane1_fmt,
|
|
plane1_intfmt, format->type);
|
|
msg_Dbg(interop, "Plane2: fmt=%#x intfmt=%#x type=%#x", plane2_fmt,
|
|
plane2_intfmt, format->plane2_type);
|
|
|
|
|
|
if (desc->pixel_size == 2)
|
|
{
|
|
if (vlc_gl_interop_GetTexFormatSize(interop, GL_TEXTURE_2D, format->fmt,
|
|
format->intfmt, GL_UNSIGNED_SHORT) != 16)
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
if (desc->plane_count >= 3)
|
|
{
|
|
interop->tex_count = desc->plane_count;
|
|
for (unsigned i = 0; i < interop->tex_count; ++i )
|
|
{
|
|
interop->texs[i] = (struct vlc_gl_tex_cfg) {
|
|
{ desc->p[i].w.num, desc->p[i].w.den },
|
|
{ desc->p[i].h.num, desc->p[i].h.den },
|
|
plane1_intfmt, plane1_fmt, format->type
|
|
};
|
|
}
|
|
}
|
|
else if (desc->plane_count == 2)
|
|
{
|
|
interop->tex_count = 2;
|
|
|
|
if (desc->pixel_size == 2 &&
|
|
vlc_gl_interop_GetTexFormatSize(interop, GL_TEXTURE_2D, format->plane2_fmt,
|
|
format->plane2_intfmt, format->plane2_type) != 16)
|
|
{
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
interop->texs[0] = (struct vlc_gl_tex_cfg) {
|
|
{ desc->p[0].w.num, desc->p[0].w.den },
|
|
{ desc->p[0].h.num, desc->p[0].h.den },
|
|
plane1_intfmt, plane1_fmt, format->type
|
|
};
|
|
interop->texs[1] = (struct vlc_gl_tex_cfg) {
|
|
{ desc->p[1].w.num, desc->p[1].w.den },
|
|
{ desc->p[1].h.num, desc->p[1].h.den },
|
|
plane2_intfmt, plane2_fmt, format->plane2_type
|
|
};
|
|
|
|
/*
|
|
* If plane_count == 2, then the chroma is semiplanar: the U and V
|
|
* planes are packed in the second plane. As a consequence, the
|
|
* horizontal scaling, as reported in the vlc_chroma_description_t, is
|
|
* doubled.
|
|
*
|
|
* But once imported as an OpenGL texture, both components are stored
|
|
* in a single texel (the two first components of the vec4).
|
|
* Therefore, from OpenGL, the width is not doubled, so the horizontal
|
|
* scaling must be divided by 2 to compensate.
|
|
*/
|
|
DivideRationalByTwo(&interop->texs[1].w);
|
|
}
|
|
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
static int
|
|
interop_rgb_base_init(struct vlc_gl_interop *interop, vlc_fourcc_t chroma)
|
|
{
|
|
switch (chroma)
|
|
{
|
|
case VLC_CODEC_RGB24:
|
|
interop->texs[0] = (struct vlc_gl_tex_cfg) {
|
|
{ 1, 1 }, { 1, 1 }, GL_RGB, GL_RGB, GL_UNSIGNED_BYTE
|
|
};
|
|
break;
|
|
#ifdef GL_BGR
|
|
case VLC_CODEC_BGR24:
|
|
interop->texs[0] = (struct vlc_gl_tex_cfg) {
|
|
{ 1, 1 }, { 1, 1 }, GL_RGB, GL_BGR, GL_UNSIGNED_BYTE
|
|
};
|
|
break;
|
|
#endif
|
|
|
|
case VLC_CODEC_RGBA:
|
|
interop->texs[0] = (struct vlc_gl_tex_cfg) {
|
|
{ 1, 1 }, { 1, 1 }, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE
|
|
};
|
|
break;
|
|
case VLC_CODEC_BGRA: {
|
|
if (vlc_gl_interop_GetTexFormatSize(interop, GL_TEXTURE_2D, GL_BGRA, GL_RGBA,
|
|
GL_UNSIGNED_BYTE) != 32)
|
|
return VLC_EGENERIC;
|
|
interop->texs[0] = (struct vlc_gl_tex_cfg) {
|
|
{ 1, 1 }, { 1, 1 }, GL_RGBA, GL_BGRA, GL_UNSIGNED_BYTE
|
|
};
|
|
break;
|
|
}
|
|
default:
|
|
return VLC_EGENERIC;
|
|
}
|
|
interop->tex_count = 1;
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
static void
|
|
interop_xyz12_init(struct vlc_gl_interop *interop)
|
|
{
|
|
interop->tex_count = 1;
|
|
interop->tex_target = GL_TEXTURE_2D;
|
|
interop->texs[0] = (struct vlc_gl_tex_cfg) {
|
|
{ 1, 1 }, { 1, 1 }, GL_RGB, GL_RGB, GL_UNSIGNED_SHORT
|
|
};
|
|
}
|
|
|
|
static int
|
|
opengl_interop_init(struct vlc_gl_interop *interop,
|
|
vlc_fourcc_t chroma, video_color_space_t yuv_space)
|
|
{
|
|
bool is_yuv = vlc_fourcc_IsYUV(chroma);
|
|
const vlc_chroma_description_t *desc =
|
|
vlc_fourcc_GetChromaDescription(chroma);
|
|
if (!desc)
|
|
return VLC_EGENERIC;
|
|
|
|
assert(!interop->fmt_out.p_palette);
|
|
interop->fmt_out.i_chroma = chroma;
|
|
interop->fmt_out.space = yuv_space;
|
|
interop->tex_target = GL_TEXTURE_2D;
|
|
|
|
if (chroma == VLC_CODEC_XYZ12)
|
|
{
|
|
interop_xyz12_init(interop);
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
if (is_yuv)
|
|
return interop_yuv_base_init(interop, chroma, desc);
|
|
|
|
return interop_rgb_base_init(interop, chroma);
|
|
}
|
|
|
|
static void
|
|
opengl_interop_generic_deinit(struct vlc_gl_interop *interop)
|
|
{
|
|
struct priv *priv = interop->priv;
|
|
for (size_t i = 0; i < PBO_DISPLAY_COUNT && priv->pbo.display_pics[i]; ++i)
|
|
picture_Release(priv->pbo.display_pics[i]);
|
|
free(priv->texture_temp_buf);
|
|
free(priv);
|
|
}
|
|
|
|
static int
|
|
opengl_interop_generic_init(struct vlc_gl_interop *interop, bool allow_dr)
|
|
{
|
|
|
|
struct priv *priv = calloc(1, sizeof(struct priv));
|
|
if (unlikely(priv == NULL))
|
|
return VLC_ENOMEM;
|
|
|
|
interop->priv = priv;
|
|
|
|
#define LOAD_SYMBOL(type, name) \
|
|
priv->gl.name = vlc_gl_GetProcAddress(interop->gl, "gl" # name); \
|
|
assert(priv->gl.name != NULL);
|
|
|
|
OPENGL_VTABLE_F(LOAD_SYMBOL);
|
|
|
|
struct vlc_gl_extension_vt extension_vt;
|
|
vlc_gl_LoadExtensionFunctions(interop->gl, &extension_vt);
|
|
|
|
/* OpenGL or OpenGL ES2 with GL_EXT_unpack_subimage ext */
|
|
priv->has_unpack_subimage = interop->gl->api_type == VLC_OPENGL
|
|
|| vlc_gl_HasExtension(&extension_vt, "GL_EXT_unpack_subimage");
|
|
|
|
/* RG textures are available natively since OpenGL 3.0 and OpenGL ES 3.0 */
|
|
priv->has_gl_3 = vlc_gl_GetVersionMajor(&extension_vt) >= 3;
|
|
|
|
priv->has_texture_rg =
|
|
(interop->gl->api_type == VLC_OPENGL
|
|
&& vlc_gl_HasExtension(&extension_vt, "GL_ARB_texture_rg"))
|
|
|| (interop->gl->api_type == VLC_OPENGL_ES2
|
|
&& vlc_gl_HasExtension(&extension_vt, "GL_EXT_texture_rg"));
|
|
|
|
video_color_space_t space;
|
|
const vlc_fourcc_t *list;
|
|
const bool is_yup = vlc_fourcc_IsYUV(interop->fmt_in.i_chroma);
|
|
|
|
if (is_yup)
|
|
{
|
|
GLint max_texture_units = 0;
|
|
priv->gl.GetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &max_texture_units);
|
|
if (max_texture_units < 3)
|
|
goto error;
|
|
|
|
list = vlc_fourcc_GetYUVFallback(interop->fmt_in.i_chroma);
|
|
space = interop->fmt_in.space;
|
|
}
|
|
else if (interop->fmt_in.i_chroma == VLC_CODEC_XYZ12)
|
|
{
|
|
list = NULL;
|
|
space = COLOR_SPACE_UNDEF;
|
|
}
|
|
else
|
|
{
|
|
list = vlc_fourcc_GetRGBFallback(interop->fmt_in.i_chroma);
|
|
space = COLOR_SPACE_UNDEF;
|
|
}
|
|
|
|
/* The pictures are uploaded upside-down */
|
|
video_format_TransformBy(&interop->fmt_out, TRANSFORM_VFLIP);
|
|
|
|
/* Check whether the given chroma is translatable to OpenGL. */
|
|
vlc_fourcc_t i_chroma = interop->fmt_in.i_chroma;
|
|
int ret = opengl_interop_init(interop, i_chroma, space);
|
|
if (ret == VLC_SUCCESS)
|
|
goto interop_init;
|
|
|
|
if (!is_yup)
|
|
{
|
|
i_chroma = VLC_CODEC_RGBA;
|
|
ret = opengl_interop_init(interop, i_chroma, space);
|
|
if (ret == VLC_SUCCESS)
|
|
goto interop_init;
|
|
}
|
|
|
|
if (list == NULL)
|
|
goto error;
|
|
|
|
/* Check whether any fallback for the chroma is translatable to OpenGL. */
|
|
while (*list)
|
|
{
|
|
ret = opengl_interop_init(interop, *list, space);
|
|
if (ret == VLC_SUCCESS)
|
|
{
|
|
i_chroma = *list;
|
|
goto interop_init;
|
|
}
|
|
list++;
|
|
}
|
|
|
|
goto error;
|
|
|
|
interop_init:
|
|
/* We found a chroma with matching parameters for OpenGL. The interop can
|
|
* be created. */
|
|
|
|
interop->fmt_in.i_chroma = i_chroma;
|
|
|
|
static const struct vlc_gl_interop_ops ops = {
|
|
.allocate_textures = tc_common_allocate_textures,
|
|
.update_textures = tc_common_update,
|
|
.close = opengl_interop_generic_deinit,
|
|
};
|
|
interop->ops = &ops;
|
|
|
|
if (allow_dr && priv->has_unpack_subimage)
|
|
{
|
|
/* Ensure we do direct rendering / PBO with OpenGL 3.0 or higher. */
|
|
const unsigned char *ogl_version = priv->gl.GetString(GL_VERSION);
|
|
const bool glver_ok = strverscmp((const char *)ogl_version, "3.0") >= 0;
|
|
|
|
const bool has_pbo = glver_ok &&
|
|
(vlc_gl_HasExtension(&extension_vt, "GL_ARB_pixel_buffer_object") ||
|
|
vlc_gl_HasExtension(&extension_vt, "GL_EXT_pixel_buffer_object"));
|
|
|
|
const bool supports_pbo = has_pbo && priv->gl.BufferData
|
|
&& priv->gl.BufferSubData;
|
|
if (supports_pbo && pbo_pics_alloc(interop) == VLC_SUCCESS)
|
|
{
|
|
static const struct vlc_gl_interop_ops pbo_ops = {
|
|
.allocate_textures = tc_common_allocate_textures,
|
|
.update_textures = tc_pbo_update,
|
|
.close = opengl_interop_generic_deinit,
|
|
};
|
|
interop->ops = &pbo_ops;
|
|
msg_Dbg(interop->gl, "PBO support enabled");
|
|
}
|
|
}
|
|
|
|
return VLC_SUCCESS;
|
|
|
|
error:
|
|
free(priv);
|
|
interop->priv = NULL;
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
static int OpenInteropSW(vlc_object_t *obj)
|
|
{
|
|
struct vlc_gl_interop *interop = (void *) obj;
|
|
return opengl_interop_generic_init(interop, false);
|
|
}
|
|
|
|
static int OpenInteropDirectRendering(vlc_object_t *obj)
|
|
{
|
|
struct vlc_gl_interop *interop = (void *) obj;
|
|
return opengl_interop_generic_init(interop, true);
|
|
}
|
|
|
|
vlc_module_begin ()
|
|
set_description("Software OpenGL interop")
|
|
set_capability("opengl sw interop", 1)
|
|
set_callback(OpenInteropSW)
|
|
set_subcategory(SUBCAT_VIDEO_VOUT)
|
|
add_shortcut("sw")
|
|
|
|
add_submodule()
|
|
set_callback(OpenInteropDirectRendering)
|
|
set_capability("opengl sw interop", 2)
|
|
add_shortcut("pbo")
|
|
vlc_module_end ()
|
|
|