par-term-render 0.7.2

GPU-accelerated rendering engine for par-term terminal emulator
Documentation
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum BuiltinTextureKind {
    Value,
    Fbm,
    Cellular,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct BuiltinTextureSpec {
    pub kind: BuiltinTextureKind,
    pub size: u32,
}

pub(crate) struct GeneratedTexture {
    pub width: u32,
    pub height: u32,
    pub pixels: Vec<u8>,
}

impl BuiltinTextureSpec {
    pub(crate) fn parse(value: &str) -> Result<Self, String> {
        let id = value
            .strip_prefix("builtin://noise/")
            .ok_or_else(|| format!("not a built-in noise texture: {value}"))?;
        let (kind, size) = match id {
            "value-128" => (BuiltinTextureKind::Value, 128),
            "value-256" => (BuiltinTextureKind::Value, 256),
            "fbm-256" => (BuiltinTextureKind::Fbm, 256),
            "fbm-512" => (BuiltinTextureKind::Fbm, 512),
            "cellular-256" => (BuiltinTextureKind::Cellular, 256),
            _ => return Err(format!("unknown built-in texture: {value}")),
        };
        Ok(Self { kind, size })
    }

    pub(crate) fn generate_rgba8(self) -> GeneratedTexture {
        let mut pixels = Vec::with_capacity((self.size * self.size * 4) as usize);
        for y in 0..self.size {
            for x in 0..self.size {
                let value = match self.kind {
                    BuiltinTextureKind::Value => value_noise(x, y),
                    BuiltinTextureKind::Fbm => fbm_noise(x, y),
                    BuiltinTextureKind::Cellular => cellular_noise(x, y, self.size),
                };
                pixels.extend_from_slice(&[value, value, value, 255]);
            }
        }
        GeneratedTexture {
            width: self.size,
            height: self.size,
            pixels,
        }
    }
}

fn hash2(x: u32, y: u32) -> u32 {
    let mut n = x.wrapping_mul(0x9E37_79B9) ^ y.wrapping_mul(0x85EB_CA6B);
    n ^= n >> 16;
    n = n.wrapping_mul(0x7FEB_352D);
    n ^= n >> 15;
    n = n.wrapping_mul(0x846C_A68B);
    n ^ (n >> 16)
}

fn value_noise(x: u32, y: u32) -> u8 {
    (hash2(x, y) & 0xff) as u8
}

fn fbm_noise(x: u32, y: u32) -> u8 {
    let mut total = 0.0f32;
    let mut amplitude = 0.5f32;
    let mut frequency = 1u32;
    for _ in 0..5 {
        total += value_noise(x / frequency, y / frequency) as f32 / 255.0 * amplitude;
        amplitude *= 0.5;
        frequency *= 2;
    }
    (total.clamp(0.0, 1.0) * 255.0).round() as u8
}

fn cellular_noise(x: u32, y: u32, size: u32) -> u8 {
    let cell = (size / 16).max(8);
    let cx = (x / cell) as i32;
    let cy = (y / cell) as i32;
    let mut best = f32::MAX;

    for oy in -1..=1 {
        for ox in -1..=1 {
            let nx = (cx + ox).max(0) as u32;
            let ny = (cy + oy).max(0) as u32;
            let hash = hash2(nx, ny);
            let px = nx * cell + (hash & 0xff) % cell;
            let py = ny * cell + ((hash >> 8) & 0xff) % cell;
            let dx = x as f32 - px as f32;
            let dy = y as f32 - py as f32;
            best = best.min((dx * dx + dy * dy).sqrt());
        }
    }

    ((best / cell as f32).clamp(0.0, 1.0) * 255.0).round() as u8
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parses_supported_builtin_noise_ids() {
        let cases = [
            ("builtin://noise/value-128", BuiltinTextureKind::Value, 128),
            ("builtin://noise/value-256", BuiltinTextureKind::Value, 256),
            ("builtin://noise/fbm-256", BuiltinTextureKind::Fbm, 256),
            ("builtin://noise/fbm-512", BuiltinTextureKind::Fbm, 512),
            (
                "builtin://noise/cellular-256",
                BuiltinTextureKind::Cellular,
                256,
            ),
        ];

        for (id, kind, size) in cases {
            let spec = BuiltinTextureSpec::parse(id).unwrap();
            assert_eq!(spec.kind, kind);
            assert_eq!(spec.size, size);
        }
    }

    #[test]
    fn rejects_unknown_builtin_noise_id() {
        let err = BuiltinTextureSpec::parse("builtin://noise/marble-256").unwrap_err();
        assert!(err.contains("unknown built-in texture"));
    }

    #[test]
    fn generated_builtin_noise_is_deterministic() {
        let spec = BuiltinTextureSpec::parse("builtin://noise/value-128").unwrap();
        let a = spec.generate_rgba8();
        let b = spec.generate_rgba8();
        assert_eq!(a.width, 128);
        assert_eq!(a.height, 128);
        assert_eq!(a.pixels, b.pixels);
    }
}