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:
21
.analyze.sh
Normal file
21
.analyze.sh
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 分析下载页面
|
||||||
|
echo "=== Page size ==="
|
||||||
|
wc -c /tmp/dl_page.html
|
||||||
|
|
||||||
|
echo "=== All URLs containing download/zip/native ==="
|
||||||
|
grep -oiE 'https?://[^"'\'' <>]+(download|\.zip|native|cubism)[^"'\'' <>]*' /tmp/dl_page.html | sort -u | head -40
|
||||||
|
|
||||||
|
echo "=== Script sources ==="
|
||||||
|
grep -oiE '<script[^>]*src="[^"]*"' /tmp/dl_page.html | head -20
|
||||||
|
|
||||||
|
echo "=== Data attributes ==="
|
||||||
|
grep -oiE 'data-[a-z]+="[^"]*"' /tmp/dl_page.html | head -20
|
||||||
|
|
||||||
|
echo "=== Form actions ==="
|
||||||
|
grep -oiE '<form[^>]*action="[^"]*"' /tmp/dl_page.html | head -10
|
||||||
|
|
||||||
|
echo "=== Any cubism.live2d URLs ==="
|
||||||
|
grep -oiE 'https?://[a-z.]*live2d[^"'\'' <>]*' /tmp/dl_page.html | sort -u | head -30
|
||||||
|
|
||||||
|
echo "=== DONE ==="
|
||||||
39
.dl_core.sh
Normal file
39
.dl_core.sh
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 尝试下载 Cubism SDK for Native (含 Core 库)
|
||||||
|
cd /tmp
|
||||||
|
echo "=== Try downloading Cubism SDK for Native ==="
|
||||||
|
|
||||||
|
# Live2D 官方下载通常用这种 URL 模式
|
||||||
|
# 先尝试常见的下载链接
|
||||||
|
URLS=(
|
||||||
|
"https://cubism.live2d.com/sdk-native/bin/CubismSdkForNative.zip"
|
||||||
|
"https://www.live2d.com/download/cubism-sdk/download-native/?agree=true"
|
||||||
|
"https://download.live2d.com/cubism-sdk/native/CubismSdkForNative.zip"
|
||||||
|
)
|
||||||
|
|
||||||
|
for url in "${URLS[@]}"; do
|
||||||
|
echo "Trying: $url"
|
||||||
|
wget --timeout=15 --tries=1 -q "$url" -O test_download.bin 2>&1
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
SIZE=$(stat -c%s test_download.bin 2>/dev/null || echo 0)
|
||||||
|
echo " Downloaded: $SIZE bytes"
|
||||||
|
if [ "$SIZE" -gt 100000 ]; then
|
||||||
|
echo " Looks like a real file! Checking type..."
|
||||||
|
file test_download.bin
|
||||||
|
mv test_download.bin CubismSdkForNative.zip
|
||||||
|
echo " SUCCESS: saved as CubismSdkForNative.zip"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " Failed or too small"
|
||||||
|
fi
|
||||||
|
rm -f test_download.bin
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "=== Direct URL attempts failed ==="
|
||||||
|
echo "Checking actual download page for hidden links..."
|
||||||
|
curl -sL "https://www.live2d.com/zh-CHS/sdk/download/native/" 2>/dev/null | grep -oiE 'href="[^"]*\.zip[^"]*"' | head -10
|
||||||
|
echo "---"
|
||||||
|
curl -sL "https://www.live2d.com/download/cubism-sdk/download-native/" 2>/dev/null | grep -oiE 'href="[^"]*download[^"]*"' | head -10
|
||||||
|
|
||||||
|
echo "=== DONE ==="
|
||||||
15
.extract.sh
Normal file
15
.extract.sh
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
cd /tmp
|
||||||
|
echo "=== Extracting SDK ==="
|
||||||
|
unzip -o cubism_sdk_native.zip -d cubism_sdk 2>&1 | tail -5
|
||||||
|
|
||||||
|
echo "=== Finding Core .so files ==="
|
||||||
|
find cubism_sdk -name "libLive2DCubismCore*" -type f 2>/dev/null
|
||||||
|
|
||||||
|
echo "=== Checking arm64 linux .so ==="
|
||||||
|
ls -lh cubism_sdk/Core/dll/experimental/linux/arm64/ 2>/dev/null
|
||||||
|
|
||||||
|
echo "=== File type ==="
|
||||||
|
file cubism_sdk/Core/dll/experimental/linux/arm64/libLive2DCubismCore.so 2>/dev/null
|
||||||
|
|
||||||
|
echo "=== DONE ==="
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,6 +3,7 @@ target/
|
|||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
*~
|
*~
|
||||||
|
extern/
|
||||||
# 模型文件不进 git,只保留下载链接在 model_manager.rs 清单里
|
# 模型文件不进 git,只保留下载链接在 model_manager.rs 清单里
|
||||||
model_download/
|
model_download/
|
||||||
model_store/
|
model_store/
|
||||||
|
|||||||
BIN
.haru.moc3
Normal file
BIN
.haru.moc3
Normal file
Binary file not shown.
115
.haru.model3.json
Normal file
115
.haru.model3.json
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
{
|
||||||
|
"Version": 3,
|
||||||
|
"FileReferences": {
|
||||||
|
"Moc": "Haru.moc3",
|
||||||
|
"Textures": [
|
||||||
|
"Haru.2048/texture_00.png",
|
||||||
|
"Haru.2048/texture_01.png"
|
||||||
|
],
|
||||||
|
"Physics": "Haru.physics3.json",
|
||||||
|
"Pose": "Haru.pose3.json",
|
||||||
|
"DisplayInfo": "Haru.cdi3.json",
|
||||||
|
"Expressions": [
|
||||||
|
{
|
||||||
|
"Name": "F01",
|
||||||
|
"File": "expressions/F01.exp3.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "F02",
|
||||||
|
"File": "expressions/F02.exp3.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "F03",
|
||||||
|
"File": "expressions/F03.exp3.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "F04",
|
||||||
|
"File": "expressions/F04.exp3.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "F05",
|
||||||
|
"File": "expressions/F05.exp3.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "F06",
|
||||||
|
"File": "expressions/F06.exp3.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "F07",
|
||||||
|
"File": "expressions/F07.exp3.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "F08",
|
||||||
|
"File": "expressions/F08.exp3.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Motions": {
|
||||||
|
"Idle": [
|
||||||
|
{
|
||||||
|
"File": "motions/haru_g_idle.motion3.json",
|
||||||
|
"FadeInTime": 0.5,
|
||||||
|
"FadeOutTime": 0.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"File": "motions/haru_g_m15.motion3.json",
|
||||||
|
"FadeInTime": 0.5,
|
||||||
|
"FadeOutTime": 0.5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"TapBody": [
|
||||||
|
{
|
||||||
|
"File": "motions/haru_g_m26.motion3.json",
|
||||||
|
"FadeInTime": 0.5,
|
||||||
|
"FadeOutTime": 0.5,
|
||||||
|
"Sound": "sounds/haru_talk_13.wav"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"File": "motions/haru_g_m06.motion3.json",
|
||||||
|
"FadeInTime": 0.5,
|
||||||
|
"FadeOutTime": 0.5,
|
||||||
|
"Sound": "sounds/haru_Info_14.wav"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"File": "motions/haru_g_m20.motion3.json",
|
||||||
|
"FadeInTime": 0.5,
|
||||||
|
"FadeOutTime": 0.5,
|
||||||
|
"Sound": "sounds/haru_normal_6.wav"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"File": "motions/haru_g_m09.motion3.json",
|
||||||
|
"FadeInTime": 0.5,
|
||||||
|
"FadeOutTime": 0.5,
|
||||||
|
"Sound": "sounds/haru_Info_04.wav"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"UserData": "Haru.userdata3.json"
|
||||||
|
},
|
||||||
|
"Groups": [
|
||||||
|
{
|
||||||
|
"Target": "Parameter",
|
||||||
|
"Name": "EyeBlink",
|
||||||
|
"Ids": [
|
||||||
|
"ParamEyeLOpen",
|
||||||
|
"ParamEyeROpen"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Target": "Parameter",
|
||||||
|
"Name": "LipSync",
|
||||||
|
"Ids": [
|
||||||
|
"ParamMouthOpenY"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"HitAreas": [
|
||||||
|
{
|
||||||
|
"Id": "HitArea",
|
||||||
|
"Name": "Head"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "HitArea2",
|
||||||
|
"Name": "Body"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
23
.ssh_check.sh
Normal file
23
.ssh_check.sh
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
echo "=== DOWNLOAD TOOLS ==="
|
||||||
|
which wget 2>/dev/null && echo "wget: yes" || echo "wget: no"
|
||||||
|
which curl 2>/dev/null && echo "curl: yes" || echo "curl: no"
|
||||||
|
|
||||||
|
echo "=== OPENGL/EGL LIBS ==="
|
||||||
|
ls /usr/lib/aarch64-linux-gnu/libEGL* 2>/dev/null || echo "no EGL lib"
|
||||||
|
ls /usr/lib/aarch64-linux-gnu/libGLESv2* 2>/dev/null || echo "no GLES lib"
|
||||||
|
ls /usr/lib/aarch64-linux-gnu/libGL* 2>/dev/null || echo "no GL lib"
|
||||||
|
|
||||||
|
echo "=== PKG CONFIG ==="
|
||||||
|
pkg-config --list-all 2>/dev/null | grep -iE 'egl|gles|gl' || echo "no pkg-config gl"
|
||||||
|
|
||||||
|
echo "=== BUILD TOOLS ==="
|
||||||
|
gcc --version 2>/dev/null | head -1 || echo "no gcc"
|
||||||
|
g++ --version 2>/dev/null | head -1 || echo "no g++"
|
||||||
|
cmake --version 2>/dev/null | head -1 || echo "no cmake"
|
||||||
|
|
||||||
|
echo "=== RUST ==="
|
||||||
|
ls ~/.rustup/toolchains/*/bin/rustc 2>/dev/null | head -1
|
||||||
|
~/.rustup/toolchains/stable-aarch64-unknown-linux-gnu/bin/rustc --version 2>/dev/null
|
||||||
|
|
||||||
|
echo "=== DONE ==="
|
||||||
148
src/plugins/live2d/animation.rs
Normal file
148
src/plugins/live2d/animation.rs
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
//! 动画系统:眨眼、嘴部动效、待机呼吸
|
||||||
|
|
||||||
|
use super::core_ffi::CubismCore;
|
||||||
|
use super::model::CubismModel;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
/// 动画状态
|
||||||
|
pub struct AnimationState {
|
||||||
|
/// 眨眼定时器
|
||||||
|
last_blink: Instant,
|
||||||
|
next_blink_interval: Duration,
|
||||||
|
blink_phase: BlinkPhase,
|
||||||
|
/// 嘴部状态
|
||||||
|
talking: bool,
|
||||||
|
mouth_open: f32,
|
||||||
|
mouth_target: f32,
|
||||||
|
last_mouth_update: Instant,
|
||||||
|
/// 呼吸
|
||||||
|
breath_phase: f32,
|
||||||
|
/// 身体摇摆
|
||||||
|
body_phase: f32,
|
||||||
|
/// 眼球跟随(简化为固定值)
|
||||||
|
eye_ball_x: f32,
|
||||||
|
eye_ball_y: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
enum BlinkPhase {
|
||||||
|
Idle,
|
||||||
|
Closing(f32), // 0→1
|
||||||
|
Opening(f32), // 1→0
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AnimationState {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
last_blink: Instant::now(),
|
||||||
|
next_blink_interval: Duration::from_secs(3),
|
||||||
|
blink_phase: BlinkPhase::Idle,
|
||||||
|
talking: false,
|
||||||
|
mouth_open: 0.0,
|
||||||
|
mouth_target: 0.0,
|
||||||
|
last_mouth_update: Instant::now(),
|
||||||
|
breath_phase: 0.0,
|
||||||
|
body_phase: 0.0,
|
||||||
|
eye_ball_x: 0.0,
|
||||||
|
eye_ball_y: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AnimationState {
|
||||||
|
pub fn set_talking(&mut self, talking: bool) {
|
||||||
|
self.talking = talking;
|
||||||
|
if !talking {
|
||||||
|
self.mouth_target = 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 更新动画并应用到 model 参数
|
||||||
|
pub fn update(&mut self, core: &CubismCore, model: &CubismModel, now: Instant, dt: f32) {
|
||||||
|
// === 呼吸 ===
|
||||||
|
self.breath_phase += dt * 0.5; // 0.5 Hz
|
||||||
|
let breath = self.breath_phase.sin() * 0.5 + 0.5;
|
||||||
|
model.set_param(core, "ParamBreath", breath);
|
||||||
|
|
||||||
|
// === 身体摇摆 ===
|
||||||
|
self.body_phase += dt * 0.3;
|
||||||
|
let body_x = self.body_phase.sin() * 8.0;
|
||||||
|
let body_y = self.body_phase.sin() * 0.5;
|
||||||
|
model.set_param(core, "ParamBodyAngleX", body_x);
|
||||||
|
model.set_param(core, "ParamBodyAngleY", body_y);
|
||||||
|
|
||||||
|
// === 眨眼 ===
|
||||||
|
self.update_blink(now, core, model);
|
||||||
|
|
||||||
|
// === 嘴部 ===
|
||||||
|
self.update_mouth(now, dt, core, model);
|
||||||
|
|
||||||
|
// === 眼球(轻微随机移动)===
|
||||||
|
self.eye_ball_x = (now.elapsed().as_secs_f32() * 0.3).sin() * 0.3;
|
||||||
|
self.eye_ball_y = (now.elapsed().as_secs_f32() * 0.2).cos() * 0.2;
|
||||||
|
model.set_param(core, "ParamEyeBallX", self.eye_ball_x);
|
||||||
|
model.set_param(core, "ParamEyeBallY", self.eye_ball_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_blink(&mut self, now: Instant, core: &CubismCore, model: &CubismModel) {
|
||||||
|
match &mut self.blink_phase {
|
||||||
|
BlinkPhase::Idle => {
|
||||||
|
if now.duration_since(self.last_blink) >= self.next_blink_interval {
|
||||||
|
self.blink_phase = BlinkPhase::Closing(0.0);
|
||||||
|
self.last_blink = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BlinkPhase::Closing(t) => {
|
||||||
|
*t += 0.15; // 闭眼速度
|
||||||
|
if *t >= 1.0 {
|
||||||
|
*t = 1.0;
|
||||||
|
self.blink_phase = BlinkPhase::Opening(1.0);
|
||||||
|
}
|
||||||
|
let eye_open = 1.0 - *t;
|
||||||
|
model.set_param(core, "ParamEyeLOpen", eye_open);
|
||||||
|
model.set_param(core, "ParamEyeROpen", eye_open);
|
||||||
|
}
|
||||||
|
BlinkPhase::Opening(t) => {
|
||||||
|
*t -= 0.1; // 睁眼速度(比闭眼慢)
|
||||||
|
if *t <= 0.0 {
|
||||||
|
*t = 0.0;
|
||||||
|
self.blink_phase = BlinkPhase::Idle;
|
||||||
|
self.last_blink = now;
|
||||||
|
// 下次眨眼间隔(2-5 秒随机)
|
||||||
|
self.next_blink_interval =
|
||||||
|
Duration::from_secs(2 + (now.elapsed().as_nanos() % 3) as u64);
|
||||||
|
}
|
||||||
|
let eye_open = 1.0 - *t;
|
||||||
|
model.set_param(core, "ParamEyeLOpen", eye_open);
|
||||||
|
model.set_param(core, "ParamEyeROpen", eye_open);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_mouth(
|
||||||
|
&mut self,
|
||||||
|
now: Instant,
|
||||||
|
dt: f32,
|
||||||
|
core: &CubismCore,
|
||||||
|
model: &CubismModel,
|
||||||
|
) {
|
||||||
|
if self.talking {
|
||||||
|
// 说话时嘴部快速开合
|
||||||
|
let t = now.elapsed().as_secs_f32();
|
||||||
|
// 用多个正弦叠加产生自然的变化
|
||||||
|
let base = (t * 12.0).sin() * 0.4 + (t * 7.0).sin() * 0.3 + (t * 19.0).sin() * 0.2;
|
||||||
|
self.mouth_target = (base * 0.5 + 0.5).clamp(0.0, 1.0) * 0.9 + 0.1;
|
||||||
|
} else {
|
||||||
|
self.mouth_target = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 平滑过渡
|
||||||
|
let lerp_speed = 15.0 * dt;
|
||||||
|
self.mouth_open += (self.mouth_target - self.mouth_open) * lerp_speed.min(1.0);
|
||||||
|
model.set_param(core, "ParamMouthOpenY", self.mouth_open);
|
||||||
|
|
||||||
|
// 嘴部形态也轻微变化
|
||||||
|
let form = (now.elapsed().as_secs_f32() * 3.0).sin() * 0.3 * if self.talking { 1.0 } else { 0.0 };
|
||||||
|
model.set_param(core, "ParamMouthForm", form);
|
||||||
|
}
|
||||||
|
}
|
||||||
155
src/plugins/live2d/assets.rs
Normal file
155
src/plugins/live2d/assets.rs
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
//! 资源管理:model3.json 解析 + PNG 纹理加载
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Context, Result};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
/// model3.json 中的 Groups 条目
|
||||||
|
#[derive(Deserialize, Clone, Debug)]
|
||||||
|
pub struct ModelGroup {
|
||||||
|
#[serde(rename = "Target")]
|
||||||
|
pub target: String,
|
||||||
|
#[serde(rename = "Name")]
|
||||||
|
pub name: String,
|
||||||
|
#[serde(rename = "Ids")]
|
||||||
|
pub ids: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// model3.json 文件引用
|
||||||
|
#[derive(Deserialize, Clone, Debug)]
|
||||||
|
pub struct FileReferences {
|
||||||
|
#[serde(rename = "Moc")]
|
||||||
|
pub moc: String,
|
||||||
|
#[serde(rename = "Textures", default)]
|
||||||
|
pub textures: Vec<String>,
|
||||||
|
#[serde(rename = "Physics", default)]
|
||||||
|
pub physics: Option<String>,
|
||||||
|
#[serde(rename = "Pose", default)]
|
||||||
|
pub pose: Option<String>,
|
||||||
|
#[serde(rename = "DisplayInfo", default)]
|
||||||
|
pub display_info: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// model3.json 根结构
|
||||||
|
#[derive(Deserialize, Clone, Debug)]
|
||||||
|
pub struct Model3Json {
|
||||||
|
#[serde(rename = "Version")]
|
||||||
|
pub version: i32,
|
||||||
|
#[serde(rename = "FileReferences")]
|
||||||
|
pub file_references: FileReferences,
|
||||||
|
#[serde(rename = "Groups", default)]
|
||||||
|
pub groups: Vec<ModelGroup>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Model3Json {
|
||||||
|
/// 从文件解析
|
||||||
|
pub fn from_path(path: &Path) -> Result<Self> {
|
||||||
|
let text = std::fs::read_to_string(path)
|
||||||
|
.with_context(|| format!("读取 model3.json 失败: {}", path.display()))?;
|
||||||
|
let json: Model3Json = serde_json::from_str(&text)
|
||||||
|
.with_context(|| format!("解析 model3.json 失败: {}", path.display()))?;
|
||||||
|
Ok(json)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取 LipSync 参数 ID 列表
|
||||||
|
pub fn lip_sync_ids(&self) -> Vec<String> {
|
||||||
|
self.groups
|
||||||
|
.iter()
|
||||||
|
.find(|g| g.target == "Parameter" && g.name == "LipSync")
|
||||||
|
.map(|g| g.ids.clone())
|
||||||
|
.unwrap_or_else(|| vec!["ParamMouthOpenY".to_string()])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取 EyeBlink 参数 ID 列表
|
||||||
|
pub fn eye_blink_ids(&self) -> Vec<String> {
|
||||||
|
self.groups
|
||||||
|
.iter()
|
||||||
|
.find(|g| g.target == "Parameter" && g.name == "EyeBlink")
|
||||||
|
.map(|g| g.ids.clone())
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
vec!["ParamEyeLOpen".to_string(), "ParamEyeROpen".to_string()]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 加载的纹理图像(RGBA)
|
||||||
|
pub struct TextureImage {
|
||||||
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
pub rgba: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 加载 PNG 纹理(使用 image crate)
|
||||||
|
pub fn load_texture(path: &Path) -> Result<TextureImage> {
|
||||||
|
let img = image::open(path)
|
||||||
|
.with_context(|| format!("加载纹理失败: {}", path.display()))?
|
||||||
|
.to_rgba8();
|
||||||
|
let width = img.width();
|
||||||
|
let height = img.height();
|
||||||
|
let rgba = img.into_raw();
|
||||||
|
Ok(TextureImage {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
rgba,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 加载 model3.json 引用的所有纹理(返回按 texture_00, texture_01 顺序的列表)
|
||||||
|
pub fn load_all_textures(
|
||||||
|
model3_dir: &Path,
|
||||||
|
file_refs: &FileReferences,
|
||||||
|
) -> Result<Vec<TextureImage>> {
|
||||||
|
let mut textures = Vec::new();
|
||||||
|
for tex_rel in &file_refs.textures {
|
||||||
|
let tex_path = model3_dir.join(tex_rel);
|
||||||
|
let img = load_texture(&tex_path)?;
|
||||||
|
println!(
|
||||||
|
"[Live2D] 纹理加载: {} ({}x{})",
|
||||||
|
tex_rel, img.width, img.height
|
||||||
|
);
|
||||||
|
textures.push(img);
|
||||||
|
}
|
||||||
|
Ok(textures)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 解析 model3.json 并返回完整路径集
|
||||||
|
pub struct ModelAssetPaths {
|
||||||
|
pub model3_json: PathBuf,
|
||||||
|
pub model3_dir: PathBuf,
|
||||||
|
pub moc3: PathBuf,
|
||||||
|
pub textures: Vec<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 从 model3.json 相对路径(如 "haru/Haru.model3.json")解析资源路径
|
||||||
|
pub fn resolve_asset_paths(
|
||||||
|
live2d_base_dir: &Path,
|
||||||
|
model3_json_rel: &str,
|
||||||
|
) -> Result<ModelAssetPaths> {
|
||||||
|
let model3_json = live2d_base_dir.join(model3_json_rel);
|
||||||
|
if !model3_json.exists() {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"model3.json 不存在: {}",
|
||||||
|
model3_json.display()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let model3_dir = model3_json
|
||||||
|
.parent()
|
||||||
|
.ok_or_else(|| anyhow!("无法获取 model3.json 的父目录"))?
|
||||||
|
.to_path_buf();
|
||||||
|
|
||||||
|
let model3 = Model3Json::from_path(&model3_json)?;
|
||||||
|
let moc3 = model3_dir.join(&model3.file_references.moc);
|
||||||
|
let textures = model3
|
||||||
|
.file_references
|
||||||
|
.textures
|
||||||
|
.iter()
|
||||||
|
.map(|t| model3_dir.join(t))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(ModelAssetPaths {
|
||||||
|
model3_json,
|
||||||
|
model3_dir,
|
||||||
|
moc3,
|
||||||
|
textures,
|
||||||
|
})
|
||||||
|
}
|
||||||
276
src/plugins/live2d/core_ffi.rs
Normal file
276
src/plugins/live2d/core_ffi.rs
Normal 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.so(Core 依赖 powf 但 NEEDED 未声明)。
|
||||||
|
pub fn load(so_path: &str) -> anyhow::Result<Self> {
|
||||||
|
// 预加载 libm,RTLD_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()
|
||||||
|
}
|
||||||
736
src/plugins/live2d/gl.rs
Normal file
736
src/plugins/live2d/gl.rs
Normal file
@@ -0,0 +1,736 @@
|
|||||||
|
//! OpenGL ES 2.0 / EGL 渲染后端
|
||||||
|
//!
|
||||||
|
//! 使用 EGL 创建离屏 PBuffer 上下文(无窗口),用 OpenGL ES 2.0 渲染 Live2D 模型。
|
||||||
|
//! 渲染结果通过 glReadPixels 读取,交给 video 插件或直接显示。
|
||||||
|
//!
|
||||||
|
//! 这里采用 X11 窗口方式渲染(设备有 X11 + kwin),直接显示到屏幕。
|
||||||
|
//! 如需离屏渲染可改为 PBuffer。
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Context, Result};
|
||||||
|
use std::ffi::c_void;
|
||||||
|
use std::os::raw::{c_char, c_int, c_uint, c_void as c_void_raw};
|
||||||
|
|
||||||
|
// ============ EGL 类型与常量 ============
|
||||||
|
|
||||||
|
pub mod egl {
|
||||||
|
pub const EGL_VERSION_1_5: c_int = 1;
|
||||||
|
|
||||||
|
pub const EGL_SURFACE_TYPE: c_int = 0x3033;
|
||||||
|
pub const EGL_WINDOW_BIT: c_int = 0x0004;
|
||||||
|
pub const EGL_PBUFFER_BIT: c_int = 0x0001;
|
||||||
|
pub const EGL_BLUE_SIZE: c_int = 0x3022;
|
||||||
|
pub const EGL_GREEN_SIZE: c_int = 0x3023;
|
||||||
|
pub const EGL_RED_SIZE: c_int = 0x3024;
|
||||||
|
pub const EGL_DEPTH_SIZE: c_int = 0x3025;
|
||||||
|
pub const EGL_ALPHA_SIZE: c_int = 0x3021;
|
||||||
|
pub const EGL_STENCIL_SIZE: c_int = 0x3026;
|
||||||
|
pub const EGL_RENDERABLE_TYPE: c_int = 0x3040;
|
||||||
|
pub const EGL_OPENGL_ES2_BIT: c_int = 0x0004;
|
||||||
|
pub const EGL_NONE: c_int = 0x3038;
|
||||||
|
pub const EGL_CONTEXT_CLIENT_VERSION: c_int = 0x3098;
|
||||||
|
|
||||||
|
pub const EGL_DISPLAY: usize = 0;
|
||||||
|
pub const EGL_NO_DISPLAY: isize = 0;
|
||||||
|
pub const EGL_NO_CONTEXT: isize = 0;
|
||||||
|
pub const EGL_NO_SURFACE: isize = 0;
|
||||||
|
|
||||||
|
pub type EGLDisplay = *mut c_void;
|
||||||
|
pub type EGLConfig = *mut c_void;
|
||||||
|
pub type EGLContext = *mut c_void;
|
||||||
|
pub type EGLSurface = *mut c_void;
|
||||||
|
pub type NativeDisplayType = *mut c_void;
|
||||||
|
pub type NativeWindowType = *mut c_void;
|
||||||
|
pub type EGLBoolean = c_uint;
|
||||||
|
pub type EGLint = c_int;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ X11 类型 ============
|
||||||
|
|
||||||
|
pub mod x11 {
|
||||||
|
use std::os::raw::{c_long, c_uint, c_void};
|
||||||
|
|
||||||
|
pub type Display = c_void;
|
||||||
|
pub type Window = c_long;
|
||||||
|
pub type XID = c_long;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct XSetWindowAttributes {
|
||||||
|
pub background_pixmap: c_long,
|
||||||
|
pub background_pixel: c_long,
|
||||||
|
pub border_pixmap: c_long,
|
||||||
|
pub border_pixel: c_long,
|
||||||
|
pub bit_gravity: c_int,
|
||||||
|
pub win_gravity: c_int,
|
||||||
|
pub backing_store: c_int,
|
||||||
|
pub backing_planes: c_long,
|
||||||
|
pub backing_pixel: c_long,
|
||||||
|
pub save_under: c_int,
|
||||||
|
pub event_mask: c_long,
|
||||||
|
pub do_not_propagate_mask: c_long,
|
||||||
|
pub override_redirect: c_int,
|
||||||
|
pub colormap: c_long,
|
||||||
|
pub cursor: c_long,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const CWOverrideRedirect: c_long = 1 << 9;
|
||||||
|
pub const CWEventMask: c_long = 1 << 11;
|
||||||
|
pub const ExposureMask: c_long = 1 << 15;
|
||||||
|
pub const StructureNotifyMask: c_long = 1 << 17;
|
||||||
|
pub const KeyPressMask: c_long = 1 << 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ GLES2 类型与常量 ============
|
||||||
|
|
||||||
|
pub mod gl {
|
||||||
|
use std::os::raw::{c_int, c_uint};
|
||||||
|
|
||||||
|
pub type GLenum = c_uint;
|
||||||
|
pub type GLuint = c_uint;
|
||||||
|
pub type GLint = c_int;
|
||||||
|
pub type GLsizei = c_int;
|
||||||
|
pub type GLbitfield = c_uint;
|
||||||
|
pub type GLfloat = f32;
|
||||||
|
|
||||||
|
pub const GL_FLOAT: GLenum = 0x1406;
|
||||||
|
pub const GL_FLOAT_VEC2: GLenum = 0x8B50;
|
||||||
|
pub const GL_FLOAT_VEC4: GLenum = 0x8B52;
|
||||||
|
pub const GL_FLOAT_MAT4: GLenum = 0x8B5C;
|
||||||
|
pub const GL_SAMPLER_2D: GLenum = 0x8B5E;
|
||||||
|
|
||||||
|
pub const GL_ARRAY_BUFFER: GLenum = 0x8892;
|
||||||
|
pub const GL_ELEMENT_ARRAY_BUFFER: GLenum = 0x8893;
|
||||||
|
|
||||||
|
pub const GL_TEXTURE_2D: GLenum = 0x0DE1;
|
||||||
|
pub const GL_TEXTURE0: GLenum = 0x84C0;
|
||||||
|
pub const GL_RGBA: GLenum = 0x1908;
|
||||||
|
pub const GL_UNSIGNED_BYTE: GLenum = 0x1401;
|
||||||
|
pub const GL_UNSIGNED_SHORT: GLenum = 0x1403;
|
||||||
|
pub const GL_NEAREST: GLenum = 0x2600;
|
||||||
|
pub const GL_LINEAR: GLenum = 0x2601;
|
||||||
|
pub const GL_CLAMP_TO_EDGE: GLenum = 0x812F;
|
||||||
|
pub const GL_TEXTURE_MIN_FILTER: GLenum = 0x2801;
|
||||||
|
pub const GL_TEXTURE_MAG_FILTER: GLenum = 0x2800;
|
||||||
|
pub const GL_TEXTURE_WRAP_S: GLenum = 0x2802;
|
||||||
|
pub const GL_TEXTURE_WRAP_T: GLenum = 0x2803;
|
||||||
|
|
||||||
|
pub const GL_VERTEX_SHADER: GLenum = 0x8B31;
|
||||||
|
pub const GL_FRAGMENT_SHADER: GLenum = 0x8B30;
|
||||||
|
pub const GL_COMPILE_STATUS: GLenum = 0x8B81;
|
||||||
|
pub const GL_LINK_STATUS: GLenum = 0x8B82;
|
||||||
|
|
||||||
|
pub const GL_TRIANGLES: GLenum = 0x0004;
|
||||||
|
pub const GL_BLEND: GLenum = 0x0BE2;
|
||||||
|
pub const GL_SRC_ALPHA: GLenum = 0x0302;
|
||||||
|
pub const GL_ONE_MINUS_SRC_ALPHA: GLenum = 0x0303;
|
||||||
|
pub const GL_ONE: GLenum = 1;
|
||||||
|
pub const GL_ZERO: GLenum = 0;
|
||||||
|
pub const GL_COLOR_BUFFER_BIT: GLbitfield = 0x4000;
|
||||||
|
pub const GL_DEPTH_BUFFER_BIT: GLbitfield = 0x0100;
|
||||||
|
pub const GL_BLEND_DST_RGB: GLenum = 0x80C8;
|
||||||
|
pub const GL_BLEND_SRC_RGB: GLenum = 0x80C9;
|
||||||
|
pub const GL_BLEND_DST_ALPHA: GLenum = 0x80CA;
|
||||||
|
pub const GL_BLEND_SRC_ALPHA: GLenum = 0x80CB;
|
||||||
|
pub const GL_BLEND_EQUATION: GLenum = 0x8009;
|
||||||
|
pub const GL_FUNC_ADD: GLenum = 0x8006;
|
||||||
|
pub const GL_FUNC_REVERSE_SUBTRACT: GLenum = 0x800B;
|
||||||
|
pub const GL_STATIC_DRAW: GLenum = 0x88E4;
|
||||||
|
pub const GL_FALSE: GLenum = 0;
|
||||||
|
pub const GL_TRUE: GLenum = 1;
|
||||||
|
pub const GL_NO_ERROR: GLenum = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 着色器源码(标准模式,支持 mask 和非 mask)
|
||||||
|
pub const VERT_SHADER_SRC: &str = r#"#version 100
|
||||||
|
attribute vec4 a_position;
|
||||||
|
attribute vec2 a_texCoord;
|
||||||
|
varying vec2 v_texCoord;
|
||||||
|
uniform mat4 u_matrix;
|
||||||
|
void main() {
|
||||||
|
gl_Position = u_matrix * a_position;
|
||||||
|
v_texCoord = a_texCoord;
|
||||||
|
v_texCoord.y = 1.0 - v_texCoord.y;
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
pub const FRAG_SHADER_SRC: &str = r#"#version 100
|
||||||
|
precision highp float;
|
||||||
|
varying vec2 v_texCoord;
|
||||||
|
uniform sampler2D s_texture0;
|
||||||
|
uniform vec4 u_baseColor;
|
||||||
|
uniform vec4 u_multiplyColor;
|
||||||
|
uniform vec4 u_screenColor;
|
||||||
|
void main() {
|
||||||
|
vec4 texColor = texture2D(s_texture0, v_texCoord);
|
||||||
|
texColor.rgb = texColor.rgb * u_multiplyColor.rgb;
|
||||||
|
texColor.rgb = texColor.rgb + u_screenColor.rgb - (texColor.rgb * u_screenColor.rgb);
|
||||||
|
vec4 color = texColor * u_baseColor;
|
||||||
|
gl_FragColor = vec4(color.rgb * color.a, color.a);
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
/// OpenGL ES2 / EGL 动态库函数集合
|
||||||
|
pub struct Gl {
|
||||||
|
_lib_egl: libloading::Library,
|
||||||
|
_lib_gl: libloading::Library,
|
||||||
|
_lib_x11: libloading::Library,
|
||||||
|
// EGL
|
||||||
|
egl_get_display:
|
||||||
|
unsafe extern "C" fn(disp: egl::NativeDisplayType) -> egl::EGLDisplay,
|
||||||
|
egl_initialize:
|
||||||
|
unsafe extern "C" fn(dpy: egl::EGLDisplay, major: *mut c_int, minor: *mut c_int) -> egl::EGLBoolean,
|
||||||
|
egl_choose_config: unsafe extern "C" fn(
|
||||||
|
dpy: egl::EGLDisplay,
|
||||||
|
attrib_list: *const egl::EGLint,
|
||||||
|
configs: *mut egl::EGLConfig,
|
||||||
|
config_size: egl::EGLint,
|
||||||
|
num_config: *mut egl::EGLint,
|
||||||
|
) -> egl::EGLBoolean,
|
||||||
|
egl_create_window_surface: unsafe extern "C" fn(
|
||||||
|
dpy: egl::EGLDisplay,
|
||||||
|
config: egl::EGLConfig,
|
||||||
|
win: egl::NativeWindowType,
|
||||||
|
attrib_list: *const egl::EGLint,
|
||||||
|
) -> egl::EGLSurface,
|
||||||
|
egl_create_context: unsafe extern "C" fn(
|
||||||
|
dpy: egl::EGLDisplay,
|
||||||
|
config: egl::EGLConfig,
|
||||||
|
share_ctx: egl::EGLContext,
|
||||||
|
attrib_list: *const egl::EGLint,
|
||||||
|
) -> egl::EGLContext,
|
||||||
|
egl_make_current: unsafe extern "C" fn(
|
||||||
|
dpy: egl::EGLDisplay,
|
||||||
|
draw: egl::EGLSurface,
|
||||||
|
read: egl::EGLSurface,
|
||||||
|
ctx: egl::EGLContext,
|
||||||
|
) -> egl::EGLBoolean,
|
||||||
|
egl_swap_buffers:
|
||||||
|
unsafe extern "C" fn(dpy: egl::EGLDisplay, surface: egl::EGLSurface) -> egl::EGLBoolean,
|
||||||
|
egl_terminate: unsafe extern "C" fn(dpy: egl::EGLDisplay) -> egl::EGLBoolean,
|
||||||
|
|
||||||
|
// GLES2
|
||||||
|
gl_clear: unsafe extern "C" fn(mask: gl::GLbitfield),
|
||||||
|
gl_clear_color: unsafe extern "C" fn(r: f32, g: f32, b: f32, a: f32),
|
||||||
|
gl_enable: unsafe extern "C" fn(cap: gl::GLenum),
|
||||||
|
gl_disable: unsafe extern "C" fn(cap: gl::GLenum),
|
||||||
|
gl_blend_func: unsafe extern "C" fn(sfactor: gl::GLenum, dfactor: gl::GLenum),
|
||||||
|
gl_blend_func_separate: unsafe extern "C" fn(
|
||||||
|
src_rgb: gl::GLenum,
|
||||||
|
dst_rgb: gl::GLenum,
|
||||||
|
src_alpha: gl::GLenum,
|
||||||
|
dst_alpha: gl::GLenum,
|
||||||
|
),
|
||||||
|
gl_viewport: unsafe extern "C" fn(x: gl::GLint, y: gl::GLint, w: gl::GLsizei, h: gl::GLsizei),
|
||||||
|
gl_create_shader: unsafe extern "C" fn(t: gl::GLenum) -> gl::GLuint,
|
||||||
|
gl_shader_source: unsafe extern "C" fn(
|
||||||
|
shader: gl::GLuint,
|
||||||
|
count: gl::GLsizei,
|
||||||
|
string: *const *const c_char,
|
||||||
|
length: *const gl::GLint,
|
||||||
|
),
|
||||||
|
gl_compile_shader: unsafe extern "C" fn(shader: gl::GLuint),
|
||||||
|
gl_get_shaderiv: unsafe extern "C" fn(
|
||||||
|
shader: gl::GLuint,
|
||||||
|
pname: gl::GLenum,
|
||||||
|
params: *mut gl::GLint,
|
||||||
|
),
|
||||||
|
gl_get_shader_info_log: unsafe extern "C" fn(
|
||||||
|
shader: gl::GLuint,
|
||||||
|
buf_size: gl::GLsizei,
|
||||||
|
length: *mut gl::GLsizei,
|
||||||
|
info_log: *mut c_char,
|
||||||
|
),
|
||||||
|
gl_create_program: unsafe extern "C" fn() -> gl::GLuint,
|
||||||
|
gl_attach_shader: unsafe extern "C" fn(program: gl::GLuint, shader: gl::GLuint),
|
||||||
|
gl_link_program: unsafe extern "C" fn(program: gl::GLuint),
|
||||||
|
gl_get_programiv: unsafe extern "C" fn(
|
||||||
|
program: gl::GLuint,
|
||||||
|
pname: gl::GLenum,
|
||||||
|
params: *mut gl::GLint,
|
||||||
|
),
|
||||||
|
gl_get_program_info_log: unsafe extern "C" fn(
|
||||||
|
program: gl::GLuint,
|
||||||
|
buf_size: gl::GLsizei,
|
||||||
|
length: *mut gl::GLsizei,
|
||||||
|
info_log: *mut c_char,
|
||||||
|
),
|
||||||
|
gl_use_program: unsafe extern "C" fn(program: gl::GLuint),
|
||||||
|
gl_get_attrib_location:
|
||||||
|
unsafe extern "C" fn(program: gl::GLuint, name: *const c_char) -> gl::GLint,
|
||||||
|
gl_get_uniform_location:
|
||||||
|
unsafe extern "C" fn(program: gl::GLuint, name: *const c_char) -> gl::GLint,
|
||||||
|
gl_uniform_matrix4fv: unsafe extern "C" fn(
|
||||||
|
location: gl::GLint,
|
||||||
|
count: gl::GLsizei,
|
||||||
|
transpose: gl::GLenum,
|
||||||
|
value: *const f32,
|
||||||
|
),
|
||||||
|
gl_uniform1i: unsafe extern "C" fn(location: gl::GLint, v: gl::GLint),
|
||||||
|
gl_uniform4f:
|
||||||
|
unsafe extern "C" fn(location: gl::GLint, x: f32, y: f32, z: f32, w: f32),
|
||||||
|
gl_gen_textures: unsafe extern "C" fn(n: gl::GLsizei, textures: *mut gl::GLuint),
|
||||||
|
gl_bind_texture: unsafe extern "C" fn(target: gl::GLenum, texture: gl::GLuint),
|
||||||
|
gl_tex_parameteri:
|
||||||
|
unsafe extern "C" fn(target: gl::GLenum, pname: gl::GLenum, param: gl::GLint),
|
||||||
|
gl_tex_image_2d: unsafe extern "C" fn(
|
||||||
|
target: gl::GLenum,
|
||||||
|
level: gl::GLint,
|
||||||
|
internalformat: gl::GLenum,
|
||||||
|
width: gl::GLsizei,
|
||||||
|
height: gl::GLsizei,
|
||||||
|
border: gl::GLint,
|
||||||
|
format: gl::GLenum,
|
||||||
|
type_: gl::GLenum,
|
||||||
|
data: *const c_void_raw,
|
||||||
|
),
|
||||||
|
gl_active_texture: unsafe extern "C" fn(texture: gl::GLenum),
|
||||||
|
gl_gen_buffers: unsafe extern "C" fn(n: gl::GLsizei, buffers: *mut gl::GLuint),
|
||||||
|
gl_bind_buffer: unsafe extern "C" fn(target: gl::GLenum, buffer: gl::GLuint),
|
||||||
|
gl_buffer_data: unsafe extern "C" fn(
|
||||||
|
target: gl::GLenum,
|
||||||
|
size: isize,
|
||||||
|
data: *const c_void_raw,
|
||||||
|
usage: gl::GLenum,
|
||||||
|
),
|
||||||
|
gl_enable_vertex_attrib_array: unsafe extern "C" fn(index: gl::GLuint),
|
||||||
|
gl_vertex_attrib_pointer: unsafe extern "C" fn(
|
||||||
|
index: gl::GLuint,
|
||||||
|
size: gl::GLint,
|
||||||
|
type_: gl::GLenum,
|
||||||
|
normalized: gl::GLenum,
|
||||||
|
stride: gl::GLsizei,
|
||||||
|
pointer: *const c_void_raw,
|
||||||
|
),
|
||||||
|
gl_draw_elements: unsafe extern "C" fn(
|
||||||
|
mode: gl::GLenum,
|
||||||
|
count: gl::GLsizei,
|
||||||
|
type_: gl::GLenum,
|
||||||
|
indices: *const c_void_raw,
|
||||||
|
),
|
||||||
|
gl_get_error: unsafe extern "C" fn() -> gl::GLenum,
|
||||||
|
gl_delete_textures: unsafe extern "C" fn(n: gl::GLsizei, textures: *const gl::GLuint),
|
||||||
|
gl_delete_buffers: unsafe extern "C" fn(n: gl::GLsizei, buffers: *const gl::GLuint),
|
||||||
|
|
||||||
|
// X11
|
||||||
|
x_open_display:
|
||||||
|
unsafe extern "C" fn(name: *const c_char) -> *mut x11::Display,
|
||||||
|
x_close_display: unsafe extern "C" fn(disp: *mut x11::Display) -> c_int,
|
||||||
|
x_create_window: unsafe extern "C" fn(
|
||||||
|
disp: *mut x11::Display,
|
||||||
|
parent: x11::Window,
|
||||||
|
x: c_int,
|
||||||
|
y: c_int,
|
||||||
|
width: c_uint,
|
||||||
|
height: c_uint,
|
||||||
|
border_width: c_uint,
|
||||||
|
depth: c_int,
|
||||||
|
class: c_int,
|
||||||
|
visual: *mut c_void,
|
||||||
|
valuemask: c_long,
|
||||||
|
attributes: *mut x11::XSetWindowAttributes,
|
||||||
|
) -> x11::Window,
|
||||||
|
x_map_window: unsafe extern "C" fn(disp: *mut x11::Display, w: x11::Window) -> c_int,
|
||||||
|
x_store_name:
|
||||||
|
unsafe extern "C" fn(disp: *mut x11::Display, w: x11::Window, name: *const c_char) -> c_int,
|
||||||
|
x_flush: unsafe extern "C" fn(disp: *mut x11::Display) -> c_int,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Gl {
|
||||||
|
/// 加载所有动态库函数
|
||||||
|
pub fn load() -> Result<Self> {
|
||||||
|
let lib_egl = unsafe { libloading::Library::new("libEGL.so.1")? };
|
||||||
|
let lib_gl = unsafe { libloading::Library::new("libGLESv2.so.2")? };
|
||||||
|
let lib_x11 = unsafe { libloading::Library::new("libX11.so.6")? };
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
Ok(Self {
|
||||||
|
egl_get_display: *lib_egl.get(b"eglGetDisplay\0")?,
|
||||||
|
egl_initialize: *lib_egl.get(b"eglInitialize\0")?,
|
||||||
|
egl_choose_config: *lib_egl.get(b"eglChooseConfig\0")?,
|
||||||
|
egl_create_window_surface: *lib_egl.get(b"eglCreateWindowSurface\0")?,
|
||||||
|
egl_create_context: *lib_egl.get(b"eglCreateContext\0")?,
|
||||||
|
egl_make_current: *lib_egl.get(b"eglMakeCurrent\0")?,
|
||||||
|
egl_swap_buffers: *lib_egl.get(b"eglSwapBuffers\0")?,
|
||||||
|
egl_terminate: *lib_egl.get(b"eglTerminate\0")?,
|
||||||
|
|
||||||
|
gl_clear: *lib_gl.get(b"glClear\0")?,
|
||||||
|
gl_clear_color: *lib_gl.get(b"glClearColor\0")?,
|
||||||
|
gl_enable: *lib_gl.get(b"glEnable\0")?,
|
||||||
|
gl_disable: *lib_gl.get(b"glDisable\0")?,
|
||||||
|
gl_blend_func: *lib_gl.get(b"glBlendFunc\0")?,
|
||||||
|
gl_blend_func_separate: *lib_gl.get(b"glBlendFuncSeparate\0")?,
|
||||||
|
gl_viewport: *lib_gl.get(b"glViewport\0")?,
|
||||||
|
gl_create_shader: *lib_gl.get(b"glCreateShader\0")?,
|
||||||
|
gl_shader_source: *lib_gl.get(b"glShaderSource\0")?,
|
||||||
|
gl_compile_shader: *lib_gl.get(b"glCompileShader\0")?,
|
||||||
|
gl_get_shaderiv: *lib_gl.get(b"glGetShaderiv\0")?,
|
||||||
|
gl_get_shader_info_log: *lib_gl.get(b"glGetShaderInfoLog\0")?,
|
||||||
|
gl_create_program: *lib_gl.get(b"glCreateProgram\0")?,
|
||||||
|
gl_attach_shader: *lib_gl.get(b"glAttachShader\0")?,
|
||||||
|
gl_link_program: *lib_gl.get(b"glLinkProgram\0")?,
|
||||||
|
gl_get_programiv: *lib_gl.get(b"glGetProgramiv\0")?,
|
||||||
|
gl_get_program_info_log: *lib_gl.get(b"glGetProgramInfoLog\0")?,
|
||||||
|
gl_use_program: *lib_gl.get(b"glUseProgram\0")?,
|
||||||
|
gl_get_attrib_location: *lib_gl.get(b"glGetAttribLocation\0")?,
|
||||||
|
gl_get_uniform_location: *lib_gl.get(b"glGetUniformLocation\0")?,
|
||||||
|
gl_uniform_matrix4fv: *lib_gl.get(b"glUniformMatrix4fv\0")?,
|
||||||
|
gl_uniform1i: *lib_gl.get(b"glUniform1i\0")?,
|
||||||
|
gl_uniform4f: *lib_gl.get(b"glUniform4f\0")?,
|
||||||
|
gl_gen_textures: *lib_gl.get(b"glGenTextures\0")?,
|
||||||
|
gl_bind_texture: *lib_gl.get(b"glBindTexture\0")?,
|
||||||
|
gl_tex_parameteri: *lib_gl.get(b"glTexParameteri\0")?,
|
||||||
|
gl_tex_image_2d: *lib_gl.get(b"glTexImage2D\0")?,
|
||||||
|
gl_active_texture: *lib_gl.get(b"glActiveTexture\0")?,
|
||||||
|
gl_gen_buffers: *lib_gl.get(b"glGenBuffers\0")?,
|
||||||
|
gl_bind_buffer: *lib_gl.get(b"glBindBuffer\0")?,
|
||||||
|
gl_buffer_data: *lib_gl.get(b"glBufferData\0")?,
|
||||||
|
gl_enable_vertex_attrib_array: *lib_gl.get(b"glEnableVertexAttribArray\0")?,
|
||||||
|
gl_vertex_attrib_pointer: *lib_gl.get(b"glVertexAttribPointer\0")?,
|
||||||
|
gl_draw_elements: *lib_gl.get(b"glDrawElements\0")?,
|
||||||
|
gl_get_error: *lib_gl.get(b"glGetError\0")?,
|
||||||
|
gl_delete_textures: *lib_gl.get(b"glDeleteTextures\0")?,
|
||||||
|
gl_delete_buffers: *lib_gl.get(b"glDeleteBuffers\0")?,
|
||||||
|
|
||||||
|
x_open_display: *lib_x11.get(b"XOpenDisplay\0")?,
|
||||||
|
x_close_display: *lib_x11.get(b"XCloseDisplay\0")?,
|
||||||
|
x_create_window: *lib_x11.get(b"XCreateWindow\0")?,
|
||||||
|
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")?,
|
||||||
|
|
||||||
|
_lib_egl: lib_egl,
|
||||||
|
_lib_gl: lib_gl,
|
||||||
|
_lib_x11: lib_x11,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// EGL/GLES2/X11 渲染上下文
|
||||||
|
pub struct RenderContext {
|
||||||
|
pub gl: Gl,
|
||||||
|
display: egl::EGLDisplay,
|
||||||
|
_config: egl::EGLConfig,
|
||||||
|
context: egl::EGLContext,
|
||||||
|
surface: egl::EGLSurface,
|
||||||
|
x_display: *mut x11::Display,
|
||||||
|
x_window: x11::Window,
|
||||||
|
pub program: GLuint,
|
||||||
|
pub attrib_position: GLint,
|
||||||
|
pub attrib_tex_coord: GLint,
|
||||||
|
pub uniform_matrix: GLint,
|
||||||
|
pub uniform_texture: GLint,
|
||||||
|
pub uniform_base_color: GLint,
|
||||||
|
pub uniform_multiply_color: GLint,
|
||||||
|
pub uniform_screen_color: GLint,
|
||||||
|
pub width: i32,
|
||||||
|
pub height: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
use gl::*;
|
||||||
|
|
||||||
|
/// 创建并链接着色器程序
|
||||||
|
fn compile_shader(gl: &Gl, shader_type: GLenum, src: &str) -> Result<GLuint> {
|
||||||
|
unsafe {
|
||||||
|
let shader = (gl.gl_create_shader)(shader_type);
|
||||||
|
if shader == 0 {
|
||||||
|
return Err(anyhow!("glCreateShader 返回 0"));
|
||||||
|
}
|
||||||
|
let src_c = std::ffi::CString::new(src)?;
|
||||||
|
let src_ptr = src_c.as_ptr();
|
||||||
|
(gl.gl_shader_source)(shader, 1, &src_ptr, std::ptr::null());
|
||||||
|
(gl.gl_compile_shader)(shader);
|
||||||
|
|
||||||
|
let mut status = 0;
|
||||||
|
(gl.gl_get_shaderiv)(shader, 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));
|
||||||
|
}
|
||||||
|
Ok(shader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderContext {
|
||||||
|
/// 创建全屏 X11 窗口 + EGL 上下文
|
||||||
|
pub fn new_fullscreen(width: i32, height: i32) -> Result<Self> {
|
||||||
|
let gl = Gl::load()?;
|
||||||
|
|
||||||
|
// X11 窗口
|
||||||
|
let x_display = unsafe { (gl.x_open_display)(std::ptr::null()) };
|
||||||
|
if x_display.is_null() {
|
||||||
|
return Err(anyhow!("XOpenDisplay 失败(DISPLAY 环境变量是否设置?)"));
|
||||||
|
}
|
||||||
|
let root_window: x11::Window = 0; // DefaultRootWindow
|
||||||
|
let mut attrs = x11::XSetWindowAttributes {
|
||||||
|
background_pixmap: 0,
|
||||||
|
background_pixel: 0,
|
||||||
|
border_pixmap: 0,
|
||||||
|
border_pixel: 0,
|
||||||
|
bit_gravity: 0,
|
||||||
|
win_gravity: 0,
|
||||||
|
backing_store: 0,
|
||||||
|
backing_planes: 0,
|
||||||
|
backing_pixel: 0,
|
||||||
|
save_under: 0,
|
||||||
|
event_mask: x11::ExposureMask | x11::StructureNotifyMask,
|
||||||
|
do_not_propagate_mask: 0,
|
||||||
|
override_redirect: 1, // 全屏无装饰
|
||||||
|
colormap: 0,
|
||||||
|
cursor: 0,
|
||||||
|
};
|
||||||
|
let x_window = unsafe {
|
||||||
|
(gl.x_create_window)(
|
||||||
|
x_display,
|
||||||
|
root_window,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
width as u32,
|
||||||
|
height as u32,
|
||||||
|
0,
|
||||||
|
24, // depth
|
||||||
|
1, // InputOutput
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
x11::CWOverrideRedirect | x11::CWEventMask,
|
||||||
|
&mut attrs,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if x_window == 0 {
|
||||||
|
return Err(anyhow!("XCreateWindow 失败"));
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
let title = std::ffi::CString::new("ShowenV2 Live2D")?;
|
||||||
|
(gl.x_store_name)(x_display, x_window, title.as_ptr());
|
||||||
|
(gl.x_map_window)(x_display, x_window);
|
||||||
|
(gl.x_flush)(x_display);
|
||||||
|
}
|
||||||
|
|
||||||
|
// EGL
|
||||||
|
let display = unsafe { (gl.egl_get_display)(egl::EGL_DEFAULT_DISPLAY_PTR()) };
|
||||||
|
if display.is_null() {
|
||||||
|
return Err(anyhow!("eglGetDisplay 失败"));
|
||||||
|
}
|
||||||
|
let mut major = 0;
|
||||||
|
let mut minor = 0;
|
||||||
|
let ok = unsafe { (gl.egl_initialize)(display, &mut major, &mut minor) };
|
||||||
|
if ok == 0 {
|
||||||
|
return Err(anyhow!("eglInitialize 失败"));
|
||||||
|
}
|
||||||
|
println!("[Live2D] EGL 版本: {}.{}", major, minor);
|
||||||
|
|
||||||
|
// 选配置
|
||||||
|
let config_attrs = [
|
||||||
|
egl::EGL_RED_SIZE, 8,
|
||||||
|
egl::EGL_GREEN_SIZE, 8,
|
||||||
|
egl::EGL_BLUE_SIZE, 8,
|
||||||
|
egl::EGL_ALPHA_SIZE, 8,
|
||||||
|
egl::EGL_SURFACE_TYPE, egl::EGL_WINDOW_BIT,
|
||||||
|
egl::EGL_RENDERABLE_TYPE, egl::EGL_OPENGL_ES2_BIT,
|
||||||
|
egl::EGL_NONE,
|
||||||
|
];
|
||||||
|
let mut config: egl::EGLConfig = std::ptr::null_mut();
|
||||||
|
let mut num_config = 0;
|
||||||
|
let ok = unsafe {
|
||||||
|
(gl.egl_choose_config)(
|
||||||
|
display,
|
||||||
|
config_attrs.as_ptr(),
|
||||||
|
&mut config,
|
||||||
|
1,
|
||||||
|
&mut num_config,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if ok == 0 || num_config == 0 {
|
||||||
|
return Err(anyhow!("eglChooseConfig 失败"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建 surface
|
||||||
|
let surface = unsafe {
|
||||||
|
(gl.egl_create_window_surface)(
|
||||||
|
display,
|
||||||
|
config,
|
||||||
|
x_window as egl::NativeWindowType,
|
||||||
|
std::ptr::null(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if surface.is_null() {
|
||||||
|
return Err(anyhow!("eglCreateWindowSurface 失败"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建 context(GLES2)
|
||||||
|
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())
|
||||||
|
};
|
||||||
|
if context.is_null() {
|
||||||
|
return Err(anyhow!("eglCreateContext 失败"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// make current
|
||||||
|
let ok = unsafe {
|
||||||
|
(gl.egl_make_current)(display, surface, surface, context)
|
||||||
|
};
|
||||||
|
if ok == 0 {
|
||||||
|
return Err(anyhow!("eglMakeCurrent 失败"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置视口
|
||||||
|
unsafe {
|
||||||
|
(gl.gl_viewport)(0, 0, width, height);
|
||||||
|
(gl.gl_clear_color)(0.0, 0.0, 0.0, 0.0);
|
||||||
|
(gl.gl_enable)(GL_BLEND);
|
||||||
|
(gl.gl_blend_func)(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编译着色器
|
||||||
|
let vert = compile_shader(&gl, GL_VERTEX_SHADER, VERT_SHADER_SRC)?;
|
||||||
|
let frag = compile_shader(&gl, GL_FRAGMENT_SHADER, FRAG_SHADER_SRC)?;
|
||||||
|
let program = unsafe {
|
||||||
|
let p = (gl.gl_create_program)();
|
||||||
|
(gl.gl_attach_shader)(p, vert);
|
||||||
|
(gl.gl_attach_shader)(p, frag);
|
||||||
|
(gl.gl_link_program)(p);
|
||||||
|
let mut status = 0;
|
||||||
|
(gl.gl_get_programiv)(p, 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));
|
||||||
|
}
|
||||||
|
p
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe { (gl.gl_use_program)(program); }
|
||||||
|
|
||||||
|
// 获取 attribute/uniform 位置
|
||||||
|
let attrib_position = unsafe {
|
||||||
|
let name = std::ffi::CString::new("a_position")?;
|
||||||
|
(gl.gl_get_attrib_location)(program, name.as_ptr())
|
||||||
|
};
|
||||||
|
let attrib_tex_coord = unsafe {
|
||||||
|
let name = std::ffi::CString::new("a_texCoord")?;
|
||||||
|
(gl.gl_get_attrib_location)(program, name.as_ptr())
|
||||||
|
};
|
||||||
|
let uniform_matrix = unsafe {
|
||||||
|
let name = std::ffi::CString::new("u_matrix")?;
|
||||||
|
(gl.gl_get_uniform_location)(program, name.as_ptr())
|
||||||
|
};
|
||||||
|
let uniform_texture = unsafe {
|
||||||
|
let name = std::ffi::CString::new("s_texture0")?;
|
||||||
|
(gl.gl_get_uniform_location)(program, name.as_ptr())
|
||||||
|
};
|
||||||
|
let uniform_base_color = unsafe {
|
||||||
|
let name = std::ffi::CString::new("u_baseColor")?;
|
||||||
|
(gl.gl_get_uniform_location)(program, name.as_ptr())
|
||||||
|
};
|
||||||
|
let uniform_multiply_color = unsafe {
|
||||||
|
let name = std::ffi::CString::new("u_multiplyColor")?;
|
||||||
|
(gl.gl_get_uniform_location)(program, name.as_ptr())
|
||||||
|
};
|
||||||
|
let uniform_screen_color = unsafe {
|
||||||
|
let name = std::ffi::CString::new("u_screenColor")?;
|
||||||
|
(gl.gl_get_uniform_location)(program, name.as_ptr())
|
||||||
|
};
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"[Live2D] GL 程序就绪: program={} pos={} tex={} mat={} tex_u={} base={} mult={} scr={}",
|
||||||
|
program, attrib_position, attrib_tex_coord, uniform_matrix,
|
||||||
|
uniform_texture, uniform_base_color, uniform_multiply_color, uniform_screen_color
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
gl,
|
||||||
|
display,
|
||||||
|
_config: config,
|
||||||
|
context,
|
||||||
|
surface,
|
||||||
|
x_display,
|
||||||
|
x_window,
|
||||||
|
program,
|
||||||
|
attrib_position,
|
||||||
|
attrib_tex_coord,
|
||||||
|
uniform_matrix,
|
||||||
|
uniform_texture,
|
||||||
|
uniform_base_color,
|
||||||
|
uniform_multiply_color,
|
||||||
|
uniform_screen_color,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 上传纹理到 GPU
|
||||||
|
pub fn create_texture(&self, img: &super::assets::TextureImage) -> GLuint {
|
||||||
|
unsafe {
|
||||||
|
let mut tex = 0;
|
||||||
|
(self.gl.gl_gen_textures)(1, &mut tex);
|
||||||
|
(self.gl.gl_bind_texture)(GL_TEXTURE_2D, tex);
|
||||||
|
(self.gl.gl_tex_parameteri)(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR as i32);
|
||||||
|
(self.gl.gl_tex_parameteri)(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR as i32);
|
||||||
|
(self.gl.gl_tex_parameteri)(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE as i32);
|
||||||
|
(self.gl.gl_tex_parameteri)(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE as i32);
|
||||||
|
(self.gl.gl_tex_image_2d)(
|
||||||
|
GL_TEXTURE_2D,
|
||||||
|
0,
|
||||||
|
GL_RGBA as i32,
|
||||||
|
img.width as i32,
|
||||||
|
img.height as i32,
|
||||||
|
0,
|
||||||
|
GL_RGBA,
|
||||||
|
GL_UNSIGNED_BYTE,
|
||||||
|
img.rgba.as_ptr() as *const c_void,
|
||||||
|
);
|
||||||
|
let err = (self.gl.gl_get_error)();
|
||||||
|
if err != GL_NO_ERROR {
|
||||||
|
eprintln!("[Live2D] glTexImage2D 错误: 0x{:x}", err);
|
||||||
|
}
|
||||||
|
tex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 交换缓冲区(显示到屏幕)
|
||||||
|
pub fn swap_buffers(&self) {
|
||||||
|
unsafe {
|
||||||
|
(self.gl.gl_clear)(GL_COLOR_BUFFER_BIT);
|
||||||
|
// 注意:clear 在绘制前调用,这里 swap 只是提交
|
||||||
|
// 实际渲染流程:clear → draw → swap
|
||||||
|
(self.gl.egl_swap_buffers)(self.display, self.surface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 清屏
|
||||||
|
pub fn clear(&self) {
|
||||||
|
unsafe { (self.gl.gl_clear)(GL_COLOR_BUFFER_BIT); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for RenderContext {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// 简单清理(EGL/X11 资源在进程退出时自动释放)
|
||||||
|
unsafe {
|
||||||
|
(self.gl.x_flush)(self.x_display);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EGL_DEFAULT_DISPLAY 常量
|
||||||
|
impl egl {
|
||||||
|
pub fn EGL_DEFAULT_DISPLAY_PTR() -> NativeDisplayType {
|
||||||
|
std::ptr::null_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/plugins/live2d/mod.rs
Normal file
19
src/plugins/live2d/mod.rs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
//! Live2D 原生渲染插件(Rust + Cubism Core FFI + OpenGL ES 2.0)
|
||||||
|
//!
|
||||||
|
//! 架构:
|
||||||
|
//! - `core_ffi`:FFI 绑定 Cubism Core C API(libLive2DCubismCore.so)
|
||||||
|
//! - `model`:moc3 加载、model 管理、参数/drawable 数据提取
|
||||||
|
//! - `assets`:model3.json 解析、PNG 纹理加载
|
||||||
|
//! - `gl`:EGL 上下文 + OpenGL ES 2.0 着色器/渲染
|
||||||
|
//! - `animation`:参数动画(眨眼、嘴部、待机呼吸)
|
||||||
|
//! - `renderer`:整合渲染循环
|
||||||
|
|
||||||
|
pub mod animation;
|
||||||
|
pub mod assets;
|
||||||
|
pub mod core_ffi;
|
||||||
|
pub mod gl;
|
||||||
|
pub mod model;
|
||||||
|
pub mod renderer;
|
||||||
|
|
||||||
|
mod plugin;
|
||||||
|
pub use plugin::Live2DPlugin;
|
||||||
316
src/plugins/live2d/model.rs
Normal file
316
src/plugins/live2d/model.rs
Normal file
@@ -0,0 +1,316 @@
|
|||||||
|
//! Cubism Model 加载与管理
|
||||||
|
//!
|
||||||
|
//! 封装 moc3 读取 → revive → model 初始化 → 参数更新 → drawable 数据提取。
|
||||||
|
|
||||||
|
use super::core_ffi::{
|
||||||
|
alloc_aligned, AlignedBuffer, CubismCore, CsmVector2, CsmVector4, CSM_ALIGNOF_MOC,
|
||||||
|
CSM_ALIGNOF_MODEL,
|
||||||
|
};
|
||||||
|
use anyhow::{anyhow, Context, Result};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
/// 一个 drawable 的渲染所需数据快照
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct DrawableSnapshot {
|
||||||
|
pub texture_index: i32,
|
||||||
|
pub draw_order: i32,
|
||||||
|
pub render_order: i32,
|
||||||
|
pub opacity: f32,
|
||||||
|
pub blend_mode: i32,
|
||||||
|
pub constant_flags: u8,
|
||||||
|
pub dynamic_flags: u8,
|
||||||
|
pub vertex_positions: Vec<[f32; 2]>,
|
||||||
|
pub vertex_uvs: Vec<[f32; 2]>,
|
||||||
|
pub indices: Vec<u16>,
|
||||||
|
pub multiply_color: [f32; 4],
|
||||||
|
pub screen_color: [f32; 4],
|
||||||
|
pub mask_counts: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Canvas 信息
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
pub struct CanvasInfo {
|
||||||
|
pub size: [f32; 2],
|
||||||
|
pub origin: [f32; 2],
|
||||||
|
pub pixels_per_unit: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 参数信息
|
||||||
|
pub struct ParameterInfo {
|
||||||
|
pub id: String,
|
||||||
|
pub min_value: f32,
|
||||||
|
pub max_value: f32,
|
||||||
|
pub default_value: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 活跃的 Cubism Model
|
||||||
|
pub struct CubismModel {
|
||||||
|
/// Core 句柄(保持引用防止 .so 卸载)
|
||||||
|
_core: std::sync::Arc<CubismCore>,
|
||||||
|
/// moc3 原始数据(对齐 64)
|
||||||
|
_moc_buf: AlignedBuffer,
|
||||||
|
/// moc 指针
|
||||||
|
moc: *mut std::ffi::c_void,
|
||||||
|
/// model 内存(对齐 16)
|
||||||
|
_model_buf: AlignedBuffer,
|
||||||
|
/// model 指针
|
||||||
|
model: *mut std::ffi::c_void,
|
||||||
|
/// 参数数量
|
||||||
|
pub param_count: usize,
|
||||||
|
/// 参数 ID 列表(与 Core 内部顺序一致)
|
||||||
|
pub param_ids: Vec<String>,
|
||||||
|
/// 参数索引映射(id → index)
|
||||||
|
pub param_index: std::collections::HashMap<String, usize>,
|
||||||
|
/// drawable 数量
|
||||||
|
pub drawable_count: usize,
|
||||||
|
/// canvas 信息
|
||||||
|
pub canvas: CanvasInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
// model 指针可跨线程发送(渲染线程独占使用)
|
||||||
|
unsafe impl Send for CubismModel {}
|
||||||
|
|
||||||
|
impl CubismModel {
|
||||||
|
/// 从 moc3 文件加载 model
|
||||||
|
pub fn load(core: std::sync::Arc<CubismCore>, moc3_path: &Path) -> Result<Self> {
|
||||||
|
let moc_data = std::fs::read(moc3_path)
|
||||||
|
.with_context(|| format!("读取 moc3 失败: {}", moc3_path.display()))?;
|
||||||
|
|
||||||
|
// 分配 64 对齐内存
|
||||||
|
let mut moc_buf = alloc_aligned(moc_data.len(), CSM_ALIGNOF_MOC)?;
|
||||||
|
unsafe {
|
||||||
|
std::ptr::copy_nonoverlapping(moc_data.as_ptr(), moc_buf.as_ptr(), moc_data.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查一致性
|
||||||
|
let valid = unsafe {
|
||||||
|
(core.csm_has_moc_consistency)(moc_buf.as_mut_void(), moc_data.len() as u32)
|
||||||
|
};
|
||||||
|
if valid != 1 {
|
||||||
|
return Err(anyhow!("moc3 一致性检查失败(文件损坏或版本不支持)"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Revive moc
|
||||||
|
let moc = unsafe {
|
||||||
|
(core.csm_revive_moc_in_place)(moc_buf.as_mut_void(), moc_data.len() as u32)
|
||||||
|
};
|
||||||
|
if moc.is_null() {
|
||||||
|
return Err(anyhow!("csmReviveMocInPlace 返回空指针"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 model 所需大小
|
||||||
|
let model_size = unsafe { (core.csm_get_sizeof_model)(moc) };
|
||||||
|
if model_size == 0 {
|
||||||
|
return Err(anyhow!("csmGetSizeofModel 返回 0"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分配 16 对齐内存
|
||||||
|
let mut model_buf = alloc_aligned(model_size as usize, CSM_ALIGNOF_MODEL)?;
|
||||||
|
let model = unsafe {
|
||||||
|
(core.csm_initialize_model_in_place)(
|
||||||
|
moc,
|
||||||
|
model_buf.as_mut_void(),
|
||||||
|
model_size,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if model.is_null() {
|
||||||
|
return Err(anyhow!("csmInitializeModelInPlace 返回空指针"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取参数信息
|
||||||
|
let param_count = unsafe { (core.csm_get_parameter_count)(model) } as usize;
|
||||||
|
let param_ids_ptr = unsafe { (core.csm_get_parameter_ids)(model) };
|
||||||
|
let mut param_ids = Vec::with_capacity(param_count);
|
||||||
|
let mut param_index = std::collections::HashMap::new();
|
||||||
|
for i in 0..param_count {
|
||||||
|
let id = unsafe {
|
||||||
|
super::core_ffi::cstr_ptr_to_string(*param_ids_ptr.add(i))
|
||||||
|
.unwrap_or_default()
|
||||||
|
};
|
||||||
|
param_index.insert(id.clone(), i);
|
||||||
|
param_ids.push(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取 drawable 数量
|
||||||
|
let drawable_count = unsafe { (core.csm_get_drawable_count)(model) } as usize;
|
||||||
|
|
||||||
|
// 读取 canvas 信息
|
||||||
|
let mut size = CsmVector2::default();
|
||||||
|
let mut origin = CsmVector2::default();
|
||||||
|
let mut ppu = 0.0f32;
|
||||||
|
unsafe {
|
||||||
|
(core.csm_read_canvas_info)(model, &mut size, &mut origin, &mut ppu);
|
||||||
|
}
|
||||||
|
|
||||||
|
let canvas = CanvasInfo {
|
||||||
|
size: [size.x, size.y],
|
||||||
|
origin: [origin.x, origin.y],
|
||||||
|
pixels_per_unit: ppu,
|
||||||
|
};
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"[Live2D] 模型加载成功: 参数={} drawable={} canvas={:?}x{:.0}ppu",
|
||||||
|
param_count, drawable_count, canvas.size, canvas.pixels_per_unit
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
_core: core,
|
||||||
|
_moc_buf: moc_buf,
|
||||||
|
moc,
|
||||||
|
_model_buf: model_buf,
|
||||||
|
model,
|
||||||
|
param_count,
|
||||||
|
param_ids,
|
||||||
|
param_index,
|
||||||
|
drawable_count,
|
||||||
|
canvas,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取参数最小值
|
||||||
|
pub fn param_min_values(&self, core: &CubismCore) -> Vec<f32> {
|
||||||
|
unsafe {
|
||||||
|
let p = (core.csm_get_parameter_minimum_values)(self.model);
|
||||||
|
std::slice::from_raw_parts(p, self.param_count).to_vec()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取参数最大值
|
||||||
|
pub fn param_max_values(&self, core: &CubismCore) -> Vec<f32> {
|
||||||
|
unsafe {
|
||||||
|
let p = (core.csm_get_parameter_maximum_values)(self.model);
|
||||||
|
std::slice::from_raw_parts(p, self.param_count).to_vec()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取参数默认值
|
||||||
|
pub fn param_default_values(&self, core: &CubismCore) -> Vec<f32> {
|
||||||
|
unsafe {
|
||||||
|
let p = (core.csm_get_parameter_default_values)(self.model);
|
||||||
|
std::slice::from_raw_parts(p, self.param_count).to_vec()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取参数值缓冲区(可读写)
|
||||||
|
pub fn param_values_mut(&self, core: &CubismCore) -> &mut [f32] {
|
||||||
|
unsafe {
|
||||||
|
let p = (core.csm_get_parameter_values)(self.model);
|
||||||
|
std::slice::from_raw_parts_mut(p, self.param_count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 按参数 ID 设置值
|
||||||
|
pub fn set_param(&self, core: &CubismCore, id: &str, value: f32) -> bool {
|
||||||
|
if let Some(&idx) = self.param_index.get(id) {
|
||||||
|
let slice = self.param_values_mut(core);
|
||||||
|
slice[idx] = value;
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 按参数 ID 获取值
|
||||||
|
pub fn get_param(&self, core: &CubismCore, id: &str) -> Option<f32> {
|
||||||
|
let idx = *self.param_index.get(id)?;
|
||||||
|
let slice = self.param_values_mut(core);
|
||||||
|
Some(slice[idx])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 将所有参数重置为默认值
|
||||||
|
pub fn reset_to_default(&self, core: &CubismCore) {
|
||||||
|
let defaults = self.param_default_values(core);
|
||||||
|
let slice = self.param_values_mut(core);
|
||||||
|
for (i, v) in defaults.into_iter().enumerate() {
|
||||||
|
slice[i] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 更新 model(应用参数变化,重新计算顶点/不透明度等)
|
||||||
|
pub fn update(&self, core: &CubismCore) {
|
||||||
|
unsafe { (core.csm_update_model)(self.model) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 重置动态标志
|
||||||
|
pub fn reset_dynamic_flags(&self, core: &CubismCore) {
|
||||||
|
unsafe { (core.csm_reset_drawable_dynamic_flags)(self.model) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取所有 drawable 的渲染数据快照(按 render order 排序)
|
||||||
|
pub fn drawable_snapshots(&self, core: &CubismCore) -> Vec<DrawableSnapshot> {
|
||||||
|
let count = self.drawable_count;
|
||||||
|
unsafe {
|
||||||
|
let render_orders = (core.csm_get_render_orders)(self.model);
|
||||||
|
let texture_indices = (core.csm_get_drawable_texture_indices)(self.model);
|
||||||
|
let draw_orders = (core.csm_get_drawable_draw_orders)(self.model);
|
||||||
|
let opacities = (core.csm_get_drawable_opacities)(self.model);
|
||||||
|
let blend_modes = (core.csm_get_drawable_blend_modes)(self.model);
|
||||||
|
let const_flags = (core.csm_get_drawable_constant_flags)(self.model);
|
||||||
|
let dyn_flags = (core.csm_get_drawable_dynamic_flags)(self.model);
|
||||||
|
let mask_counts = (core.csm_get_drawable_mask_counts)(self.model);
|
||||||
|
let vertex_counts = (core.csm_get_drawable_vertex_counts)(self.model);
|
||||||
|
let vertex_positions = (core.csm_get_drawable_vertex_positions)(self.model);
|
||||||
|
let vertex_uvs = (core.csm_get_drawable_vertex_uvs)(self.model);
|
||||||
|
let index_counts = (core.csm_get_drawable_index_counts)(self.model);
|
||||||
|
let indices = (core.csm_get_drawable_indices)(self.model);
|
||||||
|
let multiply_colors = (core.csm_get_drawable_multiply_colors)(self.model);
|
||||||
|
let screen_colors = (core.csm_get_drawable_screen_colors)(self.model);
|
||||||
|
|
||||||
|
// 先收集每个 drawable 的原始索引
|
||||||
|
let mut items: Vec<(usize, i32)> = (0..count)
|
||||||
|
.map(|i| (i, *render_orders.add(i)))
|
||||||
|
.collect();
|
||||||
|
// 按 render_order 升序排列
|
||||||
|
items.sort_by_key(|&(_, ro)| ro);
|
||||||
|
|
||||||
|
items
|
||||||
|
.into_iter()
|
||||||
|
.map(|(i, ro)| {
|
||||||
|
let vc = *vertex_counts.add(i) as usize;
|
||||||
|
let ic = *index_counts.add(i) as usize;
|
||||||
|
let vp = *vertex_positions.add(i);
|
||||||
|
let vu = *vertex_uvs.add(i);
|
||||||
|
let idx = *indices.add(i);
|
||||||
|
|
||||||
|
let mut verts = Vec::with_capacity(vc);
|
||||||
|
let mut uvs = Vec::with_capacity(vc);
|
||||||
|
for v in 0..vc {
|
||||||
|
let p = *vp.add(v);
|
||||||
|
let u = *vu.add(v);
|
||||||
|
verts.push([p.x, p.y]);
|
||||||
|
uvs.push([u.x, u.y]);
|
||||||
|
}
|
||||||
|
let mut idx_vec = Vec::with_capacity(ic);
|
||||||
|
for k in 0..ic {
|
||||||
|
idx_vec.push(*idx.add(k));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mc = *multiply_colors.add(i);
|
||||||
|
let sc = *screen_colors.add(i);
|
||||||
|
|
||||||
|
DrawableSnapshot {
|
||||||
|
texture_index: *texture_indices.add(i),
|
||||||
|
draw_order: *draw_orders.add(i),
|
||||||
|
render_order: ro,
|
||||||
|
opacity: *opacities.add(i),
|
||||||
|
blend_mode: *blend_modes.add(i),
|
||||||
|
constant_flags: *const_flags.add(i),
|
||||||
|
dynamic_flags: *dyn_flags.add(i),
|
||||||
|
vertex_positions: verts,
|
||||||
|
vertex_uvs: uvs,
|
||||||
|
indices: idx_vec,
|
||||||
|
multiply_color: [mc.x, mc.y, mc.z, mc.w],
|
||||||
|
screen_color: [sc.x, sc.y, sc.z, sc.w],
|
||||||
|
mask_counts: *mask_counts.add(i),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CubismModel {
|
||||||
|
// 保留 CsmVector4 引用避免 unused
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn _ensure_v4_linked(_v: CsmVector4) {}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ pub mod ai;
|
|||||||
pub mod ble;
|
pub mod ble;
|
||||||
pub mod device;
|
pub mod device;
|
||||||
pub mod http;
|
pub mod http;
|
||||||
|
pub mod live2d;
|
||||||
pub mod screen;
|
pub mod screen;
|
||||||
pub mod video;
|
pub mod video;
|
||||||
pub mod wifi;
|
pub mod wifi;
|
||||||
|
|||||||
Reference in New Issue
Block a user