//! OpenGL ES 2.0 / EGL 渲染后端 //! //! 使用 EGL + X11 窗口创建渲染上下文,用 OpenGL ES 2.0 渲染 Live2D 模型。 use anyhow::{anyhow, Result}; use std::ffi::c_void; use std::os::raw::{c_char, c_int, c_long, c_uint, c_void as c_void_raw}; // ============ EGL ============ pub mod egl { use std::os::raw::{c_int, c_uint, c_void}; pub const EGL_SURFACE_TYPE: c_int = 0x3033; pub const EGL_WINDOW_BIT: c_int = 0x0004; pub const EGL_BLUE_SIZE: c_int = 0x3022; pub const EGL_GREEN_SIZE: c_int = 0x3023; pub const EGL_RED_SIZE: c_int = 0x3024; pub const EGL_ALPHA_SIZE: c_int = 0x3021; pub const EGL_RENDERABLE_TYPE: c_int = 0x3040; pub const EGL_OPENGL_ES2_BIT: c_int = 0x0004; pub const EGL_NONE: c_int = 0x3038; pub const EGL_CONTEXT_CLIENT_VERSION: c_int = 0x3098; pub const EGL_DEPTH_SIZE: c_int = 0x3025; pub const EGL_STENCIL_SIZE: c_int = 0x3026; pub type EGLDisplay = *mut c_void; pub type EGLConfig = *mut c_void; pub type EGLContext = *mut c_void; pub type EGLSurface = *mut c_void; pub type NativeDisplayType = *mut c_void; pub type NativeWindowType = *mut c_void; pub type EGLBoolean = c_uint; pub type EGLint = c_int; /// EGL_DEFAULT_DISPLAY(即 NULL) pub fn default_display() -> NativeDisplayType { std::ptr::null_mut() } } // ============ X11 ============ pub mod x11 { use std::os::raw::{c_int, c_long, c_void}; pub type Display = c_void; pub type Window = c_long; #[repr(C)] pub struct XSetWindowAttributes { pub background_pixmap: c_long, pub background_pixel: c_long, pub border_pixmap: c_long, pub border_pixel: c_long, pub bit_gravity: c_int, pub win_gravity: c_int, pub backing_store: c_int, pub backing_planes: c_long, pub backing_pixel: c_long, pub save_under: c_int, pub event_mask: c_long, pub do_not_propagate_mask: c_long, pub override_redirect: c_int, pub colormap: c_long, pub cursor: c_long, } pub const CWOverrideRedirect: c_long = 1 << 9; pub const CWEventMask: c_long = 1 << 11; pub const ExposureMask: c_long = 1 << 15; pub const StructureNotifyMask: c_long = 1 << 17; pub const SubstructureNotifyMask: c_long = 1 << 19; pub const SubstructureRedirectMask: c_long = 1 << 20; /// XEvent union(简化为最大 192 字节的字节数组) #[repr(C)] pub struct XEvent { pub data: [std::os::raw::c_char; 192], } /// XClientMessageEvent 布局(用于 _NET_WM_STATE 客户端消息) #[repr(C)] pub struct XClientMessageEvent { pub ftype: c_long, // type (ClientMessage = 33) pub serial: c_long, // serial pub send_event: c_int, // send_event pub display: *mut c_void, // display pub window: c_long, // window pub message_type: c_long, // message_type atom pub format: c_int, // format (32) pub data_l: [c_long; 5], // data.l } } // ============ GLES2 ============ pub mod gl { use std::os::raw::{c_int, c_uint, c_uchar}; pub type GLenum = c_uint; pub type GLuint = c_uint; pub type GLint = c_int; pub type GLsizei = c_int; pub type GLbitfield = c_uint; pub type GLboolean = c_uchar; pub const GL_FLOAT: GLenum = 0x1406; pub const GL_ARRAY_BUFFER: GLenum = 0x8892; pub const GL_ELEMENT_ARRAY_BUFFER: GLenum = 0x8893; pub const GL_TEXTURE_2D: GLenum = 0x0DE1; pub const GL_TEXTURE0: GLenum = 0x84C0; pub const GL_RGBA: GLenum = 0x1908; pub const GL_UNSIGNED_BYTE: GLenum = 0x1401; pub const GL_UNSIGNED_SHORT: GLenum = 0x1403; pub const GL_LINEAR: GLenum = 0x2601; pub const GL_CLAMP_TO_EDGE: GLenum = 0x812F; pub const GL_TEXTURE_MIN_FILTER: GLenum = 0x2801; pub const GL_TEXTURE_MAG_FILTER: GLenum = 0x2800; pub const GL_TEXTURE_WRAP_S: GLenum = 0x2802; pub const GL_TEXTURE_WRAP_T: GLenum = 0x2803; pub const GL_VERTEX_SHADER: GLenum = 0x8B31; pub const GL_FRAGMENT_SHADER: GLenum = 0x8B30; pub const GL_COMPILE_STATUS: GLenum = 0x8B81; pub const GL_LINK_STATUS: GLenum = 0x8B82; pub const GL_TRIANGLES: GLenum = 0x0004; pub const GL_TRIANGLE_STRIP: GLenum = 0x0005; pub const GL_BLEND: GLenum = 0x0BE2; pub const GL_SRC_ALPHA: GLenum = 0x0302; pub const GL_ONE_MINUS_SRC_ALPHA: GLenum = 0x0303; pub const GL_ONE: GLenum = 1; pub const GL_ZERO: GLenum = 0; pub const GL_SRC_COLOR: GLenum = 0x0300; pub const GL_COLOR_BUFFER_BIT: GLbitfield = 0x4000; pub const GL_STATIC_DRAW: GLenum = 0x88E4; pub const GL_FALSE: GLenum = 0; pub const GL_NO_ERROR: GLenum = 0; // 遮罩(stencil)相关常量 pub const GL_DEPTH_TEST: GLenum = 0x0B71; pub const GL_STENCIL_TEST: GLenum = 0x0B90; pub const GL_STENCIL_BUFFER_BIT: GLbitfield = 0x0400; pub const GL_KEEP: GLenum = 0x1E00; pub const GL_REPLACE: GLenum = 0x1E01; pub const GL_INCR: GLenum = 0x1E02; pub const GL_DECR: GLenum = 0x1E03; pub const GL_ALWAYS: GLenum = 0x0207; pub const GL_EQUAL: GLenum = 0x0202; pub const GL_NOTEQUAL: GLenum = 0x0205; pub const GL_LESS: GLenum = 0x0201; pub const GL_TRUE: GLboolean = 1; } pub use gl::{GLenum, GLuint, GLint, GLsizei}; // ============ 着色器源码 ============ pub const VERT_SHADER_SRC: &str = r#"#version 100 attribute vec4 a_position; attribute vec2 a_texCoord; varying vec2 v_texCoord; uniform mat4 u_matrix; void main() { gl_Position = u_matrix * a_position; v_texCoord = a_texCoord; v_texCoord.y = 1.0 - v_texCoord.y; } "#; pub const FRAG_SHADER_SRC: &str = r#"#version 100 precision highp float; varying vec2 v_texCoord; uniform sampler2D s_texture0; uniform vec4 u_baseColor; uniform vec4 u_multiplyColor; uniform vec4 u_screenColor; void main() { vec4 texColor = texture2D(s_texture0, v_texCoord); texColor.rgb = texColor.rgb * u_multiplyColor.rgb; texColor.rgb = texColor.rgb + u_screenColor.rgb - (texColor.rgb * u_screenColor.rgb); vec4 color = texColor * u_baseColor; gl_FragColor = vec4(color.rgb, color.a); } "#; /// 背景渐变着色器(全屏四边形,从底部到底部到顶部做线性插值) pub const GRAD_VERT_SHADER_SRC: &str = r#"#version 100 attribute vec2 a_pos; varying float v_y; void main() { v_y = a_pos.y; gl_Position = vec4(a_pos, 0.0, 1.0); } "#; pub const GRAD_FRAG_SHADER_SRC: &str = r#"#version 100 precision highp float; varying float v_y; uniform vec4 u_top; uniform vec4 u_bottom; void main() { float t = (v_y + 1.0) * 0.5; vec3 c = mix(u_bottom.rgb, u_top.rgb, t); gl_FragColor = vec4(c, 1.0); } "#; /// 文字 overlay 着色器(像素坐标 -> 裁剪空间,采样字形纹理,按 u_color 着色) pub const TEXT_VERT_SHADER_SRC: &str = r#"#version 100 attribute vec2 a_position; attribute vec2 a_texCoord; uniform mat4 u_matrix; varying vec2 v_uv; void main() { gl_Position = u_matrix * vec4(a_position, 0.0, 1.0); v_uv = a_texCoord; } "#; pub const TEXT_FRAG_SHADER_SRC: &str = r#"#version 100 precision mediump float; varying vec2 v_uv; uniform sampler2D s_texture0; uniform vec4 u_color; void main() { float a = texture2D(s_texture0, v_uv).a; gl_FragColor = vec4(u_color.rgb, a * u_color.a); } "#; /// 遮罩(mask)着色器:仅把 mask drawable 的不透明区域写入 stencil, /// 用于裁切被遮罩的 drawable(如夹在身体里的后臂、被头发遮挡的脸)。 pub const MASK_VERT_SHADER_SRC: &str = r#"#version 100 attribute vec4 a_position; attribute vec2 a_texCoord; varying vec2 v_texCoord; uniform mat4 u_matrix; void main() { gl_Position = u_matrix * a_position; v_texCoord = a_texCoord; v_texCoord.y = 1.0 - v_texCoord.y; } "#; pub const MASK_FRAG_SHADER_SRC: &str = r#"#version 100 precision highp float; varying vec2 v_texCoord; uniform sampler2D s_texture0; uniform vec4 u_multiplyColor; uniform vec4 u_screenColor; void main() { vec4 c = texture2D(s_texture0, v_texCoord); c.rgb = c.rgb * u_multiplyColor.rgb; c.rgb = c.rgb + u_screenColor.rgb - (c.rgb * u_screenColor.rgb); // 仅不透明区域写入 stencil(裁切形状由 alpha 决定) if (c.a < 0.5) discard; gl_FragColor = vec4(1.0); } "#; // ============ 动态库函数集合 ============ pub struct Gl { _lib_egl: libloading::Library, _lib_gl: libloading::Library, _lib_x11: libloading::Library, pub egl_get_display: unsafe extern "C" fn(disp: egl::NativeDisplayType) -> egl::EGLDisplay, pub egl_initialize: unsafe extern "C" fn( dpy: egl::EGLDisplay, major: *mut c_int, minor: *mut c_int, ) -> egl::EGLBoolean, pub egl_choose_config: unsafe extern "C" fn( dpy: egl::EGLDisplay, attrib_list: *const egl::EGLint, configs: *mut egl::EGLConfig, config_size: egl::EGLint, num_config: *mut egl::EGLint, ) -> egl::EGLBoolean, pub egl_get_config_attrib: unsafe extern "C" fn( dpy: egl::EGLDisplay, config: egl::EGLConfig, attribute: egl::EGLint, value: *mut egl::EGLint, ) -> egl::EGLBoolean, pub egl_create_window_surface: unsafe extern "C" fn( dpy: egl::EGLDisplay, config: egl::EGLConfig, win: egl::NativeWindowType, attrib_list: *const egl::EGLint, ) -> egl::EGLSurface, pub egl_create_context: unsafe extern "C" fn( dpy: egl::EGLDisplay, config: egl::EGLConfig, share_ctx: egl::EGLContext, attrib_list: *const egl::EGLint, ) -> egl::EGLContext, pub egl_make_current: unsafe extern "C" fn( dpy: egl::EGLDisplay, draw: egl::EGLSurface, read: egl::EGLSurface, ctx: egl::EGLContext, ) -> egl::EGLBoolean, pub egl_swap_buffers: unsafe extern "C" fn(dpy: egl::EGLDisplay, surface: egl::EGLSurface) -> egl::EGLBoolean, pub gl_clear: unsafe extern "C" fn(mask: gl::GLbitfield), pub gl_clear_color: unsafe extern "C" fn(r: f32, g: f32, b: f32, a: f32), pub gl_enable: unsafe extern "C" fn(cap: gl::GLenum), pub gl_disable: unsafe extern "C" fn(cap: gl::GLenum), pub gl_blend_func: unsafe extern "C" fn(sfactor: gl::GLenum, dfactor: gl::GLenum), pub gl_viewport: unsafe extern "C" fn(x: gl::GLint, y: gl::GLint, w: gl::GLsizei, h: gl::GLsizei), pub gl_create_shader: unsafe extern "C" fn(t: gl::GLenum) -> gl::GLuint, pub gl_shader_source: unsafe extern "C" fn( shader: gl::GLuint, count: gl::GLsizei, string: *const *const c_char, length: *const gl::GLint, ), pub gl_compile_shader: unsafe extern "C" fn(shader: gl::GLuint), pub gl_get_shaderiv: unsafe extern "C" fn(shader: gl::GLuint, pname: gl::GLenum, params: *mut gl::GLint), pub gl_get_shader_info_log: unsafe extern "C" fn( shader: gl::GLuint, buf_size: gl::GLsizei, length: *mut gl::GLsizei, info_log: *mut c_char, ), pub gl_create_program: unsafe extern "C" fn() -> gl::GLuint, pub gl_attach_shader: unsafe extern "C" fn(program: gl::GLuint, shader: gl::GLuint), pub gl_link_program: unsafe extern "C" fn(program: gl::GLuint), pub gl_get_programiv: unsafe extern "C" fn(program: gl::GLuint, pname: gl::GLenum, params: *mut gl::GLint), pub gl_get_program_info_log: unsafe extern "C" fn( program: gl::GLuint, buf_size: gl::GLsizei, length: *mut gl::GLsizei, info_log: *mut c_char, ), pub gl_use_program: unsafe extern "C" fn(program: gl::GLuint), pub gl_get_attrib_location: unsafe extern "C" fn(program: gl::GLuint, name: *const c_char) -> gl::GLint, pub gl_get_uniform_location: unsafe extern "C" fn(program: gl::GLuint, name: *const c_char) -> gl::GLint, pub gl_uniform_matrix4fv: unsafe extern "C" fn( location: gl::GLint, count: gl::GLsizei, transpose: gl::GLenum, value: *const f32, ), pub gl_uniform1i: unsafe extern "C" fn(location: gl::GLint, v: gl::GLint), pub gl_uniform4f: unsafe extern "C" fn(location: gl::GLint, x: f32, y: f32, z: f32, w: f32), pub gl_gen_textures: unsafe extern "C" fn(n: gl::GLsizei, textures: *mut gl::GLuint), pub gl_bind_texture: unsafe extern "C" fn(target: gl::GLenum, texture: gl::GLuint), pub gl_tex_parameteri: unsafe extern "C" fn(target: gl::GLenum, pname: gl::GLenum, param: gl::GLint), pub gl_tex_image_2d: unsafe extern "C" fn( target: gl::GLenum, level: gl::GLint, internalformat: gl::GLint, width: gl::GLsizei, height: gl::GLsizei, border: gl::GLint, format: gl::GLenum, type_: gl::GLenum, data: *const c_void_raw, ), pub gl_active_texture: unsafe extern "C" fn(texture: gl::GLenum), pub gl_gen_buffers: unsafe extern "C" fn(n: gl::GLsizei, buffers: *mut gl::GLuint), pub gl_bind_buffer: unsafe extern "C" fn(target: gl::GLenum, buffer: gl::GLuint), pub gl_buffer_data: unsafe extern "C" fn( target: gl::GLenum, size: isize, data: *const c_void_raw, usage: gl::GLenum, ), pub gl_enable_vertex_attrib_array: unsafe extern "C" fn(index: gl::GLuint), pub gl_vertex_attrib_pointer: unsafe extern "C" fn( index: gl::GLuint, size: gl::GLint, type_: gl::GLenum, normalized: gl::GLenum, stride: gl::GLsizei, pointer: *const c_void_raw, ), pub gl_draw_elements: unsafe extern "C" fn( mode: gl::GLenum, count: gl::GLsizei, type_: gl::GLenum, indices: *const c_void_raw, ), pub gl_draw_arrays: unsafe extern "C" fn(mode: gl::GLenum, first: gl::GLint, count: gl::GLsizei), pub gl_get_error: unsafe extern "C" fn() -> gl::GLenum, pub gl_delete_buffers: unsafe extern "C" fn(n: gl::GLsizei, buffers: *const gl::GLuint), pub gl_stencil_func: unsafe extern "C" fn(func: gl::GLenum, ref_: gl::GLint, mask: gl::GLuint), pub gl_stencil_op: unsafe extern "C" fn(fail: gl::GLenum, zfail: gl::GLenum, zpass: gl::GLenum), pub gl_stencil_mask: unsafe extern "C" fn(mask: gl::GLuint), pub gl_color_mask: unsafe extern "C" fn( r: gl::GLboolean, g: gl::GLboolean, b: gl::GLboolean, a: gl::GLboolean, ), pub x_open_display: unsafe extern "C" fn(name: *const c_char) -> *mut x11::Display, pub x_create_window: unsafe extern "C" fn( disp: *mut x11::Display, parent: x11::Window, x: c_int, y: c_int, width: c_uint, height: c_uint, border_width: c_uint, depth: c_int, class: c_int, visual: *mut c_void, valuemask: c_long, attributes: *mut x11::XSetWindowAttributes, ) -> x11::Window, pub x_map_window: unsafe extern "C" fn(disp: *mut x11::Display, w: x11::Window) -> c_int, pub x_store_name: unsafe extern "C" fn(disp: *mut x11::Display, w: x11::Window, name: *const c_char) -> c_int, pub x_flush: unsafe extern "C" fn(disp: *mut x11::Display) -> c_int, pub x_default_root_window: unsafe extern "C" fn(disp: *mut x11::Display) -> x11::Window, pub x_sync: unsafe extern "C" fn(disp: *mut x11::Display, discard: c_int) -> c_int, pub x_raise_window: unsafe extern "C" fn(disp: *mut x11::Display, w: x11::Window) -> c_int, pub x_display_width: unsafe extern "C" fn(disp: *mut x11::Display, screen: c_int) -> c_int, pub x_display_height: unsafe extern "C" fn(disp: *mut x11::Display, screen: c_int) -> c_int, pub x_intern_atom: unsafe extern "C" fn( disp: *mut x11::Display, atom_name: *const c_char, only_if_exists: c_int, ) -> c_long, pub x_change_property: unsafe extern "C" fn( disp: *mut x11::Display, w: x11::Window, property: c_long, ftype: c_long, format: c_int, mode: c_int, data: *const c_void, nelements: c_int, ) -> c_int, pub x_send_event: unsafe extern "C" fn( disp: *mut x11::Display, w: x11::Window, propagate: c_int, event_mask: c_long, event_send: *mut x11::XEvent, ) -> c_int, } impl Gl { pub fn load() -> Result { let lib_egl = unsafe { libloading::Library::new("libEGL.so.1")? }; let lib_gl = unsafe { libloading::Library::new("libGLESv2.so.2")? }; let lib_x11 = unsafe { libloading::Library::new("libX11.so.6")? }; unsafe { Ok(Self { egl_get_display: *lib_egl.get(b"eglGetDisplay\0")?, egl_initialize: *lib_egl.get(b"eglInitialize\0")?, egl_choose_config: *lib_egl.get(b"eglChooseConfig\0")?, egl_get_config_attrib: *lib_egl.get(b"eglGetConfigAttrib\0")?, egl_create_window_surface: *lib_egl.get(b"eglCreateWindowSurface\0")?, egl_create_context: *lib_egl.get(b"eglCreateContext\0")?, egl_make_current: *lib_egl.get(b"eglMakeCurrent\0")?, egl_swap_buffers: *lib_egl.get(b"eglSwapBuffers\0")?, gl_clear: *lib_gl.get(b"glClear\0")?, gl_clear_color: *lib_gl.get(b"glClearColor\0")?, gl_enable: *lib_gl.get(b"glEnable\0")?, gl_disable: *lib_gl.get(b"glDisable\0")?, gl_blend_func: *lib_gl.get(b"glBlendFunc\0")?, gl_viewport: *lib_gl.get(b"glViewport\0")?, gl_create_shader: *lib_gl.get(b"glCreateShader\0")?, gl_shader_source: *lib_gl.get(b"glShaderSource\0")?, gl_compile_shader: *lib_gl.get(b"glCompileShader\0")?, gl_get_shaderiv: *lib_gl.get(b"glGetShaderiv\0")?, gl_get_shader_info_log: *lib_gl.get(b"glGetShaderInfoLog\0")?, gl_create_program: *lib_gl.get(b"glCreateProgram\0")?, gl_attach_shader: *lib_gl.get(b"glAttachShader\0")?, gl_link_program: *lib_gl.get(b"glLinkProgram\0")?, gl_get_programiv: *lib_gl.get(b"glGetProgramiv\0")?, gl_get_program_info_log: *lib_gl.get(b"glGetProgramInfoLog\0")?, gl_use_program: *lib_gl.get(b"glUseProgram\0")?, gl_get_attrib_location: *lib_gl.get(b"glGetAttribLocation\0")?, gl_get_uniform_location: *lib_gl.get(b"glGetUniformLocation\0")?, gl_uniform_matrix4fv: *lib_gl.get(b"glUniformMatrix4fv\0")?, gl_uniform1i: *lib_gl.get(b"glUniform1i\0")?, gl_uniform4f: *lib_gl.get(b"glUniform4f\0")?, gl_gen_textures: *lib_gl.get(b"glGenTextures\0")?, gl_bind_texture: *lib_gl.get(b"glBindTexture\0")?, gl_tex_parameteri: *lib_gl.get(b"glTexParameteri\0")?, gl_tex_image_2d: *lib_gl.get(b"glTexImage2D\0")?, gl_active_texture: *lib_gl.get(b"glActiveTexture\0")?, gl_gen_buffers: *lib_gl.get(b"glGenBuffers\0")?, gl_bind_buffer: *lib_gl.get(b"glBindBuffer\0")?, gl_buffer_data: *lib_gl.get(b"glBufferData\0")?, gl_enable_vertex_attrib_array: *lib_gl.get(b"glEnableVertexAttribArray\0")?, gl_vertex_attrib_pointer: *lib_gl.get(b"glVertexAttribPointer\0")?, gl_draw_elements: *lib_gl.get(b"glDrawElements\0")?, gl_draw_arrays: *lib_gl.get(b"glDrawArrays\0")?, gl_get_error: *lib_gl.get(b"glGetError\0")?, gl_delete_buffers: *lib_gl.get(b"glDeleteBuffers\0")?, gl_stencil_func: *lib_gl.get(b"glStencilFunc\0")?, gl_stencil_op: *lib_gl.get(b"glStencilOp\0")?, gl_stencil_mask: *lib_gl.get(b"glStencilMask\0")?, gl_color_mask: *lib_gl.get(b"glColorMask\0")?, x_open_display: *lib_x11.get(b"XOpenDisplay\0")?, x_create_window: *lib_x11.get(b"XCreateWindow\0")?, x_map_window: *lib_x11.get(b"XMapWindow\0")?, x_store_name: *lib_x11.get(b"XStoreName\0")?, x_flush: *lib_x11.get(b"XFlush\0")?, x_default_root_window: *lib_x11.get(b"XDefaultRootWindow\0")?, x_sync: *lib_x11.get(b"XSync\0")?, x_raise_window: *lib_x11.get(b"XRaiseWindow\0")?, x_display_width: *lib_x11.get(b"XDisplayWidth\0")?, x_display_height: *lib_x11.get(b"XDisplayHeight\0")?, x_intern_atom: *lib_x11.get(b"XInternAtom\0")?, x_change_property: *lib_x11.get(b"XChangeProperty\0")?, x_send_event: *lib_x11.get(b"XSendEvent\0")?, _lib_egl: lib_egl, _lib_gl: lib_gl, _lib_x11: lib_x11, }) } } } // ============ 渲染上下文 ============ pub struct RenderContext { pub gl: Gl, pub display: egl::EGLDisplay, pub surface: egl::EGLSurface, pub context: egl::EGLContext, pub x_display: *mut x11::Display, pub x_window: x11::Window, pub program: GLuint, pub attrib_position: GLint, pub attrib_tex_coord: GLint, pub uniform_matrix: GLint, pub uniform_texture: GLint, pub uniform_base_color: GLint, pub uniform_multiply_color: GLint, pub uniform_screen_color: GLint, pub grad_program: GLuint, pub grad_attrib_pos: GLint, pub grad_uniform_top: GLint, pub grad_uniform_bottom: GLint, // 遮罩(mask)程序 pub mask_program: GLuint, pub mask_attrib_position: GLint, pub mask_attrib_tex_coord: GLint, pub mask_uniform_matrix: GLint, pub mask_uniform_texture: GLint, pub mask_uniform_multiply_color: GLint, pub mask_uniform_screen_color: GLint, // 文字 overlay 程序 pub text_program: GLuint, pub text_uniform_matrix: GLint, pub text_uniform_color: GLint, pub text_uniform_texture: GLint, pub text_attrib_position: GLint, pub text_attrib_tex_coord: GLint, pub width: i32, pub height: i32, } // 含裸指针,手动声明 Send(仅在单渲染线程使用) unsafe impl Send for RenderContext {} fn compile_shader(gl: &Gl, shader_type: GLenum, src: &str) -> Result { unsafe { let shader = (gl.gl_create_shader)(shader_type); if shader == 0 { return Err(anyhow!("glCreateShader 返回 0")); } let src_c = std::ffi::CString::new(src)?; let src_ptr = src_c.as_ptr(); (gl.gl_shader_source)(shader, 1, &src_ptr, std::ptr::null()); (gl.gl_compile_shader)(shader); let mut status = 0; (gl.gl_get_shaderiv)(shader, gl::GL_COMPILE_STATUS, &mut status); if status == 0 { let mut log: Vec = vec![0u8; 1024]; (gl.gl_get_shader_info_log)(shader, 1024, std::ptr::null_mut(), log.as_mut_ptr() as *mut c_char); let log_str = String::from_utf8_lossy(&log); return Err(anyhow!("shader compile failed: {}", log_str)); } Ok(shader) } } impl RenderContext { pub fn new_fullscreen(width: i32, height: i32) -> Result { let gl = Gl::load()?; // X11 窗口 let x_display = unsafe { (gl.x_open_display)(std::ptr::null()) }; if x_display.is_null() { return Err(anyhow!("XOpenDisplay failed")); } let root_window = unsafe { (gl.x_default_root_window)(x_display) }; // 使用屏幕实际分辨率,确保全屏覆盖 let screen = 0; // XDefaultScreen(display),通常 0 let screen_w = unsafe { (gl.x_display_width)(x_display, screen) }; let screen_h = unsafe { (gl.x_display_height)(x_display, screen) }; // 全屏渲染:直接使用显示器实际分辨率创建窗口与 EGL surface,避免 // 「帧缓冲 800x800 被放进 1920x1080 窗口、模型只出现在左下角」的问题。 // 若 X11 取不到屏幕分辨率,再退回传入的 width/height。 let win_w = if screen_w > 0 && screen_h > 0 { screen_w } else { width }; let win_h = if screen_w > 0 && screen_h > 0 { screen_h } else { height }; println!("[Live2D] 窗口尺寸: {}x{} (屏幕实际 {}x{})", win_w, win_h, screen_w, screen_h); let mut attrs = x11::XSetWindowAttributes { background_pixmap: 0, background_pixel: 0, border_pixmap: 0, border_pixel: 0, bit_gravity: 0, win_gravity: 0, backing_store: 0, backing_planes: 0, backing_pixel: 0, save_under: 0, event_mask: x11::ExposureMask | x11::StructureNotifyMask, do_not_propagate_mask: 0, override_redirect: 0, colormap: 0, cursor: 0, }; let x_window = unsafe { (gl.x_create_window)( x_display, root_window, 0, 0, win_w as c_uint, win_h as c_uint, 0, 24, 1, std::ptr::null_mut(), x11::CWEventMask, &mut attrs, ) }; if x_window == 0 { return Err(anyhow!("XCreateWindow failed")); } unsafe { let title = std::ffi::CString::new("ShowenV2 Live2D")?; (gl.x_store_name)(x_display, x_window, title.as_ptr()); // 让 kwin 把窗口设为全屏(_NET_WM_STATE_FULLSCREEN) // 通过发送 ClientMessage 事件给 root window let net_wm_state = (gl.x_intern_atom)(x_display, b"_NET_WM_STATE\0".as_ptr() as *const c_char, 1); let net_wm_state_fs = (gl.x_intern_atom)(x_display, b"_NET_WM_STATE_FULLSCREEN\0".as_ptr() as *const c_char, 1); let net_wm_state_above = (gl.x_intern_atom)(x_display, b"_NET_WM_STATE_ABOVE\0".as_ptr() as *const c_char, 1); // 先 map 窗口,kwin 才会处理 (gl.x_map_window)(x_display, x_window); (gl.x_sync)(x_display, 0); // 发送 _NET_WM_STATE 客户端消息(两个状态:FULLSCREEN + ABOVE) let mut event: x11::XClientMessageEvent = std::mem::zeroed(); event.ftype = 33; // ClientMessage event.display = x_display as *mut c_void; event.window = x_window; event.message_type = net_wm_state; event.format = 32; event.data_l[0] = 1; // _NET_WM_STATE_ADD event.data_l[1] = net_wm_state_fs; event.data_l[2] = net_wm_state_above; event.data_l[3] = 1; // source indication: application let event_mask = x11::SubstructureNotifyMask | x11::SubstructureRedirectMask; (gl.x_send_event)( x_display, root_window, 0, event_mask, &mut event as *mut x11::XClientMessageEvent as *mut x11::XEvent, ); (gl.x_sync)(x_display, 0); println!("[Live2D] 已请求 kwin 全屏置顶"); } // EGL let display = unsafe { (gl.egl_get_display)(egl::default_display()) }; if display.is_null() { return Err(anyhow!("eglGetDisplay 失败")); } let mut major = 0; let mut minor = 0; if unsafe { (gl.egl_initialize)(display, &mut major, &mut minor) } == 0 { return Err(anyhow!("eglInitialize 失败")); } println!("[Live2D] EGL {}.{}", major, minor); // 主配置请求 depth+stencil(Live2D 遮罩需要 stencil); // 若设备不支持该组合,退回不带 depth/stencil 的配置。 let config_attrs = [ egl::EGL_RED_SIZE, 8, egl::EGL_GREEN_SIZE, 8, egl::EGL_BLUE_SIZE, 8, egl::EGL_ALPHA_SIZE, 8, egl::EGL_SURFACE_TYPE, egl::EGL_WINDOW_BIT, egl::EGL_RENDERABLE_TYPE, egl::EGL_OPENGL_ES2_BIT, egl::EGL_DEPTH_SIZE, 16, egl::EGL_STENCIL_SIZE, 8, egl::EGL_NONE, ]; let config_attrs_fallback = [ egl::EGL_RED_SIZE, 8, egl::EGL_GREEN_SIZE, 8, egl::EGL_BLUE_SIZE, 8, egl::EGL_ALPHA_SIZE, 8, egl::EGL_SURFACE_TYPE, egl::EGL_WINDOW_BIT, egl::EGL_RENDERABLE_TYPE, egl::EGL_OPENGL_ES2_BIT, egl::EGL_NONE, ]; let mut config: egl::EGLConfig = std::ptr::null_mut(); let mut num_config = 0; let mut choose = |attrs: &[c_int]| -> bool { let mut cfg: egl::EGLConfig = std::ptr::null_mut(); let mut n = 0; let ok = unsafe { (gl.egl_choose_config)(display, attrs.as_ptr(), &mut cfg, 1, &mut n) } != 0 && n > 0; if ok { config = cfg; num_config = n; } ok }; if !choose(&config_attrs) && !choose(&config_attrs_fallback) { return Err(anyhow!("eglChooseConfig 失败")); } println!( "[Live2D] EGL 配置选定: 含stencil={} (num_config={})", config_attrs.len() > 9, num_config ); // 真实查询 chosen config 的 depth/stencil 位数,确认 stencil 缓冲真正分配。 let mut stencil_size: egl::EGLint = -1; let mut depth_size: egl::EGLint = -1; unsafe { (gl.egl_get_config_attrib)(display, config, egl::EGL_STENCIL_SIZE, &mut stencil_size); (gl.egl_get_config_attrib)(display, config, egl::EGL_DEPTH_SIZE, &mut depth_size); } println!( "[Live2D] EGL 实际分配: depth={} stencil={} (stencil<=0 表示遮罩不可用)", depth_size, stencil_size ); let surface = unsafe { (gl.egl_create_window_surface)(display, config, x_window as egl::NativeWindowType, std::ptr::null()) }; if surface.is_null() { return Err(anyhow!("eglCreateWindowSurface 失败")); } let ctx_attrs = [egl::EGL_CONTEXT_CLIENT_VERSION, 2, egl::EGL_NONE]; let context = unsafe { (gl.egl_create_context)(display, config, std::ptr::null_mut(), ctx_attrs.as_ptr()) }; if context.is_null() { return Err(anyhow!("eglCreateContext 失败")); } if unsafe { (gl.egl_make_current)(display, surface, surface, context) } == 0 { return Err(anyhow!("eglMakeCurrent 失败")); } unsafe { (gl.gl_viewport)(0, 0, win_w, win_h); (gl.gl_clear_color)(0.0, 0.0, 0.0, 1.0); (gl.gl_enable)(gl::GL_BLEND); (gl.gl_blend_func)(gl::GL_SRC_ALPHA, gl::GL_ONE_MINUS_SRC_ALPHA); } let vert = compile_shader(&gl, gl::GL_VERTEX_SHADER, VERT_SHADER_SRC)?; let frag = compile_shader(&gl, gl::GL_FRAGMENT_SHADER, FRAG_SHADER_SRC)?; let program = unsafe { let p = (gl.gl_create_program)(); (gl.gl_attach_shader)(p, vert); (gl.gl_attach_shader)(p, frag); (gl.gl_link_program)(p); let mut status = 0; (gl.gl_get_programiv)(p, gl::GL_LINK_STATUS, &mut status); if status == 0 { let mut log: Vec = vec![0u8; 1024]; (gl.gl_get_program_info_log)(p, 1024, std::ptr::null_mut(), log.as_mut_ptr() as *mut c_char); let log_str = String::from_utf8_lossy(&log); return Err(anyhow!("shader link failed: {}", log_str)); } p }; unsafe { (gl.gl_use_program)(program); } let get = |name: &str| -> Result { let c = std::ffi::CString::new(name)?; Ok(unsafe { (gl.gl_get_attrib_location)(program, c.as_ptr()) }) }; let getu = |name: &str| -> Result { let c = std::ffi::CString::new(name)?; Ok(unsafe { (gl.gl_get_uniform_location)(program, c.as_ptr()) }) }; let attrib_position = get("a_position")?; let attrib_tex_coord = get("a_texCoord")?; let uniform_matrix = getu("u_matrix")?; let uniform_texture = getu("s_texture0")?; let uniform_base_color = getu("u_baseColor")?; let uniform_multiply_color = getu("u_multiplyColor")?; let uniform_screen_color = getu("u_screenColor")?; // 背景渐变程序 let grad_vert = compile_shader(&gl, gl::GL_VERTEX_SHADER, GRAD_VERT_SHADER_SRC)?; let grad_frag = compile_shader(&gl, gl::GL_FRAGMENT_SHADER, GRAD_FRAG_SHADER_SRC)?; let grad_program = unsafe { let p = (gl.gl_create_program)(); (gl.gl_attach_shader)(p, grad_vert); (gl.gl_attach_shader)(p, grad_frag); (gl.gl_link_program)(p); let mut status = 0; (gl.gl_get_programiv)(p, gl::GL_LINK_STATUS, &mut status); if status == 0 { let mut log: Vec = vec![0u8; 1024]; (gl.gl_get_program_info_log)(p, 1024, std::ptr::null_mut(), log.as_mut_ptr() as *mut c_char); return Err(anyhow!("gradient shader link failed: {}", String::from_utf8_lossy(&log))); } p }; let grad_attrib_pos = { let c = std::ffi::CString::new("a_pos")?; unsafe { (gl.gl_get_attrib_location)(grad_program, c.as_ptr()) } }; let grad_uniform_top = { let c = std::ffi::CString::new("u_top")?; unsafe { (gl.gl_get_uniform_location)(grad_program, c.as_ptr()) } }; let grad_uniform_bottom = { let c = std::ffi::CString::new("u_bottom")?; unsafe { (gl.gl_get_uniform_location)(grad_program, c.as_ptr()) } }; // 遮罩(mask)程序:仅用于把 mask drawable 写入 stencil let mask_vert = compile_shader(&gl, gl::GL_VERTEX_SHADER, MASK_VERT_SHADER_SRC)?; let mask_frag = compile_shader(&gl, gl::GL_FRAGMENT_SHADER, MASK_FRAG_SHADER_SRC)?; let mask_program = unsafe { let p = (gl.gl_create_program)(); (gl.gl_attach_shader)(p, mask_vert); (gl.gl_attach_shader)(p, mask_frag); (gl.gl_link_program)(p); let mut status = 0; (gl.gl_get_programiv)(p, gl::GL_LINK_STATUS, &mut status); if status == 0 { let mut log: Vec = vec![0u8; 1024]; (gl.gl_get_program_info_log)(p, 1024, std::ptr::null_mut(), log.as_mut_ptr() as *mut c_char); return Err(anyhow!("mask shader link failed: {}", String::from_utf8_lossy(&log))); } p }; let mask_attrib_position = { let c = std::ffi::CString::new("a_position")?; unsafe { (gl.gl_get_attrib_location)(mask_program, c.as_ptr()) } }; let mask_attrib_tex_coord = { let c = std::ffi::CString::new("a_texCoord")?; unsafe { (gl.gl_get_attrib_location)(mask_program, c.as_ptr()) } }; let mask_uniform_matrix = { let c = std::ffi::CString::new("u_matrix")?; unsafe { (gl.gl_get_uniform_location)(mask_program, c.as_ptr()) } }; let mask_uniform_texture = { let c = std::ffi::CString::new("s_texture0")?; unsafe { (gl.gl_get_uniform_location)(mask_program, c.as_ptr()) } }; let mask_uniform_multiply_color = { let c = std::ffi::CString::new("u_multiplyColor")?; unsafe { (gl.gl_get_uniform_location)(mask_program, c.as_ptr()) } }; let mask_uniform_screen_color = { let c = std::ffi::CString::new("u_screenColor")?; unsafe { (gl.gl_get_uniform_location)(mask_program, c.as_ptr()) } }; // 文字 overlay 程序 let text_vert = compile_shader(&gl, gl::GL_VERTEX_SHADER, TEXT_VERT_SHADER_SRC)?; let text_frag = compile_shader(&gl, gl::GL_FRAGMENT_SHADER, TEXT_FRAG_SHADER_SRC)?; let text_program = unsafe { let p = (gl.gl_create_program)(); (gl.gl_attach_shader)(p, text_vert); (gl.gl_attach_shader)(p, text_frag); (gl.gl_link_program)(p); let mut status = 0; (gl.gl_get_programiv)(p, gl::GL_LINK_STATUS, &mut status); if status == 0 { let mut log: Vec = vec![0u8; 1024]; (gl.gl_get_program_info_log)(p, 1024, std::ptr::null_mut(), log.as_mut_ptr() as *mut c_char); return Err(anyhow!("text shader link failed: {}", String::from_utf8_lossy(&log))); } p }; let text_uniform_matrix = { let c = std::ffi::CString::new("u_matrix")?; unsafe { (gl.gl_get_uniform_location)(text_program, c.as_ptr()) } }; let text_uniform_color = { let c = std::ffi::CString::new("u_color")?; unsafe { (gl.gl_get_uniform_location)(text_program, c.as_ptr()) } }; let text_uniform_texture = { let c = std::ffi::CString::new("s_texture0")?; unsafe { (gl.gl_get_uniform_location)(text_program, c.as_ptr()) } }; let text_attrib_position = { let c = std::ffi::CString::new("a_position")?; unsafe { (gl.gl_get_attrib_location)(text_program, c.as_ptr()) } }; let text_attrib_tex_coord = { let c = std::ffi::CString::new("a_texCoord")?; unsafe { (gl.gl_get_attrib_location)(text_program, c.as_ptr()) } }; println!( "[Live2D] GL 程序就绪: program={} pos={} tex={} mat={} tex_u={} base={} mult={} scr={}", program, attrib_position, attrib_tex_coord, uniform_matrix, uniform_texture, uniform_base_color, uniform_multiply_color, uniform_screen_color ); println!( "[Live2D] 渐变程序就绪: grad={} pos={} top={} bottom={}", grad_program, grad_attrib_pos, grad_uniform_top, grad_uniform_bottom ); Ok(Self { gl, display, surface, context, x_display, x_window, program, attrib_position, attrib_tex_coord, uniform_matrix, uniform_texture, uniform_base_color, uniform_multiply_color, uniform_screen_color, grad_program, grad_attrib_pos, grad_uniform_top, grad_uniform_bottom, mask_program, mask_attrib_position, mask_attrib_tex_coord, mask_uniform_matrix, mask_uniform_texture, mask_uniform_multiply_color, mask_uniform_screen_color, text_program, text_uniform_matrix, text_uniform_color, text_uniform_texture, text_attrib_position, text_attrib_tex_coord, width: win_w, height: win_h, }) } pub fn create_texture(&self, img: &super::assets::TextureImage) -> GLuint { unsafe { let mut tex = 0; (self.gl.gl_gen_textures)(1, &mut tex); (self.gl.gl_bind_texture)(gl::GL_TEXTURE_2D, tex); (self.gl.gl_tex_parameteri)(gl::GL_TEXTURE_2D, gl::GL_TEXTURE_MIN_FILTER, gl::GL_LINEAR as GLint); (self.gl.gl_tex_parameteri)(gl::GL_TEXTURE_2D, gl::GL_TEXTURE_MAG_FILTER, gl::GL_LINEAR as GLint); (self.gl.gl_tex_parameteri)(gl::GL_TEXTURE_2D, gl::GL_TEXTURE_WRAP_S, gl::GL_CLAMP_TO_EDGE as GLint); (self.gl.gl_tex_parameteri)(gl::GL_TEXTURE_2D, gl::GL_TEXTURE_WRAP_T, gl::GL_CLAMP_TO_EDGE as GLint); (self.gl.gl_tex_image_2d)( gl::GL_TEXTURE_2D, 0, gl::GL_RGBA as GLint, img.width as GLsizei, img.height as GLsizei, 0, gl::GL_RGBA, gl::GL_UNSIGNED_BYTE, img.rgba.as_ptr() as *const c_void, ); let err = (self.gl.gl_get_error)(); if err != gl::GL_NO_ERROR { eprintln!("[Live2D] glTexImage2D 错误: 0x{:x}", err); } tex } } pub fn swap_buffers(&self) { unsafe { (self.gl.egl_swap_buffers)(self.display, self.surface); } } /// 绘制全屏渐变背景(在模型之前调用)。top/bottom 为 RGB,范围 0..1。 pub fn draw_background(&self, top: [f32; 3], bottom: [f32; 3]) { unsafe { // 背景不透明,先关闭混合 (self.gl.gl_disable)(gl::GL_BLEND); (self.gl.gl_use_program)(self.grad_program); let verts: [f32; 8] = [ -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0, ]; let mut vbo = 0; (self.gl.gl_gen_buffers)(1, &mut vbo); (self.gl.gl_bind_buffer)(gl::GL_ARRAY_BUFFER, vbo); (self.gl.gl_buffer_data)( gl::GL_ARRAY_BUFFER, (verts.len() * 4) as isize, verts.as_ptr() as *const c_void, gl::GL_STATIC_DRAW, ); (self.gl.gl_enable_vertex_attrib_array)(self.grad_attrib_pos as u32); (self.gl.gl_vertex_attrib_pointer)( self.grad_attrib_pos as u32, 2, gl::GL_FLOAT, gl::GL_FALSE, 0, std::ptr::null(), ); (self.gl.gl_uniform4f)(self.grad_uniform_top, top[0], top[1], top[2], 1.0); (self.gl.gl_uniform4f)(self.grad_uniform_bottom, bottom[0], bottom[1], bottom[2], 1.0); (self.gl.gl_draw_arrays)(gl::GL_TRIANGLE_STRIP, 0, 4); (self.gl.gl_delete_buffers)(1, &vbo); // 恢复混合,供后续模型绘制 (self.gl.gl_enable)(gl::GL_BLEND); } } pub fn clear(&self) { unsafe { (self.gl.gl_clear)(gl::GL_COLOR_BUFFER_BIT); } } /// 将窗口提升到 X11 堆叠顺序的最顶层 pub fn raise_window(&self) { unsafe { (self.gl.x_raise_window)(self.x_display, self.x_window); } } /// 将 EGL 上下文绑定到当前线程(EGL 上下文是线程绑定的) pub fn make_current(&self) -> Result<()> { if unsafe { (self.gl.egl_make_current)(self.display, self.surface, self.surface, self.context) } == 0 { return Err(anyhow!("eglMakeCurrent 失败(子线程)")); } Ok(()) } /// 释放当前线程的 EGL 上下文绑定,允许其他线程绑定 pub fn release_current(&self) { unsafe { (self.gl.egl_make_current)(self.display, std::ptr::null_mut(), std::ptr::null_mut(), std::ptr::null_mut()); } } }