Skip to content
Snippets Groups Projects
Commit 663c8cb3 authored by Niklas Haas's avatar Niklas Haas
Browse files

opengl: add raw texture interop API

VLC needs this in order to integrate the pl_opengl abstraction into
their OpenGL-based video output module.

Closes #91
parent e562b731
No related branches found
No related tags found
1 merge request!98Add support for generalized samplers, OpenGL interop, etc.
......@@ -2,7 +2,7 @@ project('libplacebo', ['c', 'cpp'],
license: 'LGPL2.1+',
default_options: ['c_std=c99', 'cpp_std=c++11', 'warning_level=2'],
meson_version: '>=0.49',
version: '2.70.0',
version: '2.71.0',
)
# Version number
......
......@@ -102,4 +102,59 @@ const struct pl_swapchain *pl_opengl_create_swapchain(const struct pl_opengl *gl
void pl_opengl_swapchain_update_fb(const struct pl_swapchain *sw,
const struct pl_opengl_framebuffer *fb);
struct pl_opengl_wrap_params {
// The GLuint texture object itself.
unsigned int texture;
// The image's dimensions (unused dimensions must be 0)
int width;
int height;
int depth;
// The GLenum for the texture target to use, e.g. GL_TEXTURE_2D. Optional.
// If this is left as 0, the target is inferred from the number of
// dimensions. Users may want to set this to something specific like
// GL_TEXTURE_EXTERNAL_OES depending on the nature of the texture.
unsigned int target;
// The texture's GLint sized internal format (e.g. GL_RGBA16F)
int iformat;
// The GLint texture min/mag filter. libplacebo does not distinguish
// between magnification/minification, so both must be the same. Optional,
// defaults to GL_LINEAR.
int filter;
// The GLint texture address mode. libplacebo does not distinguish by
// dimension, so all must be the same. Optional, defaults to GL_REPEAT.
int address_mode;
};
// Wraps an external OpenGL object into a `pl_tex` abstraction. Due to the
// internally synchronized nature of OpenGL, no explicit synchronization
// is needed between libplacebo `pl_tex_` operations, and host accesses to
// the texture. Wrapping the same OpenGL texture multiple times is permitted.
// Note that this function transfers no ownership.
//
// This wrapper can be destroyed by simply calling `pl_tex_destroy` on it,
// which will not destroy the underlying OpenGL texture.
//
// This function may fail, in which case it returns NULL.
const struct pl_tex *pl_opengl_wrap(const struct pl_gpu *gpu,
const struct pl_opengl_wrap_params *params);
// Analogous to `pl_opengl_wrap`, this function takes any `pl_tex` (including
// ones created by `pl_tex_create`) and unwraps it to expose the underlying
// OpenGL texture to the user. Note that this function transfers no ownership,
// i.e. the texture object and framebuffer shall not be destroyed by the user.
//
// Returns the OpenGL texture. `out_target` and `out_iformat` will be updated
// to hold the target type and internal format, respectively. (Optional)
//
// For renderable/blittable textures, `out_fbo` will be updated to the ID of
// the framebuffer attached to this texture, or 0 if there is none. (Optional)
unsigned int pl_opengl_unwrap(const struct pl_gpu *gpu, const struct pl_tex *tex,
unsigned int *out_target, int *out_iformat,
unsigned int *out_fbo);
#endif // LIBPLACEBO_OPENGL_H_
......@@ -297,6 +297,7 @@ error:
struct pl_tex_gl {
GLenum target;
GLuint texture;
bool wrapped;
GLint filter;
GLuint fbo; // or 0
bool wrapped_fb;
......@@ -310,9 +311,10 @@ struct pl_tex_gl {
static void gl_tex_destroy(const struct pl_gpu *gpu, const struct pl_tex *tex)
{
struct pl_tex_gl *tex_gl = TA_PRIV(tex);
if (tex_gl->fbo)
if (tex_gl->fbo && !tex_gl->wrapped_fb)
glDeleteFramebuffers(1, &tex_gl->fbo);
glDeleteTextures(1, &tex_gl->texture);
if (!tex_gl->wrapped)
glDeleteTextures(1, &tex_gl->texture);
talloc_free((void *) tex);
gl_check_err(gpu, "gl_tex_destroy");
......@@ -569,6 +571,193 @@ error:
return NULL;
}
const struct pl_tex *pl_opengl_wrap(const struct pl_gpu *gpu,
const struct pl_opengl_wrap_params *params)
{
struct pl_gl *p = TA_PRIV(gpu);
const struct pl_fmt *fmt = NULL;
const struct gl_format *glfmt = NULL;
for (int i = 0; i < gpu->num_formats; i++) {
const struct gl_format **glfmtp = TA_PRIV(gpu->formats[i]);
if ((*glfmtp)->ifmt == params->iformat) {
fmt = gpu->formats[i];
glfmt = *glfmtp;
break;
}
}
if (!fmt) {
PL_ERR(gpu, "Failed mapping iformat %d to any equivalent `pl_fmt`",
params->iformat);
return NULL;
}
enum pl_sampler_type sampler_type;
switch (params->target) {
case GL_TEXTURE_1D:
if (params->width || params->depth) {
PL_ERR(gpu, "Invalid texture dimensions for GL_TEXTURE_1D");
return NULL;
}
// fall through
case GL_TEXTURE_2D:
if (params->depth) {
PL_ERR(gpu, "Invalid texture dimensions for GL_TEXTURE_2D");
return NULL;
}
// fall through
case 0:
case GL_TEXTURE_3D:
sampler_type = PL_SAMPLER_NORMAL;
break;
case GL_TEXTURE_RECTANGLE: sampler_type = PL_SAMPLER_RECT; break;
case GL_TEXTURE_EXTERNAL_OES: sampler_type = PL_SAMPLER_EXTERNAL; break;
default:
PL_ERR(gpu, "Failed mapping texture target %u to any equivalent "
"`pl_sampler_type`", params->target);
return NULL;
}
enum pl_tex_sample_mode sample_mode;
switch (params->filter) {
case 0: // fall through
case GL_LINEAR: sample_mode = PL_TEX_SAMPLE_LINEAR; break;
case GL_NEAREST: sample_mode = PL_TEX_SAMPLE_NEAREST; break;
default:
PL_ERR(gpu, "Failed mapping texture filter %d to `pl_tex_sample_mode`",
params->filter);
return NULL;
}
enum pl_tex_address_mode address_mode;
switch (params->address_mode) {
case 0: // fall through
case GL_REPEAT: address_mode = PL_TEX_ADDRESS_REPEAT; break;
case GL_CLAMP_TO_EDGE: address_mode = PL_TEX_ADDRESS_CLAMP; break;
case GL_MIRRORED_REPEAT: address_mode = PL_TEX_ADDRESS_MIRROR; break;
default:
PL_ERR(gpu, "Failed mapping address mode %d to `pl_tex_address_mode`",
params->address_mode);
return NULL;
}
struct pl_tex *tex = talloc_priv(NULL, struct pl_tex, struct pl_tex_gl);
struct pl_tex_gl *tex_gl = TA_PRIV(tex);
*tex = (struct pl_tex) {
.sampler_type = sampler_type,
.params = {
.w = params->width,
.h = params->height,
.d = params->depth,
.format = fmt,
.sampleable = true,
.storable = fmt->caps & PL_FMT_CAP_STORABLE,
.host_writable = true,
.sample_mode = sample_mode,
.address_mode = address_mode,
},
};
*tex_gl = (struct pl_tex_gl) {
.target = params->target,
.texture = params->texture,
.wrapped = true,
.filter = PL_DEF(params->filter, GL_LINEAR),
.iformat = glfmt->ifmt,
.format = glfmt->fmt,
.type = glfmt->type,
};
int dims = pl_tex_params_dimension(tex->params);
if (!tex_gl->target) {
switch (dims) {
case 1: tex_gl->target = GL_TEXTURE_1D; break;
case 2: tex_gl->target = GL_TEXTURE_2D; break;
case 3: tex_gl->target = GL_TEXTURE_3D; break;
}
}
bool can_fbo = (fmt->caps & PL_FMT_CAP_RENDERABLE) &&
sampler_type != PL_SAMPLER_EXTERNAL &&
dims < 3;
if (can_fbo) {
glGenFramebuffers(1, &tex_gl->fbo);
glBindFramebuffer(GL_FRAMEBUFFER, tex_gl->fbo);
switch (dims) {
case 1:
glFramebufferTexture1D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
tex_gl->target, tex_gl->texture, 0);
break;
case 2:
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
tex_gl->target, tex_gl->texture, 0);
break;
case 3: abort();
}
GLenum err = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (err != GL_FRAMEBUFFER_COMPLETE) {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
PL_ERR(gpu, "Failed creating framebuffer: error code %d", err);
goto error;
}
tex->params.renderable = true;
if (dims == 2 && (fmt->caps & PL_FMT_CAP_BLITTABLE)) {
tex->params.blit_src = true;
tex->params.blit_dst = true;
}
if (p->gles_ver) {
GLint read_type = 0, read_fmt = 0;
glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &read_type);
glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &read_fmt);
tex->params.host_readable = read_type == tex_gl->type &&
read_fmt == tex_gl->format;
} else {
tex->params.host_readable = true;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
if (!gl_check_err(gpu, "pl_opengl_wrap: fbo"))
goto error;
}
return tex;
error:
gl_tex_destroy(gpu, tex);
return NULL;
}
unsigned int pl_opengl_unwrap(const struct pl_gpu *gpu, const struct pl_tex *tex,
unsigned int *out_target, int *out_iformat,
unsigned int *out_fbo)
{
struct pl_tex_gl *tex_gl = TA_PRIV(tex);
if (!tex_gl->texture) {
PL_ERR(gpu, "Trying to call `pl_opengl_unwrap` on a pseudo-texture "
"(perhaps obtained by `pl_swapchain_start_frame`?)");
return 0;
}
if (out_target)
*out_target = tex_gl->target;
if (out_iformat)
*out_iformat = tex_gl->iformat;
if (out_fbo)
*out_fbo = tex_gl->fbo;
return tex_gl->texture;
}
static void gl_tex_invalidate(const struct pl_gpu *gpu, const struct pl_tex *tex)
{
struct pl_gl *p = TA_PRIV(gpu);
......
......@@ -3,6 +3,45 @@
#include <epoxy/gl.h>
#include <epoxy/egl.h>
static void opengl_interop_tests(const struct pl_gpu *gpu)
{
const struct pl_fmt *fmt = pl_find_fmt(gpu, PL_FMT_UNORM, 1, 0, 0,
PL_FMT_CAP_RENDERABLE |
PL_FMT_CAP_LINEAR);
if (!fmt)
return;
const struct pl_tex *export = pl_tex_create(gpu, &(struct pl_tex_params) {
.w = 32,
.h = 32,
.format = fmt,
.sampleable = true,
.renderable = true,
.blit_dst = fmt->caps & PL_FMT_CAP_BLITTABLE,
.sample_mode = PL_TEX_SAMPLE_LINEAR,
.address_mode = PL_TEX_ADDRESS_REPEAT,
});
REQUIRE(export);
struct pl_opengl_wrap_params wrap = {
.width = export->params.w,
.height = export->params.h,
.depth = export->params.d,
};
wrap.texture = pl_opengl_unwrap(gpu, export, &wrap.target, &wrap.iformat, NULL);
REQUIRE(wrap.texture);
const struct pl_tex *import = pl_opengl_wrap(gpu, &wrap);
REQUIRE(import);
REQUIRE(import->params.renderable);
REQUIRE(import->params.blit_dst == export->params.blit_dst);
pl_tex_destroy(gpu, &import);
pl_tex_destroy(gpu, &export);
}
int main()
{
// Create the OpenGL context
......@@ -124,6 +163,7 @@ int main()
last_limits = gpu->limits;
gpu_tests(gpu);
opengl_interop_tests(gpu);
pl_opengl_destroy(&gl);
eglDestroyContext(dpy, egl);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment