blob: 485afc0be5ba21df93db6006a27c6b0356d68664 [file] [log] [blame]
/*
* Copyright 2016 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "bs_drm.h"
static const char *get_egl_error();
static const char *get_gl_framebuffer_error();
struct bs_egl {
bool setup;
EGLDisplay display;
EGLContext ctx;
bool use_image_flush_external;
bool use_dma_buf_import_modifiers;
// Names are the original gl/egl function names with the prefix chopped off.
PFNEGLCREATEIMAGEKHRPROC CreateImageKHR;
PFNEGLDESTROYIMAGEKHRPROC DestroyImageKHR;
PFNEGLIMAGEFLUSHEXTERNALEXTPROC ImageFlushExternal;
PFNGLEGLIMAGETARGETTEXTURE2DOESPROC EGLImageTargetTexture2DOES;
};
struct bs_egl_fb {
GLuint tex;
GLuint fb;
};
struct bs_egl *bs_egl_new()
{
struct bs_egl *self = calloc(1, sizeof(struct bs_egl));
assert(self);
self->display = EGL_NO_DISPLAY;
self->ctx = EGL_NO_CONTEXT;
return self;
}
void bs_egl_destroy(struct bs_egl **egl)
{
assert(egl);
struct bs_egl *self = *egl;
assert(self);
if (self->ctx != EGL_NO_CONTEXT) {
assert(self->display != EGL_NO_DISPLAY);
eglMakeCurrent(self->display, NULL, NULL, NULL);
eglDestroyContext(self->display, self->ctx);
}
if (self->display != EGL_NO_DISPLAY)
eglTerminate(self->display);
free(self);
*egl = NULL;
}
bool bs_egl_setup(struct bs_egl *self)
{
assert(self);
assert(!self->setup);
self->CreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress("eglCreateImageKHR");
self->DestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR");
self->ImageFlushExternal =
(PFNEGLIMAGEFLUSHEXTERNALEXTPROC)eglGetProcAddress("eglImageFlushExternalEXT");
self->EGLImageTargetTexture2DOES =
(PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES");
if (!self->CreateImageKHR || !self->DestroyImageKHR || !self->EGLImageTargetTexture2DOES) {
bs_debug_error(
"eglGetProcAddress returned NULL for a required extension entry point.");
return false;
}
self->display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (self->display == EGL_NO_DISPLAY) {
bs_debug_error("failed to get egl display");
return false;
}
if (!eglInitialize(self->display, NULL /* ignore version */, NULL /* ignore version */)) {
bs_debug_error("failed to initialize egl: %s\n", get_egl_error());
return false;
}
// Get any EGLConfig. We need one to create a context, but it isn't used to create any
// surfaces.
const EGLint config_attribs[] = { EGL_SURFACE_TYPE, EGL_DONT_CARE, EGL_NONE };
EGLConfig egl_config;
EGLint num_configs;
if (!eglChooseConfig(self->display, config_attribs, &egl_config, 1,
&num_configs /* unused but can't be null */)) {
bs_debug_error("eglChooseConfig() failed with error: %s", get_egl_error());
goto terminate_display;
}
if (!eglBindAPI(EGL_OPENGL_ES_API)) {
bs_debug_error("failed to bind OpenGL ES: %s", get_egl_error());
goto terminate_display;
}
const EGLint context_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
self->ctx = eglCreateContext(self->display, egl_config,
EGL_NO_CONTEXT /* no shared context */, context_attribs);
if (self->ctx == EGL_NO_CONTEXT) {
bs_debug_error("failed to create OpenGL ES Context: %s", get_egl_error());
goto terminate_display;
}
if (!eglMakeCurrent(self->display, EGL_NO_SURFACE /* no default draw surface */,
EGL_NO_SURFACE /* no default draw read */, self->ctx)) {
bs_debug_error("failed to make the OpenGL ES Context current: %s", get_egl_error());
goto destroy_context;
}
const char *egl_extensions = eglQueryString(self->display, EGL_EXTENSIONS);
if (!bs_egl_has_extension("EGL_KHR_image_base", egl_extensions)) {
bs_debug_error("EGL_KHR_image_base extension not supported");
goto destroy_context;
}
if (!bs_egl_has_extension("EGL_EXT_image_dma_buf_import", egl_extensions)) {
bs_debug_error("EGL_EXT_image_dma_buf_import extension not supported");
goto destroy_context;
}
if (bs_egl_has_extension("EGL_EXT_image_flush_external", egl_extensions)) {
if (!self->ImageFlushExternal) {
bs_debug_print("WARNING", __func__, __FILE__, __LINE__,
"EGL_EXT_image_flush_external extension is supported, but "
"eglGetProcAddress returned NULL.");
} else {
self->use_image_flush_external = true;
}
}
if (bs_egl_has_extension("EGL_EXT_image_dma_buf_import_modifiers", egl_extensions))
self->use_dma_buf_import_modifiers = true;
const char *gl_extensions = (const char *)glGetString(GL_EXTENSIONS);
if (!bs_egl_has_extension("GL_OES_EGL_image", gl_extensions)) {
bs_debug_error("GL_OES_EGL_image extension not supported");
goto destroy_context;
}
self->setup = true;
return true;
destroy_context:
eglDestroyContext(self->display, self->ctx);
terminate_display:
eglTerminate(self->display);
self->display = EGL_NO_DISPLAY;
return false;
}
bool bs_egl_make_current(struct bs_egl *self)
{
assert(self);
assert(self->display != EGL_NO_DISPLAY);
assert(self->ctx != EGL_NO_CONTEXT);
return eglMakeCurrent(self->display, EGL_NO_SURFACE /* No default draw surface */,
EGL_NO_SURFACE /* No default draw read */, self->ctx);
}
static EGLImageKHR _bs_egl_image_create(struct bs_egl *self, int prime_fd, int width, int height,
uint32_t format, int pitch, int offset,
uint64_t *format_modifier)
{
assert(self);
assert(self->CreateImageKHR);
assert(self->display != EGL_NO_DISPLAY);
EGLint khr_image_attrs[17] = { EGL_DMA_BUF_PLANE0_FD_EXT,
prime_fd,
EGL_WIDTH,
width,
EGL_HEIGHT,
height,
EGL_LINUX_DRM_FOURCC_EXT,
(int)format,
EGL_DMA_BUF_PLANE0_PITCH_EXT,
pitch,
EGL_DMA_BUF_PLANE0_OFFSET_EXT,
offset,
EGL_NONE };
if (self->use_dma_buf_import_modifiers) {
assert(format_modifier);
uint64_t modifier = format_modifier ? *format_modifier : 0;
khr_image_attrs[12] = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT;
khr_image_attrs[13] = modifier & 0xfffffffful;
khr_image_attrs[14] = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT;
khr_image_attrs[15] = modifier >> 32;
khr_image_attrs[16] = EGL_NONE;
}
EGLImageKHR image =
self->CreateImageKHR(self->display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT,
NULL /* no client buffer */, khr_image_attrs);
if (image == EGL_NO_IMAGE_KHR) {
bs_debug_error("failed to make image from target buffer: %s", get_egl_error());
return EGL_NO_IMAGE_KHR;
}
return image;
}
EGLImageKHR bs_egl_image_create(struct bs_egl *self, int prime_fd, int width, int height,
uint32_t format, int pitch, int offset)
{
return _bs_egl_image_create(self, prime_fd, width, height, format, pitch, offset, NULL);
}
EGLImageKHR bs_egl_image_create_with_modifier(struct bs_egl *self, int prime_fd, int width,
int height, uint32_t format, int pitch, int offset,
uint64_t format_modifier)
{
return _bs_egl_image_create(self, prime_fd, width, height, format, pitch, offset,
&format_modifier);
}
EGLImageKHR bs_egl_image_create_gbm(struct bs_egl *self, struct gbm_bo *bo)
{
assert(bo);
int fd = gbm_bo_get_fd(bo);
if (fd < 0) {
bs_debug_error("failed to get fb for bo: %d", fd);
return EGL_NO_IMAGE_KHR;
}
uint64_t modifier = self->use_dma_buf_import_modifiers ? gbm_bo_get_format_modifier(bo) : 0;
return bs_egl_image_create_with_modifier(
self, fd, gbm_bo_get_width(bo), gbm_bo_get_height(bo), gbm_bo_get_format(bo),
gbm_bo_get_stride(bo), 0 /* no offset */, modifier);
}
void bs_egl_image_destroy(struct bs_egl *self, EGLImageKHR *image)
{
assert(self);
assert(image);
assert(*image != EGL_NO_IMAGE_KHR);
assert(self->DestroyImageKHR);
self->DestroyImageKHR(self->display, *image);
*image = EGL_NO_IMAGE_KHR;
}
bool bs_egl_image_flush_external(struct bs_egl *self, EGLImageKHR image)
{
assert(self);
assert(image != EGL_NO_IMAGE_KHR);
if (!self->use_image_flush_external)
return true;
const EGLAttrib attrs[] = { EGL_NONE };
return self->ImageFlushExternal(self->display, image, attrs);
}
struct bs_egl_fb *bs_egl_fb_new(struct bs_egl *self, EGLImageKHR image)
{
assert(self);
assert(self->EGLImageTargetTexture2DOES);
struct bs_egl_fb *fb = calloc(1, sizeof(struct bs_egl_fb));
assert(fb);
glGenTextures(1, &fb->tex);
glBindTexture(GL_TEXTURE_2D, fb->tex);
self->EGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)image);
glBindTexture(GL_TEXTURE_2D, 0);
glGenFramebuffers(1, &fb->fb);
glBindFramebuffer(GL_FRAMEBUFFER, fb->fb);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb->tex, 0);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
bs_debug_error("failed framebuffer check for created target buffer: %s",
get_gl_framebuffer_error());
glDeleteFramebuffers(1, &fb->fb);
glDeleteTextures(1, &fb->tex);
free(fb);
return NULL;
}
return fb;
}
void bs_egl_fb_destroy(struct bs_egl_fb **fb)
{
assert(fb);
struct bs_egl_fb *self = *fb;
assert(self);
glDeleteFramebuffers(1, &self->fb);
glDeleteTextures(1, &self->tex);
free(self);
*fb = NULL;
}
GLuint bs_egl_fb_name(struct bs_egl_fb *self)
{
assert(self);
return self->fb;
}
bool bs_egl_target_texture2D(struct bs_egl *self, EGLImageKHR image)
{
assert(self);
assert(self->EGLImageTargetTexture2DOES);
self->EGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)image);
GLint error = glGetError();
return (error == GL_NO_ERROR);
}
bool bs_egl_has_extension(const char *extension, const char *extensions)
{
const char *start, *where, *terminator;
start = extensions;
for (;;) {
where = (char *)strstr((const char *)start, extension);
if (!where)
break;
terminator = where + strlen(extension);
if (where == start || *(where - 1) == ' ')
if (*terminator == ' ' || *terminator == '\0')
return true;
start = terminator;
}
return false;
}
static const char *get_egl_error()
{
switch (eglGetError()) {
case EGL_SUCCESS:
return "EGL_SUCCESS";
case EGL_NOT_INITIALIZED:
return "EGL_NOT_INITIALIZED";
case EGL_BAD_ACCESS:
return "EGL_BAD_ACCESS";
case EGL_BAD_ALLOC:
return "EGL_BAD_ALLOC";
case EGL_BAD_ATTRIBUTE:
return "EGL_BAD_ATTRIBUTE";
case EGL_BAD_CONTEXT:
return "EGL_BAD_CONTEXT";
case EGL_BAD_CONFIG:
return "EGL_BAD_CONFIG";
case EGL_BAD_CURRENT_SURFACE:
return "EGL_BAD_CURRENT_SURFACE";
case EGL_BAD_DISPLAY:
return "EGL_BAD_DISPLAY";
case EGL_BAD_SURFACE:
return "EGL_BAD_SURFACE";
case EGL_BAD_MATCH:
return "EGL_BAD_MATCH";
case EGL_BAD_PARAMETER:
return "EGL_BAD_PARAMETER";
case EGL_BAD_NATIVE_PIXMAP:
return "EGL_BAD_NATIVE_PIXMAP";
case EGL_BAD_NATIVE_WINDOW:
return "EGL_BAD_NATIVE_WINDOW";
case EGL_CONTEXT_LOST:
return "EGL_CONTEXT_LOST";
default:
return "EGL_???";
}
}
static const char *get_gl_framebuffer_error()
{
switch (glCheckFramebufferStatus(GL_FRAMEBUFFER)) {
case GL_FRAMEBUFFER_COMPLETE:
return "GL_FRAMEBUFFER_COMPLETE";
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
return "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT";
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
return "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT";
case GL_FRAMEBUFFER_UNSUPPORTED:
return "GL_FRAMEBUFFER_UNSUPPORTED";
case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
return "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS";
default:
return "GL_FRAMEBUFFER_???";
}
}