// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd.
//
// SPDX-License-Identifier: Expat

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <drm_fourcc.h>
#include <kywc/log.h>

#include "render/opengl.h"
#include "render/pixel_format.h"

#include "quad_ex_frag_str.h"
#include "quad_ex_vert_str.h"
#include "quad_frag_str.h"
#include "quad_vert_str.h"
#include "tex_common_vert_str.h"
#include "tex_ex_vert_str.h"
#include "tex_external_ex_frag_str.h"
#include "tex_external_frag_str.h"
#include "tex_mix_frag_str.h"
#include "tex_mix_vert_str.h"
#include "tex_rgba_ex_frag_str.h"
#include "tex_rgba_frag_str.h"
#include "tex_rgbx_ex_frag_str.h"
#include "tex_rgbx_frag_str.h"

static const float transforms[][9] = {
	[WL_OUTPUT_TRANSFORM_NORMAL] = {
		1.0f, 0.0f, 0.0f,
		0.0f, 1.0f, 0.0f,
		0.0f, 0.0f, 1.0f,
	},
	[WL_OUTPUT_TRANSFORM_90] = {
		0.0f, 1.0f, 0.0f,
		-1.0f, 0.0f, 0.0f,
		0.0f, 0.0f, 1.0f,
	},
	[WL_OUTPUT_TRANSFORM_180] = {
		-1.0f, 0.0f, 0.0f,
		0.0f, -1.0f, 0.0f,
		0.0f, 0.0f, 1.0f,
	},
	[WL_OUTPUT_TRANSFORM_270] = {
		0.0f, -1.0f, 0.0f,
		1.0f, 0.0f, 0.0f,
		0.0f, 0.0f, 1.0f,
	},
	[WL_OUTPUT_TRANSFORM_FLIPPED] = {
		-1.0f, 0.0f, 0.0f,
		0.0f, 1.0f, 0.0f,
		0.0f, 0.0f, 1.0f,
	},
	[WL_OUTPUT_TRANSFORM_FLIPPED_90] = {
		0.0f, 1.0f, 0.0f,
		1.0f, 0.0f, 0.0f,
		0.0f, 0.0f, 1.0f,
	},
	[WL_OUTPUT_TRANSFORM_FLIPPED_180] = {
		1.0f, 0.0f, 0.0f,
		0.0f, -1.0f, 0.0f,
		0.0f, 0.0f, 1.0f,
	},
	[WL_OUTPUT_TRANSFORM_FLIPPED_270] = {
		0.0f, -1.0f, 0.0f,
		-1.0f, 0.0f, 0.0f,
		0.0f, 0.0f, 1.0f,
	},
};

void ky_opengl_matrix_projection(float mat[static 9], int width, int height,
                                 enum wl_output_transform transform)
{
    memset(mat, 0, sizeof(*mat) * 9);

    const float *t = transforms[transform];
    float x = 2.0f / width;
    float y = 2.0f / height;

    // Rotation + reflection
    mat[0] = x * t[0];
    mat[1] = x * t[1];
    mat[3] = y * -t[3];
    mat[4] = y * -t[4];

    // Translation
    mat[2] = -copysign(1.0f, mat[0] + mat[1]);
    mat[5] = -copysign(1.0f, mat[3] + mat[4]);

    // Identity
    mat[8] = 1.0f;
}

static const struct wlr_renderer_impl renderer_impl;
static const struct wlr_render_timer_impl render_timer_impl;

bool wlr_renderer_is_opengl(struct wlr_renderer *wlr_renderer)
{
    return wlr_renderer->WLR_PRIVATE.impl == &renderer_impl;
}

struct ky_opengl_renderer *ky_opengl_renderer_from_wlr_renderer(struct wlr_renderer *wlr_renderer)
{
    assert(wlr_renderer->WLR_PRIVATE.impl == &renderer_impl);
    struct ky_opengl_renderer *renderer = wl_container_of(wlr_renderer, renderer, wlr_renderer);
    return renderer;
}

static struct ky_opengl_render_timer *gl_get_render_timer(struct wlr_render_timer *wlr_timer)
{
    assert(wlr_timer->impl == &render_timer_impl);
    struct ky_opengl_render_timer *timer = wl_container_of(wlr_timer, timer, base);
    return timer;
}

