feat(live2d): implement OpenGL ES 2.0 rendering backend with EGL support

- Add `gl.rs` for OpenGL ES 2.0 and EGL context management.
- Introduce `mod.rs` to organize Live2D plugin modules.
- Create `model.rs` for loading and managing Cubism models, including parameter handling and drawable data extraction.
- Implement shader compilation and rendering context setup for Live2D models.
This commit is contained in:
2026-07-07 16:04:49 +08:00
parent 9637bdd323
commit 2aac62b2bb
14 changed files with 1865 additions and 0 deletions

View File

@@ -0,0 +1,276 @@
//! Cubism Core FFI 绑定
//!
//! 直接对应 `Live2DCubismCore.h` 的 C API。
//! 动态加载 libLive2DCubismCore.so需 libm.so 预加载,因 Core 内部使用 powf
use libloading::{Library, Symbol};
use std::ffi::CString;
use std::os::raw::{c_char, c_int, c_uchar, c_uint, c_void};
/// moc 对齐要求(字节)
pub const CSM_ALIGNOF_MOC: usize = 64;
/// model 对齐要求(字节)
pub const CSM_ALIGNOF_MODEL: usize = 16;
/// 可绘制常量标志位
pub mod constant_flags {
pub const BLEND_ADDITIVE: u8 = 1 << 0;
pub const BLEND_MULTIPLICATIVE: u8 = 1 << 1;
pub const IS_DOUBLE_SIDED: u8 = 1 << 2;
pub const IS_INVERTED_MASK: u8 = 1 << 3;
}
/// 可绘制动态标志位
pub mod dynamic_flags {
pub const IS_VISIBLE: u8 = 1 << 0;
pub const VISIBILITY_DID_CHANGE: u8 = 1 << 1;
pub const OPACITY_DID_CHANGE: u8 = 1 << 2;
pub const DRAW_ORDER_DID_CHANGE: u8 = 1 << 3;
pub const RENDER_ORDER_DID_CHANGE: u8 = 1 << 4;
pub const VERTEX_POSITIONS_DID_CHANGE: u8 = 1 << 5;
pub const BLEND_COLOR_DID_CHANGE: u8 = 1 << 6;
}
/// 混合模式
pub mod blend_mode {
pub const NORMAL: c_int = 0;
pub const ADDITIVE: c_int = 1;
pub const MULTIPLICATIVE: c_int = 2;
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct CsmVector2 {
pub x: f32,
pub y: f32,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct CsmVector4 {
pub x: f32,
pub y: f32,
pub z: f32,
pub w: f32,
}
/// Cubism Core 动态加载封装
pub struct CubismCore {
_libm: Library,
_lib: Library,
pub csm_get_version: unsafe extern "C" fn() -> c_uint,
pub csm_get_latest_moc_version: unsafe extern "C" fn() -> c_uint,
pub csm_get_moc_version:
unsafe extern "C" fn(address: *const c_void, size: c_uint) -> c_uint,
pub csm_has_moc_consistency:
unsafe extern "C" fn(address: *mut c_void, size: c_uint) -> c_int,
pub csm_revive_moc_in_place:
unsafe extern "C" fn(address: *mut c_void, size: c_uint) -> *mut c_void,
pub csm_get_sizeof_model: unsafe extern "C" fn(moc: *const c_void) -> c_uint,
pub csm_initialize_model_in_place: unsafe extern "C" fn(
moc: *const c_void,
address: *mut c_void,
size: c_uint,
) -> *mut c_void,
pub csm_update_model: unsafe extern "C" fn(model: *mut c_void),
pub csm_get_render_orders: unsafe extern "C" fn(model: *const c_void) -> *const c_int,
pub csm_read_canvas_info: unsafe extern "C" fn(
model: *const c_void,
out_size: *mut CsmVector2,
out_origin: *mut CsmVector2,
out_ppu: *mut f32,
),
pub csm_get_parameter_count: unsafe extern "C" fn(model: *const c_void) -> c_int,
pub csm_get_parameter_ids:
unsafe extern "C" fn(model: *const c_void) -> *const *const c_char,
pub csm_get_parameter_minimum_values:
unsafe extern "C" fn(model: *const c_void) -> *const f32,
pub csm_get_parameter_maximum_values:
unsafe extern "C" fn(model: *const c_void) -> *const f32,
pub csm_get_parameter_default_values:
unsafe extern "C" fn(model: *const c_void) -> *const f32,
pub csm_get_parameter_values: unsafe extern "C" fn(model: *mut c_void) -> *mut f32,
pub csm_get_part_count: unsafe extern "C" fn(model: *const c_void) -> c_int,
pub csm_get_part_opacities: unsafe extern "C" fn(model: *mut c_void) -> *mut f32,
pub csm_get_drawable_count: unsafe extern "C" fn(model: *const c_void) -> c_int,
pub csm_get_drawable_ids:
unsafe extern "C" fn(model: *const c_void) -> *const *const c_char,
pub csm_get_drawable_constant_flags:
unsafe extern "C" fn(model: *const c_void) -> *const c_uchar,
pub csm_get_drawable_dynamic_flags:
unsafe extern "C" fn(model: *const c_void) -> *const c_uchar,
pub csm_get_drawable_blend_modes:
unsafe extern "C" fn(model: *const c_void) -> *const c_int,
pub csm_get_drawable_texture_indices:
unsafe extern "C" fn(model: *const c_void) -> *const c_int,
pub csm_get_drawable_draw_orders:
unsafe extern "C" fn(model: *const c_void) -> *const c_int,
pub csm_get_drawable_opacities:
unsafe extern "C" fn(model: *const c_void) -> *const f32,
pub csm_get_drawable_mask_counts:
unsafe extern "C" fn(model: *const c_void) -> *const c_int,
pub csm_get_drawable_masks:
unsafe extern "C" fn(model: *const c_void) -> *const *const c_int,
pub csm_get_drawable_vertex_counts:
unsafe extern "C" fn(model: *const c_void) -> *const c_int,
pub csm_get_drawable_vertex_positions:
unsafe extern "C" fn(model: *const c_void) -> *const *const CsmVector2,
pub csm_get_drawable_vertex_uvs:
unsafe extern "C" fn(model: *const c_void) -> *const *const CsmVector2,
pub csm_get_drawable_index_counts:
unsafe extern "C" fn(model: *const c_void) -> *const c_int,
pub csm_get_drawable_indices:
unsafe extern "C" fn(model: *const c_void) -> *const *const u16,
pub csm_get_drawable_multiply_colors:
unsafe extern "C" fn(model: *const c_void) -> *const CsmVector4,
pub csm_get_drawable_screen_colors:
unsafe extern "C" fn(model: *const c_void) -> *const CsmVector4,
pub csm_reset_drawable_dynamic_flags: unsafe extern "C" fn(model: *mut c_void),
}
impl CubismCore {
/// 加载 Cubism Core 动态库
///
/// `so_path` 为 libLive2DCubismCore.so 的路径。
/// 内部会先 dlopen libm.soCore 依赖 powf 但 NEEDED 未声明)。
pub fn load(so_path: &str) -> anyhow::Result<Self> {
// 预加载 libmRTLD_GLOBAL 让后续 .so 能解析到 powf
let libm = unsafe {
Library::new("libm.so.6").or_else(|_| Library::new("libm.so.6.1"))?
};
let lib = unsafe { Library::new(so_path)? };
unsafe {
Ok(Self {
csm_get_version: *lib.get(b"csmGetVersion\0")?,
csm_get_latest_moc_version: *lib.get(b"csmGetLatestMocVersion\0")?,
csm_get_moc_version: *lib.get(b"csmGetMocVersion\0")?,
csm_has_moc_consistency: *lib.get(b"csmHasMocConsistency\0")?,
csm_revive_moc_in_place: *lib.get(b"csmReviveMocInPlace\0")?,
csm_get_sizeof_model: *lib.get(b"csmGetSizeofModel\0")?,
csm_initialize_model_in_place: *lib.get(b"csmInitializeModelInPlace\0")?,
csm_update_model: *lib.get(b"csmUpdateModel\0")?,
csm_get_render_orders: *lib.get(b"csmGetRenderOrders\0")?,
csm_read_canvas_info: *lib.get(b"csmReadCanvasInfo\0")?,
csm_get_parameter_count: *lib.get(b"csmGetParameterCount\0")?,
csm_get_parameter_ids: *lib.get(b"csmGetParameterIds\0")?,
csm_get_parameter_minimum_values: *lib
.get(b"csmGetParameterMinimumValues\0")?,
csm_get_parameter_maximum_values: *lib
.get(b"csmGetParameterMaximumValues\0")?,
csm_get_parameter_default_values: *lib
.get(b"csmGetParameterDefaultValues\0")?,
csm_get_parameter_values: *lib.get(b"csmGetParameterValues\0")?,
csm_get_part_count: *lib.get(b"csmGetPartCount\0")?,
csm_get_part_opacities: *lib.get(b"csmGetPartOpacities\0")?,
csm_get_drawable_count: *lib.get(b"csmGetDrawableCount\0")?,
csm_get_drawable_ids: *lib.get(b"csmGetDrawableIds\0")?,
csm_get_drawable_constant_flags: *lib
.get(b"csmGetDrawableConstantFlags\0")?,
csm_get_drawable_dynamic_flags: *lib
.get(b"csmGetDrawableDynamicFlags\0")?,
csm_get_drawable_blend_modes: *lib.get(b"csmGetDrawableBlendModes\0")?,
csm_get_drawable_texture_indices: *lib
.get(b"csmGetDrawableTextureIndices\0")?,
csm_get_drawable_draw_orders: *lib.get(b"csmGetDrawableDrawOrders\0")?,
csm_get_drawable_opacities: *lib.get(b"csmGetDrawableOpacities\0")?,
csm_get_drawable_mask_counts: *lib.get(b"csmGetDrawableMaskCounts\0")?,
csm_get_drawable_masks: *lib.get(b"csmGetDrawableMasks\0")?,
csm_get_drawable_vertex_counts: *lib
.get(b"csmGetDrawableVertexCounts\0")?,
csm_get_drawable_vertex_positions: *lib
.get(b"csmGetDrawableVertexPositions\0")?,
csm_get_drawable_vertex_uvs: *lib.get(b"csmGetDrawableVertexUvs\0")?,
csm_get_drawable_index_counts: *lib.get(b"csmGetDrawableIndexCounts\0")?,
csm_get_drawable_indices: *lib.get(b"csmGetDrawableIndices\0")?,
csm_get_drawable_multiply_colors: *lib
.get(b"csmGetDrawableMultiplyColors\0")?,
csm_get_drawable_screen_colors: *lib
.get(b"csmGetDrawableScreenColors\0")?,
csm_reset_drawable_dynamic_flags: *lib
.get(b"csmResetDrawableDynamicFlags\0")?,
_libm: libm,
_lib: lib,
})
}
}
/// 查询 Core 版本(返回原始 u32格式 0xMMmmpppp
pub fn version(&self) -> u32 {
unsafe { (self.csm_get_version)() }
}
/// 格式化版本号为 "major.minor.patch"
pub fn version_string(&self) -> String {
let v = self.version();
format!(
"{}.{}.{}",
(v >> 24) & 0xFF,
(v >> 16) & 0xFF,
v & 0xFFFF
)
}
}
/// 分配对齐内存(返回对齐指针和底层分配,底层分配需保持存活)
pub fn alloc_aligned(size: usize, align: usize) -> anyhow::Result<AlignedBuffer> {
let layout = std::alloc::Layout::from_size_align(size, align)?;
let ptr = unsafe { std::alloc::alloc(layout) };
if ptr.is_null() {
anyhow::bail!("aligned alloc failed for size={size} align={align}");
}
Ok(AlignedBuffer { ptr, layout })
}
pub struct AlignedBuffer {
ptr: *mut u8,
layout: std::alloc::Layout,
}
impl AlignedBuffer {
pub fn as_ptr(&self) -> *mut u8 {
self.ptr
}
pub fn as_mut_void(&mut self) -> *mut c_void {
self.ptr as *mut c_void
}
pub fn as_void(&self) -> *const c_void {
self.ptr as *const c_void
}
}
impl Drop for AlignedBuffer {
fn drop(&mut self) {
unsafe { std::alloc::dealloc(self.ptr, self.layout) }
}
}
unsafe impl Send for AlignedBuffer {}
unsafe impl Sync for AlignedBuffer {}
/// 将 C 字符串指针转换为 Rust String空指针返回 None
pub unsafe fn cstr_ptr_to_string(ptr: *const c_char) -> Option<String> {
if ptr.is_null() {
return None;
}
let raw = std::slice::from_raw_parts(ptr as *const u8, libc_strlen(ptr));
Some(String::from_utf8_lossy(raw).into_owned())
}
/// 简易 strlen避免依赖 libc crate
unsafe fn libc_strlen(ptr: *const c_char) -> usize {
let mut p = ptr as *const u8;
let mut n = 0;
while *p != 0 {
p = p.add(1);
n += 1;
}
n
}
// 保留 CString 防止 unused 警告(未来日志函数需要)
#[allow(dead_code)]
fn _ensure_cstring_linked() -> CString {
CString::new("").unwrap()
}