/* * 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 #include #include #include #include #include #include #include #include #include #include #include #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(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 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"; 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 void compile_shaders() { GLuint vert = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vert, 1, &vertex_shader_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, &fragment_shader_src, NULL); glCompileShader(frag); glGetShaderiv(frag, GL_COMPILE_STATUS, &is_compiled); assert(is_compiled); shader_program = glCreateProgram(); glAttachShader(shader_program, vert); glAttachShader(shader_program, frag); glBindAttribLocation(shader_program, ATTR_INDEX_POS, "pos"); glBindAttribLocation(shader_program, ATTR_INDEX_TEXTURE, "texture"); glLinkProgram(shader_program); glDeleteShader(vert); glDeleteShader(frag); GLint is_linked = 0; glGetProgramiv(shader_program, GL_LINK_STATUS, &is_linked); assert(is_linked); uniforms.u_tex = glGetUniformLocation(shader_program, "u_tex"); } 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; compile_shaders(); return 0; failure: eglDestroyContext(egl_display, egl_context); return -1; } void egl_finish(void) { 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); } 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; } /* Possible improvements: * - Hold onto imported textures and only import damaged areas * - Only render the damaged areas on the buffer */ 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); GLuint tex = 0; glGenTextures(1, &tex); glBindTexture(GL_TEXTURE_2D, tex); 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); 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); 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); gl_draw(); glFinish(); glBindTexture(GL_TEXTURE_2D, 0); glDeleteTextures(1, &tex); glBindFramebuffer(GL_FRAMEBUFFER, 0); glDeleteFramebuffers(1, &fbo.fbo); glDeleteRenderbuffers(1, &fbo.rbo); }