static void destroy_buffer(struct ky_opengl_buffer *buffer)
{
    wl_list_remove(&buffer->link);
    wlr_addon_finish(&buffer->addon);

    struct ky_egl_context prev_ctx;
    ky_egl_make_current(buffer->renderer->egl, &prev_ctx);

    ky_opengl_push_debug(buffer->renderer);

    glDeleteFramebuffers(1, &buffer->fbo);
    glDeleteRenderbuffers(1, &buffer->rbo);
    glDeleteTextures(1, &buffer->tex);

    ky_opengl_pop_debug(buffer->renderer);

    ky_egl_destroy_image(buffer->renderer->egl, buffer->image);

    ky_egl_restore_context(&prev_ctx);

    free(buffer);
}

static void handle_buffer_destroy(struct wlr_addon *addon)
{
    struct ky_opengl_buffer *buffer = wl_container_of(addon, buffer, addon);
    destroy_buffer(buffer);
}

static const struct wlr_addon_interface buffer_addon_impl = {
    .name = "ky_opengl_buffer",
    .destroy = handle_buffer_destroy,
};

GLuint ky_opengl_buffer_get_fbo(struct ky_opengl_buffer *buffer)
{
    if (buffer->external_only) {
        kywc_log(KYWC_ERROR, "DMA-BUF format is external-only");
        return 0;
    }

    if (buffer->fbo) {
        return buffer->fbo;
    }

    ky_opengl_push_debug(buffer->renderer);

    if (!buffer->rbo) {
        glGenRenderbuffers(1, &buffer->rbo);
        glBindRenderbuffer(GL_RENDERBUFFER, buffer->rbo);
        glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, buffer->image);
        glBindRenderbuffer(GL_RENDERBUFFER, 0);
    }

    glGenFramebuffers(1, &buffer->fbo);
    glBindFramebuffer(GL_FRAMEBUFFER, buffer->fbo);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, buffer->rbo);
    GLenum fb_status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    if (fb_status != GL_FRAMEBUFFER_COMPLETE) {
        kywc_log(KYWC_ERROR, "Failed to create FBO");
        glDeleteFramebuffers(1, &buffer->fbo);
        buffer->fbo = 0;
    }

    ky_opengl_pop_debug(buffer->renderer);

    return buffer->fbo;
}

struct ky_opengl_buffer *ky_opengl_buffer_get(struct ky_opengl_renderer *renderer,
                                              struct wlr_buffer *wlr_buffer)
{
    struct wlr_addon *addon = wlr_addon_find(&wlr_buffer->addons, renderer, &buffer_addon_impl);
    if (addon) {
        struct ky_opengl_buffer *buffer = wl_container_of(addon, buffer, addon);
        return buffer;
    }
    return NULL;
}

struct ky_opengl_buffer *ky_opengl_buffer_get_or_create(struct ky_opengl_renderer *renderer,
                                                        struct wlr_buffer *wlr_buffer)
{
    struct wlr_addon *addon = wlr_addon_find(&wlr_buffer->addons, renderer, &buffer_addon_impl);
    if (addon) {
        struct ky_opengl_buffer *buffer = wl_container_of(addon, buffer, addon);
        return buffer;
    }

    struct ky_opengl_buffer *buffer = calloc(1, sizeof(*buffer));
    if (buffer == NULL) {
        kywc_log_errno(KYWC_ERROR, "Allocation failed");
        return NULL;
    }
    buffer->buffer = wlr_buffer;
    buffer->renderer = renderer;

    struct wlr_dmabuf_attributes dmabuf = { 0 };
    if (!wlr_buffer_get_dmabuf(wlr_buffer, &dmabuf)) {
        goto error_buffer;
    }

    buffer->image = ky_egl_create_image_from_dmabuf(renderer->egl, &dmabuf, &buffer->external_only);
    if (buffer->image == EGL_NO_IMAGE_KHR) {
        goto error_buffer;
    }

    wlr_addon_init(&buffer->addon, &wlr_buffer->addons, renderer, &buffer_addon_impl);

    wl_list_insert(&renderer->buffers, &buffer->link);

    kywc_log(KYWC_DEBUG, "Created GL FBO for buffer %dx%d", wlr_buffer->width, wlr_buffer->height);

    return buffer;

error_buffer:
    free(buffer);
    return NULL;
}

static const struct wlr_drm_format_set *gl_get_texture_formats(struct wlr_renderer *wlr_renderer,
                                                               uint32_t buffer_caps)
{
    struct ky_opengl_renderer *renderer = ky_opengl_renderer_from_wlr_renderer(wlr_renderer);

    if (buffer_caps & WLR_BUFFER_CAP_DMABUF) {
        return &renderer->egl->dmabuf_texture_formats;
    } else if (buffer_caps & WLR_BUFFER_CAP_DATA_PTR) {
        return &renderer->shm_texture_formats;
    } else {
        return NULL;
    }
}

