aetheric-gpu 0.1.0-alpha

Aetheric Silicon: turn this host's RAM into a Digital GPU endpoint
//! Software rasterizer benchmark.
//!
//! Targets: 720p (1280x720) at 30+ fps on the X5680 (no AVX2/FMA path, scalar with AVX1).
//!
//! This benchmarks the foundation: for each pixel, run a fragment-shader-equivalent
//! of ~30-50 f32 ops similar to a real fragment shader in a modern game.

use std::time::Instant;

const WIDTH: usize = 1280;
const HEIGHT: usize = 720;

/// One pixel's fragment shader work.
/// 30-50 f32 ops per pixel, similar to a real Phong-lighting fragment shader.
#[inline(always)]
fn fragment_shader(x: u32, y: u32, frame: u32) -> u8 {
    let x_f = x as f32 / WIDTH as f32;
    let y_f = y as f32 / HEIGHT as f32;
    let t = frame as f32 * 0.01_f32;

    // Lighting inputs
    let nx = x_f * 4.0_f32 + t;
    let ny = y_f * 4.0_f32 + t;
    let nz = x_f * y_f * 8.0_f32;

    // Lambert diffuse
    let lx = 0.4_f32 + 0.6_f32 * t.sin();
    let ly = 0.4_f32 + 0.6_f32 * t.cos();
    let lz = 1.0_f32;
    let lambert = (nx * lx + ny * ly + nz * lz).max(0.0_f32);

    // Half-vector specular approximation
    let vx = (x_f - 0.5_f32) * 2.0_f32;
    let vy = (y_f - 0.5_f32) * 2.0_f32;
    let vz = 1.0_f32;
    let v_norm = (vx * vx + vy * vy + vz * vz).sqrt();
    let spec = (vx * lx + vy * ly + vz * lz) / v_norm;

    let intensity = (lambert * 0.7_f32 + spec * 0.3_f32).max(0.0_f32);
    (intensity * 255.0_f32) as u8
}

/// Render one frame (scalar — what we have on Westmere).
#[inline(never)]
fn render_frame(buf: &mut [u8], frame: u32) {
    let mut off = 0;
    for y in 0..HEIGHT {
        for x in 0..WIDTH {
            let r = fragment_shader(x as u32, y as u32, frame);
            buf[off] = r;
            buf[off + 1] = r;
            buf[off + 2] = r;
            buf[off + 3] = 255;
            off += 4;
        }
    }
}

fn main() {
    println!("Aetheric Silicon — Software Rasterizer Benchmark");
    println!("Target: {}x{} (720p) at >= 30 fps on X5680\n", WIDTH, HEIGHT);

    let mut buf = vec![0u8; WIDTH * HEIGHT * 4];
    println!("Frame buffer: {} MiB", buf.len() / (1024 * 1024));

    // Warm-up
    for f in 0..3 {
        render_frame(&mut buf, f);
    }

    let n_frames = 30;
    let start = Instant::now();
    for f in 3..(3 + n_frames) {
        render_frame(&mut buf, f);
    }
    let elapsed = start.elapsed();

    let ms_per_frame = elapsed.as_secs_f64() * 1000.0 / n_frames as f64;
    let fps = n_frames as f64 / elapsed.as_secs_f64();
    let mpix_s = (WIDTH * HEIGHT) as f64 * fps / 1e6;

    println!("--- RESULTS ---");
    println!("Time / frame:        {:.2} ms ({:.1} fps)", ms_per_frame, fps);
    println!("Pixels / sec:        {:.1} Mpixels/s", mpix_s);
    println!("Total bench time:    {:.2} seconds", elapsed.as_secs_f64());

    println!("\n--- COMPARISON ---");
    let target_fps = 30.0_f64;
    if fps >= target_fps {
        println!("[v] HIT the 30 fps 720p target on X5680");
    } else {
        println!("[x] below 30 fps target (got {:.1} fps)", fps);
    }

    // dGPU on 720p60 = 1920x1080x60  ~ 125 Mpixels/s at fragment rate
    let dgpu_1080_p60 = 1920.0 * 1080.0 * 60.0;
    let our_pix = (WIDTH * HEIGHT) as f64 * fps;
    println!("\nWe deliver {:.0} Mpixels/s", mpix_s);
    println!("vs ~125 Mpixels/s for dGPU at 1080p60 = {:.2}x ratio", mpix_s * 1e6 / dgpu_1080_p60);
}