/* * Copyright (C) 2024 Mikhail Burakov. This file is part of streamer. * * streamer is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * streamer 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with streamer. If not, see . */ #include "gpu_context.h" #include #include #include #include #include #include #include #include "util.h" extern const char _binary_vertex_glsl_start[]; extern const char _binary_vertex_glsl_end[]; extern const char _binary_luma_glsl_start[]; extern const char _binary_luma_glsl_end[]; extern const char _binary_chroma_glsl_start[]; extern const char _binary_chroma_glsl_end[]; struct GpuContext { // EGL objects EGLDisplay egl_display; EGLContext egl_context; // OpenGL functions PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES; // OpenGL objects GLuint program_luma; GLuint program_chroma; GLint sample_offsets; GLuint framebuffer; GLuint vertices; }; static const char* EglErrorString(EGLint error) { static const char* const kEglErrorStrings[] = { "EGL_SUCCESS", "EGL_NOT_INITIALIZED", "EGL_BAD_ACCESS", "EGL_BAD_ALLOC", "EGL_BAD_ATTRIBUTE", "EGL_BAD_CONFIG", "EGL_BAD_CONTEXT", "EGL_BAD_CURRENT_SURFACE", "EGL_BAD_DISPLAY", "EGL_BAD_MATCH", "EGL_BAD_NATIVE_PIXMAP", "EGL_BAD_NATIVE_WINDOW", "EGL_BAD_PARAMETER", "EGL_BAD_SURFACE", "EGL_CONTEXT_LOST", }; return EGL_SUCCESS <= error && error < EGL_SUCCESS + (EGLint)LENGTH(kEglErrorStrings) ? kEglErrorStrings[error - EGL_SUCCESS] : "???"; } static const char* GlErrorString(GLenum error) { static const char* const kGlErrorStrings[] = { "GL_INVALID_ENUM", "GL_INVALID_VALUE", "GL_INVALID_OPERATION", "GL_STACK_OVERFLOW", "GL_STACK_UNDERFLOW", "GL_OUT_OF_MEMORY", "GL_INVALID_FRAMEBUFFER_OPERATION", "GL_CONTEXT_LOST", }; if (error == GL_NO_ERROR) return "GL_NO_ERROR"; return GL_INVALID_ENUM <= error && error < GL_INVALID_ENUM + LENGTH(kGlErrorStrings) ? kGlErrorStrings[error - GL_INVALID_ENUM] : "???"; } #define DEFINE_CHECK_BUILDABLE_FUNCTION(what, err, op) \ static bool CheckBuildable##what(GLuint buildable) { \ GLenum error = glGetError(); \ if (error != GL_NO_ERROR) { \ LOG("Failed to " err " (%s)", GlErrorString(error)); \ return false; \ } \ GLint status; \ glGet##what##iv(buildable, op, &status); \ if (status != GL_TRUE) { \ GLint log_length; \ glGet##what##iv(buildable, GL_INFO_LOG_LENGTH, &log_length); \ char message[log_length]; \ memset(message, 0, sizeof(message)); \ glGet##what##InfoLog(buildable, log_length, NULL, message); \ LOG("%s", message); \ return false; \ } \ return true; \ } DEFINE_CHECK_BUILDABLE_FUNCTION(Shader, "compile shader", GL_COMPILE_STATUS) DEFINE_CHECK_BUILDABLE_FUNCTION(Program, "link program", GL_LINK_STATUS) static GLuint CreateGlProgram(const char* vs_begin, const char* vs_end, const char* fs_begin, const char* fs_end) { GLuint program = 0; GLuint vertex = glCreateShader(GL_VERTEX_SHADER); if (!vertex) { LOG("Failed to create vertex shader (%s)", GlErrorString(glGetError())); goto bail_out; } GLsizei size = (GLsizei)(vs_end - vs_begin); glShaderSource(vertex, 1, &vs_begin, &size); glCompileShader(vertex); if (!CheckBuildableShader(vertex)) { goto delete_vs; } GLuint fragment = glCreateShader(GL_FRAGMENT_SHADER); if (!fragment) { LOG("Failed to create fragment shader (%s)", GlErrorString(glGetError())); goto delete_vs; } size = (GLsizei)(fs_end - fs_begin); glShaderSource(fragment, 1, &fs_begin, &size); glCompileShader(fragment); if (!CheckBuildableShader(fragment)) { goto delete_fs; } program = glCreateProgram(); if (!program) { LOG("Failed to create shader program (%s)", GlErrorString(glGetError())); goto delete_fs; } glAttachShader(program, vertex); glAttachShader(program, fragment); glLinkProgram(program); if (!CheckBuildableProgram(program)) { glDeleteProgram(program); program = 0; goto delete_fs; } delete_fs: glDeleteShader(fragment); delete_vs: glDeleteShader(vertex); bail_out: return program; } static bool GpuContextInitOpenGL(struct GpuContext* gpu_context) { // TODO(mburakov): Check extensions? gpu_context->glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress( "glEGLImageTargetTexture2DOES"); if (!gpu_context->glEGLImageTargetTexture2DOES) { LOG("Failed to get address of glEGLImageTargetTexture2DOES"); return false; } gpu_context->program_luma = CreateGlProgram(_binary_vertex_glsl_start, _binary_vertex_glsl_end, _binary_luma_glsl_start, _binary_luma_glsl_end); if (!gpu_context->program_luma) { LOG("Failed to create luma program"); return false; } gpu_context->program_chroma = CreateGlProgram(_binary_vertex_glsl_start, _binary_vertex_glsl_end, _binary_chroma_glsl_start, _binary_chroma_glsl_end); if (!gpu_context->program_chroma) { LOG("Failed to create chroma program"); goto rollback_program_luma; } gpu_context->sample_offsets = glGetUniformLocation(gpu_context->program_chroma, "sample_offsets"); if (gpu_context->sample_offsets == -1) { LOG("Failed to get sample_offsets uniform location (%s)", GlErrorString(glGetError())); goto rollback_program_chroma; } gpu_context->framebuffer = 0; glGenFramebuffers(1, &gpu_context->framebuffer); glBindFramebuffer(GL_FRAMEBUFFER, gpu_context->framebuffer); if (!gpu_context->framebuffer) { LOG("Failed to allocate framebuffer (%s)", GlErrorString(glGetError())); goto rollback_program_chroma; } gpu_context->vertices = 0; glGenBuffers(1, &gpu_context->vertices); glBindBuffer(GL_ARRAY_BUFFER, gpu_context->vertices); if (!gpu_context->vertices) { LOG("Failed to allocate buffer (%s)", GlErrorString(glGetError())); goto rollback_framebuffer; } static const GLfloat kVertices[] = {.0f, .0f, 1.f, 0.f, 1.f, 1.f, .0f, 1.f}; glBufferData(GL_ARRAY_BUFFER, sizeof(kVertices), kVertices, GL_STATIC_DRAW); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, NULL); glEnableVertexAttribArray(0); GLenum error = glGetError(); if (error != GL_NO_ERROR) { LOG("Failed to initialize array buffer (%s)", GlErrorString(error)); goto rollback_vertices; } return true; rollback_vertices: glDeleteBuffers(1, &gpu_context->vertices); rollback_framebuffer: glDeleteFramebuffers(1, &gpu_context->framebuffer); rollback_program_chroma: glDeleteProgram(gpu_context->program_chroma); rollback_program_luma: glDeleteProgram(gpu_context->program_luma); return false; } struct GpuContext* GpuContextCreate(EGLNativeDisplayType native_display) { struct GpuContext* gpu_context = malloc(sizeof(struct GpuContext)); if (!gpu_context) { LOG("Failed to allocate gpu context (%s)", strerror(errno)); return NULL; } gpu_context->egl_display = eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, native_display, NULL); if (gpu_context->egl_display == EGL_NO_DISPLAY) { LOG("Failed to get platform display (%s)", EglErrorString(eglGetError())); goto rollback_gpu_context; } EGLint major, minor; if (!eglInitialize(gpu_context->egl_display, &major, &minor)) { LOG("Failed to initialize display (%s)", EglErrorString(eglGetError())); goto rollback_egl_display; } LOG("Initialized EGL %d.%d", major, minor); if (!eglBindAPI(EGL_OPENGL_ES_API)) { LOG("Failed to bind EGL API (%s)", EglErrorString(eglGetError())); goto rollback_egl_display; } static const EGLint kEglContextAttribs[] = { #define _(...) __VA_ARGS__ _(EGL_CONTEXT_MAJOR_VERSION, 3), _(EGL_CONTEXT_MINOR_VERSION, 1), EGL_NONE, #undef _ }; gpu_context->egl_context = eglCreateContext(gpu_context->egl_display, EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, kEglContextAttribs); if (gpu_context->egl_context == EGL_NO_CONTEXT) { LOG("Failed to create EGL context (%s)", EglErrorString(eglGetError())); goto rollback_egl_display; } if (!eglMakeCurrent(gpu_context->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, gpu_context->egl_context)) { LOG("Failed to make EGL context current (%s)", EglErrorString(eglGetError())); goto rollback_egl_context; } bool result = GpuContextInitOpenGL(gpu_context); assert(eglMakeCurrent(gpu_context->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); if (!result) { LOG("Failed to initialize OpenGL objects"); goto rollback_egl_context; } return gpu_context; rollback_egl_context: assert(eglDestroyContext(gpu_context->egl_display, gpu_context->egl_context)); rollback_egl_display: assert(eglTerminate(gpu_context->egl_display)); rollback_gpu_context: free(gpu_context); return NULL; } bool GpuContextCreateImage(const struct GpuContext* gpu_context, const EGLAttrib* attrib_list, struct GpuContextImage* gpu_context_image) { EGLImage egl_image = eglCreateImage(gpu_context->egl_display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, NULL, attrib_list); if (egl_image == EGL_NO_IMAGE) { LOG("Failed to create EGL image (%s)", EglErrorString(eglGetError())); return false; } if (!eglMakeCurrent(gpu_context->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, gpu_context->egl_context)) { LOG("Failed to make EGL context current (%s)", EglErrorString(eglGetError())); goto rollback_egl_image; } GLuint gl_texture = 0; glGenTextures(1, &gl_texture); if (!gl_texture) { LOG("Failed to allocate texture (%s)", GlErrorString(glGetError())); goto rollback_egl_make_current; } glBindTexture(GL_TEXTURE_2D, gl_texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); gpu_context->glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, egl_image); glBindTexture(GL_TEXTURE_2D, 0); GLenum error = glGetError(); if (error != GL_NO_ERROR) { LOG("Failed to initialize texture (%s)", GlErrorString(error)); goto rollback_gl_texture; } assert(eglMakeCurrent(gpu_context->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); *gpu_context_image = (struct GpuContextImage){ .egl_image = egl_image, .gl_texture = gl_texture, }; return true; rollback_gl_texture: glDeleteTextures(1, &gl_texture); rollback_egl_make_current: assert(eglMakeCurrent(gpu_context->egl_context, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); rollback_egl_image: assert(eglDestroyImage(gpu_context->egl_display, egl_image)); return false; } static bool GpuContextRender(GLuint source, GLuint target) { glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, target, 0); GLenum framebuffer_status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (framebuffer_status != GL_FRAMEBUFFER_COMPLETE) { LOG("Framebuffer is incomplete (0x%x)", framebuffer_status); return false; } glBindTexture(GL_TEXTURE_2D, source); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); GLenum error = glGetError(); if (error != GL_NO_ERROR) { LOG("Failed to render (%s)", GlErrorString(error)); return false; } return true; } bool GpuContextConvertColorspace(const struct GpuContext* gpu_context, EGLAttrib width, EGLAttrib height, GLuint source, GLuint luma, GLuint chroma) { if (!eglMakeCurrent(gpu_context->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, gpu_context->egl_context)) { LOG("Failed to make EGL context current (%s)", EglErrorString(eglGetError())); return false; } glUseProgram(gpu_context->program_luma); glViewport(0, 0, (GLsizei)width, (GLsizei)height); if (!GpuContextRender(source, luma)) { LOG("Failed to convert luma plane"); goto rollback_egl_make_current; } const GLfloat sample_offsets[] = { #define _(...) __VA_ARGS__ _(0.f, 0.f), _(1.f / (GLfloat)width, 0.f), _(0.f, 1.f / (GLfloat)height), _(1.f / (GLfloat)width, 1.f / (GLfloat)height), #undef _ }; glUseProgram(gpu_context->program_chroma); glUniform2fv(gpu_context->sample_offsets, 4, sample_offsets); glViewport(0, 0, (GLsizei)width / 2, (GLsizei)height / 2); if (!GpuContextRender(source, chroma)) { LOG("Failed to convert chroma plane"); goto rollback_egl_make_current; } EGLSync sync = eglCreateSync(gpu_context->egl_display, EGL_SYNC_FENCE, NULL); if (sync == EGL_NO_SYNC) { LOG("Failed to create EGL fence sync (%s)", EglErrorString(eglGetError())); goto rollback_egl_make_current; } if (!eglClientWaitSync(gpu_context->egl_display, sync, 0, EGL_FOREVER)) { LOG("Failed to wait EGLfence sync (%s)", EglErrorString(eglGetError())); goto rollback_sync; } assert(eglDestroySync(gpu_context->egl_display, sync)); assert(eglMakeCurrent(gpu_context->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); return true; rollback_sync: assert(eglMakeCurrent(gpu_context->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); rollback_egl_make_current: assert(eglMakeCurrent(gpu_context->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); return false; } void GpuContextDestroyImage(const struct GpuContext* gpu_context, const struct GpuContextImage* gpu_context_image) { assert(eglMakeCurrent(gpu_context->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, gpu_context->egl_context)); glDeleteTextures(1, &gpu_context_image->gl_texture); assert(eglMakeCurrent(gpu_context->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); assert( eglDestroyImage(gpu_context->egl_display, gpu_context_image->egl_image)); } void GpuContextDestroy(struct GpuContext* gpu_context) { assert(eglMakeCurrent(gpu_context->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, gpu_context->egl_context)); glBindBuffer(GL_ARRAY_BUFFER, 0); glDeleteBuffers(1, &gpu_context->vertices); glBindFramebuffer(GL_FRAMEBUFFER, 0); glDeleteFramebuffers(1, &gpu_context->framebuffer); glUseProgram(0); glDeleteProgram(gpu_context->program_chroma); glDeleteProgram(gpu_context->program_luma); assert(eglMakeCurrent(gpu_context->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); assert(eglDestroyContext(gpu_context->egl_display, gpu_context->egl_context)); assert(eglTerminate(gpu_context->egl_display)); free(gpu_context); }