static const struct wlr_drm_format_set *gl_get_render_formats(struct wlr_renderer *wlr_renderer)
{
    struct ky_opengl_renderer *renderer = ky_opengl_renderer_from_wlr_renderer(wlr_renderer);
    return &renderer->egl->dmabuf_render_formats;
}

static int gl_get_drm_fd(struct wlr_renderer *wlr_renderer)
{
    struct ky_opengl_renderer *renderer = ky_opengl_renderer_from_wlr_renderer(wlr_renderer);

    if (renderer->drm_fd < 0) {
        renderer->drm_fd = ky_egl_dup_drm_fd(renderer->egl);
    }

    return renderer->drm_fd;
}

struct ky_egl *ky_opengl_renderer_get_egl(struct wlr_renderer *wlr_renderer)
{
    struct ky_opengl_renderer *renderer = ky_opengl_renderer_from_wlr_renderer(wlr_renderer);
    return renderer->egl;
}

static void gl_destroy(struct wlr_renderer *wlr_renderer)
{
    struct ky_opengl_renderer *renderer = ky_opengl_renderer_from_wlr_renderer(wlr_renderer);

    ky_egl_make_current(renderer->egl, NULL);

    struct ky_opengl_texture *tex, *tex_tmp;
    wl_list_for_each_safe(tex, tex_tmp, &renderer->textures, link) {
        ky_opengl_texture_destroy(tex);
    }

    struct ky_opengl_buffer *buffer, *buffer_tmp;
    wl_list_for_each_safe(buffer, buffer_tmp, &renderer->buffers, link) {
        destroy_buffer(buffer);
    }

    ky_opengl_push_debug(renderer);
    glDeleteProgram(renderer->shaders.quad.program);
    glDeleteProgram(renderer->shaders.tex_rgba.program);
    glDeleteProgram(renderer->shaders.tex_rgbx.program);
    glDeleteProgram(renderer->shaders.tex_ext.program);
    ky_opengl_pop_debug(renderer);

    if (renderer->exts.KHR_debug) {
        glDisable(GL_DEBUG_OUTPUT_KHR);
        glDebugMessageCallbackKHR(NULL, NULL);
    }

    ky_egl_unset_current(renderer->egl);
    ky_egl_destroy(renderer->egl);

    wlr_drm_format_set_finish(&renderer->shm_texture_formats);

    if (renderer->drm_fd >= 0) {
        close(renderer->drm_fd);
    }

    free(renderer);
}

static struct wlr_render_pass *gl_begin_buffer_pass(struct wlr_renderer *wlr_renderer,
                                                    struct wlr_buffer *wlr_buffer,
                                                    const struct wlr_buffer_pass_options *options)
{
    struct ky_opengl_renderer *renderer = ky_opengl_renderer_from_wlr_renderer(wlr_renderer);

    struct ky_egl_context prev_ctx = { 0 };
    if (!ky_egl_make_current(renderer->egl, &prev_ctx)) {
        return NULL;
    }

    struct ky_opengl_render_timer *timer = NULL;
    if (options->timer) {
        timer = gl_get_render_timer(options->timer);
        clock_gettime(CLOCK_MONOTONIC, &timer->cpu_start);
    }

    struct ky_opengl_buffer *buffer = ky_opengl_buffer_get_or_create(renderer, wlr_buffer);
    if (!buffer) {
        return NULL;
    }

    struct ky_opengl_render_pass *pass = ky_opengl_begin_buffer_pass(buffer, &prev_ctx, timer);
    if (!pass) {
        return NULL;
    }
    return &pass->base;
}

static struct wlr_render_timer *gl_render_timer_create(struct wlr_renderer *wlr_renderer)
{
    struct ky_opengl_renderer *renderer = ky_opengl_renderer_from_wlr_renderer(wlr_renderer);
    if (!renderer->exts.EXT_disjoint_timer_query) {
        kywc_log(KYWC_ERROR, "Can't create timer, EXT_disjoint_timer_query not available");
        return NULL;
    }

    struct ky_opengl_render_timer *timer = calloc(1, sizeof(*timer));
    if (!timer) {
        return NULL;
    }
    timer->base.impl = &render_timer_impl;
    timer->renderer = renderer;

    struct ky_egl_context prev_ctx;
    ky_egl_make_current(renderer->egl, &prev_ctx);
    glGenQueriesEXT(1, &timer->id);
    ky_egl_restore_context(&prev_ctx);

    return &timer->base;
}

