feat: 添加 Live2D 渲染插件及相关依赖,优化构建和启动脚本

This commit is contained in:
2026-07-08 17:32:56 +08:00
parent a8d7c03139
commit 547085c819
15 changed files with 306 additions and 70 deletions

View File

@@ -40,7 +40,7 @@ pub mod egl {
// ============ X11 ============
pub mod x11 {
use std::os::raw::{c_int, c_long, c_uint, c_void};
use std::os::raw::{c_int, c_long, c_void};
pub type Display = c_void;
pub type Window = c_long;
@@ -68,6 +68,27 @@ pub mod x11 {
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 ============
@@ -291,6 +312,33 @@ pub struct Gl {
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 {
@@ -349,6 +397,14 @@ impl Gl {
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,
@@ -364,6 +420,7 @@ 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,
@@ -395,13 +452,10 @@ fn compile_shader(gl: &Gl, shader_type: GLenum, src: &str) -> Result<GLuint> {
let mut status = 0;
(gl.gl_get_shaderiv)(shader, gl::GL_COMPILE_STATUS, &mut status);
if status == 0 {
let mut log = vec![0i8; 1024];
(gl.gl_get_shader_info_log)(shader, 1024, std::ptr::null_mut(), log.as_mut_ptr());
let log_str = String::from_utf8_lossy(std::slice::from_raw_parts(
log.as_ptr() as *const u8,
1024,
));
return Err(anyhow!("着色器编译失败: {}", log_str));
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)
}
@@ -414,8 +468,20 @@ impl RenderContext {
// X11 窗口
let x_display = unsafe { (gl.x_open_display)(std::ptr::null()) };
if x_display.is_null() {
return Err(anyhow!("XOpenDisplay 失败"));
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) };
// 用传入的 width/height 创建窗口(配置值),不用屏幕分辨率
// EGL surface 尺寸需与窗口匹配,大窗口可能导致 GPU 限制问题)
let win_w = width;
let win_h = height;
println!("[Live2D] 窗口尺寸: {}x{} (屏幕 {}x{})", win_w, win_h, screen_w, screen_h);
let mut attrs = x11::XSetWindowAttributes {
background_pixmap: 0,
background_pixel: 0,
@@ -429,34 +495,66 @@ impl RenderContext {
save_under: 0,
event_mask: x11::ExposureMask | x11::StructureNotifyMask,
do_not_propagate_mask: 0,
override_redirect: 1,
override_redirect: 0,
colormap: 0,
cursor: 0,
};
let x_window = unsafe {
(gl.x_create_window)(
x_display,
root_window,
0,
0,
0,
width as c_uint,
height as c_uint,
win_w as c_uint,
win_h as c_uint,
0,
24,
1,
std::ptr::null_mut(),
x11::CWOverrideRedirect | x11::CWEventMask,
x11::CWEventMask,
&mut attrs,
)
};
if x_window == 0 {
return Err(anyhow!("XCreateWindow 失败"));
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_flush)(x_display);
(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
@@ -503,7 +601,7 @@ impl RenderContext {
}
let ctx_attrs = [egl::EGL_CONTEXT_CLIENT_VERSION, 2, egl::EGL_NONE];
let context = unsafe { (gl.egl_create_context)(display, config, std::ptr::null(), ctx_attrs.as_ptr()) };
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 失败"));
}
@@ -513,7 +611,7 @@ impl RenderContext {
}
unsafe {
(gl.gl_viewport)(0, 0, width, height);
(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);
@@ -529,13 +627,10 @@ impl RenderContext {
let mut status = 0;
(gl.gl_get_programiv)(p, gl::GL_LINK_STATUS, &mut status);
if status == 0 {
let mut log = vec![0i8; 1024];
(gl.gl_get_program_info_log)(p, 1024, std::ptr::null_mut(), log.as_mut_ptr());
let log_str = String::from_utf8_lossy(std::slice::from_raw_parts(
log.as_ptr() as *const u8,
1024,
));
return Err(anyhow!("着色器链接失败: {}", log_str));
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
};
@@ -568,6 +663,7 @@ impl RenderContext {
gl,
display,
surface,
context,
x_display,
x_window,
program,
@@ -620,4 +716,26 @@ impl RenderContext {
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());
}
}
}