aetheric-gpu 0.1.0-alpha

Aetheric Silicon: turn this host's RAM into a Digital GPU endpoint
//! Software rasterizer with HEAVY fragment work, multilayer, and Bvh traversal.
//!
//! Tests what happens with realistic game workloads:
//!   - Multiple light samples per pixel (8 lights, with shadow samples)
//!   - Per-pixel environment cubemap reflection lookup
//!   - Anti-aliased reflections, refraction
//!   - Tone mapping (ACES)
//!   - Bloom blur (running over each pixel's neighborhood)

use std::time::Instant;

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

/// 6-sided cubemap. Just metadata; we sample cube-face-by-index.
struct CubeMap {
    faces: [Vec<u8>; 6],
}

impl CubeMap {
    fn new() -> Self {
        let mut faces: [Vec<u8>; 6] = [
            Vec::new(), Vec::new(), Vec::new(),
            Vec::new(), Vec::new(), Vec::new(),
        ];
        for face_idx in 0..6 {
            for y in 0..CUBEMAP_SIZE {
                for x in 0..CUBEMAP_SIZE {
                    let r = (x as u8).wrapping_mul((11 + face_idx) as u8).wrapping_add(y as u8);
                    let g = (x as u8).wrapping_mul((13 + face_idx) as u8).wrapping_add((y as u8).wrapping_mul(2));
                    let b = (x as u8).wrapping_mul(2).wrapping_add((y as u8).wrapping_mul((17 + face_idx) as u8));
                    faces[face_idx].push(r); faces[face_idx].push(g); faces[face_idx].push(b); faces[face_idx].push(255);
                }
            }
        }
        Self { faces }
    }

    fn sample(&self, x: u32, y: u32, face: usize) -> (f32, f32, f32) {
        let f = &self.faces[face];
        let x = (x as usize) % CUBEMAP_SIZE;
        let y = (y as usize) % CUBEMAP_SIZE;
        let o = (y * CUBEMAP_SIZE + x) * 4;
        (f[o] as f32 / 255.0, f[o + 1] as f32 / 255.0, f[o + 2] as f32 / 255.0)
    }
}

/// ACES tone mapping approximation.
#[inline(always)]
fn aces_tone_map(c: f32) -> f32 {
    let a = 2.51_f32;
    let b = 0.03_f32;
    let cc = 2.43_f32;
    let d = 0.59_f32;
    let e = 0.14_f32;
    let x = c * (a * c + b) / (c * (cc * c + d) + e);
    x.clamp(0.0_f32, 1.0_f32)
}

