Files
ShowenV2/src/plugins/live2d/gl.rs
XiuchengWu 7cb8cee70d feat: add device-side subtitle overlay using FreeType for rendering Chinese text
- Updated Cargo.toml to include freetype-rs for font rendering.
- Modified message.rs to introduce a new Message variant for subtitles.
- Enhanced HttpPlugin to send subtitle messages to the live2d plugin.
- Implemented text rendering in the live2d plugin using FreeType for Chinese characters.
- Added OpenGL shaders for rendering text overlays.
- Created a new text.rs module for handling text rasterization and texture rendering.
- Updated renderer.rs to integrate subtitle rendering logic.
2026-07-09 09:15:23 +08:00

1084 lines
42 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 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<Self> {
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<GLuint> {
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<u8> = 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<Self> {
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+stencilLive2D 遮罩需要 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<u8> = 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<GLint> {
let c = std::ffi::CString::new(name)?;
Ok(unsafe { (gl.gl_get_attrib_location)(program, c.as_ptr()) })
};
let getu = |name: &str| -> Result<GLint> {
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<u8> = 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<u8> = 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<u8> = 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());
}
}
}