diff --git a/.build.sh b/.build.sh index b16b65c..dbcaa95 100644 --- a/.build.sh +++ b/.build.sh @@ -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=$?" diff --git a/.run.sh b/.run.sh new file mode 100644 index 0000000..a08bb22 --- /dev/null +++ b/.run.sh @@ -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 diff --git a/.start.sh b/.start.sh new file mode 100644 index 0000000..a661e13 --- /dev/null +++ b/.start.sh @@ -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=$!" diff --git a/Cargo.lock b/Cargo.lock index ab70a56..e08ff56 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/s3.xwd b/s3.xwd new file mode 100644 index 0000000..6425050 Binary files /dev/null and b/s3.xwd differ diff --git a/s4.xwd b/s4.xwd new file mode 100644 index 0000000..8931714 Binary files /dev/null and b/s4.xwd differ diff --git a/s6.xwd b/s6.xwd new file mode 100644 index 0000000..b4389b6 Binary files /dev/null and b/s6.xwd differ diff --git a/src/plugins/live2d/core_ffi.rs b/src/plugins/live2d/core_ffi.rs index 0760069..78232f3 100644 --- a/src/plugins/live2d/core_ffi.rs +++ b/src/plugins/live2d/core_ffi.rs @@ -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; diff --git a/src/plugins/live2d/gl.rs b/src/plugins/live2d/gl.rs index 42b8872..4cb5418 100644 --- a/src/plugins/live2d/gl.rs +++ b/src/plugins/live2d/gl.rs @@ -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 { 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 = 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 = 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()); + } + } } diff --git a/src/plugins/live2d/plugin.rs b/src/plugins/live2d/plugin.rs index 16510e3..580c8ab 100644 --- a/src/plugins/live2d/plugin.rs +++ b/src/plugins/live2d/plugin.rs @@ -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) { diff --git a/src/plugins/live2d/renderer.rs b/src/plugins/live2d/renderer.rs index 7f833f2..56c7a10 100644 --- a/src/plugins/live2d/renderer.rs +++ b/src/plugins/live2d/renderer.rs @@ -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 { 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 = 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 = 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,20 +244,14 @@ 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, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, ] -} +} \ No newline at end of file diff --git a/src/plugins/video/mod.rs b/src/plugins/video/mod.rs index bbeb0fb..2c24b74 100644 --- a/src/plugins/video/mod.rs +++ b/src/plugins/video/mod.rs @@ -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(); diff --git a/w3.xwd b/w3.xwd new file mode 100644 index 0000000..5ef0a2b Binary files /dev/null and b/w3.xwd differ diff --git a/w5.xwd b/w5.xwd new file mode 100644 index 0000000..8d89d48 Binary files /dev/null and b/w5.xwd differ diff --git a/w6.xwd b/w6.xwd new file mode 100644 index 0000000..0d9a23c Binary files /dev/null and b/w6.xwd differ