- 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.
1084 lines
42 KiB
Rust
1084 lines
42 KiB
Rust
//! OpenGL ES 2.0 / EGL 渲染后端
|
||
//!
|
||
//! 使用 EGL + X11 窗口创建渲染上下文,用 OpenGL ES 2.0 渲染 Live2D 模型。
|
||
|
||
use anyhow::{anyhow, Result};
|
||
use std::ffi::c_void;
|
||
use std::os::raw::{c_char, c_int, c_long, c_uint, c_void as c_void_raw};
|
||
|
||
// ============ EGL ============
|
||
|
||
pub mod egl {
|
||
use std::os::raw::{c_int, c_uint, c_void};
|
||
|
||
pub const EGL_SURFACE_TYPE: c_int = 0x3033;
|
||
pub const EGL_WINDOW_BIT: c_int = 0x0004;
|
||
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_ALPHA_SIZE: c_int = 0x3021;
|
||
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_DEPTH_SIZE: c_int = 0x3025;
|
||
pub const EGL_STENCIL_SIZE: c_int = 0x3026;
|
||
|
||
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;
|
||
|
||
/// EGL_DEFAULT_DISPLAY(即 NULL)
|
||
pub fn default_display() -> NativeDisplayType {
|
||
std::ptr::null_mut()
|
||
}
|
||
}
|
||
|
||
// ============ X11 ============
|
||
|
||
pub mod x11 {
|
||
use std::os::raw::{c_int, c_long, c_void};
|
||
|
||
pub type Display = c_void;
|
||
pub type Window = 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 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 ============
|
||
|
||
pub mod gl {
|
||
use std::os::raw::{c_int, c_uint, c_uchar};
|
||
|
||
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 GLboolean = c_uchar;
|
||
|
||
pub const GL_FLOAT: GLenum = 0x1406;
|
||
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_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_TRIANGLE_STRIP: GLenum = 0x0005;
|
||
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_SRC_COLOR: GLenum = 0x0300;
|
||
pub const GL_COLOR_BUFFER_BIT: GLbitfield = 0x4000;
|
||
pub const GL_STATIC_DRAW: GLenum = 0x88E4;
|
||
pub const GL_FALSE: GLenum = 0;
|
||
pub const GL_NO_ERROR: GLenum = 0;
|
||
|
||
// 遮罩(stencil)相关常量
|
||
pub const GL_DEPTH_TEST: GLenum = 0x0B71;
|
||
pub const GL_STENCIL_TEST: GLenum = 0x0B90;
|
||
pub const GL_STENCIL_BUFFER_BIT: GLbitfield = 0x0400;
|
||
pub const GL_KEEP: GLenum = 0x1E00;
|
||
pub const GL_REPLACE: GLenum = 0x1E01;
|
||
pub const GL_INCR: GLenum = 0x1E02;
|
||
pub const GL_DECR: GLenum = 0x1E03;
|
||
pub const GL_ALWAYS: GLenum = 0x0207;
|
||
pub const GL_EQUAL: GLenum = 0x0202;
|
||
pub const GL_NOTEQUAL: GLenum = 0x0205;
|
||
pub const GL_LESS: GLenum = 0x0201;
|
||
pub const GL_TRUE: GLboolean = 1;
|
||
}
|
||
|
||
pub use gl::{GLenum, GLuint, GLint, GLsizei};
|
||
|
||
// ============ 着色器源码 ============
|
||
|
||
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);
|
||
}
|
||
"#;
|
||
|
||
/// 背景渐变着色器(全屏四边形,从底部到底部到顶部做线性插值)
|
||
pub const GRAD_VERT_SHADER_SRC: &str = r#"#version 100
|
||
attribute vec2 a_pos;
|
||
varying float v_y;
|
||
void main() {
|
||
v_y = a_pos.y;
|
||
gl_Position = vec4(a_pos, 0.0, 1.0);
|
||
}
|
||
"#;
|
||
|
||
pub const GRAD_FRAG_SHADER_SRC: &str = r#"#version 100
|
||
precision highp float;
|
||
varying float v_y;
|
||
uniform vec4 u_top;
|
||
uniform vec4 u_bottom;
|
||
void main() {
|
||
float t = (v_y + 1.0) * 0.5;
|
||
vec3 c = mix(u_bottom.rgb, u_top.rgb, t);
|
||
gl_FragColor = vec4(c, 1.0);
|
||
}
|
||
"#;
|
||
|
||
/// 文字 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
|
||
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 MASK_FRAG_SHADER_SRC: &str = r#"#version 100
|
||
precision highp float;
|
||
varying vec2 v_texCoord;
|
||
uniform sampler2D s_texture0;
|
||
uniform vec4 u_multiplyColor;
|
||
uniform vec4 u_screenColor;
|
||
void main() {
|
||
vec4 c = texture2D(s_texture0, v_texCoord);
|
||
c.rgb = c.rgb * u_multiplyColor.rgb;
|
||
c.rgb = c.rgb + u_screenColor.rgb - (c.rgb * u_screenColor.rgb);
|
||
// 仅不透明区域写入 stencil(裁切形状由 alpha 决定)
|
||
if (c.a < 0.5) discard;
|
||
gl_FragColor = vec4(1.0);
|
||
}
|
||
"#;
|
||
|
||
// ============ 动态库函数集合 ============
|
||
|
||
pub struct Gl {
|
||
_lib_egl: libloading::Library,
|
||
_lib_gl: libloading::Library,
|
||
_lib_x11: libloading::Library,
|
||
|
||
pub egl_get_display: unsafe extern "C" fn(disp: egl::NativeDisplayType) -> egl::EGLDisplay,
|
||
pub egl_initialize: unsafe extern "C" fn(
|
||
dpy: egl::EGLDisplay,
|
||
major: *mut c_int,
|
||
minor: *mut c_int,
|
||
) -> egl::EGLBoolean,
|
||
pub 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,
|
||
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,
|
||
win: egl::NativeWindowType,
|
||
attrib_list: *const egl::EGLint,
|
||
) -> egl::EGLSurface,
|
||
pub egl_create_context: unsafe extern "C" fn(
|
||
dpy: egl::EGLDisplay,
|
||
config: egl::EGLConfig,
|
||
share_ctx: egl::EGLContext,
|
||
attrib_list: *const egl::EGLint,
|
||
) -> egl::EGLContext,
|
||
pub egl_make_current: unsafe extern "C" fn(
|
||
dpy: egl::EGLDisplay,
|
||
draw: egl::EGLSurface,
|
||
read: egl::EGLSurface,
|
||
ctx: egl::EGLContext,
|
||
) -> egl::EGLBoolean,
|
||
pub egl_swap_buffers:
|
||
unsafe extern "C" fn(dpy: egl::EGLDisplay, surface: egl::EGLSurface) -> egl::EGLBoolean,
|
||
|
||
pub gl_clear: unsafe extern "C" fn(mask: gl::GLbitfield),
|
||
pub gl_clear_color: unsafe extern "C" fn(r: f32, g: f32, b: f32, a: f32),
|
||
pub gl_enable: unsafe extern "C" fn(cap: gl::GLenum),
|
||
pub gl_disable: unsafe extern "C" fn(cap: gl::GLenum),
|
||
pub gl_blend_func: unsafe extern "C" fn(sfactor: gl::GLenum, dfactor: gl::GLenum),
|
||
pub gl_viewport: unsafe extern "C" fn(x: gl::GLint, y: gl::GLint, w: gl::GLsizei, h: gl::GLsizei),
|
||
pub gl_create_shader: unsafe extern "C" fn(t: gl::GLenum) -> gl::GLuint,
|
||
pub gl_shader_source: unsafe extern "C" fn(
|
||
shader: gl::GLuint,
|
||
count: gl::GLsizei,
|
||
string: *const *const c_char,
|
||
length: *const gl::GLint,
|
||
),
|
||
pub gl_compile_shader: unsafe extern "C" fn(shader: gl::GLuint),
|
||
pub gl_get_shaderiv:
|
||
unsafe extern "C" fn(shader: gl::GLuint, pname: gl::GLenum, params: *mut gl::GLint),
|
||
pub 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,
|
||
),
|
||
pub gl_create_program: unsafe extern "C" fn() -> gl::GLuint,
|
||
pub gl_attach_shader: unsafe extern "C" fn(program: gl::GLuint, shader: gl::GLuint),
|
||
pub gl_link_program: unsafe extern "C" fn(program: gl::GLuint),
|
||
pub gl_get_programiv:
|
||
unsafe extern "C" fn(program: gl::GLuint, pname: gl::GLenum, params: *mut gl::GLint),
|
||
pub 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,
|
||
),
|
||
pub gl_use_program: unsafe extern "C" fn(program: gl::GLuint),
|
||
pub gl_get_attrib_location:
|
||
unsafe extern "C" fn(program: gl::GLuint, name: *const c_char) -> gl::GLint,
|
||
pub gl_get_uniform_location:
|
||
unsafe extern "C" fn(program: gl::GLuint, name: *const c_char) -> gl::GLint,
|
||
pub gl_uniform_matrix4fv: unsafe extern "C" fn(
|
||
location: gl::GLint,
|
||
count: gl::GLsizei,
|
||
transpose: gl::GLenum,
|
||
value: *const f32,
|
||
),
|
||
pub gl_uniform1i: unsafe extern "C" fn(location: gl::GLint, v: gl::GLint),
|
||
pub gl_uniform4f: unsafe extern "C" fn(location: gl::GLint, x: f32, y: f32, z: f32, w: f32),
|
||
pub gl_gen_textures: unsafe extern "C" fn(n: gl::GLsizei, textures: *mut gl::GLuint),
|
||
pub gl_bind_texture: unsafe extern "C" fn(target: gl::GLenum, texture: gl::GLuint),
|
||
pub gl_tex_parameteri:
|
||
unsafe extern "C" fn(target: gl::GLenum, pname: gl::GLenum, param: gl::GLint),
|
||
pub gl_tex_image_2d: unsafe extern "C" fn(
|
||
target: gl::GLenum,
|
||
level: gl::GLint,
|
||
internalformat: gl::GLint,
|
||
width: gl::GLsizei,
|
||
height: gl::GLsizei,
|
||
border: gl::GLint,
|
||
format: gl::GLenum,
|
||
type_: gl::GLenum,
|
||
data: *const c_void_raw,
|
||
),
|
||
pub gl_active_texture: unsafe extern "C" fn(texture: gl::GLenum),
|
||
pub gl_gen_buffers: unsafe extern "C" fn(n: gl::GLsizei, buffers: *mut gl::GLuint),
|
||
pub gl_bind_buffer: unsafe extern "C" fn(target: gl::GLenum, buffer: gl::GLuint),
|
||
pub gl_buffer_data: unsafe extern "C" fn(
|
||
target: gl::GLenum,
|
||
size: isize,
|
||
data: *const c_void_raw,
|
||
usage: gl::GLenum,
|
||
),
|
||
pub gl_enable_vertex_attrib_array: unsafe extern "C" fn(index: gl::GLuint),
|
||
pub 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,
|
||
),
|
||
pub gl_draw_elements: unsafe extern "C" fn(
|
||
mode: gl::GLenum,
|
||
count: gl::GLsizei,
|
||
type_: gl::GLenum,
|
||
indices: *const c_void_raw,
|
||
),
|
||
pub gl_draw_arrays: unsafe extern "C" fn(mode: gl::GLenum, first: gl::GLint, count: gl::GLsizei),
|
||
pub gl_get_error: unsafe extern "C" fn() -> gl::GLenum,
|
||
pub gl_delete_buffers: unsafe extern "C" fn(n: gl::GLsizei, buffers: *const gl::GLuint),
|
||
pub gl_stencil_func: unsafe extern "C" fn(func: gl::GLenum, ref_: gl::GLint, mask: gl::GLuint),
|
||
pub gl_stencil_op: unsafe extern "C" fn(fail: gl::GLenum, zfail: gl::GLenum, zpass: gl::GLenum),
|
||
pub gl_stencil_mask: unsafe extern "C" fn(mask: gl::GLuint),
|
||
pub gl_color_mask: unsafe extern "C" fn(
|
||
r: gl::GLboolean,
|
||
g: gl::GLboolean,
|
||
b: gl::GLboolean,
|
||
a: gl::GLboolean,
|
||
),
|
||
|
||
pub x_open_display: unsafe extern "C" fn(name: *const c_char) -> *mut x11::Display,
|
||
pub 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,
|
||
pub x_map_window: unsafe extern "C" fn(disp: *mut x11::Display, w: x11::Window) -> c_int,
|
||
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 {
|
||
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_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")?,
|
||
egl_swap_buffers: *lib_egl.get(b"eglSwapBuffers\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_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_draw_arrays: *lib_gl.get(b"glDrawArrays\0")?,
|
||
gl_get_error: *lib_gl.get(b"glGetError\0")?,
|
||
gl_delete_buffers: *lib_gl.get(b"glDeleteBuffers\0")?,
|
||
gl_stencil_func: *lib_gl.get(b"glStencilFunc\0")?,
|
||
gl_stencil_op: *lib_gl.get(b"glStencilOp\0")?,
|
||
gl_stencil_mask: *lib_gl.get(b"glStencilMask\0")?,
|
||
gl_color_mask: *lib_gl.get(b"glColorMask\0")?,
|
||
|
||
x_open_display: *lib_x11.get(b"XOpenDisplay\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")?,
|
||
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,
|
||
_lib_x11: lib_x11,
|
||
})
|
||
}
|
||
}
|
||
}
|
||
|
||
// ============ 渲染上下文 ============
|
||
|
||
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,
|
||
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 grad_program: GLuint,
|
||
pub grad_attrib_pos: GLint,
|
||
pub grad_uniform_top: GLint,
|
||
pub grad_uniform_bottom: GLint,
|
||
// 遮罩(mask)程序
|
||
pub mask_program: GLuint,
|
||
pub mask_attrib_position: GLint,
|
||
pub mask_attrib_tex_coord: GLint,
|
||
pub mask_uniform_matrix: GLint,
|
||
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,
|
||
}
|
||
|
||
// 含裸指针,手动声明 Send(仅在单渲染线程使用)
|
||
unsafe impl Send for RenderContext {}
|
||
|
||
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::GL_COMPILE_STATUS, &mut status);
|
||
if status == 0 {
|
||
let mut log: Vec<u8> = 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)
|
||
}
|
||
}
|
||
|
||
impl RenderContext {
|
||
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 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) };
|
||
// 全屏渲染:直接使用显示器实际分辨率创建窗口与 EGL surface,避免
|
||
// 「帧缓冲 800x800 被放进 1920x1080 窗口、模型只出现在左下角」的问题。
|
||
// 若 X11 取不到屏幕分辨率,再退回传入的 width/height。
|
||
let win_w = if screen_w > 0 && screen_h > 0 { screen_w } else { width };
|
||
let win_h = if screen_w > 0 && screen_h > 0 { screen_h } else { height };
|
||
println!("[Live2D] 窗口尺寸: {}x{} (屏幕实际 {}x{})", win_w, win_h, screen_w, screen_h);
|
||
|
||
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: 0,
|
||
colormap: 0,
|
||
cursor: 0,
|
||
};
|
||
let x_window = unsafe {
|
||
(gl.x_create_window)(
|
||
x_display,
|
||
root_window,
|
||
0,
|
||
0,
|
||
win_w as c_uint,
|
||
win_h as c_uint,
|
||
0,
|
||
24,
|
||
1,
|
||
std::ptr::null_mut(),
|
||
x11::CWEventMask,
|
||
&mut attrs,
|
||
)
|
||
};
|
||
if x_window == 0 {
|
||
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_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
|
||
let display = unsafe { (gl.egl_get_display)(egl::default_display()) };
|
||
if display.is_null() {
|
||
return Err(anyhow!("eglGetDisplay 失败"));
|
||
}
|
||
let mut major = 0;
|
||
let mut minor = 0;
|
||
if unsafe { (gl.egl_initialize)(display, &mut major, &mut minor) } == 0 {
|
||
return Err(anyhow!("eglInitialize 失败"));
|
||
}
|
||
println!("[Live2D] EGL {}.{}", major, minor);
|
||
|
||
// 主配置请求 depth+stencil(Live2D 遮罩需要 stencil);
|
||
// 若设备不支持该组合,退回不带 depth/stencil 的配置。
|
||
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_DEPTH_SIZE, 16,
|
||
egl::EGL_STENCIL_SIZE, 8,
|
||
egl::EGL_NONE,
|
||
];
|
||
let config_attrs_fallback = [
|
||
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 mut choose = |attrs: &[c_int]| -> bool {
|
||
let mut cfg: egl::EGLConfig = std::ptr::null_mut();
|
||
let mut n = 0;
|
||
let ok = unsafe {
|
||
(gl.egl_choose_config)(display, attrs.as_ptr(), &mut cfg, 1, &mut n)
|
||
} != 0 && n > 0;
|
||
if ok {
|
||
config = cfg;
|
||
num_config = n;
|
||
}
|
||
ok
|
||
};
|
||
if !choose(&config_attrs) && !choose(&config_attrs_fallback) {
|
||
return Err(anyhow!("eglChooseConfig 失败"));
|
||
}
|
||
println!(
|
||
"[Live2D] EGL 配置选定: 含stencil={} (num_config={})",
|
||
config_attrs.len() > 9,
|
||
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())
|
||
};
|
||
if surface.is_null() {
|
||
return Err(anyhow!("eglCreateWindowSurface 失败"));
|
||
}
|
||
|
||
let ctx_attrs = [egl::EGL_CONTEXT_CLIENT_VERSION, 2, egl::EGL_NONE];
|
||
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 失败"));
|
||
}
|
||
|
||
if unsafe { (gl.egl_make_current)(display, surface, surface, context) } == 0 {
|
||
return Err(anyhow!("eglMakeCurrent 失败"));
|
||
}
|
||
|
||
unsafe {
|
||
(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);
|
||
}
|
||
|
||
let vert = compile_shader(&gl, gl::GL_VERTEX_SHADER, VERT_SHADER_SRC)?;
|
||
let frag = compile_shader(&gl, 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::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);
|
||
let log_str = String::from_utf8_lossy(&log);
|
||
return Err(anyhow!("shader link failed: {}", log_str));
|
||
}
|
||
p
|
||
};
|
||
unsafe { (gl.gl_use_program)(program); }
|
||
|
||
let get = |name: &str| -> Result<GLint> {
|
||
let c = std::ffi::CString::new(name)?;
|
||
Ok(unsafe { (gl.gl_get_attrib_location)(program, c.as_ptr()) })
|
||
};
|
||
let getu = |name: &str| -> Result<GLint> {
|
||
let c = std::ffi::CString::new(name)?;
|
||
Ok(unsafe { (gl.gl_get_uniform_location)(program, c.as_ptr()) })
|
||
};
|
||
|
||
let attrib_position = get("a_position")?;
|
||
let attrib_tex_coord = get("a_texCoord")?;
|
||
let uniform_matrix = getu("u_matrix")?;
|
||
let uniform_texture = getu("s_texture0")?;
|
||
let uniform_base_color = getu("u_baseColor")?;
|
||
let uniform_multiply_color = getu("u_multiplyColor")?;
|
||
let uniform_screen_color = getu("u_screenColor")?;
|
||
|
||
// 背景渐变程序
|
||
let grad_vert = compile_shader(&gl, gl::GL_VERTEX_SHADER, GRAD_VERT_SHADER_SRC)?;
|
||
let grad_frag = compile_shader(&gl, gl::GL_FRAGMENT_SHADER, GRAD_FRAG_SHADER_SRC)?;
|
||
let grad_program = unsafe {
|
||
let p = (gl.gl_create_program)();
|
||
(gl.gl_attach_shader)(p, grad_vert);
|
||
(gl.gl_attach_shader)(p, grad_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!("gradient shader link failed: {}", String::from_utf8_lossy(&log)));
|
||
}
|
||
p
|
||
};
|
||
let grad_attrib_pos = {
|
||
let c = std::ffi::CString::new("a_pos")?;
|
||
unsafe { (gl.gl_get_attrib_location)(grad_program, c.as_ptr()) }
|
||
};
|
||
let grad_uniform_top = {
|
||
let c = std::ffi::CString::new("u_top")?;
|
||
unsafe { (gl.gl_get_uniform_location)(grad_program, c.as_ptr()) }
|
||
};
|
||
let grad_uniform_bottom = {
|
||
let c = std::ffi::CString::new("u_bottom")?;
|
||
unsafe { (gl.gl_get_uniform_location)(grad_program, c.as_ptr()) }
|
||
};
|
||
|
||
// 遮罩(mask)程序:仅用于把 mask drawable 写入 stencil
|
||
let mask_vert = compile_shader(&gl, gl::GL_VERTEX_SHADER, MASK_VERT_SHADER_SRC)?;
|
||
let mask_frag = compile_shader(&gl, gl::GL_FRAGMENT_SHADER, MASK_FRAG_SHADER_SRC)?;
|
||
let mask_program = unsafe {
|
||
let p = (gl.gl_create_program)();
|
||
(gl.gl_attach_shader)(p, mask_vert);
|
||
(gl.gl_attach_shader)(p, mask_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!("mask shader link failed: {}", String::from_utf8_lossy(&log)));
|
||
}
|
||
p
|
||
};
|
||
let mask_attrib_position = {
|
||
let c = std::ffi::CString::new("a_position")?;
|
||
unsafe { (gl.gl_get_attrib_location)(mask_program, c.as_ptr()) }
|
||
};
|
||
let mask_attrib_tex_coord = {
|
||
let c = std::ffi::CString::new("a_texCoord")?;
|
||
unsafe { (gl.gl_get_attrib_location)(mask_program, c.as_ptr()) }
|
||
};
|
||
let mask_uniform_matrix = {
|
||
let c = std::ffi::CString::new("u_matrix")?;
|
||
unsafe { (gl.gl_get_uniform_location)(mask_program, c.as_ptr()) }
|
||
};
|
||
let mask_uniform_texture = {
|
||
let c = std::ffi::CString::new("s_texture0")?;
|
||
unsafe { (gl.gl_get_uniform_location)(mask_program, c.as_ptr()) }
|
||
};
|
||
let mask_uniform_multiply_color = {
|
||
let c = std::ffi::CString::new("u_multiplyColor")?;
|
||
unsafe { (gl.gl_get_uniform_location)(mask_program, c.as_ptr()) }
|
||
};
|
||
let mask_uniform_screen_color = {
|
||
let c = std::ffi::CString::new("u_screenColor")?;
|
||
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,
|
||
uniform_texture, uniform_base_color, uniform_multiply_color, uniform_screen_color
|
||
);
|
||
println!(
|
||
"[Live2D] 渐变程序就绪: grad={} pos={} top={} bottom={}",
|
||
grad_program, grad_attrib_pos, grad_uniform_top, grad_uniform_bottom
|
||
);
|
||
|
||
Ok(Self {
|
||
gl,
|
||
display,
|
||
surface,
|
||
context,
|
||
x_display,
|
||
x_window,
|
||
program,
|
||
attrib_position,
|
||
attrib_tex_coord,
|
||
uniform_matrix,
|
||
uniform_texture,
|
||
uniform_base_color,
|
||
uniform_multiply_color,
|
||
uniform_screen_color,
|
||
grad_program,
|
||
grad_attrib_pos,
|
||
grad_uniform_top,
|
||
grad_uniform_bottom,
|
||
mask_program,
|
||
mask_attrib_position,
|
||
mask_attrib_tex_coord,
|
||
mask_uniform_matrix,
|
||
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,
|
||
})
|
||
}
|
||
|
||
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::GL_TEXTURE_2D, tex);
|
||
(self.gl.gl_tex_parameteri)(gl::GL_TEXTURE_2D, gl::GL_TEXTURE_MIN_FILTER, gl::GL_LINEAR as GLint);
|
||
(self.gl.gl_tex_parameteri)(gl::GL_TEXTURE_2D, gl::GL_TEXTURE_MAG_FILTER, gl::GL_LINEAR as GLint);
|
||
(self.gl.gl_tex_parameteri)(gl::GL_TEXTURE_2D, gl::GL_TEXTURE_WRAP_S, gl::GL_CLAMP_TO_EDGE as GLint);
|
||
(self.gl.gl_tex_parameteri)(gl::GL_TEXTURE_2D, gl::GL_TEXTURE_WRAP_T, gl::GL_CLAMP_TO_EDGE as GLint);
|
||
(self.gl.gl_tex_image_2d)(
|
||
gl::GL_TEXTURE_2D,
|
||
0,
|
||
gl::GL_RGBA as GLint,
|
||
img.width as GLsizei,
|
||
img.height as GLsizei,
|
||
0,
|
||
gl::GL_RGBA,
|
||
gl::GL_UNSIGNED_BYTE,
|
||
img.rgba.as_ptr() as *const c_void,
|
||
);
|
||
let err = (self.gl.gl_get_error)();
|
||
if err != gl::GL_NO_ERROR {
|
||
eprintln!("[Live2D] glTexImage2D 错误: 0x{:x}", err);
|
||
}
|
||
tex
|
||
}
|
||
}
|
||
|
||
pub fn swap_buffers(&self) {
|
||
unsafe {
|
||
(self.gl.egl_swap_buffers)(self.display, self.surface);
|
||
}
|
||
}
|
||
|
||
/// 绘制全屏渐变背景(在模型之前调用)。top/bottom 为 RGB,范围 0..1。
|
||
pub fn draw_background(&self, top: [f32; 3], bottom: [f32; 3]) {
|
||
unsafe {
|
||
// 背景不透明,先关闭混合
|
||
(self.gl.gl_disable)(gl::GL_BLEND);
|
||
(self.gl.gl_use_program)(self.grad_program);
|
||
|
||
let verts: [f32; 8] = [
|
||
-1.0, -1.0,
|
||
1.0, -1.0,
|
||
-1.0, 1.0,
|
||
1.0, 1.0,
|
||
];
|
||
let mut vbo = 0;
|
||
(self.gl.gl_gen_buffers)(1, &mut vbo);
|
||
(self.gl.gl_bind_buffer)(gl::GL_ARRAY_BUFFER, vbo);
|
||
(self.gl.gl_buffer_data)(
|
||
gl::GL_ARRAY_BUFFER,
|
||
(verts.len() * 4) as isize,
|
||
verts.as_ptr() as *const c_void,
|
||
gl::GL_STATIC_DRAW,
|
||
);
|
||
(self.gl.gl_enable_vertex_attrib_array)(self.grad_attrib_pos as u32);
|
||
(self.gl.gl_vertex_attrib_pointer)(
|
||
self.grad_attrib_pos as u32,
|
||
2,
|
||
gl::GL_FLOAT,
|
||
gl::GL_FALSE,
|
||
0,
|
||
std::ptr::null(),
|
||
);
|
||
(self.gl.gl_uniform4f)(self.grad_uniform_top, top[0], top[1], top[2], 1.0);
|
||
(self.gl.gl_uniform4f)(self.grad_uniform_bottom, bottom[0], bottom[1], bottom[2], 1.0);
|
||
(self.gl.gl_draw_arrays)(gl::GL_TRIANGLE_STRIP, 0, 4);
|
||
(self.gl.gl_delete_buffers)(1, &vbo);
|
||
|
||
// 恢复混合,供后续模型绘制
|
||
(self.gl.gl_enable)(gl::GL_BLEND);
|
||
}
|
||
}
|
||
|
||
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());
|
||
}
|
||
}
|
||
}
|