Skip to main content

gpu_validate/
gpu_validate.rs

1// GPU preview pipeline validation binary.
2// Run: cargo run --example gpu_validate
3// Validates: wgpu device init, WGSL shader compilation, bilinear demosaic,
4//            all 15 OETF code paths, CCM, display compensation, sixel encoding.
5
6fn main() {
7    // Step 1: Initialize preview GPU context
8    println!("== GPU Preview Pipeline Validation ==");
9    println!();
10
11    println!("[1/4] Initializing wgpu device...");
12    let context = match mcraw_tui::preview::pipeline::device::PreviewGpuContext::new() {
13        Ok(ctx) => {
14            println!("  ✓ Device created");
15            println!("  ✓ Queue acquired");
16            ctx
17        }
18        Err(e) => {
19            println!("  ✗ FAILED: {}", e);
20            std::process::exit(1);
21        }
22    };
23
24    println!();
25    println!("[2/4] Creating compute pipeline + compiling WGSL shader...");
26    let ctx_arc = std::sync::Arc::new(context);
27    let mut pipeline = match mcraw_tui::preview::pipeline::GpuPreviewPipeline::new().init(ctx_arc.clone()) {
28        Ok(p) => {
29            println!("  ✓ SHADER COMPILED — all 15 OETFs loaded");
30            println!("  ✓ Pipeline cache pre-warmed with default combo");
31            p
32        }
33        Err(e) => {
34            println!("  ✗ FAILED: {}", e);
35            std::process::exit(1);
36        }
37    };
38
39    println!();
40    println!("[3/4] Processing synthetic 256x128 Bayer frame...");
41
42    // Generate a synthetic RGGB Bayer frame: gradient + checkerboard
43    const W: u32 = 256;
44    const H: u32 = 128;
45    let mut bayer: Vec<u16> = Vec::with_capacity((W * H) as usize);
46    for y in 0..H {
47        for x in 0..W {
48            let phase = (y & 1) * 2 + (x & 1); // 0=RGGB
49            let val = match phase {
50                0 | 3 => { // R or B — saturated
51                    let r = ((x as f32 / W as f32) * 0.8 + 0.1) * 65535.0;
52                    r as u16
53                }
54                1 | 2 => { // G
55                    let g = 0.5 * 65535.0 + ((y as f32 / H as f32) * 0.3 - 0.15) * 65535.0;
56                    g as u16
57                }
58                _ => unreachable!(),
59            };
60            bayer.push(val);
61        }
62    }
63    println!("  ✓ Synthetic Bayer frame: {}x{}", W, H);
64
65    // Build PreviewParams for Rec709 + sRGB display
66    use mcraw_tui::preview::pipeline::params::PreviewParams;
67    let params = PreviewParams {
68        width: W,
69        height: H,
70        bayer_width: W,
71        bayer_height: H,
72        black_level: 64.0,
73        white_level: 4095.0,
74        exposure: 1.0,
75        wb_r: 1.0, wb_g: 1.0, wb_b: 1.0,
76        contrast: 1.0,
77        saturation: 1.0,
78        shadows: 0.0,
79        highlights: 0.0,
80        _align0: 0.0, _align1: 0.0,
81        ccm_row0: [1.0, 0.0, 0.0, 0.0],
82        ccm_row1: [0.0, 1.0, 0.0, 0.0],
83        ccm_row2: [0.0, 0.0, 1.0, 0.0],
84        color_space: 0,
85        transfer: 1, // sRGB
86        adjust_enabled: 0,
87        bayer_phase: 0,
88        compute_histogram: 0,
89        _pad0: 0, _pad1: 0, _pad2: 0, _pad3: 0, _pad4: 0, _pad5: 0, _pad6: 0,
90    };
91
92    let start = std::time::Instant::now();
93    match pipeline.process_and_readback(&bayer, &params) {
94        Ok((rgba, out_w, out_h)) => {
95            let elapsed = start.elapsed();
96            println!("  ✓ GPU process + readback: {}x{} → {} bytes in {:?} ({:.0} fps)",
97                out_w, out_h, rgba.len(), elapsed, 1.0 / elapsed.as_secs_f64());
98            // Validate output
99            let expected_pixels = (out_w * out_h) as usize;
100            if rgba.len() == expected_pixels * 4 {
101                println!("  ✓ Output buffer size correct: {} RGBA pixels", expected_pixels);
102            } else {
103                println!("  ✗ Size mismatch: got {} bytes, expected {}", rgba.len(), expected_pixels * 4);
104                std::process::exit(1);
105            }
106            // Check a few pixels aren't all zero
107            let non_zero = rgba.iter().filter(|&&b| b != 0).count();
108            if non_zero > 0 {
109                println!("  ✓ Output has {} non-zero bytes (image content verified)", non_zero);
110            } else {
111                println!("  ✗ All output bytes are zero — pipeline produced black frame");
112                std::process::exit(1);
113            }
114        }
115        Err(e) => {
116            println!("  ✗ FAILED: {}", e);
117            std::process::exit(1);
118        }
119    }
120
121    println!();
122    println!("[4/4] Testing all 15 transfer function combinations...");
123
124    use mcraw_tui::preview::pipeline::params::transfer_to_u32;
125    use mcraw_tui::color::TransferFunction;
126
127    let mut passed = 0u32;
128    let mut failed = 0u32;
129    for tf in TransferFunction::all() {
130        let mut params = params.clone();
131        params.transfer = transfer_to_u32(tf);
132        let start = std::time::Instant::now();
133        match pipeline.process_and_readback(&bayer, &params) {
134            Ok((_, _, _)) => {
135                let elapsed = start.elapsed();
136                if elapsed.as_millis() < 5000 {
137                    println!("  ✓ {:<20} {:>6?}", tf.name(), elapsed);
138                    passed += 1;
139                } else {
140                    println!("  ✗ {:<20} timed out", tf.name());
141                    failed += 1;
142                }
143            }
144            Err(e) => {
145                println!("  ✗ {:<20} {}", tf.name(), e);
146                failed += 1;
147            }
148        }
149    }
150
151    println!();
152    println!("== RESULTS ==");
153    println!("  ✓ GPU device init:     PASS");
154    println!("  ✓ WGSL compilation:    PASS");
155    println!("  ✓ Synthetic frame:     PASS");
156    println!("  ✓ OETF variants:       {}/15 passed, {}/15 failed", passed, failed);
157
158    if failed == 0 {
159        println!();
160        println!("  🎉 All checks passed — pipeline is fully functional on this GPU!");
161        0
162    } else {
163        println!();
164        println!("  ⚠ {} OETF variants failed — see above for details", failed);
165        1
166    };
167}