feat: 添加 Live2D 渲染插件及相关依赖,优化构建和启动脚本
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
cd /home/showen/Showen/ShowenV2
|
cd /home/showen/Showen/ShowenV2
|
||||||
export PATH=/home/showen/.rustup/toolchains/stable-aarch64-unknown-linux-gnu/bin:$PATH
|
export PATH=/home/showen/.rustup/toolchains/stable-aarch64-unknown-linux-gnu/bin:$PATH
|
||||||
cargo check 2>&1 | grep -E "^error" | head -60
|
pkill -9 -f showen_v2 2>/dev/null
|
||||||
echo "---SUMMARY---"
|
sleep 1
|
||||||
cargo check 2>&1 | tail -3
|
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",
|
"objc2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytemuck"
|
||||||
|
version = "1.25.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder-lite"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.11.1"
|
version = "1.11.1"
|
||||||
@@ -251,6 +263,15 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"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]]
|
[[package]]
|
||||||
name = "filetime"
|
name = "filetime"
|
||||||
version = "0.2.27"
|
version = "0.2.27"
|
||||||
@@ -604,6 +625,19 @@ dependencies = [
|
|||||||
"icu_properties",
|
"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]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.13.0"
|
version = "2.13.0"
|
||||||
@@ -734,6 +768,16 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"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]]
|
[[package]]
|
||||||
name = "multer"
|
name = "multer"
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
@@ -874,6 +918,19 @@ version = "0.2.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
|
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]]
|
[[package]]
|
||||||
name = "potential_utf"
|
name = "potential_utf"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
@@ -901,6 +958,12 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pxfm"
|
||||||
|
version = "0.1.30"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.45"
|
version = "1.0.45"
|
||||||
@@ -1164,6 +1227,7 @@ dependencies = [
|
|||||||
"dbus-crossroads",
|
"dbus-crossroads",
|
||||||
"flate2",
|
"flate2",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
"image",
|
||||||
"libloading",
|
"libloading",
|
||||||
"opencv",
|
"opencv",
|
||||||
"rand",
|
"rand",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
//! 直接对应 `Live2DCubismCore.h` 的 C API。
|
//! 直接对应 `Live2DCubismCore.h` 的 C API。
|
||||||
//! 动态加载 libLive2DCubismCore.so(需 libm.so 预加载,因 Core 内部使用 powf)。
|
//! 动态加载 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};
|
use std::os::raw::{c_char, c_int, c_uchar, c_uint, c_void};
|
||||||
|
|
||||||
/// moc 对齐要求(字节)
|
/// moc 对齐要求(字节)
|
||||||
@@ -32,6 +32,7 @@ pub mod dynamic_flags {
|
|||||||
|
|
||||||
/// 混合模式
|
/// 混合模式
|
||||||
pub mod blend_mode {
|
pub mod blend_mode {
|
||||||
|
use std::os::raw::c_int;
|
||||||
pub const NORMAL: c_int = 0;
|
pub const NORMAL: c_int = 0;
|
||||||
pub const ADDITIVE: c_int = 1;
|
pub const ADDITIVE: c_int = 1;
|
||||||
pub const MULTIPLICATIVE: c_int = 2;
|
pub const MULTIPLICATIVE: c_int = 2;
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ pub mod egl {
|
|||||||
// ============ X11 ============
|
// ============ X11 ============
|
||||||
|
|
||||||
pub mod 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 Display = c_void;
|
||||||
pub type Window = c_long;
|
pub type Window = c_long;
|
||||||
@@ -68,6 +68,27 @@ pub mod x11 {
|
|||||||
pub const CWEventMask: c_long = 1 << 11;
|
pub const CWEventMask: c_long = 1 << 11;
|
||||||
pub const ExposureMask: c_long = 1 << 15;
|
pub const ExposureMask: c_long = 1 << 15;
|
||||||
pub const StructureNotifyMask: c_long = 1 << 17;
|
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 ============
|
// ============ GLES2 ============
|
||||||
@@ -291,6 +312,33 @@ pub struct Gl {
|
|||||||
pub x_store_name:
|
pub x_store_name:
|
||||||
unsafe extern "C" fn(disp: *mut x11::Display, w: x11::Window, name: *const c_char) -> c_int,
|
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_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 {
|
impl Gl {
|
||||||
@@ -349,6 +397,14 @@ impl Gl {
|
|||||||
x_map_window: *lib_x11.get(b"XMapWindow\0")?,
|
x_map_window: *lib_x11.get(b"XMapWindow\0")?,
|
||||||
x_store_name: *lib_x11.get(b"XStoreName\0")?,
|
x_store_name: *lib_x11.get(b"XStoreName\0")?,
|
||||||
x_flush: *lib_x11.get(b"XFlush\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_egl: lib_egl,
|
||||||
_lib_gl: lib_gl,
|
_lib_gl: lib_gl,
|
||||||
@@ -364,6 +420,7 @@ pub struct RenderContext {
|
|||||||
pub gl: Gl,
|
pub gl: Gl,
|
||||||
pub display: egl::EGLDisplay,
|
pub display: egl::EGLDisplay,
|
||||||
pub surface: egl::EGLSurface,
|
pub surface: egl::EGLSurface,
|
||||||
|
pub context: egl::EGLContext,
|
||||||
pub x_display: *mut x11::Display,
|
pub x_display: *mut x11::Display,
|
||||||
pub x_window: x11::Window,
|
pub x_window: x11::Window,
|
||||||
pub program: GLuint,
|
pub program: GLuint,
|
||||||
@@ -395,13 +452,10 @@ fn compile_shader(gl: &Gl, shader_type: GLenum, src: &str) -> Result<GLuint> {
|
|||||||
let mut status = 0;
|
let mut status = 0;
|
||||||
(gl.gl_get_shaderiv)(shader, gl::GL_COMPILE_STATUS, &mut status);
|
(gl.gl_get_shaderiv)(shader, gl::GL_COMPILE_STATUS, &mut status);
|
||||||
if status == 0 {
|
if status == 0 {
|
||||||
let mut log = vec![0i8; 1024];
|
let mut log: Vec<u8> = vec![0u8; 1024];
|
||||||
(gl.gl_get_shader_info_log)(shader, 1024, std::ptr::null_mut(), log.as_mut_ptr());
|
(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(std::slice::from_raw_parts(
|
let log_str = String::from_utf8_lossy(&log);
|
||||||
log.as_ptr() as *const u8,
|
return Err(anyhow!("shader compile failed: {}", log_str));
|
||||||
1024,
|
|
||||||
));
|
|
||||||
return Err(anyhow!("着色器编译失败: {}", log_str));
|
|
||||||
}
|
}
|
||||||
Ok(shader)
|
Ok(shader)
|
||||||
}
|
}
|
||||||
@@ -414,8 +468,20 @@ impl RenderContext {
|
|||||||
// X11 窗口
|
// X11 窗口
|
||||||
let x_display = unsafe { (gl.x_open_display)(std::ptr::null()) };
|
let x_display = unsafe { (gl.x_open_display)(std::ptr::null()) };
|
||||||
if x_display.is_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 {
|
let mut attrs = x11::XSetWindowAttributes {
|
||||||
background_pixmap: 0,
|
background_pixmap: 0,
|
||||||
background_pixel: 0,
|
background_pixel: 0,
|
||||||
@@ -429,34 +495,66 @@ impl RenderContext {
|
|||||||
save_under: 0,
|
save_under: 0,
|
||||||
event_mask: x11::ExposureMask | x11::StructureNotifyMask,
|
event_mask: x11::ExposureMask | x11::StructureNotifyMask,
|
||||||
do_not_propagate_mask: 0,
|
do_not_propagate_mask: 0,
|
||||||
override_redirect: 1,
|
override_redirect: 0,
|
||||||
colormap: 0,
|
colormap: 0,
|
||||||
cursor: 0,
|
cursor: 0,
|
||||||
};
|
};
|
||||||
let x_window = unsafe {
|
let x_window = unsafe {
|
||||||
(gl.x_create_window)(
|
(gl.x_create_window)(
|
||||||
x_display,
|
x_display,
|
||||||
|
root_window,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
win_w as c_uint,
|
||||||
width as c_uint,
|
win_h as c_uint,
|
||||||
height as c_uint,
|
|
||||||
0,
|
0,
|
||||||
24,
|
24,
|
||||||
1,
|
1,
|
||||||
std::ptr::null_mut(),
|
std::ptr::null_mut(),
|
||||||
x11::CWOverrideRedirect | x11::CWEventMask,
|
x11::CWEventMask,
|
||||||
&mut attrs,
|
&mut attrs,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
if x_window == 0 {
|
if x_window == 0 {
|
||||||
return Err(anyhow!("XCreateWindow 失败"));
|
return Err(anyhow!("XCreateWindow failed"));
|
||||||
}
|
}
|
||||||
unsafe {
|
unsafe {
|
||||||
let title = std::ffi::CString::new("ShowenV2 Live2D")?;
|
let title = std::ffi::CString::new("ShowenV2 Live2D")?;
|
||||||
(gl.x_store_name)(x_display, x_window, title.as_ptr());
|
(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_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
|
// EGL
|
||||||
@@ -503,7 +601,7 @@ impl RenderContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let ctx_attrs = [egl::EGL_CONTEXT_CLIENT_VERSION, 2, egl::EGL_NONE];
|
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() {
|
if context.is_null() {
|
||||||
return Err(anyhow!("eglCreateContext 失败"));
|
return Err(anyhow!("eglCreateContext 失败"));
|
||||||
}
|
}
|
||||||
@@ -513,7 +611,7 @@ impl RenderContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unsafe {
|
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_clear_color)(0.0, 0.0, 0.0, 1.0);
|
||||||
(gl.gl_enable)(gl::GL_BLEND);
|
(gl.gl_enable)(gl::GL_BLEND);
|
||||||
(gl.gl_blend_func)(gl::GL_SRC_ALPHA, gl::GL_ONE_MINUS_SRC_ALPHA);
|
(gl.gl_blend_func)(gl::GL_SRC_ALPHA, gl::GL_ONE_MINUS_SRC_ALPHA);
|
||||||
@@ -529,13 +627,10 @@ impl RenderContext {
|
|||||||
let mut status = 0;
|
let mut status = 0;
|
||||||
(gl.gl_get_programiv)(p, gl::GL_LINK_STATUS, &mut status);
|
(gl.gl_get_programiv)(p, gl::GL_LINK_STATUS, &mut status);
|
||||||
if status == 0 {
|
if status == 0 {
|
||||||
let mut log = vec![0i8; 1024];
|
let mut log: Vec<u8> = vec![0u8; 1024];
|
||||||
(gl.gl_get_program_info_log)(p, 1024, std::ptr::null_mut(), log.as_mut_ptr());
|
(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(std::slice::from_raw_parts(
|
let log_str = String::from_utf8_lossy(&log);
|
||||||
log.as_ptr() as *const u8,
|
return Err(anyhow!("shader link failed: {}", log_str));
|
||||||
1024,
|
|
||||||
));
|
|
||||||
return Err(anyhow!("着色器链接失败: {}", log_str));
|
|
||||||
}
|
}
|
||||||
p
|
p
|
||||||
};
|
};
|
||||||
@@ -568,6 +663,7 @@ impl RenderContext {
|
|||||||
gl,
|
gl,
|
||||||
display,
|
display,
|
||||||
surface,
|
surface,
|
||||||
|
context,
|
||||||
x_display,
|
x_display,
|
||||||
x_window,
|
x_window,
|
||||||
program,
|
program,
|
||||||
@@ -620,4 +716,26 @@ impl RenderContext {
|
|||||||
pub fn clear(&self) {
|
pub fn clear(&self) {
|
||||||
unsafe { (self.gl.gl_clear)(gl::GL_COLOR_BUFFER_BIT); }
|
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()),
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +143,23 @@ impl Live2DPlugin {
|
|||||||
));
|
));
|
||||||
self.renderer = Some(Arc::clone(&renderer));
|
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 || {
|
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 {
|
loop {
|
||||||
if stop_flag.load(Ordering::SeqCst) {
|
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::animation::AnimationState;
|
||||||
use super::assets::{self, Model3Json, TextureImage};
|
use super::assets::{self, Model3Json, TextureImage};
|
||||||
use super::core_ffi::{self, CubismCore};
|
use super::core_ffi::{self, CubismCore};
|
||||||
use super::gl::{self as gl_api, RenderContext};
|
use super::gl::{self as gl_api, RenderContext};
|
||||||
|
use gl_api::gl as gl_const;
|
||||||
use super::model::{CubismModel, DrawableSnapshot};
|
use super::model::{CubismModel, DrawableSnapshot};
|
||||||
use anyhow::{Context, Result};
|
use anyhow::Result;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
@@ -31,12 +32,12 @@ impl Live2DRenderer {
|
|||||||
height: i32,
|
height: i32,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let core = Arc::new(CubismCore::load(core_so_path)?);
|
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 paths = assets::resolve_asset_paths(live2d_base_dir, model3_json_rel)?;
|
||||||
let model3 = Model3Json::from_path(&paths.model3_json)?;
|
let model3 = Model3Json::from_path(&paths.model3_json)?;
|
||||||
println!(
|
println!(
|
||||||
"[Live2D] model3.json: 鐗堟湰={} moc={} 绾圭悊鏁?{}",
|
"[Live2D] model3.json: ver={} moc={} textures={}",
|
||||||
model3.version,
|
model3.version,
|
||||||
model3.file_references.moc,
|
model3.file_references.moc,
|
||||||
model3.file_references.textures.len()
|
model3.file_references.textures.len()
|
||||||
@@ -48,7 +49,7 @@ impl Live2DRenderer {
|
|||||||
let gl_ctx = RenderContext::new_fullscreen(width, height)?;
|
let gl_ctx = RenderContext::new_fullscreen(width, height)?;
|
||||||
|
|
||||||
let texture_ids: Vec<u32> = textures.iter().map(|t| gl_ctx.create_texture(t)).collect();
|
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 canvas = model.canvas;
|
||||||
let model_matrix = compute_model_matrix(canvas, width, height);
|
let model_matrix = compute_model_matrix(canvas, width, height);
|
||||||
@@ -84,13 +85,13 @@ impl Live2DRenderer {
|
|||||||
unsafe {
|
unsafe {
|
||||||
let gl = &self.gl_ctx.gl;
|
let gl = &self.gl_ctx.gl;
|
||||||
(gl.gl_clear_color)(0.0, 0.0, 0.0, 1.0);
|
(gl.gl_clear_color)(0.0, 0.0, 0.0, 1.0);
|
||||||
(gl.gl_clear)(gl_api::GL_COLOR_BUFFER_BIT);
|
(gl.gl_clear)(gl_const::GL_COLOR_BUFFER_BIT);
|
||||||
(gl.gl_enable)(gl_api::GL_BLEND);
|
(gl.gl_enable)(gl_const::GL_BLEND);
|
||||||
(gl.gl_use_program)(self.gl_ctx.program);
|
(gl.gl_use_program)(self.gl_ctx.program);
|
||||||
(gl.gl_uniform_matrix4fv)(
|
(gl.gl_uniform_matrix4fv)(
|
||||||
self.gl_ctx.uniform_matrix,
|
self.gl_ctx.uniform_matrix,
|
||||||
1,
|
1,
|
||||||
gl_api::GL_FALSE,
|
gl_const::GL_FALSE,
|
||||||
self.model_matrix.as_ptr(),
|
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.gl.egl_swap_buffers)(self.gl_ctx.display, self.gl_ctx.surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 每帧提升窗口到最顶层,防止桌面/其他窗口遮挡
|
||||||
|
self.gl_ctx.raise_window();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_drawable(&self, snap: &DrawableSnapshot) {
|
fn draw_drawable(&self, snap: &DrawableSnapshot) {
|
||||||
let gl = &self.gl_ctx.gl;
|
let gl = &self.gl_ctx.gl;
|
||||||
unsafe {
|
unsafe {
|
||||||
// 绾圭悊
|
|
||||||
let tex_idx = snap.texture_index as usize;
|
let tex_idx = snap.texture_index as usize;
|
||||||
if tex_idx < self.texture_ids.len() {
|
if tex_idx < self.texture_ids.len() {
|
||||||
(gl.gl_active_texture)(gl_api::GL_TEXTURE0);
|
(gl.gl_active_texture)(gl_const::GL_TEXTURE0);
|
||||||
(gl.gl_bind_texture)(gl_api::GL_TEXTURE_2D, self.texture_ids[tex_idx]);
|
(gl.gl_bind_texture)(gl_const::GL_TEXTURE_2D, self.texture_ids[tex_idx]);
|
||||||
(gl.gl_uniform1i)(self.gl_ctx.uniform_texture, 0);
|
(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);
|
(gl.gl_uniform4f)(self.gl_ctx.uniform_base_color, 1.0, 1.0, 1.0, snap.opacity);
|
||||||
// multiply/screen color
|
|
||||||
let mc = snap.multiply_color;
|
let mc = snap.multiply_color;
|
||||||
(gl.gl_uniform4f)(self.gl_ctx.uniform_multiply_color, mc[0], mc[1], mc[2], mc[3]);
|
(gl.gl_uniform4f)(self.gl_ctx.uniform_multiply_color, mc[0], mc[1], mc[2], mc[3]);
|
||||||
let sc = snap.screen_color;
|
let sc = snap.screen_color;
|
||||||
(gl.gl_uniform4f)(self.gl_ctx.uniform_screen_color, sc[0], sc[1], sc[2], sc[3]);
|
(gl.gl_uniform4f)(self.gl_ctx.uniform_screen_color, sc[0], sc[1], sc[2], sc[3]);
|
||||||
|
|
||||||
// 娣峰悎妯″紡
|
|
||||||
match snap.blend_mode {
|
match snap.blend_mode {
|
||||||
core_ffi::blend_mode::NORMAL => {
|
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 => {
|
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 => {
|
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 vc = snap.vertex_positions.len();
|
||||||
let mut vertex_data: Vec<f32> = Vec::with_capacity(vc * 4);
|
let mut vertex_data: Vec<f32> = Vec::with_capacity(vc * 4);
|
||||||
for i in 0..vc {
|
for i in 0..vc {
|
||||||
@@ -157,22 +157,22 @@ impl Live2DRenderer {
|
|||||||
|
|
||||||
let mut vbo = 0u32;
|
let mut vbo = 0u32;
|
||||||
(gl.gl_gen_buffers)(1, &mut vbo);
|
(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.gl_buffer_data)(
|
||||||
gl_api::GL_ARRAY_BUFFER,
|
gl_const::GL_ARRAY_BUFFER,
|
||||||
(vertex_data.len() * 4) as isize,
|
(vertex_data.len() * 4) as isize,
|
||||||
vertex_data.as_ptr() as *const std::ffi::c_void,
|
vertex_data.as_ptr() as *const std::ffi::c_void,
|
||||||
gl_api::GL_STATIC_DRAW,
|
gl_const::GL_STATIC_DRAW,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut ibo = 0u32;
|
let mut ibo = 0u32;
|
||||||
(gl.gl_gen_buffers)(1, &mut ibo);
|
(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.gl_buffer_data)(
|
||||||
gl_api::GL_ELEMENT_ARRAY_BUFFER,
|
gl_const::GL_ELEMENT_ARRAY_BUFFER,
|
||||||
(snap.indices.len() * 2) as isize,
|
(snap.indices.len() * 2) as isize,
|
||||||
snap.indices.as_ptr() as *const std::ffi::c_void,
|
snap.indices.as_ptr() as *const std::ffi::c_void,
|
||||||
gl_api::GL_STATIC_DRAW,
|
gl_const::GL_STATIC_DRAW,
|
||||||
);
|
);
|
||||||
|
|
||||||
let stride = 4 * 4;
|
let stride = 4 * 4;
|
||||||
@@ -181,8 +181,8 @@ impl Live2DRenderer {
|
|||||||
(gl.gl_vertex_attrib_pointer)(
|
(gl.gl_vertex_attrib_pointer)(
|
||||||
self.gl_ctx.attrib_position as u32,
|
self.gl_ctx.attrib_position as u32,
|
||||||
2,
|
2,
|
||||||
gl_api::GL_FLOAT,
|
gl_const::GL_FLOAT,
|
||||||
gl_api::GL_FALSE,
|
gl_const::GL_FALSE,
|
||||||
stride,
|
stride,
|
||||||
std::ptr::null(),
|
std::ptr::null(),
|
||||||
);
|
);
|
||||||
@@ -192,17 +192,17 @@ impl Live2DRenderer {
|
|||||||
(gl.gl_vertex_attrib_pointer)(
|
(gl.gl_vertex_attrib_pointer)(
|
||||||
self.gl_ctx.attrib_tex_coord as u32,
|
self.gl_ctx.attrib_tex_coord as u32,
|
||||||
2,
|
2,
|
||||||
gl_api::GL_FLOAT,
|
gl_const::GL_FLOAT,
|
||||||
gl_api::GL_FALSE,
|
gl_const::GL_FALSE,
|
||||||
stride,
|
stride,
|
||||||
(2 * 4) as *const std::ffi::c_void,
|
(2 * 4) as *const std::ffi::c_void,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
(gl.gl_draw_elements)(
|
(gl.gl_draw_elements)(
|
||||||
gl_api::GL_TRIANGLES,
|
gl_const::GL_TRIANGLES,
|
||||||
snap.indices.len() as i32,
|
snap.indices.len() as i32,
|
||||||
gl_api::GL_UNSIGNED_SHORT,
|
gl_const::GL_UNSIGNED_SHORT,
|
||||||
std::ptr::null(),
|
std::ptr::null(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -218,7 +218,7 @@ impl Live2DRenderer {
|
|||||||
while self.running {
|
while self.running {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
if let Err(e) = self.render_frame() {
|
if let Err(e) = self.render_frame() {
|
||||||
eprintln!("[Live2D] 娓叉煋甯уけ璐? {e}");
|
eprintln!("[Live2D] render frame failed: {e}");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let elapsed = start.elapsed();
|
let elapsed = start.elapsed();
|
||||||
@@ -234,8 +234,9 @@ impl Live2DRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 璁$畻 model 鈫?NDC 鍙樻崲鐭╅樀锛堝垪浼樺厛 4x4锛?///
|
/// Compute model -> NDC transform matrix (column-major 4x4).
|
||||||
/// Cubism 鍧愭爣锛氬師鐐瑰湪 canvas 涓績锛孻 杞村悜涓婏紝鍗曚綅涓哄儚绱犮€?/// 闇€瑕佺瓑姣旂缉鏀句娇妯″瀷瀹屾暣鏄剧ず鍦ㄧ獥鍙e唴銆?fn compute_model_matrix(
|
/// Cubism coords: origin at canvas center, Y up, unit = pixel.
|
||||||
|
fn compute_model_matrix(
|
||||||
canvas: super::model::CanvasInfo,
|
canvas: super::model::CanvasInfo,
|
||||||
_width: i32,
|
_width: i32,
|
||||||
_height: i32,
|
_height: i32,
|
||||||
@@ -243,16 +244,10 @@ impl Live2DRenderer {
|
|||||||
let canvas_w = canvas.size[0].max(1.0);
|
let canvas_w = canvas.size[0].max(1.0);
|
||||||
let canvas_h = canvas.size[1].max(1.0);
|
let canvas_h = canvas.size[1].max(1.0);
|
||||||
|
|
||||||
// 璁$畻缂╂斁锛氳妯″瀷绛夋瘮閫傚簲绐楀彛
|
|
||||||
let scale_x = 2.0 / canvas_w;
|
let scale_x = 2.0 / canvas_w;
|
||||||
let scale_y = 2.0 / canvas_h;
|
let scale_y = 2.0 / canvas_h;
|
||||||
let scale = scale_x.min(scale_y);
|
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,
|
scale, 0.0, 0.0, 0.0,
|
||||||
0.0, scale, 0.0, 0.0,
|
0.0, scale, 0.0, 0.0,
|
||||||
|
|||||||
@@ -118,6 +118,17 @@ impl Plugin for VideoPlugin {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.context("video plugin context is not initialized")?;
|
.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 processor = Arc::new(Mutex::new(VideoProcessor::new((*ctx.config).clone())?));
|
||||||
let worker_processor = Arc::clone(&processor);
|
let worker_processor = Arc::clone(&processor);
|
||||||
let tx = ctx.tx.clone();
|
let tx = ctx.tx.clone();
|
||||||
|
|||||||
Reference in New Issue
Block a user