static int64_t timespec_to_nsec(const struct timespec *a)
{
    return (int64_t)a->tv_sec * 1000000000 + a->tv_nsec;
}

static int gl_get_render_time(struct wlr_render_timer *wlr_timer)
{
    struct ky_opengl_render_timer *timer = gl_get_render_timer(wlr_timer);
    struct ky_opengl_renderer *renderer = timer->renderer;

    struct ky_egl_context prev_ctx;
    ky_egl_make_current(renderer->egl, &prev_ctx);

    GLint64 disjoint;
    glGetInteger64v(GL_GPU_DISJOINT_EXT, &disjoint);
    if (disjoint) {
        kywc_log(KYWC_ERROR, "A disjoint operation occurred and the render timer is invalid");
        ky_egl_restore_context(&prev_ctx);
        return -1;
    }

    GLint available;
    glGetQueryObjectivEXT(timer->id, GL_QUERY_RESULT_AVAILABLE_EXT, &available);
    if (!available) {
        kywc_log(KYWC_ERROR, "Timer was read too early, gpu isn't done!");
        ky_egl_restore_context(&prev_ctx);
        return -1;
    }

    GLuint64 gl_render_end;
    glGetQueryObjectui64vEXT(timer->id, GL_QUERY_RESULT_EXT, &gl_render_end);

    int64_t cpu_nsec_total =
        timespec_to_nsec(&timer->cpu_end) - timespec_to_nsec(&timer->cpu_start);

    ky_egl_restore_context(&prev_ctx);
    return gl_render_end - timer->gl_cpu_end + cpu_nsec_total;
}

static void gl_render_timer_destroy(struct wlr_render_timer *wlr_timer)
{
    struct ky_opengl_render_timer *timer = wl_container_of(wlr_timer, timer, base);
    struct ky_opengl_renderer *renderer = timer->renderer;

    struct ky_egl_context prev_ctx;
    ky_egl_make_current(renderer->egl, &prev_ctx);
    glDeleteQueriesEXT(1, &timer->id);
    ky_egl_restore_context(&prev_ctx);
    free(timer);
}

static const struct wlr_renderer_impl renderer_impl = {
    .destroy = gl_destroy,
    .get_texture_formats = gl_get_texture_formats,
    .get_render_formats = gl_get_render_formats,
    .get_drm_fd = gl_get_drm_fd,
    .texture_from_buffer = ky_opengl_texture_from_buffer,
    .begin_buffer_pass = gl_begin_buffer_pass,
    .render_timer_create = gl_render_timer_create,
};

static const struct wlr_render_timer_impl render_timer_impl = {
    .get_duration_ns = gl_get_render_time,
    .destroy = gl_render_timer_destroy,
};

// https://www.khronos.org/opengl/wiki/Debug_Output
void ky_opengl_push_debug_(struct ky_opengl_renderer *renderer, const char *file, const char *func)
{
    if (!renderer->exts.KHR_debug) {
        return;
    }

    int len = snprintf(NULL, 0, "%s:%s", file, func) + 1;
    char str[len];
    snprintf(str, len, "%s:%s", file, func);
    glPushDebugGroupKHR(GL_DEBUG_SOURCE_APPLICATION_KHR, 1, -1, str);
}

void ky_opengl_pop_debug(struct ky_opengl_renderer *renderer)
{
    if (renderer->exts.KHR_debug) {
        glPopDebugGroupKHR();
    }
}

static enum kywc_log_level gl_log_level_to_kywc(GLenum type)
{
    switch (type) {
    case GL_DEBUG_TYPE_ERROR_KHR:
        return KYWC_ERROR;
    case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_KHR:
        return KYWC_DEBUG;
    case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_KHR:
        return KYWC_ERROR;
    case GL_DEBUG_TYPE_PORTABILITY_KHR:
        return KYWC_DEBUG;
    case GL_DEBUG_TYPE_PERFORMANCE_KHR:
        return KYWC_DEBUG;
    case GL_DEBUG_TYPE_OTHER_KHR:
        return KYWC_DEBUG;
    case GL_DEBUG_TYPE_MARKER_KHR:
        return KYWC_DEBUG;
    case GL_DEBUG_TYPE_PUSH_GROUP_KHR:
        return KYWC_DEBUG;
    case GL_DEBUG_TYPE_POP_GROUP_KHR:
        return KYWC_DEBUG;
    default:
        return KYWC_DEBUG;
    }
}

static void gl_log(GLenum src, GLenum type, GLuint id, GLenum severity, GLsizei len,
                   const GLchar *msg, const void *user)
{
    kywc_log(gl_log_level_to_kywc(type), "[GL] %s", msg);
}

