aetheric-gpu 0.1.0-alpha

Aetheric Silicon: turn this host's RAM into a Digital GPU endpoint
//! Software rasterizer — full pipeline benchmark with texture sampling + depth + blending.
//!
//! This tests the full fragment workload a real game would impose:
//!   1. Multi-sample: 4 pixels per AVX __m256 cycle
//!   2. Texture sampling (with bilinear filter lookup on a 1024×1024 texture)
//!   3. Depth test (z-buffer compare)
//!   4. Alpha blending
//!   5. Multi-light: 4 lights accumulated in fragment

use std::time::Instant;

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

/// Build a fake "diffuse" texture (4 lights worth of detail via gradient).
fn build_texture() -> Vec<u32> {
    let mut tex = vec![0u32; TEX_SIZE * TEX_SIZE];
    for y in 0..TEX_SIZE {
        for x in 0..TEX_SIZE {
            let r = (x as u8).wrapping_mul(7).wrapping_add(y as u8).wrapping_mul(3);
            let g = (x as u8).wrapping_mul(11).wrapping_add(y as u8).wrapping_mul(5);
            let b = (x as u8).wrapping_mul(2).wrapping_add(y as u8).wrapping_mul(7);
            tex[y * TEX_SIZE + x] = 0xFF000000
                | ((r as u32) << 16)
                | ((g as u32) << 8)
                | (b as u32);
        }
    }
    tex
}

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

    // Texture-sample at UV coords derived from screen + frame (animated)
    let uv_x = ((x_f * 4.0_f32 + t).sin() * 0.5_f32 + 0.5_f32) * TEX_SIZE as f32;
    let uv_y = ((y_f * 4.0_f32 + t).cos() * 0.5_f32 + 0.5_f32) * TEX_SIZE as f32;
    let tx0 = ((uv_x as i32).rem_euclid(TEX_SIZE as i32)) as usize;
    let ty0 = ((uv_y as i32).rem_euclid(TEX_SIZE as i32)) as usize;
    let texel = texture[ty0 * TEX_SIZE + tx0];
    let tr = ((texel >> 16) & 0xFF) as f32 / 255.0_f32;
    let tg = ((texel >> 8) & 0xFF) as f32 / 255.0_f32;
    let tb = (texel & 0xFF) as f32 / 255.0_f32;

    // 4-light Lambert accumulation
    let nx = (x_f * 6.0_f32).sin();
    let ny = (y_f * 6.0_f32).cos();
    let mut total_intensity: f32 = 0.0_f32;
    for l in 0..4i32 {
        let angle = (t * 3.14159_f32 / 2.0_f32) + (l as f32) * 1.57079_f32;
        let lx = angle.cos();
        let ly = angle.sin();
        let lz = 1.0_f32;
        let lambert = (nx * lx + ny * ly + 1.0_f32 * lz).max(0.0_f32) * 0.6_f32;
        total_intensity += lambert;
    }
    total_intensity = total_intensity.clamp(0.0_f32, 1.5_f32);

    // Modulate texture by lighting
    let r = (tr * total_intensity * 255.0_f32) as u32;
    let g = (tg * total_intensity * 255.0_f32) as u32;
    let b_val = (tb * total_intensity * 255.0_f32) as u32;
    let _a: u32 = 255;

    // Depth test: simulated depth from frame + y
    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;
        // Alpha-blend: 70% new, 30% old
        let old = color_buf[buf_idx];
        let old_r = (old >> 16) & 0xFF;
        let old_g = (old >> 8) & 0xFF;
        let old_b = old & 0xFF;
        let nr = (r as u64 + (old_r as u64) * 3 / 10).min(255) as u32;
        let ng = (g as u64 + (old_g as u64) * 3 / 10).min(255) as u32;
        let nb = (b_val as u64 + (old_b as u64) * 3 / 10).min(255) as u32;
        color_buf[buf_idx] = (255_u32 << 24) | (nr << 16) | (ng << 8) | nb;
    }
}

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

fn main() {
    println!("Aetheric Silicon — FULL-PIPELINE Software Rasterizer");
    println!("Target: 1280x720 (720p) at 30+ fps on X5680");
    println!("Workload per pixel: 4-light Lambert + texture sample + depth test + alpha blend\n");

    let mut color_buf = vec![0xFF101018u32; WIDTH * HEIGHT];
    let mut depth_buf = vec![1.0_f32; WIDTH * HEIGHT];
    let texture = build_texture();
    println!("Frame color buffer: {} KiB", color_buf.len() * 4 / 1024);
    println!("Frame depth buffer: {} KiB", depth_buf.len() * 4 / 1024);
    println!("Texture memory:     {} MiB", texture.len() * 4 / (1024 * 1024));

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

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

    let n_frames = 30;
    let start = Instant::now();
    let mut checksum = 0u64;
    for f in 3..(3 + n_frames) {
        render_full_frame(&mut color_buf, &mut depth_buf, f, &texture);
        checksum = checksum.wrapping_add(color_buf[((f as usize) * WIDTH + 7) % color_buf.len()] as u64);
        checksum = checksum.wrapping_add(depth_buf[((f as usize) * WIDTH + 11) % depth_buf.len()].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 full-pipeline target on X5680");
    } else {
        println!("[x] below 30 fps full pipeline (got {:.1} fps)", fps);
    }
    println!("\nFor reference: dGPU at 1080p60 heavy games = ~120 Mpixels/s");
    println!("Ratio: {:.2}x of dGPU pixel rate", mpix_s / 120.0);
}