diff --git a/src/LunarWM_wayland.c b/src/LunarWM_wayland.c index 28777d8..b2ef8b5 100644 --- a/src/LunarWM_wayland.c +++ b/src/LunarWM_wayland.c @@ -4,7 +4,6 @@ #include "vec.h" #include -#include #include #include #include @@ -26,6 +25,301 @@ static inline SphericalCoord get_forward_spherical_with_nearest( return Vector3ToSpherical(vec); } +struct ExternalTexturePipeline { + bool attempted_init; + bool ready; + GLuint program; + GLint sampler_loc; + GLuint vao; + GLuint vbo; +}; + +static struct ExternalTexturePipeline g_external_pipeline = { 0 }; + +static GLuint compile_shader(GLenum type, char const *source) +{ + GLuint shader = glCreateShader(type); + if (shader == 0) { + TraceLog( + LOG_ERROR, "Failed to create shader object for external texture"); + return 0; + } + glShaderSource(shader, 1, &source, NULL); + glCompileShader(shader); + GLint status = GL_FALSE; + glGetShaderiv(shader, GL_COMPILE_STATUS, &status); + if (status != GL_TRUE) { + GLint log_len = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_len); + if (log_len > 1) { + char *log = malloc((size_t)log_len); + if (log) { + glGetShaderInfoLog(shader, log_len, NULL, log); + TraceLog(LOG_ERROR, + "External texture shader compile failed: %s", log); + free(log); + } + } + glDeleteShader(shader); + return 0; + } + return shader; +} + +static bool ensure_external_pipeline(void) +{ + if (g_external_pipeline.attempted_init) + return g_external_pipeline.ready; + + g_external_pipeline.attempted_init = true; + + static char const *vertex_src_300 + = "#version 300 es\n" + "precision highp float;\n" + "layout(location = 0) in vec2 a_pos;\n" + "layout(location = 1) in vec2 a_uv;\n" + "out vec2 v_uv;\n" + "void main() {\n" + " v_uv = a_uv;\n" + " gl_Position = vec4(a_pos, 0.0, 1.0);\n" + "}\n"; + + static char const *fragment_src_300 + = "#version 300 es\n" + "#extension GL_OES_EGL_image_external_essl3 : require\n" + "precision mediump float;\n" + "in vec2 v_uv;\n" + "layout(location = 0) out vec4 fragColor;\n" + "uniform samplerExternalOES u_texture;\n" + "void main() {\n" + " fragColor = texture(u_texture, v_uv);\n" + "}\n"; + + static char const *vertex_src_100 + = "#version 100\n" + "attribute vec2 a_pos;\n" + "attribute vec2 a_uv;\n" + "varying vec2 v_uv;\n" + "void main() {\n" + " v_uv = a_uv;\n" + " gl_Position = vec4(a_pos, 0.0, 1.0);\n" + "}\n"; + + static char const *fragment_src_100 + = "#version 100\n" + "#extension GL_OES_EGL_image_external : require\n" + "precision mediump float;\n" + "varying vec2 v_uv;\n" + "uniform samplerExternalOES u_texture;\n" + "void main() {\n" + " gl_FragColor = texture2D(u_texture, v_uv);\n" + "}\n"; + + struct { + char const *vs; + char const *fs; + } const variants[] = { + { vertex_src_300, fragment_src_300 }, + { vertex_src_100, fragment_src_100 }, + }; + + GLuint program = 0; + GLint sampler_loc = -1; + size_t chosen_idx = (size_t)-1; + for (size_t i = 0; i < ARRAY_SZ(variants); ++i) { + GLuint vert = compile_shader(GL_VERTEX_SHADER, variants[i].vs); + if (vert == 0) + continue; + GLuint frag = compile_shader(GL_FRAGMENT_SHADER, variants[i].fs); + if (frag == 0) { + glDeleteShader(vert); + continue; + } + + GLuint prog = glCreateProgram(); + if (prog == 0) { + TraceLog(LOG_ERROR, + "Failed to create shader program for external texture"); + glDeleteShader(vert); + glDeleteShader(frag); + continue; + } + glAttachShader(prog, vert); + glAttachShader(prog, frag); + glBindAttribLocation(prog, 0, "a_pos"); + glBindAttribLocation(prog, 1, "a_uv"); + glLinkProgram(prog); + GLint link_status = GL_FALSE; + glGetProgramiv(prog, GL_LINK_STATUS, &link_status); + if (link_status != GL_TRUE) { + GLint log_len = 0; + glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &log_len); + if (log_len > 1) { + char *log = malloc((size_t)log_len); + if (log) { + glGetProgramInfoLog(prog, log_len, NULL, log); + TraceLog(LOG_ERROR, + "External texture program link failed: %s", log); + free(log); + } + } + glDeleteShader(vert); + glDeleteShader(frag); + glDeleteProgram(prog); + continue; + } + glDetachShader(prog, vert); + glDetachShader(prog, frag); + glDeleteShader(vert); + glDeleteShader(frag); + + sampler_loc = glGetUniformLocation(prog, "u_texture"); + if (sampler_loc < 0) { + TraceLog(LOG_ERROR, + "External texture program missing u_texture uniform"); + glDeleteProgram(prog); + continue; + } + + program = prog; + chosen_idx = i; + break; + } + + if (program == 0) + return false; + + if (chosen_idx == 1) { + TraceLog( + LOG_DEBUG, "External texture shader fallback to ESSL 100 variant"); + } + + GLuint vao = 0; + GLuint vbo = 0; + glGenVertexArrays(1, &vao); + glGenBuffers(1, &vbo); + if (vao == 0 || vbo == 0) { + TraceLog(LOG_ERROR, "Failed to allocate buffers for external texture"); + if (vao) + glDeleteVertexArrays(1, &vao); + if (vbo) + glDeleteBuffers(1, &vbo); + glDeleteProgram(program); + return false; + } + + GLint prev_vao = 0; + glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &prev_vao); + GLint prev_array_buffer = 0; + glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &prev_array_buffer); + glBindVertexArray(vao); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 16, NULL, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(0); + glVertexAttribPointer( + 0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 4, (void *)0); + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 4, + (void *)(sizeof(float) * 2)); + glBindBuffer(GL_ARRAY_BUFFER, (GLuint)prev_array_buffer); + glBindVertexArray((GLuint)prev_vao); + + g_external_pipeline.program = program; + g_external_pipeline.sampler_loc = sampler_loc; + g_external_pipeline.vao = vao; + g_external_pipeline.vbo = vbo; + g_external_pipeline.ready = true; + return true; +} + +static bool draw_external_texture( + struct wlr_gles2_texture_attribs const *attribs, int tex_width, + int tex_height, Rectangle src, Rectangle dst, int target_width, + int target_height) +{ + if (!attribs || attribs->target != GL_TEXTURE_EXTERNAL_OES) + return false; + if (tex_width <= 0 || tex_height <= 0 || target_width <= 0 + || target_height <= 0) + return false; + if (!ensure_external_pipeline()) + return false; + + rlDrawRenderBatchActive(); + + float x0 = dst.x; + float y0 = dst.y; + float x1 = dst.x + dst.width; + float y1 = dst.y + dst.height; + + float ndc_x0 = (x0 / (float)target_width) * 2.0f - 1.0f; + float ndc_x1 = (x1 / (float)target_width) * 2.0f - 1.0f; + float ndc_y0 = 1.0f - (y0 / (float)target_height) * 2.0f; + float ndc_y1 = 1.0f - (y1 / (float)target_height) * 2.0f; + + float inv_tex_w = 1.0f / (float)tex_width; + float inv_tex_h = 1.0f / (float)tex_height; + float u0 = src.x * inv_tex_w; + float v0 = src.y * inv_tex_h; + float u1 = (src.x + src.width) * inv_tex_w; + float v1 = (src.y + src.height) * inv_tex_h; + + GLfloat vertices[] = { + ndc_x0, + ndc_y0, + u0, + v0, + ndc_x1, + ndc_y0, + u1, + v0, + ndc_x0, + ndc_y1, + u0, + v1, + ndc_x1, + ndc_y1, + u1, + v1, + }; + + GLint prev_program = 0; + glGetIntegerv(GL_CURRENT_PROGRAM, &prev_program); + + GLint prev_active_texture = 0; + glGetIntegerv(GL_ACTIVE_TEXTURE, &prev_active_texture); + glActiveTexture(GL_TEXTURE0); + GLint prev_tex0_external = 0; + glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &prev_tex0_external); + GLint prev_tex0_2d = 0; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &prev_tex0_2d); + + GLint prev_array_buffer = 0; + glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &prev_array_buffer); + GLint prev_element_array_buffer = 0; + glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &prev_element_array_buffer); + GLint prev_vertex_array = 0; + glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &prev_vertex_array); + + glUseProgram(g_external_pipeline.program); + glUniform1i(g_external_pipeline.sampler_loc, 0); + glBindTexture(GL_TEXTURE_EXTERNAL_OES, attribs->tex); + glBindVertexArray(g_external_pipeline.vao); + glBindBuffer(GL_ARRAY_BUFFER, g_external_pipeline.vbo); + glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glBindBuffer(GL_ARRAY_BUFFER, (GLuint)prev_array_buffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, (GLuint)prev_element_array_buffer); + glBindVertexArray((GLuint)prev_vertex_array); + glBindTexture(GL_TEXTURE_EXTERNAL_OES, (GLuint)prev_tex0_external); + glBindTexture(GL_TEXTURE_2D, (GLuint)prev_tex0_2d); + glUseProgram((GLuint)prev_program); + glActiveTexture((GLenum)prev_active_texture); + + return true; +} + static void LunarWM_Toplevel_release_surface_rt(LunarWM_Toplevel *tl) { if (!tl) @@ -220,6 +514,36 @@ struct SurfaceComposeCtx { bool *has_alpha; }; +char const *GLInternalFormatName(GLenum format) +{ + switch (format) { + case GL_RGBA8: + return "GL_RGBA8"; + case GL_RGB8: + return "GL_RGB8"; + case GL_RGB565: + return "GL_RGB565"; + case GL_RGBA4: + return "GL_RGBA4"; + case GL_RGB5_A1: + return "GL_RGB5_A1"; + case GL_DEPTH_COMPONENT16: + return "GL_DEPTH_COMPONENT16"; + case GL_DEPTH_COMPONENT24: + return "GL_DEPTH_COMPONENT24"; + case GL_DEPTH24_STENCIL8: + return "GL_DEPTH24_STENCIL8"; + case GL_DEPTH32F_STENCIL8: + return "GL_DEPTH32F_STENCIL8"; + case GL_R8: + return "GL_R8"; + case GL_RG8: + return "GL_RG8"; + default: + return "Unknown format"; + } +} + static void surface_compose_draw( struct wlr_surface *surface, int sx, int sy, void *user_data) { @@ -244,9 +568,8 @@ static void surface_compose_draw( .width = (int)wlr_tex->width, .height = (int)wlr_tex->height, .mipmaps = 1, - .format = gles_tex->has_alpha - ? PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 - : PIXELFORMAT_UNCOMPRESSED_R8G8B8, + .format = gles_tex->has_alpha ? PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 + : PIXELFORMAT_UNCOMPRESSED_R8G8B8, }; float dest_w = (float)surface->current.width; @@ -264,7 +587,20 @@ static void surface_compose_draw( dest_h, }; - DrawTexturePro(tex, src, dst, (Vector2) { 0.0f, 0.0f }, 0.0f, WHITE); + if (attribs.target == GL_TEXTURE_EXTERNAL_OES) { + static bool external_draw_warned = false; + if (!draw_external_texture(&attribs, tex.width, tex.height, src, dst, + ctx->extents.width, ctx->extents.height)) { + if (!external_draw_warned) { + TraceLog(LOG_WARNING, + "Failed to draw external texture, skipping frame"); + external_draw_warned = true; + } + } + } else { + assert(attribs.target == GL_TEXTURE_2D); + DrawTexturePro(tex, src, dst, (Vector2) { 0.0f, 0.0f }, 0.0f, WHITE); + } } bool LunarWM_Toplevel_update(LunarWM_Toplevel *this)