static GLuint compile_shader(struct ky_opengl_renderer *renderer, GLenum type, const GLchar *src)
{
    ky_opengl_push_debug(renderer);

    GLuint shader = glCreateShader(type);
    glShaderSource(shader, 1, &src, NULL);
    glCompileShader(shader);

    GLint ok;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
    if (ok == GL_FALSE) {
        kywc_log(KYWC_ERROR, "Failed to compile shader");
        glDeleteShader(shader);
        shader = 0;
    }

    ky_opengl_pop_debug(renderer);
    return shader;
}

GLuint ky_opengl_create_program(struct ky_opengl_renderer *renderer, const GLchar *vert_src,
                                const GLchar *frag_src)
{
    ky_opengl_push_debug(renderer);

    GLuint vert = compile_shader(renderer, GL_VERTEX_SHADER, vert_src);
    if (!vert) {
        goto error;
    }

    GLuint frag = compile_shader(renderer, GL_FRAGMENT_SHADER, frag_src);
    if (!frag) {
        glDeleteShader(vert);
        goto error;
    }

    GLuint prog = glCreateProgram();
    glAttachShader(prog, vert);
    glAttachShader(prog, frag);
    glLinkProgram(prog);

    glDetachShader(prog, vert);
    glDetachShader(prog, frag);
    glDeleteShader(vert);
    glDeleteShader(frag);

    GLint ok;
    glGetProgramiv(prog, GL_LINK_STATUS, &ok);
    if (ok == GL_FALSE) {
        kywc_log(KYWC_ERROR, "Failed to link shader: \n%s\n%s", vert_src, frag_src);
        glDeleteProgram(prog);
        goto error;
    }

    ky_opengl_pop_debug(renderer);
    return prog;

error:
    ky_opengl_pop_debug(renderer);
    return 0;
}

static struct wlr_renderer *ky_opengl_renderer_create(struct ky_egl *egl)
{
    if (!ky_egl_make_current(egl, NULL)) {
        return NULL;
    }

    struct ky_opengl_renderer *renderer = calloc(1, sizeof(*renderer));
    if (renderer == NULL) {
        return NULL;
    }
    wlr_renderer_init(&renderer->wlr_renderer, &renderer_impl, WLR_BUFFER_CAP_DMABUF);

    wl_list_init(&renderer->buffers);
    wl_list_init(&renderer->textures);

    renderer->egl = egl;
    renderer->drm_fd = -1;
    renderer->is_core_profile =
        epoxy_gl_version() >= 31 && !epoxy_has_gl_extension("GL_ARB_compatibility");

    kywc_log(KYWC_INFO, "Creating OpenGL renderer");
    kywc_log(KYWC_INFO, "Using %s", glGetString(GL_VERSION));
    kywc_log(KYWC_INFO, "GL vendor: %s", glGetString(GL_VENDOR));
    kywc_log(KYWC_INFO, "GL renderer: %s", glGetString(GL_RENDERER));

    if (!renderer->egl->exts.EXT_image_dma_buf_import) {
        kywc_log(KYWC_ERROR, "EGL_EXT_image_dma_buf_import not supported");
        free(renderer);
        return NULL;
    }
    if (egl->is_gles && !epoxy_has_gl_extension("GL_EXT_texture_format_BGRA8888")) {
        kywc_log(KYWC_ERROR, "BGRA8888 format not supported by GLES");
        free(renderer);
        return NULL;
    }
    if (egl->is_gles && epoxy_gl_version() < 30 &&
        !epoxy_has_gl_extension("GL_EXT_unpack_subimage")) {
        kywc_log(KYWC_ERROR, "GL_EXT_unpack_subimage not supported");
        free(renderer);
        return NULL;
    }

    renderer->exts.EXT_read_format_bgra =
        !egl->is_gles || epoxy_has_gl_extension("GL_EXT_read_format_bgra");

    renderer->exts.EXT_texture_type_2_10_10_10_REV =
        epoxy_has_gl_extension("GL_EXT_texture_type_2_10_10_10_REV");

    renderer->exts.OES_texture_half_float_linear =
        epoxy_has_gl_extension("GL_OES_texture_half_float_linear");

    renderer->exts.EXT_texture_norm16 = epoxy_has_gl_extension("GL_EXT_texture_norm16");

    renderer->exts.OES_egl_image_external = epoxy_has_gl_extension("GL_OES_EGL_image_external");

    renderer->exts.OES_egl_image = epoxy_has_gl_extension("GL_OES_EGL_image");

