wlvncc/src/renderer-egl.c

543 lines
13 KiB
C

/*
* Copyright (c) 2022 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include "buffer.h"
#include "renderer.h"
#include "renderer-egl.h"
#include "vnc.h"
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <unistd.h>
#include <math.h>
#include <gbm.h>
#include <drm_fourcc.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <libavutil/frame.h>
#include <libavutil/hwcontext_drm.h>
#define XSTR(s) STR(s)
#define STR(s) #s
#define EGL_EXTENSION_LIST \
X(PFNEGLGETPLATFORMDISPLAYEXTPROC, eglGetPlatformDisplayEXT) \
X(PFNEGLCREATEIMAGEKHRPROC, eglCreateImageKHR) \
X(PFNEGLDESTROYIMAGEKHRPROC, eglDestroyImageKHR) \
#define GL_EXTENSION_LIST \
X(PFNGLEGLIMAGETARGETTEXTURE2DOESPROC, glEGLImageTargetTexture2DOES) \
X(PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC, glEGLImageTargetRenderbufferStorageOES) \
#define X(t, n) static t n;
EGL_EXTENSION_LIST
GL_EXTENSION_LIST
#undef X
enum {
ATTR_INDEX_POS = 0,
ATTR_INDEX_TEXTURE,
};
struct fbo_info {
GLuint fbo;
GLuint rbo;
int width, height;
};
static EGLDisplay egl_display = EGL_NO_DISPLAY;
static EGLContext egl_context = EGL_NO_CONTEXT;
static GLuint shader_program = 0;
static GLuint shader_program_ext = 0;
static GLuint texture = 0;
static const char *vertex_shader_src =
"attribute vec2 pos;\n"
"attribute vec2 texture;\n"
"varying vec2 v_texture;\n"
"void main() {\n"
" v_texture = vec2(texture.s, 1.0 - texture.t);\n"
" gl_Position = vec4(pos, 0.0, 1.0);\n"
"}\n";
static const char *fragment_shader_src =
"precision mediump float;\n"
"uniform sampler2D u_tex;\n"
"varying vec2 v_texture;\n"
"void main() {\n"
" gl_FragColor = texture2D(u_tex, v_texture);\n"
"}\n";
static const char *fragment_shader_ext_src =
"#extension GL_OES_EGL_image_external: require\n\n"
"precision mediump float;\n"
"uniform samplerExternalOES u_tex;\n"
"varying vec2 v_texture;\n"
"void main() {\n"
" gl_FragColor = texture2D(u_tex, v_texture);\n"
"}\n";
struct {
GLuint u_tex;
} uniforms;
static int egl_load_egl_ext(void)
{
#define X(t, n) \
n = (t)eglGetProcAddress(XSTR(n)); \
if (!n) \
return -1;
EGL_EXTENSION_LIST
#undef X
return 0;
}
static int egl_load_gl_ext(void)
{
#define X(t, n) \
n = (t)eglGetProcAddress(XSTR(n)); \
if (!n) \
return -1;
GL_EXTENSION_LIST
#undef X
return 0;
}
static int compile_shaders(const char* vert_src, const char* frag_src)
{
GLuint vert = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vert, 1, &vert_src, NULL);
glCompileShader(vert);
GLint is_compiled = 0;
glGetShaderiv(vert, GL_COMPILE_STATUS, &is_compiled);
assert(is_compiled);
GLuint frag = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(frag, 1, &frag_src, NULL);
glCompileShader(frag);
glGetShaderiv(frag, GL_COMPILE_STATUS, &is_compiled);
assert(is_compiled);
int prog = glCreateProgram();
glAttachShader(prog, vert);
glAttachShader(prog, frag);
glBindAttribLocation(prog, ATTR_INDEX_POS, "pos");
glBindAttribLocation(prog, ATTR_INDEX_TEXTURE, "texture");
glLinkProgram(prog);
glDeleteShader(vert);
glDeleteShader(frag);
GLint is_linked = 0;
glGetProgramiv(prog, GL_LINK_STATUS, &is_linked);
assert(is_linked);
uniforms.u_tex = glGetUniformLocation(prog, "u_tex");
return prog;
}
int egl_init(void)
{
int rc;
rc = eglBindAPI(EGL_OPENGL_ES_API);
if (!rc)
return -1;
if (egl_load_egl_ext() < 0)
return -1;
egl_display = eglGetPlatformDisplayEXT(EGL_PLATFORM_SURFACELESS_MESA,
EGL_DEFAULT_DISPLAY, NULL);
if (egl_display == EGL_NO_DISPLAY)
return -1;
rc = eglInitialize(egl_display, NULL, NULL);
if (!rc)
goto failure;
static const EGLint attribs[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
egl_context = eglCreateContext(egl_display, EGL_NO_CONFIG_KHR,
EGL_NO_CONTEXT, attribs);
if (egl_context == EGL_NO_CONTEXT)
goto failure;
if (!eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
egl_context))
goto failure;
if (egl_load_gl_ext() < 0)
goto failure;
shader_program = compile_shaders(vertex_shader_src,
fragment_shader_src);
shader_program_ext = compile_shaders(vertex_shader_src,
fragment_shader_ext_src);
return 0;
failure:
eglDestroyContext(egl_display, egl_context);
return -1;
}
void egl_finish(void)
{
if (texture)
glDeleteTextures(1, &texture);
if (shader_program_ext)
glDeleteProgram(shader_program_ext);
if (shader_program)
glDeleteProgram(shader_program);
eglDestroyContext(egl_display, egl_context);
}
static inline void append_attr(EGLint* dst, int* i, EGLint name, EGLint value)
{
dst[*i] = name;
i[0] += 1;
dst[*i] = value;
i[0] += 1;
}
static void fbo_from_gbm_bo(struct fbo_info* dst, struct gbm_bo* bo)
{
memset(dst, 0, sizeof(*dst));
int index = 0;
EGLint attr[128];
// Won't do multi-planar...
int n_planes = gbm_bo_get_plane_count(bo);
assert(n_planes == 1);
int width = gbm_bo_get_width(bo);
int height = gbm_bo_get_height(bo);
append_attr(attr, &index, EGL_WIDTH, width);
append_attr(attr, &index, EGL_HEIGHT, height);
append_attr(attr, &index, EGL_LINUX_DRM_FOURCC_EXT,
gbm_bo_get_format(bo));
int fd = gbm_bo_get_fd(bo);
// Plane 0:
uint64_t mod = gbm_bo_get_modifier(bo);
uint32_t mod_hi = mod >> 32;
uint32_t mod_lo = mod & 0xffffffff;
append_attr(attr, &index, EGL_DMA_BUF_PLANE0_FD_EXT, fd);
append_attr(attr, &index, EGL_DMA_BUF_PLANE0_OFFSET_EXT,
gbm_bo_get_offset(bo, 0));
append_attr(attr, &index, EGL_DMA_BUF_PLANE0_PITCH_EXT,
gbm_bo_get_stride(bo));
append_attr(attr, &index, EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, mod_lo);
append_attr(attr, &index, EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, mod_hi);
attr[index++] = EGL_NONE;
EGLImageKHR image = eglCreateImageKHR(egl_display, EGL_NO_CONTEXT,
EGL_LINUX_DMA_BUF_EXT, NULL, attr);
assert(image != EGL_NO_IMAGE_KHR);
GLuint rbo = 0;
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, image);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
GLuint fbo = 0;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_RENDERBUFFER, rbo);
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
assert(status == GL_FRAMEBUFFER_COMPLETE);
dst->fbo = fbo;
dst->rbo = rbo;
dst->width = width;
dst->height = height;
eglDestroyImageKHR(egl_display, image);
close(fd);
}
#define X(lc, uc) \
static EGLint plane_ ## lc ## _key(int plane) \
{ \
switch (plane) { \
case 0: return EGL_DMA_BUF_PLANE0_ ## uc ## _EXT; \
case 1: return EGL_DMA_BUF_PLANE1_ ## uc ## _EXT; \
case 2: return EGL_DMA_BUF_PLANE2_ ## uc ## _EXT; \
case 3: return EGL_DMA_BUF_PLANE3_ ## uc ## _EXT; \
} \
return EGL_NONE; \
}
X(fd, FD);
X(offset, OFFSET);
X(pitch, PITCH);
X(modifier_lo, MODIFIER_LO);
X(modifier_hi, MODIFIER_HI);
#undef X
static void dmabuf_attr_append_planes(EGLint* dst, int* index,
struct AVDRMFrameDescriptor* desc)
{
struct AVDRMPlaneDescriptor *plane;
struct AVDRMObjectDescriptor *obj;
for (int i = 0; i < desc->nb_layers; ++i) {
assert(desc->layers[i].nb_planes == 1);
plane = &desc->layers[i].planes[0];
obj = &desc->objects[plane->object_index];
append_attr(dst, index, plane_fd_key(i), obj->fd);
append_attr(dst, index, plane_offset_key(i), plane->offset);
append_attr(dst, index, plane_pitch_key(i), plane->pitch);
append_attr(dst, index, plane_modifier_lo_key(i),
obj->format_modifier);
append_attr(dst, index, plane_modifier_hi_key(i),
obj->format_modifier >> 32);
}
}
static GLuint texture_from_av_frame(const struct AVFrame* frame)
{
int index = 0;
EGLint attr[128];
AVDRMFrameDescriptor *desc = (void*)frame->data[0];
append_attr(attr, &index, EGL_WIDTH, frame->width);
append_attr(attr, &index, EGL_HEIGHT, frame->height);
append_attr(attr, &index, EGL_LINUX_DRM_FOURCC_EXT, DRM_FORMAT_NV12);
append_attr(attr, &index, EGL_IMAGE_PRESERVED_KHR, EGL_TRUE);
dmabuf_attr_append_planes(attr, &index, desc);
attr[index++] = EGL_NONE;
EGLImageKHR image = eglCreateImageKHR(egl_display, EGL_NO_CONTEXT,
EGL_LINUX_DMA_BUF_EXT, NULL, attr);
assert(image != EGL_NO_IMAGE_KHR);
GLuint tex = 0;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_EXTERNAL_OES, tex);
glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, image);
eglDestroyImageKHR(egl_display, image);
glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0);
return tex;
}
void gl_draw(void)
{
static const GLfloat s_vertices[4][2] = {
{ -1.0, 1.0 },
{ 1.0, 1.0 },
{ -1.0, -1.0 },
{ 1.0, -1.0 },
};
static const GLfloat s_positions[4][2] = {
{ 0, 0 },
{ 1, 0 },
{ 0, 1 },
{ 1, 1 },
};
glVertexAttribPointer(ATTR_INDEX_POS, 2, GL_FLOAT, GL_FALSE, 0,
s_vertices);
glVertexAttribPointer(ATTR_INDEX_TEXTURE, 2, GL_FLOAT, GL_FALSE, 0,
s_positions);
glEnableVertexAttribArray(ATTR_INDEX_POS);
glEnableVertexAttribArray(ATTR_INDEX_TEXTURE);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableVertexAttribArray(ATTR_INDEX_TEXTURE);
glDisableVertexAttribArray(ATTR_INDEX_POS);
}
GLenum gl_format_from_drm(uint32_t format)
{
switch (format) {
case DRM_FORMAT_ARGB8888:
case DRM_FORMAT_XRGB8888:
return GL_BGRA_EXT;
case DRM_FORMAT_ABGR8888:
case DRM_FORMAT_XBGR8888:
return GL_RGBA;
}
return 0;
}
void import_image_with_damage(const struct image* src,
struct pixman_region16* damage)
{
GLenum fmt = gl_format_from_drm(src->format);
int n_rects = 0;
struct pixman_box16* rects =
pixman_region_rectangles(damage, &n_rects);
for (int i = 0; i < n_rects; ++i) {
int x = rects[i].x1;
int y = rects[i].y1;
int width = rects[i].x2 - x;
int height = rects[i].y2 - y;
glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, x);
glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, y);
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, fmt,
GL_UNSIGNED_BYTE, src->pixels);
}
glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, 0);
glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, 0);
}
void render_image_egl(struct buffer* dst, const struct image* src,
double scale, int x_pos, int y_pos)
{
struct fbo_info fbo;
fbo_from_gbm_bo(&fbo, dst->bo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo.fbo);
glClearColor(0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
bool is_new_texture = !texture;
if (!texture)
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, src->stride / 4);
if (is_new_texture) {
GLenum fmt = gl_format_from_drm(src->format);
glTexImage2D(GL_TEXTURE_2D, 0, fmt, src->width, src->height, 0,
fmt, GL_UNSIGNED_BYTE, src->pixels);
} else {
import_image_with_damage(src,
(struct pixman_region16*)src->damage);
}
glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0);
int width = round((double)src->width * scale);
int height = round((double)src->height * scale);
glViewport(x_pos, y_pos, width, height);
glUseProgram(shader_program);
struct pixman_box16* ext = pixman_region_extents(&dst->damage);
glScissor(ext->x1, ext->y1, ext->x2 - ext->x1, ext->y2 - ext->y1);
glEnable(GL_SCISSOR_TEST);
gl_draw();
glDisable(GL_SCISSOR_TEST);
glFinish();
glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDeleteFramebuffers(1, &fbo.fbo);
glDeleteRenderbuffers(1, &fbo.rbo);
}
void render_av_frames_egl(struct buffer* dst, struct vnc_av_frame** src,
int n_av_frames, double scale, int x_pos, int y_pos)
{
struct fbo_info fbo;
fbo_from_gbm_bo(&fbo, dst->bo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo.fbo);
glClearColor(0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
struct pixman_box16* ext = pixman_region_extents(&dst->damage);
glScissor(ext->x1, ext->y1, ext->x2 - ext->x1, ext->y2 - ext->y1);
glEnable(GL_SCISSOR_TEST);
glUseProgram(shader_program_ext);
for (int i = 0; i < n_av_frames; ++i) {
const struct vnc_av_frame* frame = src[i];
int width = round((double)frame->width * scale);
int height = round((double)frame->height * scale);
glViewport(x_pos + frame->x, y_pos + frame->y, width, height);
GLuint tex = texture_from_av_frame(frame->frame);
glBindTexture(GL_TEXTURE_EXTERNAL_OES, tex);
gl_draw();
glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0);
}
glDisable(GL_SCISSOR_TEST);
glFinish();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDeleteFramebuffers(1, &fbo.fbo);
glDeleteRenderbuffers(1, &fbo.rbo);
}