summaryrefslogtreecommitdiff
path: root/gpu.c
diff options
context:
space:
mode:
Diffstat (limited to 'gpu.c')
-rw-r--r--gpu.c563
1 files changed, 563 insertions, 0 deletions
diff --git a/gpu.c b/gpu.c
new file mode 100644
index 0000000..081acbe
--- /dev/null
+++ b/gpu.c
@@ -0,0 +1,563 @@
+/*
+ * Copyright (C) 2023 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "gpu.h"
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <GLES2/gl2.h>
+#include <GLES3/gl32.h>
+#include <drm_fourcc.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+// Must be included last
+#include <GLES2/gl2ext.h>
+
+#include "util.h"
+
+// TODO(mburakov): It should be theoretically possible to do everything in a
+// single pass using a compute shader and GLES3. Unfortunately my test machine
+// reports the primary framebuffer as multiplane. This is probably the reason
+// why texture created from it can not be sampled using imageLoad in a compute
+// shader even though it's still RGB. Fallback to GLES2 and per-plane textures
+// for now, and figure out details later.
+
+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 {
+ EGLDisplay display;
+ EGLContext context;
+ PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;
+ GLuint program_luma;
+ GLuint program_chroma;
+ GLint chroma_offsets;
+ GLuint framebuffer;
+ GLuint vertices;
+};
+
+struct GpuFrame {
+ struct GpuContext* gpu_context;
+ uint32_t width, height;
+ int dmabuf_fds[4];
+ EGLImage images[2];
+ GLuint textures[2];
+};
+
+static const char* EglErrorString(EGLint error) {
+ static const char* const egl_error_strings[] = {
+ "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_CONTEXT_LOST
+ ? egl_error_strings[error - EGL_SUCCESS]
+ : "???";
+}
+
+static const char* GlErrorString(GLenum error) {
+ static const char* const gl_error_strings[] = {
+ "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_CONTEXT_LOST
+ ? gl_error_strings[error - GL_INVALID_ENUM]
+ : "???";
+}
+
+#define DEFINE_CHECK_BUILDABLE_FUNCTION(postfix, err_msg, getter_fn, \
+ status_enum, logger_fn) \
+ static bool CheckBuildable##postfix(GLuint buildable) { \
+ GLenum error = glGetError(); \
+ if (error != GL_NO_ERROR) { \
+ LOG(err_msg " (%s)", GlErrorString(error)); \
+ return false; \
+ } \
+ GLint status; \
+ getter_fn(buildable, status_enum, &status); \
+ if (status != GL_TRUE) { \
+ GLint log_length; \
+ getter_fn(buildable, GL_INFO_LOG_LENGTH, &log_length); \
+ char message[log_length]; \
+ memset(message, 0, sizeof(message)); \
+ logger_fn(buildable, log_length, NULL, message); \
+ LOG("%s", message); \
+ return false; \
+ } \
+ return true; \
+ }
+
+DEFINE_CHECK_BUILDABLE_FUNCTION(Shader, "Failed to compile shader",
+ glGetShaderiv, GL_COMPILE_STATUS,
+ glGetShaderInfoLog)
+DEFINE_CHECK_BUILDABLE_FUNCTION(Program, "Failed to link program",
+ glGetProgramiv, GL_LINK_STATUS,
+ glGetProgramInfoLog)
+
+static bool HasExtension(const char* haystack, const char* needle) {
+ bool result = !!strstr(haystack, needle);
+ if (!result) LOG("Unsupported extension %s", needle);
+ return result;
+}
+
+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 SetupImgInputUniform(GLuint program) {
+ GLint img_input = glGetUniformLocation(program, "img_input");
+ if (img_input == -1) {
+ LOG("Failed to find img_input uniform (%s)", GlErrorString(glGetError()));
+ return false;
+ }
+
+ glUseProgram(program);
+ glUniform1i(img_input, 0);
+ GLenum error = glGetError();
+ if (error != GL_NO_ERROR) {
+ LOG("Failed to set img_input uniform (%s)", GlErrorString(glGetError()));
+ return false;
+ }
+ return true;
+}
+
+struct GpuContext* GpuContextCreate(void) {
+ struct AUTO(GpuContext)* gpu_context = malloc(sizeof(struct GpuContext));
+ if (!gpu_context) {
+ LOG("Failed to allocate gpu context (%s)", strerror(errno));
+ return NULL;
+ }
+ *gpu_context = (struct GpuContext){
+ .display = EGL_NO_DISPLAY,
+ .context = EGL_NO_CONTEXT,
+ .glEGLImageTargetTexture2DOES = NULL,
+ .program_luma = 0,
+ .program_chroma = 0,
+ .chroma_offsets = -1,
+ .framebuffer = 0,
+ .vertices = 0,
+ };
+
+ const char* egl_ext = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
+ if (!egl_ext) {
+ LOG("Failed to query platformless egl extensions (%s)",
+ EglErrorString(eglGetError()));
+ return NULL;
+ }
+
+ LOG("EGL_EXTENSIONS: %s", egl_ext);
+ if (!HasExtension(egl_ext, "EGL_MESA_platform_surfaceless")) return NULL;
+ gpu_context->display =
+ eglGetPlatformDisplay(EGL_PLATFORM_SURFACELESS_MESA, NULL, NULL);
+ if (gpu_context->display == EGL_NO_DISPLAY) {
+ LOG("Failed to get egl display (%s)", EglErrorString(eglGetError()));
+ return NULL;
+ }
+
+ EGLint major, minor;
+ if (!eglInitialize(gpu_context->display, &major, &minor)) {
+ LOG("Failed to initialize egl display (%s)", EglErrorString(eglGetError()));
+ return NULL;
+ }
+
+ LOG("Initialized EGL %d.%d", major, minor);
+ egl_ext = eglQueryString(gpu_context->display, EGL_EXTENSIONS);
+ if (!egl_ext) {
+ LOG("Failed to query egl extensions (%s)", EglErrorString(eglGetError()));
+ return NULL;
+ }
+
+ LOG("EGL_EXTENSIONS: %s", egl_ext);
+ if (!HasExtension(egl_ext, "EGL_KHR_surfaceless_context") ||
+ !HasExtension(egl_ext, "EGL_KHR_no_config_context") ||
+ !HasExtension(egl_ext, "EGL_EXT_image_dma_buf_import") ||
+ !HasExtension(egl_ext, "EGL_EXT_image_dma_buf_import_modifiers"))
+ return NULL;
+
+ if (!eglBindAPI(EGL_OPENGL_ES_API)) {
+ LOG("Failed to bind egl api (%s)", EglErrorString(eglGetError()));
+ return NULL;
+ }
+
+ static const EGLint context_attribs[] = {
+#define _(...) __VA_ARGS__
+ _(EGL_CONTEXT_MAJOR_VERSION, 3),
+ _(EGL_CONTEXT_MINOR_VERSION, 1),
+ EGL_NONE,
+#undef _
+ };
+ gpu_context->context = eglCreateContext(
+ gpu_context->display, EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, context_attribs);
+ if (gpu_context->context == EGL_NO_CONTEXT) {
+ LOG("Failed to create egl context (%s)", EglErrorString(eglGetError()));
+ return NULL;
+ }
+
+ if (!eglMakeCurrent(gpu_context->display, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ gpu_context->context)) {
+ LOG("Failed to make egl context current (%s)",
+ EglErrorString(eglGetError()));
+ return NULL;
+ }
+
+ const char* gl_ext = (const char*)glGetString(GL_EXTENSIONS);
+ if (!gl_ext) {
+ LOG("Failed to get gl extensions (%s)", GlErrorString(glGetError()));
+ return NULL;
+ }
+
+ LOG("GL_EXTENSIONS: %s", gl_ext);
+ if (!HasExtension(gl_ext, "GL_OES_EGL_image")) return NULL;
+ gpu_context->glEGLImageTargetTexture2DOES =
+ (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress(
+ "glEGLImageTargetTexture2DOES");
+ if (!gpu_context->glEGLImageTargetTexture2DOES) {
+ LOG("Failed to lookup glEGLImageTargetTexture2DOES function");
+ return NULL;
+ }
+
+ 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 ||
+ !SetupImgInputUniform(gpu_context->program_luma)) {
+ LOG("Failed to create luma program");
+ return NULL;
+ }
+
+ 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 ||
+ !SetupImgInputUniform(gpu_context->program_luma)) {
+ LOG("Failed to create chroma program");
+ return NULL;
+ }
+ gpu_context->chroma_offsets =
+ glGetUniformLocation(gpu_context->program_chroma, "chroma_offsets");
+ if (gpu_context->chroma_offsets == -1) {
+ LOG("Failed to find chroma_offsets uniform (%s)",
+ GlErrorString(glGetError()));
+ return NULL;
+ }
+
+ glGenFramebuffers(1, &gpu_context->framebuffer);
+ glBindFramebuffer(GL_FRAMEBUFFER, gpu_context->framebuffer);
+ glGenBuffers(1, &gpu_context->vertices);
+ glBindBuffer(GL_ARRAY_BUFFER, gpu_context->vertices);
+ static const GLfloat vertices[] = {0, 0, 1, 0, 1, 1, 0, 1};
+ glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, 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 create gl objects (%s)", GlErrorString(glGetError()));
+ return NULL;
+ }
+ return RELEASE(gpu_context);
+}
+
+bool GpuContextSync(struct GpuContext* gpu_context) {
+ EGLSync sync = eglCreateSync(gpu_context->display, EGL_SYNC_FENCE, NULL);
+ if (sync == EGL_NO_SYNC) {
+ LOG("Failed to create egl fence sync (%s)", EglErrorString(eglGetError()));
+ return false;
+ }
+ eglClientWaitSync(gpu_context->display, sync, 0, EGL_FOREVER);
+ eglDestroySync(gpu_context->display, sync);
+ return true;
+}
+
+void GpuContextDestroy(struct GpuContext** gpu_context) {
+ if (!gpu_context || !*gpu_context) return;
+ if ((*gpu_context)->vertices) glDeleteBuffers(1, &(*gpu_context)->vertices);
+ if ((*gpu_context)->framebuffer)
+ glDeleteFramebuffers(1, &(*gpu_context)->framebuffer);
+ if ((*gpu_context)->program_chroma)
+ glDeleteProgram((*gpu_context)->program_chroma);
+ if ((*gpu_context)->program_luma)
+ glDeleteProgram((*gpu_context)->program_luma);
+ if ((*gpu_context)->context != EGL_NO_CONTEXT) {
+ eglMakeCurrent((*gpu_context)->display, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ EGL_NO_CONTEXT);
+ eglDestroyContext((*gpu_context)->display, (*gpu_context)->context);
+ }
+ if ((*gpu_context)->display != EGL_NO_DISPLAY)
+ eglTerminate((*gpu_context)->display);
+ free(*gpu_context);
+ *gpu_context = NULL;
+}
+
+static EGLImage CreateEglImage(struct GpuContext* gpu_context, uint32_t width,
+ uint32_t height, uint32_t fourcc, size_t nplanes,
+ const struct GpuFramePlane* planes) {
+ static const EGLAttrib attrib_keys[] = {
+ EGL_DMA_BUF_PLANE0_FD_EXT, EGL_DMA_BUF_PLANE0_OFFSET_EXT,
+ EGL_DMA_BUF_PLANE0_PITCH_EXT, EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT,
+ EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE1_FD_EXT,
+ EGL_DMA_BUF_PLANE1_OFFSET_EXT, EGL_DMA_BUF_PLANE1_PITCH_EXT,
+ EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT, EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT,
+ EGL_DMA_BUF_PLANE2_FD_EXT, EGL_DMA_BUF_PLANE2_OFFSET_EXT,
+ EGL_DMA_BUF_PLANE2_PITCH_EXT, EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT,
+ EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE3_FD_EXT,
+ EGL_DMA_BUF_PLANE3_OFFSET_EXT, EGL_DMA_BUF_PLANE3_PITCH_EXT,
+ EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT, EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT,
+ };
+
+ EGLAttrib attrib_list[7 + LENGTH(attrib_keys) * 2] = {
+#define _(...) __VA_ARGS__
+ _(EGL_WIDTH, width),
+ _(EGL_HEIGHT, height),
+ _(EGL_LINUX_DRM_FOURCC_EXT, fourcc),
+#undef _
+ };
+
+ EGLAttrib* pairs = &attrib_list[6];
+ const EGLAttrib* key = &attrib_keys[0];
+ for (size_t i = 0; i < nplanes; i++) {
+ if (planes[i].dmabuf_fd == -1) break;
+ *pairs++ = *key++;
+ *pairs++ = planes[i].dmabuf_fd;
+ *pairs++ = *key++;
+ *pairs++ = planes[i].offset;
+ *pairs++ = *key++;
+ *pairs++ = planes[i].pitch;
+ *pairs++ = *key++;
+ *pairs++ = planes[i].modifier >> 32;
+ *pairs++ = *key++;
+ *pairs++ = planes[i].modifier & UINT32_MAX;
+ }
+
+ *pairs = EGL_NONE;
+ EGLImage image = eglCreateImage(gpu_context->display, EGL_NO_CONFIG_KHR,
+ EGL_LINUX_DMA_BUF_EXT, NULL, attrib_list);
+ if (image == EGL_NO_IMAGE)
+ LOG("Failed to create egl image (%s)", EglErrorString(eglGetError()));
+ return image;
+}
+
+static GLuint CreateTexture(struct GpuContext* gpu_context, EGLImage image) {
+ GLuint texture = 0;
+ glGenTextures(1, &texture);
+ glBindTexture(GL_TEXTURE_2D, 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, image);
+
+ GLenum error = glGetError();
+ if (error != GL_NO_ERROR) {
+ LOG("Failed to create texture (%s)", GlErrorString(error));
+ glDeleteTextures(1, &texture);
+ return 0;
+ }
+ return texture;
+}
+
+struct GpuFrame* GpuFrameCreate(struct GpuContext* gpu_context, uint32_t width,
+ uint32_t height, uint32_t fourcc,
+ size_t nplanes,
+ const struct GpuFramePlane* planes) {
+ struct AUTO(GpuFrame)* gpu_frame = malloc(sizeof(struct GpuFrame));
+ if (!gpu_frame) {
+ LOG("Failed to allocate gpu frame (%s)", strerror(errno));
+ return NULL;
+ }
+ *gpu_frame = (struct GpuFrame){
+ .gpu_context = gpu_context,
+ .width = width,
+ .height = height,
+ .dmabuf_fds = {-1, -1, -1, -1},
+ .images = {EGL_NO_IMAGE, EGL_NO_IMAGE},
+ .textures = {0, 0},
+ };
+
+ for (size_t i = 0; i < nplanes; i++) {
+ gpu_frame->dmabuf_fds[i] = dup(planes[i].dmabuf_fd);
+ if (gpu_frame->dmabuf_fds[i] == -1) {
+ LOG("Failed to dup dmabuf fd (%s)", strerror(errno));
+ return NULL;
+ }
+ }
+
+ struct GpuFramePlane dummy_planes[nplanes];
+ for (size_t i = 0; i < nplanes; i++) {
+ dummy_planes[i] = (struct GpuFramePlane){
+ .dmabuf_fd = gpu_frame->dmabuf_fds[i],
+ .offset = planes[i].offset,
+ .pitch = planes[i].pitch,
+ .modifier = planes[i].modifier,
+ };
+ }
+
+ if (fourcc == DRM_FORMAT_NV12) {
+ gpu_frame->images[0] = CreateEglImage(gpu_context, width, height,
+ DRM_FORMAT_R8, 1, &dummy_planes[0]);
+ if (gpu_frame->images[0] == EGL_NO_IMAGE) {
+ LOG("Failed to create luma plane image");
+ return NULL;
+ }
+ gpu_frame->images[1] = CreateEglImage(gpu_context, width / 2, height / 2,
+ DRM_FORMAT_GR88, 1, &dummy_planes[1]);
+ if (gpu_frame->images[1] == EGL_NO_IMAGE) {
+ LOG("Failed to create chroma plane image");
+ return NULL;
+ }
+ } else {
+ gpu_frame->images[0] = CreateEglImage(gpu_context, width, height, fourcc,
+ nplanes, dummy_planes);
+ if (gpu_frame->images[0] == EGL_NO_IMAGE) {
+ LOG("Failed to create multiplanar image");
+ return NULL;
+ }
+ }
+
+ for (size_t i = 0; i < LENGTH(gpu_frame->images); i++) {
+ if (gpu_frame->images[i] == EGL_NO_IMAGE) break;
+ gpu_frame->textures[i] = CreateTexture(gpu_context, gpu_frame->images[i]);
+ if (!gpu_frame->textures[i]) {
+ LOG("Failed to create texture");
+ return NULL;
+ }
+ }
+
+ return RELEASE(gpu_frame);
+}
+
+void GpuFrameGetSize(const struct GpuFrame* gpu_frame, uint32_t* width,
+ uint32_t* height) {
+ *width = gpu_frame->width;
+ *height = gpu_frame->height;
+}
+
+static bool GpuFrameConvertImpl(GLuint from, GLuint to) {
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ to, 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, from);
+ glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+ GLenum error = glGetError();
+ if (error != GL_NO_ERROR) {
+ LOG("Failed to convert plane (%s)", GlErrorString(error));
+ return false;
+ }
+ return true;
+}
+
+bool GpuFrameConvert(const struct GpuFrame* from, const struct GpuFrame* to) {
+ glUseProgram(from->gpu_context->program_luma);
+ glViewport(0, 0, (GLsizei)to->width, (GLsizei)to->height);
+ if (!GpuFrameConvertImpl(from->textures[0], to->textures[0])) {
+ LOG("Failed to convert luma plane");
+ return false;
+ }
+
+ glUseProgram(from->gpu_context->program_chroma);
+ glUniform2f(from->gpu_context->chroma_offsets, 1.f / (GLfloat)from->width,
+ 1.f / (GLfloat)from->height);
+ glViewport(0, 0, (GLsizei)to->width / 2, (GLsizei)to->height / 2);
+ if (!GpuFrameConvertImpl(from->textures[0], to->textures[1])) {
+ LOG("Failed to convert chroma plane");
+ return false;
+ }
+ return true;
+}
+
+void GpuFrameDestroy(struct GpuFrame** gpu_frame) {
+ if (!gpu_frame || !*gpu_frame) return;
+ for (size_t i = LENGTH((*gpu_frame)->textures); i; i--) {
+ if ((*gpu_frame)->textures[i])
+ glDeleteTextures(1, &(*gpu_frame)->textures[i]);
+ }
+ for (size_t i = LENGTH((*gpu_frame)->images); i; i--) {
+ if ((*gpu_frame)->images[i] != EGL_NO_IMAGE)
+ eglDestroyImage((*gpu_frame)->gpu_context->display,
+ (*gpu_frame)->images[i]);
+ }
+ for (size_t i = LENGTH((*gpu_frame)->dmabuf_fds); i; i--) {
+ if ((*gpu_frame)->dmabuf_fds[i] != -1) close((*gpu_frame)->dmabuf_fds[i]);
+ }
+ free(*gpu_frame);
+ *gpu_frame = NULL;
+}