    renderer->exts.KHR_robustness = epoxy_has_gl_extension("GL_KHR_robustness");
    if (renderer->exts.KHR_robustness) {
        GLint notif_strategy = 0;
        glGetIntegerv(GL_RESET_NOTIFICATION_STRATEGY_KHR, &notif_strategy);
        switch (notif_strategy) {
        case GL_LOSE_CONTEXT_ON_RESET_KHR:
            kywc_log(KYWC_DEBUG, "GPU reset notifications are enabled");
            break;
        case GL_NO_RESET_NOTIFICATION_KHR:
            kywc_log(KYWC_DEBUG, "GPU reset notifications are disabled");
            break;
        }
    }

    renderer->exts.EXT_disjoint_timer_query = epoxy_has_gl_extension("GL_EXT_disjoint_timer_query");

    renderer->exts.KHR_debug = epoxy_has_gl_extension("GL_KHR_debug");
    if (renderer->exts.KHR_debug) {
        glEnable(GL_DEBUG_OUTPUT_KHR);
        glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR);
        glDebugMessageCallbackKHR(gl_log, NULL);
        // Silence unwanted message types
        glDebugMessageControlKHR(GL_DONT_CARE, GL_DEBUG_TYPE_POP_GROUP_KHR, GL_DONT_CARE, 0, NULL,
                                 GL_FALSE);
        glDebugMessageControlKHR(GL_DONT_CARE, GL_DEBUG_TYPE_PUSH_GROUP_KHR, GL_DONT_CARE, 0, NULL,
                                 GL_FALSE);
    }

    ky_opengl_push_debug(renderer);

    GLuint prog;
    renderer->shaders.quad.program = prog =
        ky_opengl_create_program(renderer, quad_vert_str, quad_frag_str);
    if (!renderer->shaders.quad.program) {
        goto error;
    }
    renderer->shaders.quad.uv2ndc = glGetUniformLocation(prog, "uv2ndc");
    renderer->shaders.quad.color = glGetUniformLocation(prog, "color");
    renderer->shaders.quad.uv_attrib = glGetAttribLocation(prog, "inUV");
    renderer->shaders.quad.uv_rotation = -1;
    renderer->shaders.quad.aspect = -1;
    renderer->shaders.quad.anti_aliasing = -1;
    renderer->shaders.quad.round_corner_radius = -1;

    renderer->shaders.tex_rgba.program = prog =
        ky_opengl_create_program(renderer, tex_common_vert_str, tex_rgba_frag_str);
    if (!renderer->shaders.tex_rgba.program) {
        goto error;
    }
    renderer->shaders.tex_rgba.uv2texcoord = glGetUniformLocation(prog, "uv2texcoord");
    renderer->shaders.tex_rgba.uv2ndc = glGetUniformLocation(prog, "uv2ndc");
    renderer->shaders.tex_rgba.tex = glGetUniformLocation(prog, "tex");
    renderer->shaders.tex_rgba.alpha = glGetUniformLocation(prog, "alpha");
    renderer->shaders.tex_rgba.uv_attrib = glGetAttribLocation(prog, "inUV");
    renderer->shaders.tex_rgba.uv_rotation = -1;
    renderer->shaders.tex_rgba.aspect = -1;
    renderer->shaders.tex_rgba.anti_aliasing = -1;
    renderer->shaders.tex_rgba.round_corner_radius = -1;

    renderer->shaders.tex_rgbx.program = prog =
        ky_opengl_create_program(renderer, tex_common_vert_str, tex_rgbx_frag_str);
    if (!renderer->shaders.tex_rgbx.program) {
        goto error;
    }
    renderer->shaders.tex_rgbx.uv2texcoord = glGetUniformLocation(prog, "uv2texcoord");
    renderer->shaders.tex_rgbx.uv2ndc = glGetUniformLocation(prog, "uv2ndc");
    renderer->shaders.tex_rgbx.tex = glGetUniformLocation(prog, "tex");
    renderer->shaders.tex_rgbx.alpha = glGetUniformLocation(prog, "alpha");
    renderer->shaders.tex_rgbx.uv_attrib = glGetAttribLocation(prog, "inUV");
    renderer->shaders.tex_rgbx.uv_rotation = -1;
    renderer->shaders.tex_rgbx.aspect = -1;
    renderer->shaders.tex_rgbx.anti_aliasing = -1;
    renderer->shaders.tex_rgbx.round_corner_radius = -1;

    if (renderer->exts.OES_egl_image_external) {
        renderer->shaders.tex_ext.program = prog =
            ky_opengl_create_program(renderer, tex_common_vert_str, tex_external_frag_str);
        if (!renderer->shaders.tex_ext.program) {
            goto error;
        }
        renderer->shaders.tex_ext.uv2texcoord = glGetUniformLocation(prog, "uv2texcoord");
        renderer->shaders.tex_ext.uv2ndc = glGetUniformLocation(prog, "uv2ndc");
        renderer->shaders.tex_ext.tex = glGetUniformLocation(prog, "tex");
        renderer->shaders.tex_ext.alpha = glGetUniformLocation(prog, "alpha");
        renderer->shaders.tex_ext.uv_attrib = glGetAttribLocation(prog, "inUV");
        renderer->shaders.tex_ext.uv_rotation = -1;
        renderer->shaders.tex_ext.aspect = -1;
        renderer->shaders.tex_ext.anti_aliasing = -1;
        renderer->shaders.tex_ext.round_corner_radius = -1;
    }

    // round corner clip shader
    renderer->shaders.quad_ex.program = prog =
        ky_opengl_create_program(renderer, quad_ex_vert_str, quad_ex_frag_str);
    if (!renderer->shaders.quad_ex.program) {
        goto error;
    }
    renderer->shaders.quad_ex.uv_rotation = glGetUniformLocation(prog, "uvRotation");
    renderer->shaders.quad_ex.uv2ndc = glGetUniformLocation(prog, "uv2ndc");
    renderer->shaders.quad_ex.color = glGetUniformLocation(prog, "color");
    renderer->shaders.quad_ex.anti_aliasing = glGetUniformLocation(prog, "antiAliasing");
    renderer->shaders.quad_ex.aspect = glGetUniformLocation(prog, "aspect");
    renderer->shaders.quad_ex.round_corner_radius = glGetUniformLocation(prog, "roundCornerRadius");
    renderer->shaders.quad_ex.uv_attrib = glGetAttribLocation(prog, "inUV");

    renderer->shaders.tex_rgba_ex.program = prog =
        ky_opengl_create_program(renderer, tex_ex_vert_str, tex_rgba_ex_frag_str);
    if (!renderer->shaders.tex_rgba_ex.program) {
        goto error;
    }
    renderer->shaders.tex_rgba_ex.uv_rotation = glGetUniformLocation(prog, "uvRotation");
    renderer->shaders.tex_rgba_ex.uv2texcoord = glGetUniformLocation(prog, "uv2texcoord");
    renderer->shaders.tex_rgba_ex.uv2ndc = glGetUniformLocation(prog, "uv2ndc");
    renderer->shaders.tex_rgba_ex.tex = glGetUniformLocation(prog, "tex");
    renderer->shaders.tex_rgba_ex.alpha = glGetUniformLocation(prog, "alpha");
    renderer->shaders.tex_rgba_ex.force_opaque = glGetUniformLocation(prog, "forceOpaque");
    renderer->shaders.tex_rgba_ex.anti_aliasing = glGetUniformLocation(prog, "antiAliasing");
    renderer->shaders.tex_rgba_ex.aspect = glGetUniformLocation(prog, "aspect");
    renderer->shaders.tex_rgba_ex.round_corner_radius =
        glGetUniformLocation(prog, "roundCornerRadius");
    renderer->shaders.tex_rgba_ex.uv_attrib = glGetAttribLocation(prog, "inUV");

    renderer->shaders.tex_rgbx_ex.program = prog =
        ky_opengl_create_program(renderer, tex_ex_vert_str, tex_rgbx_ex_frag_str);
    if (!renderer->shaders.tex_rgbx_ex.program) {
        goto error;
    }
    renderer->shaders.tex_rgbx_ex.uv_rotation = glGetUniformLocation(prog, "uvRotation");
    renderer->shaders.tex_rgbx_ex.uv2texcoord = glGetUniformLocation(prog, "uv2texcoord");
    renderer->shaders.tex_rgbx_ex.uv2ndc = glGetUniformLocation(prog, "uv2ndc");
    renderer->shaders.tex_rgbx_ex.tex = glGetUniformLocation(prog, "tex");
    renderer->shaders.tex_rgbx_ex.alpha = glGetUniformLocation(prog, "alpha");
    renderer->shaders.tex_rgbx_ex.force_opaque = glGetUniformLocation(prog, "forceOpaque");
    renderer->shaders.tex_rgbx_ex.anti_aliasing = glGetUniformLocation(prog, "antiAliasing");
    renderer->shaders.tex_rgbx_ex.aspect = glGetUniformLocation(prog, "aspect");
    renderer->shaders.tex_rgbx_ex.round_corner_radius =
        glGetUniformLocation(prog, "roundCornerRadius");
    renderer->shaders.tex_rgbx_ex.uv_attrib = glGetAttribLocation(prog, "inUV");

    if (renderer->exts.OES_egl_image_external) {
        renderer->shaders.tex_ext_ex.program = prog =
            ky_opengl_create_program(renderer, tex_ex_vert_str, tex_external_ex_frag_str);
        if (!renderer->shaders.tex_ext.program) {
            goto error;
        }
        renderer->shaders.tex_ext_ex.uv_rotation = glGetUniformLocation(prog, "uvRotation");
        renderer->shaders.tex_ext_ex.uv2texcoord = glGetUniformLocation(prog, "uv2texcoord");
        renderer->shaders.tex_ext_ex.uv2ndc = glGetUniformLocation(prog, "uv2ndc");
        renderer->shaders.tex_ext_ex.tex = glGetUniformLocation(prog, "tex");
        renderer->shaders.tex_ext_ex.alpha = glGetUniformLocation(prog, "alpha");
        renderer->shaders.tex_ext_ex.force_opaque = glGetUniformLocation(prog, "forceOpaque");
        renderer->shaders.tex_ext_ex.anti_aliasing = glGetUniformLocation(prog, "antiAliasing");
        renderer->shaders.tex_ext_ex.aspect = glGetUniformLocation(prog, "aspect");
        renderer->shaders.tex_ext_ex.round_corner_radius =
            glGetUniformLocation(prog, "roundCornerRadius");
        renderer->shaders.tex_ext_ex.uv_attrib = glGetAttribLocation(prog, "inUV");
    }

    renderer->shaders.tex_mix.program = prog =
        ky_opengl_create_program(renderer, tex_mix_vert_str, tex_mix_frag_str);
    if (!renderer->shaders.tex_mix.program) {
        goto error;
    }

    renderer->shaders.tex_mix.uv2texcoord = glGetUniformLocation(prog, "uv2texcoord");
    renderer->shaders.tex_mix.uv2texcoord2 = glGetUniformLocation(prog, "uv2texcoord2");
    renderer->shaders.tex_mix.uv2ndc = glGetUniformLocation(prog, "uv2ndc");
    renderer->shaders.tex_mix.tex_a = glGetUniformLocation(prog, "textureA");
    renderer->shaders.tex_mix.tex_b = glGetUniformLocation(prog, "textureB");
    renderer->shaders.tex_mix.alpha = glGetUniformLocation(prog, "alpha");
    renderer->shaders.tex_mix.uvpos_attrib = glGetAttribLocation(prog, "inUV");
    renderer->shaders.tex_mix.blend_factor = glGetUniformLocation(prog, "blendFactor");

    ky_opengl_pop_debug(renderer);

    ky_egl_unset_current(renderer->egl);

    ky_opengl_get_shm_formats(renderer, &renderer->shm_texture_formats);

    return &renderer->wlr_renderer;

