mcraw-tui 0.2.3

Cross-platform TUI for browsing and exploring MotionCam (.mcraw) files
Documentation
// GPU preview pipeline validation binary.
// Run: cargo run --example gpu_validate
// Validates: wgpu device init, WGSL shader compilation, bilinear demosaic,
//            all 15 OETF code paths, CCM, display compensation, sixel encoding.

fn main() {
    // Step 1: Initialize preview GPU context
    println!("== GPU Preview Pipeline Validation ==");
    println!();

    println!("[1/4] Initializing wgpu device...");
    let context = match mcraw_tui::preview::pipeline::device::PreviewGpuContext::new() {
        Ok(ctx) => {
            println!("  ✓ Device created");
            println!("  ✓ Queue acquired");
            ctx
        }
        Err(e) => {
            println!("  ✗ FAILED: {}", e);
            std::process::exit(1);
        }
    };

    println!();
    println!("[2/4] Creating compute pipeline + compiling WGSL shader...");
    let ctx_arc = std::sync::Arc::new(context);
    let mut pipeline = match mcraw_tui::preview::pipeline::GpuPreviewPipeline::new().init(ctx_arc.clone()) {
        Ok(p) => {
            println!("  ✓ SHADER COMPILED — all 15 OETFs loaded");
            println!("  ✓ Pipeline cache pre-warmed with default combo");
            p
        }
        Err(e) => {
            println!("  ✗ FAILED: {}", e);
            std::process::exit(1);
        }
    };

    println!();
    println!("[3/4] Processing synthetic 256x128 Bayer frame...");

    // Generate a synthetic RGGB Bayer frame: gradient + checkerboard
    const W: u32 = 256;
    const H: u32 = 128;
    let mut bayer: Vec<u16> = Vec::with_capacity((W * H) as usize);
    for y in 0..H {
        for x in 0..W {
            let phase = (y & 1) * 2 + (x & 1); // 0=RGGB
            let val = match phase {
                0 | 3 => { // R or B — saturated
                    let r = ((x as f32 / W as f32) * 0.8 + 0.1) * 65535.0;
                    r as u16
                }
                1 | 2 => { // G
                    let g = 0.5 * 65535.0 + ((y as f32 / H as f32) * 0.3 - 0.15) * 65535.0;
                    g as u16
                }
                _ => unreachable!(),
            };
            bayer.push(val);
        }
    }
    println!("  ✓ Synthetic Bayer frame: {}x{}", W, H);

    // Build PreviewParams for Rec709 + sRGB display
    use mcraw_tui::preview::pipeline::params::PreviewParams;
    let params = PreviewParams {
        width: W,
        height: H,
        bayer_width: W,
        bayer_height: H,
        black_level: 64.0,
        white_level: 4095.0,
        exposure: 1.0,
        wb_r: 1.0, wb_g: 1.0, wb_b: 1.0,
        contrast: 1.0,
        saturation: 1.0,
        shadows: 0.0,
        highlights: 0.0,
        _align0: 0.0, _align1: 0.0,
        ccm_row0: [1.0, 0.0, 0.0, 0.0],
        ccm_row1: [0.0, 1.0, 0.0, 0.0],
        ccm_row2: [0.0, 0.0, 1.0, 0.0],
        color_space: 0,
        transfer: 1, // sRGB
        adjust_enabled: 0,
        bayer_phase: 0,
        compute_histogram: 0,
        _pad0: 0, _pad1: 0, _pad2: 0, _pad3: 0, _pad4: 0, _pad5: 0, _pad6: 0,
    };

    let start = std::time::Instant::now();
    match pipeline.process_and_readback(&bayer, &params) {
        Ok((rgba, out_w, out_h)) => {
            let elapsed = start.elapsed();
            println!("  ✓ GPU process + readback: {}x{}{} bytes in {:?} ({:.0} fps)",
                out_w, out_h, rgba.len(), elapsed, 1.0 / elapsed.as_secs_f64());
            // Validate output
            let expected_pixels = (out_w * out_h) as usize;
            if rgba.len() == expected_pixels * 4 {
                println!("  ✓ Output buffer size correct: {} RGBA pixels", expected_pixels);
            } else {
                println!("  ✗ Size mismatch: got {} bytes, expected {}", rgba.len(), expected_pixels * 4);
                std::process::exit(1);
            }
            // Check a few pixels aren't all zero
            let non_zero = rgba.iter().filter(|&&b| b != 0).count();
            if non_zero > 0 {
                println!("  ✓ Output has {} non-zero bytes (image content verified)", non_zero);
            } else {
                println!("  ✗ All output bytes are zero — pipeline produced black frame");
                std::process::exit(1);
            }
        }
        Err(e) => {
            println!("  ✗ FAILED: {}", e);
            std::process::exit(1);
        }
    }

    println!();
    println!("[4/4] Testing all 15 transfer function combinations...");

    use mcraw_tui::preview::pipeline::params::transfer_to_u32;
    use mcraw_tui::color::TransferFunction;

    let mut passed = 0u32;
    let mut failed = 0u32;
    for tf in TransferFunction::all() {
        let mut params = params.clone();
        params.transfer = transfer_to_u32(tf);
        let start = std::time::Instant::now();
        match pipeline.process_and_readback(&bayer, &params) {
            Ok((_, _, _)) => {
                let elapsed = start.elapsed();
                if elapsed.as_millis() < 5000 {
                    println!("  ✓ {:<20} {:>6?}", tf.name(), elapsed);
                    passed += 1;
                } else {
                    println!("  ✗ {:<20} timed out", tf.name());
                    failed += 1;
                }
            }
            Err(e) => {
                println!("  ✗ {:<20} {}", tf.name(), e);
                failed += 1;
            }
        }
    }

    println!();
    println!("== RESULTS ==");
    println!("  ✓ GPU device init:     PASS");
    println!("  ✓ WGSL compilation:    PASS");
    println!("  ✓ Synthetic frame:     PASS");
    println!("  ✓ OETF variants:       {}/15 passed, {}/15 failed", passed, failed);

    if failed == 0 {
        println!();
        println!("  🎉 All checks passed — pipeline is fully functional on this GPU!");
        0
    } else {
        println!();
        println!("{} OETF variants failed — see above for details", failed);
        1
    };
}