feat: add device-side subtitle overlay using FreeType for rendering Chinese text
- Updated Cargo.toml to include freetype-rs for font rendering. - Modified message.rs to introduce a new Message variant for subtitles. - Enhanced HttpPlugin to send subtitle messages to the live2d plugin. - Implemented text rendering in the live2d plugin using FreeType for Chinese characters. - Added OpenGL shaders for rendering text overlays. - Created a new text.rs module for handling text rasterization and texture rendering. - Updated renderer.rs to integrate subtitle rendering logic.
This commit is contained in:
@@ -205,6 +205,29 @@ void main() {
|
||||
}
|
||||
"#;
|
||||
|
||||
/// 文字 overlay 着色器(像素坐标 -> 裁剪空间,采样字形纹理,按 u_color 着色)
|
||||
pub const TEXT_VERT_SHADER_SRC: &str = r#"#version 100
|
||||
attribute vec2 a_position;
|
||||
attribute vec2 a_texCoord;
|
||||
uniform mat4 u_matrix;
|
||||
varying vec2 v_uv;
|
||||
void main() {
|
||||
gl_Position = u_matrix * vec4(a_position, 0.0, 1.0);
|
||||
v_uv = a_texCoord;
|
||||
}
|
||||
"#;
|
||||
|
||||
pub const TEXT_FRAG_SHADER_SRC: &str = r#"#version 100
|
||||
precision mediump float;
|
||||
varying vec2 v_uv;
|
||||
uniform sampler2D s_texture0;
|
||||
uniform vec4 u_color;
|
||||
void main() {
|
||||
float a = texture2D(s_texture0, v_uv).a;
|
||||
gl_FragColor = vec4(u_color.rgb, a * u_color.a);
|
||||
}
|
||||
"#;
|
||||
|
||||
/// 遮罩(mask)着色器:仅把 mask drawable 的不透明区域写入 stencil,
|
||||
/// 用于裁切被遮罩的 drawable(如夹在身体里的后臂、被头发遮挡的脸)。
|
||||
pub const MASK_VERT_SHADER_SRC: &str = r#"#version 100
|
||||
@@ -255,6 +278,12 @@ pub struct Gl {
|
||||
config_size: egl::EGLint,
|
||||
num_config: *mut egl::EGLint,
|
||||
) -> egl::EGLBoolean,
|
||||
pub egl_get_config_attrib: unsafe extern "C" fn(
|
||||
dpy: egl::EGLDisplay,
|
||||
config: egl::EGLConfig,
|
||||
attribute: egl::EGLint,
|
||||
value: *mut egl::EGLint,
|
||||
) -> egl::EGLBoolean,
|
||||
pub egl_create_window_surface: unsafe extern "C" fn(
|
||||
dpy: egl::EGLDisplay,
|
||||
config: egl::EGLConfig,
|
||||
@@ -433,6 +462,7 @@ impl Gl {
|
||||
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_get_config_attrib: *lib_egl.get(b"eglGetConfigAttrib\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")?,
|
||||
@@ -530,6 +560,13 @@ pub struct RenderContext {
|
||||
pub mask_uniform_texture: GLint,
|
||||
pub mask_uniform_multiply_color: GLint,
|
||||
pub mask_uniform_screen_color: GLint,
|
||||
// 文字 overlay 程序
|
||||
pub text_program: GLuint,
|
||||
pub text_uniform_matrix: GLint,
|
||||
pub text_uniform_color: GLint,
|
||||
pub text_uniform_texture: GLint,
|
||||
pub text_attrib_position: GLint,
|
||||
pub text_attrib_tex_coord: GLint,
|
||||
pub width: i32,
|
||||
pub height: i32,
|
||||
}
|
||||
@@ -714,6 +751,18 @@ impl RenderContext {
|
||||
num_config
|
||||
);
|
||||
|
||||
// 真实查询 chosen config 的 depth/stencil 位数,确认 stencil 缓冲真正分配。
|
||||
let mut stencil_size: egl::EGLint = -1;
|
||||
let mut depth_size: egl::EGLint = -1;
|
||||
unsafe {
|
||||
(gl.egl_get_config_attrib)(display, config, egl::EGL_STENCIL_SIZE, &mut stencil_size);
|
||||
(gl.egl_get_config_attrib)(display, config, egl::EGL_DEPTH_SIZE, &mut depth_size);
|
||||
}
|
||||
println!(
|
||||
"[Live2D] EGL 实际分配: depth={} stencil={} (stencil<=0 表示遮罩不可用)",
|
||||
depth_size, stencil_size
|
||||
);
|
||||
|
||||
let surface = unsafe {
|
||||
(gl.egl_create_window_surface)(display, config, x_window as egl::NativeWindowType, std::ptr::null())
|
||||
};
|
||||
@@ -846,6 +895,44 @@ impl RenderContext {
|
||||
unsafe { (gl.gl_get_uniform_location)(mask_program, c.as_ptr()) }
|
||||
};
|
||||
|
||||
// 文字 overlay 程序
|
||||
let text_vert = compile_shader(&gl, gl::GL_VERTEX_SHADER, TEXT_VERT_SHADER_SRC)?;
|
||||
let text_frag = compile_shader(&gl, gl::GL_FRAGMENT_SHADER, TEXT_FRAG_SHADER_SRC)?;
|
||||
let text_program = unsafe {
|
||||
let p = (gl.gl_create_program)();
|
||||
(gl.gl_attach_shader)(p, text_vert);
|
||||
(gl.gl_attach_shader)(p, text_frag);
|
||||
(gl.gl_link_program)(p);
|
||||
let mut status = 0;
|
||||
(gl.gl_get_programiv)(p, gl::GL_LINK_STATUS, &mut status);
|
||||
if status == 0 {
|
||||
let mut log: Vec<u8> = vec![0u8; 1024];
|
||||
(gl.gl_get_program_info_log)(p, 1024, std::ptr::null_mut(), log.as_mut_ptr() as *mut c_char);
|
||||
return Err(anyhow!("text shader link failed: {}", String::from_utf8_lossy(&log)));
|
||||
}
|
||||
p
|
||||
};
|
||||
let text_uniform_matrix = {
|
||||
let c = std::ffi::CString::new("u_matrix")?;
|
||||
unsafe { (gl.gl_get_uniform_location)(text_program, c.as_ptr()) }
|
||||
};
|
||||
let text_uniform_color = {
|
||||
let c = std::ffi::CString::new("u_color")?;
|
||||
unsafe { (gl.gl_get_uniform_location)(text_program, c.as_ptr()) }
|
||||
};
|
||||
let text_uniform_texture = {
|
||||
let c = std::ffi::CString::new("s_texture0")?;
|
||||
unsafe { (gl.gl_get_uniform_location)(text_program, c.as_ptr()) }
|
||||
};
|
||||
let text_attrib_position = {
|
||||
let c = std::ffi::CString::new("a_position")?;
|
||||
unsafe { (gl.gl_get_attrib_location)(text_program, c.as_ptr()) }
|
||||
};
|
||||
let text_attrib_tex_coord = {
|
||||
let c = std::ffi::CString::new("a_texCoord")?;
|
||||
unsafe { (gl.gl_get_attrib_location)(text_program, c.as_ptr()) }
|
||||
};
|
||||
|
||||
println!(
|
||||
"[Live2D] GL 程序就绪: program={} pos={} tex={} mat={} tex_u={} base={} mult={} scr={}",
|
||||
program, attrib_position, attrib_tex_coord, uniform_matrix,
|
||||
@@ -882,6 +969,12 @@ impl RenderContext {
|
||||
mask_uniform_texture,
|
||||
mask_uniform_multiply_color,
|
||||
mask_uniform_screen_color,
|
||||
text_program,
|
||||
text_uniform_matrix,
|
||||
text_uniform_color,
|
||||
text_uniform_texture,
|
||||
text_attrib_position,
|
||||
text_attrib_tex_coord,
|
||||
width: win_w,
|
||||
height: win_h,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user