Skip to main content

oxihuman_morph/
gpu_morph_target.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! GPU morph target upload stub.
6
7/// Upload state for a GPU morph target buffer.
8#[derive(Debug, Clone, Copy, PartialEq)]
9pub enum GpuUploadState {
10    Pending,
11    Uploaded,
12    Dirty,
13    Failed,
14}
15
16/// GPU morph target descriptor.
17#[derive(Debug, Clone)]
18pub struct GpuMorphTarget {
19    pub name: String,
20    pub vertex_count: usize,
21    pub gpu_buffer_id: u64,
22    pub upload_state: GpuUploadState,
23    pub weight: f32,
24    pub enabled: bool,
25}
26
27impl GpuMorphTarget {
28    pub fn new(name: impl Into<String>, vertex_count: usize) -> Self {
29        GpuMorphTarget {
30            name: name.into(),
31            vertex_count,
32            gpu_buffer_id: 0,
33            upload_state: GpuUploadState::Pending,
34            weight: 0.0,
35            enabled: true,
36        }
37    }
38}
39
40/// Create a new GPU morph target.
41pub fn new_gpu_morph_target(name: impl Into<String>, vertex_count: usize) -> GpuMorphTarget {
42    GpuMorphTarget::new(name, vertex_count)
43}
44
45/// Simulate uploading data to the GPU (stub: sets state to Uploaded).
46pub fn gmt_upload(target: &mut GpuMorphTarget, _data: &[[f32; 3]]) {
47    /* Stub: mark as uploaded; no actual GPU call */
48    target.upload_state = GpuUploadState::Uploaded;
49    target.gpu_buffer_id = target.vertex_count as u64 * 7919;
50}
51
52/// Mark the target as dirty (needs re-upload).
53pub fn gmt_mark_dirty(target: &mut GpuMorphTarget) {
54    target.upload_state = GpuUploadState::Dirty;
55}
56
57/// Set the blend weight.
58pub fn gmt_set_weight(target: &mut GpuMorphTarget, weight: f32) {
59    target.weight = weight.clamp(0.0, 1.0);
60}
61
62/// Enable or disable.
63pub fn gmt_set_enabled(target: &mut GpuMorphTarget, enabled: bool) {
64    target.enabled = enabled;
65}
66
67/// Serialize to JSON-like string.
68pub fn gmt_to_json(target: &GpuMorphTarget) -> String {
69    let state = match target.upload_state {
70        GpuUploadState::Pending => "pending",
71        GpuUploadState::Uploaded => "uploaded",
72        GpuUploadState::Dirty => "dirty",
73        GpuUploadState::Failed => "failed",
74    };
75    format!(
76        r#"{{"name":"{}","vertex_count":{},"state":"{}","weight":{},"enabled":{}}}"#,
77        target.name, target.vertex_count, state, target.weight, target.enabled
78    )
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn test_initial_state_pending() {
87        let t = new_gpu_morph_target("mouth_open", 256);
88        assert_eq!(
89            t.upload_state,
90            GpuUploadState::Pending, /* must start as Pending */
91        );
92    }
93
94    #[test]
95    fn test_upload_changes_state() {
96        let mut t = new_gpu_morph_target("x", 4);
97        gmt_upload(&mut t, &[[0.0; 3]; 4]);
98        assert_eq!(
99            t.upload_state,
100            GpuUploadState::Uploaded, /* state must be Uploaded after upload */
101        );
102    }
103
104    #[test]
105    fn test_upload_sets_buffer_id() {
106        let mut t = new_gpu_morph_target("x", 4);
107        gmt_upload(&mut t, &[[0.0; 3]; 4]);
108        assert_ne!(
109            t.gpu_buffer_id,
110            0, /* buffer id must be non-zero after upload */
111        );
112    }
113
114    #[test]
115    fn test_mark_dirty() {
116        let mut t = new_gpu_morph_target("x", 4);
117        gmt_upload(&mut t, &[[0.0; 3]; 4]);
118        gmt_mark_dirty(&mut t);
119        assert_eq!(
120            t.upload_state,
121            GpuUploadState::Dirty, /* state must be Dirty after mark */
122        );
123    }
124
125    #[test]
126    fn test_set_weight_clamped() {
127        let mut t = new_gpu_morph_target("x", 2);
128        gmt_set_weight(&mut t, 1.5);
129        assert!((t.weight - 1.0).abs() < 1e-6, /* weight must be clamped to 1.0 */);
130    }
131
132    #[test]
133    fn test_set_weight_zero() {
134        let mut t = new_gpu_morph_target("x", 2);
135        gmt_set_weight(&mut t, -0.5);
136        assert!((t.weight).abs() < 1e-6, /* weight must be clamped to 0.0 */);
137    }
138
139    #[test]
140    fn test_set_enabled() {
141        let mut t = new_gpu_morph_target("x", 2);
142        gmt_set_enabled(&mut t, false);
143        assert!(!t.enabled /* must be disabled */,);
144    }
145
146    #[test]
147    fn test_to_json_contains_state() {
148        let t = new_gpu_morph_target("brow", 10);
149        let j = gmt_to_json(&t);
150        assert!(j.contains("\"state\"") /* json must contain state */,);
151    }
152
153    #[test]
154    fn test_vertex_count_stored() {
155        let t = new_gpu_morph_target("v", 512);
156        assert_eq!(t.vertex_count, 512 /* vertex count must match */,);
157    }
158
159    #[test]
160    fn test_enabled_default() {
161        let t = new_gpu_morph_target("v", 1);
162        assert!(t.enabled /* must be enabled by default */,);
163    }
164}