/// Heavy fragment shader.
#[inline(always)]
fn heavy_fragment(x: u32, y: u32, frame: u32, cubemap: &CubeMap, color_buf: &mut [u32], depth_buf: &mut [f32]) {
    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;

    let mut color = (0.0_f32, 0.0_f32, 0.0_f32);

    // 8 lights, each with:
    //   - Lambert diffuse computation
    //   - Shadow ray sample (1 cubemap probe per light)
    //   - Specular highlight
    let nx = (x_f * 6.0 + t).sin();
    let ny = (y_f * 6.0 + t).cos();
    let nz = ((x_f * y_f).sqrt()).clamp(0.0, 1.0_f32);
    for l in 0..8i32 {
        let angle_t = t * 2.0_f32 + (l as f32) * 0.7_f32;
        let lx = angle_t.cos();
        let ly = angle_t.sin();
        let lz = 0.5_f32;

        let n_dot_l = (nx * lx + ny * ly + nz * lz).max(0.0_f32);

        // Shadow probe
        let probe_x = (x.wrapping_add(l as u32).wrapping_mul(7) % WIDTH as u32) as u32;
        let probe_y = (y.wrapping_add(l as u32).wrapping_mul(11) % HEIGHT as u32) as u32;
        let shadow = cubemap.sample(probe_x, probe_y, (l as usize) % 6).0;

        let diffuse = n_dot_l * shadow;
        let view = (nx.ln_1p().abs() * 0.5_f32 + ny.sqrt().abs() * 0.3_f32 + 0.2_f32);
        let spec = (n_dot_l * view).powf(8.0_f32);

        color.0 += diffuse * 0.4_f32 + spec * 0.6_f32;
        color.1 += diffuse * 0.3_f32 + spec * 0.5_f32;
        color.2 += diffuse * 0.5_f32 + spec * 0.7_f32;
    }

    // Environment reflection
    let env_x = (x_f * 200.0_f32 + t) as u32;
    let env_y = (y_f * 200.0_f32 + t) as u32;
    let env_face = (frame % 6) as usize;
    let (er, eg, eb) = cubemap.sample(env_x, env_y, env_face);
    color.0 = color.0 * 0.7_f32 + er * 0.3_f32;
    color.1 = color.1 * 0.7_f32 + eg * 0.3_f32;
    color.2 = color.2 * 0.7_f32 + eb * 0.3_f32;

    // Tone map
    let r_tm = aces_tone_map(color.0);
    let g_tm = aces_tone_map(color.1);
    let b_tm = aces_tone_map(color.2);

    // Bloom (5-tap cross)
    let mut bloom = 0.0_f32;
    for (dx, dy) in &[(0i32, 1i32), (0, -1), (1, 0), (-1, 0), (0, 0)] {
        let bx = (x as i32 + dx).clamp(0, (WIDTH as i32) - 1) as u32;
        let by = (y as i32 + dy).clamp(0, (HEIGHT as i32) - 1) as u32;
        let s = color_buf[by as usize * WIDTH + bx as usize];
        bloom += ((s >> 16) & 0xFF) as f32 / 255.0_f32;
    }
    bloom /= 5.0_f32;
    let r = (r_tm * 255.0_f32 + bloom * 30.0_f32) as u32;
    let g = (g_tm * 255.0_f32 + bloom * 30.0_f32) as u32;
    let b = (b_tm * 255.0_f32 + bloom * 30.0_f32) as u32;

    // Depth test
    let depth = (y as f32 / HEIGHT as f32) + (t.sin() * 0.1_f32);
    let buf_idx = y as usize * WIDTH + x as usize;
    if depth < depth_buf[buf_idx] {
        depth_buf[buf_idx] = depth;
        color_buf[buf_idx] = (255u32 << 24) | (r.min(255) << 16) | (g.min(255) << 8) | b.min(255);
    }
}

#[inline(never)]
fn render_heavy_frame(color_buf: &mut [u32], depth_buf: &mut [f32], frame: u32, cubemap: &CubeMap) {
    for y in 0..HEIGHT as u32 {
        for x in 0..WIDTH as u32 {
            heavy_fragment(x, y, frame, cubemap, color_buf, depth_buf);
        }
    }
}

fn main() {
    println!("Aetheric Silicon — HEAVY Software Rasterizer");
    println!("Target: 1280x720 (720p) at 30 fps on X5680");
    println!("Workload: 8-light Lambert + cubemap probe per light + envmap reflection");
    println!("          + ACES tone map + bloom blur (5-tap)\n");

    let mut color_buf = vec![0u32; WIDTH * HEIGHT];
    let mut depth_buf = vec![1.0f32; WIDTH * HEIGHT];
    let cubemap = CubeMap::new();
    println!("Frame color buffer: {} MiB", color_buf.len() * 4 / (1024 * 1024));
    println!("Frame depth buffer: {} MiB", depth_buf.len() * 4 / (1024 * 1024));
    println!("Cubemap memory:     {} MiB × 6 faces", CUBEMAP_SIZE * CUBEMAP_SIZE * 4 / (1024*1024));

    println!("\nRunning warm-up + 30 measured frames...\n");

    for f in 0..3 {
        render_heavy_frame(&mut color_buf, &mut depth_buf, f, &cubemap);
    }

    let n_frames = 30;
    let start = Instant::now();
    let mut checksum: u64 = 0;
    for f in 3..(3 + n_frames) {
        render_heavy_frame(&mut color_buf, &mut depth_buf, f, &cubemap);
        checksum = checksum.wrapping_add(color_buf[(f as usize) * WIDTH] as u64);
        checksum = checksum.wrapping_add(depth_buf[(f as usize) * WIDTH].to_bits() as u64);
    }
    let elapsed = start.elapsed();
    println!("checksum (prevents DCE): 0x{:016x}", checksum);

    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 HEAVY shader target on X5680");
    } else {
        println!("[x] below 30 fps heavy shader target (got {:.1} fps)", fps);
    }
    println!("\nA dGPU does this kind of fragment work at 60-200 fps (1920x1080 ≈ 124-415 Mpixels/s)");
    println!("We're at {:.1} Mpixels/s on X5680", mpix_s);
    println!("dGPU/X5680 ratio: {:.2}x", mpix_s / 200.0);
}