error:
    glDeleteProgram(renderer->shaders.quad.program);
    glDeleteProgram(renderer->shaders.tex_rgba.program);
    glDeleteProgram(renderer->shaders.tex_rgbx.program);
    glDeleteProgram(renderer->shaders.tex_ext.program);
    glDeleteProgram(renderer->shaders.quad_ex.program);
    glDeleteProgram(renderer->shaders.tex_rgba_ex.program);
    glDeleteProgram(renderer->shaders.tex_rgbx_ex.program);
    glDeleteProgram(renderer->shaders.tex_ext_ex.program);

    ky_opengl_pop_debug(renderer);

    if (renderer->exts.KHR_debug) {
        glDisable(GL_DEBUG_OUTPUT_KHR);
        glDebugMessageCallbackKHR(NULL, NULL);
    }

    ky_egl_unset_current(renderer->egl);

    free(renderer);
    return NULL;
}

struct wlr_renderer *ky_opengl_renderer_create_with_drm_fd(int drm_fd)
{
    struct ky_egl *egl = ky_egl_create_with_drm_fd(drm_fd);
    if (!egl) {
        kywc_log(KYWC_ERROR, "Could not initialize EGL");
        return NULL;
    }

    struct wlr_renderer *renderer = ky_opengl_renderer_create(egl);
    if (!renderer) {
        kywc_log(KYWC_ERROR, "Failed to create OpenGL renderer");
        ky_egl_destroy(egl);
        return NULL;
    }

    return renderer;
}
