feat: 添加 Live2D 渲染插件及相关依赖,优化构建和启动脚本
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
#!/bin/bash
|
||||
cd /home/showen/Showen/ShowenV2
|
||||
export PATH=/home/showen/.rustup/toolchains/stable-aarch64-unknown-linux-gnu/bin:$PATH
|
||||
cargo check 2>&1 | grep -E "^error" | head -60
|
||||
echo "---SUMMARY---"
|
||||
cargo check 2>&1 | tail -3
|
||||
pkill -9 -f showen_v2 2>/dev/null
|
||||
sleep 1
|
||||
cargo build --release 2>&1 | tail -3
|
||||
echo "BUILD_EXIT=$?"
|
||||
|
||||
11
.run.sh
Normal file
11
.run.sh
Normal file
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
killall showen_v2 2>/dev/null
|
||||
sleep 1
|
||||
cd /home/showen/Showen/ShowenV2
|
||||
DISPLAY=:0 RUST_LOG=info nohup ./target/release/showen_v2 --config configs/live2d_anime.json > /tmp/sl2.log 2>&1 &
|
||||
echo "PID=$!"
|
||||
sleep 8
|
||||
echo "=== LOG ==="
|
||||
cat /tmp/sl2.log | tail -50
|
||||
echo "=== PS ==="
|
||||
ps aux | grep showen_v2 | grep -v grep
|
||||
12
.start.sh
Normal file
12
.start.sh
Normal file
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
# 杀旧进程
|
||||
pkill -9 -f showen_v2 2>/dev/null
|
||||
sleep 1
|
||||
|
||||
cd /home/showen/Showen/ShowenV2
|
||||
export DISPLAY=:0
|
||||
export RUST_LOG=info
|
||||
|
||||
# 后台启动
|
||||
nohup ./target/release/showen_v2 --config configs/live2d_anime.json > /tmp/sl2.log 2>&1 &
|
||||
echo "PID=$!"
|
||||
64
Cargo.lock
generated
64
Cargo.lock
generated
@@ -65,12 +65,24 @@ dependencies = [
|
||||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder-lite"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.11.1"
|
||||
@@ -251,6 +263,15 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fdeflate"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
|
||||
dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.27"
|
||||
@@ -604,6 +625,19 @@ dependencies = [
|
||||
"icu_properties",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.25.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder-lite",
|
||||
"moxcms",
|
||||
"num-traits",
|
||||
"png",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.13.0"
|
||||
@@ -734,6 +768,16 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "moxcms"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"pxfm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multer"
|
||||
version = "2.1.0"
|
||||
@@ -874,6 +918,19 @@ version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crc32fast",
|
||||
"fdeflate",
|
||||
"flate2",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.4"
|
||||
@@ -901,6 +958,12 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pxfm"
|
||||
version = "0.1.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
@@ -1164,6 +1227,7 @@ dependencies = [
|
||||
"dbus-crossroads",
|
||||
"flate2",
|
||||
"futures-util",
|
||||
"image",
|
||||
"libloading",
|
||||
"opencv",
|
||||
"rand",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//! 直接对应 `Live2DCubismCore.h` 的 C API。
|
||||
//! 动态加载 libLive2DCubismCore.so(需 libm.so 预加载,因 Core 内部使用 powf)。
|
||||
|
||||
use libloading::{Library, Symbol};
|
||||
use libloading::Library;
|
||||
use std::os::raw::{c_char, c_int, c_uchar, c_uint, c_void};
|
||||
|
||||
/// moc 对齐要求(字节)
|
||||
@@ -32,6 +32,7 @@ pub mod dynamic_flags {
|
||||
|
||||
/// 混合模式
|
||||
pub mod blend_mode {
|
||||
use std::os::raw::c_int;
|
||||
pub const NORMAL: c_int = 0;
|
||||
pub const ADDITIVE: c_int = 1;
|
||||
pub const MULTIPLICATIVE: c_int = 2;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +75,13 @@ impl Plugin for Live2DPlugin {
|
||||
message: Message::PluginReady(self.id().to_string()),
|
||||
})?;
|
||||
|
||||
// 如果初始配置就是 Live2D 模式,立即启动渲染
|
||||
let config = (*ctx.config).clone();
|
||||
if config.character.render_type == RenderType::Live2d {
|
||||
println!("[Live2DPlugin] 检测到初始配置为 Live2D 模式,启动渲染");
|
||||
self.start_live2d(&config)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -136,7 +143,23 @@ impl Live2DPlugin {
|
||||
));
|
||||
self.renderer = Some(Arc::clone(&renderer));
|
||||
|
||||
// 渲染器初始化(含 texture 上传)已完成,释放主线程的 EGL 上下文绑定
|
||||
// 让渲染子线程能重新绑定(同一个 EGL context 不能同时在两个线程 current)
|
||||
{
|
||||
let r = renderer.lock().unwrap();
|
||||
r.gl_ctx.release_current();
|
||||
}
|
||||
|
||||
let handle = std::thread::spawn(move || {
|
||||
// EGL 上下文是线程绑定的,需在渲染线程重新绑定
|
||||
{
|
||||
let r = renderer.lock().unwrap();
|
||||
if let Err(e) = r.gl_ctx.make_current() {
|
||||
eprintln!("[Live2DPlugin] eglMakeCurrent 失败: {e}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染循环
|
||||
loop {
|
||||
if stop_flag.load(Ordering::SeqCst) {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
//! Live2D 娓叉煋鍣細鏁村悎 Core + Model + GL + Animation
|
||||
//! Live2D Renderer: Core + Model + GL + Animation integration
|
||||
|
||||
use super::animation::AnimationState;
|
||||
use super::assets::{self, Model3Json, TextureImage};
|
||||
use super::core_ffi::{self, CubismCore};
|
||||
use super::gl::{self as gl_api, RenderContext};
|
||||
use gl_api::gl as gl_const;
|
||||
use super::model::{CubismModel, DrawableSnapshot};
|
||||
use anyhow::{Context, Result};
|
||||
use anyhow::Result;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
@@ -31,12 +32,12 @@ impl Live2DRenderer {
|
||||
height: i32,
|
||||
) -> Result<Self> {
|
||||
let core = Arc::new(CubismCore::load(core_so_path)?);
|
||||
println!("[Live2D] Core 鐗堟湰: {}", core.version_string());
|
||||
println!("[Live2D] Core version: {}", core.version_string());
|
||||
|
||||
let paths = assets::resolve_asset_paths(live2d_base_dir, model3_json_rel)?;
|
||||
let model3 = Model3Json::from_path(&paths.model3_json)?;
|
||||
println!(
|
||||
"[Live2D] model3.json: 鐗堟湰={} moc={} 绾圭悊鏁?{}",
|
||||
"[Live2D] model3.json: ver={} moc={} textures={}",
|
||||
model3.version,
|
||||
model3.file_references.moc,
|
||||
model3.file_references.textures.len()
|
||||
@@ -48,7 +49,7 @@ impl Live2DRenderer {
|
||||
let gl_ctx = RenderContext::new_fullscreen(width, height)?;
|
||||
|
||||
let texture_ids: Vec<u32> = textures.iter().map(|t| gl_ctx.create_texture(t)).collect();
|
||||
println!("[Live2D] 宸蹭笂浼?{} 涓汗鐞嗗埌 GPU", texture_ids.len());
|
||||
println!("[Live2D] Uploaded {} textures", texture_ids.len());
|
||||
|
||||
let canvas = model.canvas;
|
||||
let model_matrix = compute_model_matrix(canvas, width, height);
|
||||
@@ -84,13 +85,13 @@ impl Live2DRenderer {
|
||||
unsafe {
|
||||
let gl = &self.gl_ctx.gl;
|
||||
(gl.gl_clear_color)(0.0, 0.0, 0.0, 1.0);
|
||||
(gl.gl_clear)(gl_api::GL_COLOR_BUFFER_BIT);
|
||||
(gl.gl_enable)(gl_api::GL_BLEND);
|
||||
(gl.gl_clear)(gl_const::GL_COLOR_BUFFER_BIT);
|
||||
(gl.gl_enable)(gl_const::GL_BLEND);
|
||||
(gl.gl_use_program)(self.gl_ctx.program);
|
||||
(gl.gl_uniform_matrix4fv)(
|
||||
self.gl_ctx.uniform_matrix,
|
||||
1,
|
||||
gl_api::GL_FALSE,
|
||||
gl_const::GL_FALSE,
|
||||
self.model_matrix.as_ptr(),
|
||||
);
|
||||
}
|
||||
@@ -108,44 +109,43 @@ impl Live2DRenderer {
|
||||
(self.gl_ctx.gl.egl_swap_buffers)(self.gl_ctx.display, self.gl_ctx.surface);
|
||||
}
|
||||
|
||||
// 每帧提升窗口到最顶层,防止桌面/其他窗口遮挡
|
||||
self.gl_ctx.raise_window();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw_drawable(&self, snap: &DrawableSnapshot) {
|
||||
let gl = &self.gl_ctx.gl;
|
||||
unsafe {
|
||||
// 绾圭悊
|
||||
let tex_idx = snap.texture_index as usize;
|
||||
if tex_idx < self.texture_ids.len() {
|
||||
(gl.gl_active_texture)(gl_api::GL_TEXTURE0);
|
||||
(gl.gl_bind_texture)(gl_api::GL_TEXTURE_2D, self.texture_ids[tex_idx]);
|
||||
(gl.gl_active_texture)(gl_const::GL_TEXTURE0);
|
||||
(gl.gl_bind_texture)(gl_const::GL_TEXTURE_2D, self.texture_ids[tex_idx]);
|
||||
(gl.gl_uniform1i)(self.gl_ctx.uniform_texture, 0);
|
||||
}
|
||||
|
||||
// base color锛堜笉閫忔槑搴︿箻鍒?alpha锛? (gl.gl_uniform4f)(self.gl_ctx.uniform_base_color, 1.0, 1.0, 1.0, snap.opacity);
|
||||
// multiply/screen color
|
||||
(gl.gl_uniform4f)(self.gl_ctx.uniform_base_color, 1.0, 1.0, 1.0, snap.opacity);
|
||||
let mc = snap.multiply_color;
|
||||
(gl.gl_uniform4f)(self.gl_ctx.uniform_multiply_color, mc[0], mc[1], mc[2], mc[3]);
|
||||
let sc = snap.screen_color;
|
||||
(gl.gl_uniform4f)(self.gl_ctx.uniform_screen_color, sc[0], sc[1], sc[2], sc[3]);
|
||||
|
||||
// 娣峰悎妯″紡
|
||||
match snap.blend_mode {
|
||||
core_ffi::blend_mode::NORMAL => {
|
||||
(gl.gl_blend_func)(gl_api::GL_SRC_ALPHA, gl_api::GL_ONE_MINUS_SRC_ALPHA);
|
||||
(gl.gl_blend_func)(gl_const::GL_SRC_ALPHA, gl_const::GL_ONE_MINUS_SRC_ALPHA);
|
||||
}
|
||||
core_ffi::blend_mode::ADDITIVE => {
|
||||
(gl.gl_blend_func)(gl_api::GL_SRC_ALPHA, gl_api::GL_ONE);
|
||||
(gl.gl_blend_func)(gl_const::GL_SRC_ALPHA, gl_const::GL_ONE);
|
||||
}
|
||||
core_ffi::blend_mode::MULTIPLICATIVE => {
|
||||
(gl.gl_blend_func)(gl_api::GL_ZERO, gl_api::GL_SRC_COLOR);
|
||||
(gl.gl_blend_func)(gl_const::GL_ZERO, gl_const::GL_SRC_COLOR);
|
||||
}
|
||||
_ => {
|
||||
(gl.gl_blend_func)(gl_api::GL_SRC_ALPHA, gl_api::GL_ONE_MINUS_SRC_ALPHA);
|
||||
(gl.gl_blend_func)(gl_const::GL_SRC_ALPHA, gl_const::GL_ONE_MINUS_SRC_ALPHA);
|
||||
}
|
||||
}
|
||||
|
||||
// 椤剁偣鏁版嵁锛歱osition(xy) + texCoord(uv) 浜ら敊
|
||||
let vc = snap.vertex_positions.len();
|
||||
let mut vertex_data: Vec<f32> = Vec::with_capacity(vc * 4);
|
||||
for i in 0..vc {
|
||||
@@ -157,22 +157,22 @@ impl Live2DRenderer {
|
||||
|
||||
let mut vbo = 0u32;
|
||||
(gl.gl_gen_buffers)(1, &mut vbo);
|
||||
(gl.gl_bind_buffer)(gl_api::GL_ARRAY_BUFFER, vbo);
|
||||
(gl.gl_bind_buffer)(gl_const::GL_ARRAY_BUFFER, vbo);
|
||||
(gl.gl_buffer_data)(
|
||||
gl_api::GL_ARRAY_BUFFER,
|
||||
gl_const::GL_ARRAY_BUFFER,
|
||||
(vertex_data.len() * 4) as isize,
|
||||
vertex_data.as_ptr() as *const std::ffi::c_void,
|
||||
gl_api::GL_STATIC_DRAW,
|
||||
gl_const::GL_STATIC_DRAW,
|
||||
);
|
||||
|
||||
let mut ibo = 0u32;
|
||||
(gl.gl_gen_buffers)(1, &mut ibo);
|
||||
(gl.gl_bind_buffer)(gl_api::GL_ELEMENT_ARRAY_BUFFER, ibo);
|
||||
(gl.gl_bind_buffer)(gl_const::GL_ELEMENT_ARRAY_BUFFER, ibo);
|
||||
(gl.gl_buffer_data)(
|
||||
gl_api::GL_ELEMENT_ARRAY_BUFFER,
|
||||
gl_const::GL_ELEMENT_ARRAY_BUFFER,
|
||||
(snap.indices.len() * 2) as isize,
|
||||
snap.indices.as_ptr() as *const std::ffi::c_void,
|
||||
gl_api::GL_STATIC_DRAW,
|
||||
gl_const::GL_STATIC_DRAW,
|
||||
);
|
||||
|
||||
let stride = 4 * 4;
|
||||
@@ -181,8 +181,8 @@ impl Live2DRenderer {
|
||||
(gl.gl_vertex_attrib_pointer)(
|
||||
self.gl_ctx.attrib_position as u32,
|
||||
2,
|
||||
gl_api::GL_FLOAT,
|
||||
gl_api::GL_FALSE,
|
||||
gl_const::GL_FLOAT,
|
||||
gl_const::GL_FALSE,
|
||||
stride,
|
||||
std::ptr::null(),
|
||||
);
|
||||
@@ -192,17 +192,17 @@ impl Live2DRenderer {
|
||||
(gl.gl_vertex_attrib_pointer)(
|
||||
self.gl_ctx.attrib_tex_coord as u32,
|
||||
2,
|
||||
gl_api::GL_FLOAT,
|
||||
gl_api::GL_FALSE,
|
||||
gl_const::GL_FLOAT,
|
||||
gl_const::GL_FALSE,
|
||||
stride,
|
||||
(2 * 4) as *const std::ffi::c_void,
|
||||
);
|
||||
}
|
||||
|
||||
(gl.gl_draw_elements)(
|
||||
gl_api::GL_TRIANGLES,
|
||||
gl_const::GL_TRIANGLES,
|
||||
snap.indices.len() as i32,
|
||||
gl_api::GL_UNSIGNED_SHORT,
|
||||
gl_const::GL_UNSIGNED_SHORT,
|
||||
std::ptr::null(),
|
||||
);
|
||||
|
||||
@@ -218,7 +218,7 @@ impl Live2DRenderer {
|
||||
while self.running {
|
||||
let start = Instant::now();
|
||||
if let Err(e) = self.render_frame() {
|
||||
eprintln!("[Live2D] 娓叉煋甯уけ璐? {e}");
|
||||
eprintln!("[Live2D] render frame failed: {e}");
|
||||
break;
|
||||
}
|
||||
let elapsed = start.elapsed();
|
||||
@@ -234,8 +234,9 @@ impl Live2DRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
/// 璁$畻 model 鈫?NDC 鍙樻崲鐭╅樀锛堝垪浼樺厛 4x4锛?///
|
||||
/// Cubism 鍧愭爣锛氬師鐐瑰湪 canvas 涓績锛孻 杞村悜涓婏紝鍗曚綅涓哄儚绱犮€?/// 闇€瑕佺瓑姣旂缉鏀句娇妯″瀷瀹屾暣鏄剧ず鍦ㄧ獥鍙e唴銆?fn compute_model_matrix(
|
||||
/// Compute model -> NDC transform matrix (column-major 4x4).
|
||||
/// Cubism coords: origin at canvas center, Y up, unit = pixel.
|
||||
fn compute_model_matrix(
|
||||
canvas: super::model::CanvasInfo,
|
||||
_width: i32,
|
||||
_height: i32,
|
||||
@@ -243,16 +244,10 @@ impl Live2DRenderer {
|
||||
let canvas_w = canvas.size[0].max(1.0);
|
||||
let canvas_h = canvas.size[1].max(1.0);
|
||||
|
||||
// 璁$畻缂╂斁锛氳妯″瀷绛夋瘮閫傚簲绐楀彛
|
||||
let scale_x = 2.0 / canvas_w;
|
||||
let scale_y = 2.0 / canvas_h;
|
||||
let scale = scale_x.min(scale_y);
|
||||
|
||||
// 鍒椾紭鍏堢煩闃碉細
|
||||
// [ scale 0 0 0 ]
|
||||
// [ 0 scale 0 0 ]
|
||||
// [ 0 0 1 0 ]
|
||||
// [ 0 0 0 1 ]
|
||||
[
|
||||
scale, 0.0, 0.0, 0.0,
|
||||
0.0, scale, 0.0, 0.0,
|
||||
|
||||
@@ -118,6 +118,17 @@ impl Plugin for VideoPlugin {
|
||||
.as_ref()
|
||||
.context("video plugin context is not initialized")?;
|
||||
|
||||
// 如果是 Live2D 模式,不启动视频播放
|
||||
if ctx.config.character.render_type == crate::core::config::RenderType::Live2d {
|
||||
println!("[VideoPlugin] Live2D 模式,跳过视频播放启动");
|
||||
ctx.tx.send(Envelope {
|
||||
from: self.id().to_string(),
|
||||
to: Destination::Manager,
|
||||
message: Message::PluginReady(self.id().to_string()),
|
||||
})?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let processor = Arc::new(Mutex::new(VideoProcessor::new((*ctx.config).clone())?));
|
||||
let worker_processor = Arc::clone(&processor);
|
||||
let tx = ctx.tx.clone();
|
||||
|
||||
Reference in New Issue
Block a user