mirage-engine 0.2.0

Mirage, an immediate-mode 3D engine for simple games on desktop and the browser
Documentation
//! What `#[derive(ShaderValues)]` declares and lays out, seen the way a game
//! sees it: through a style that reads the values, and an effect that reads
//! none.

use mirage_engine::prelude::*;

meshes! { enum Only { Quad } }

/// Values of every kind a shader reads, in an order that pads three of them
/// and packs one into the lane a triple leaves free.
#[derive(Default, ShaderValues)]
struct Ripples {
    height: f32,
    center: Vec2,
    tint: Color,
    axis: Vec3,
    speed: f32,
    turn: Mat4,
    steps: u32,
}

/// A style that reads them, which is what proves the derive fills the seat.
impl SurfaceStyle for Ripples {
    const PASS: DrawPass = DrawPass::Translucent;
    const SURFACE: Option<&'static str> =
        Some("fn surface(surface: Surface) -> Surface { return surface; }");
}

/// An effect that reads no values at all, which a unit struct is for.
#[derive(ShaderValues)]
struct Grain;

impl PostEffect for Grain {
    const STAGE: EffectStage = EffectStage::ToneMapped;
    const SHADER: &'static str = "fn draw(pixel: Pixel) -> vec4<f32> { return pixel.color; }";
}

surface_styles! { enum Looks { Ripples } }
post_effects! { enum Over { Grain } }

/// A game drawing with both, which is what proves the sets take them.
struct Sea;

impl Game for Sea {
    type Meshes = Only;
    type Sounds = NoSounds;
    type InputActions = NoInputActions;
    type Skyboxes = NoSkyboxes;
    type SurfaceStyles = Looks;
    type PostEffects = Over;

    fn tick(&mut self, _ctx: &mut TickContext<'_, Sea>) {}

    fn frame(&mut self, ctx: &mut FrameContext<'_, Sea>) {
        ctx.set_surface_style(Ripples::default());
        ctx.set_post_effect(Grain);
        ctx.draw(Quad.at(Vec3::ZERO).surface_style::<Ripples>());
    }
}

/// The values written out, which is what the shader reads at those bytes.
fn written(values: &Ripples) -> Vec<u8> {
    let mut into = Vec::new();
    values.write(&mut into);
    into
}

fn number(written: &[u8], at: usize) -> f32 {
    f32::from_le_bytes(written[at..at + 4].try_into().expect("four bytes"))
}

fn count(written: &[u8], at: usize) -> u32 {
    u32::from_le_bytes(written[at..at + 4].try_into().expect("four bytes"))
}

#[test]
fn every_field_is_declared_by_its_own_name_in_the_order_it_was_written() {
    assert_eq!(Ripples::TYPE, "Ripples");
    assert_eq!(
        Ripples::DECLARATION,
        "struct Ripples {\n    \
             height: f32,\n    \
             center: vec2<f32>,\n    \
             tint: vec4<f32>,\n    \
             axis: vec3<f32>,\n    \
             speed: f32,\n    \
             turn: mat4x4<f32>,\n    \
             steps: u32,\n\
         }"
    );
}

#[test]
fn every_value_is_laid_out_where_the_shader_reads_it() {
    let values = Ripples {
        height: 1.5,
        center: Vec2::new(2.0, 3.0),
        tint: Color::rgba(0.1, 0.2, 0.3, 0.4),
        axis: Vec3::new(4.0, 5.0, 6.0),
        speed: 7.0,
        turn: Mat4::from_scale(Vec3::splat(8.0)),
        steps: 9,
    };
    let written = written(&values);

    assert_eq!(written.len(), 128, "and the whole is a round block of them");
    assert_eq!(number(&written, 0), 1.5);
    assert_eq!(
        (number(&written, 8), number(&written, 12)),
        (2.0, 3.0),
        "a pair starts at the next pair of lanes"
    );
    assert_eq!(number(&written, 16), 0.1, "and a color at the next block");
    assert_eq!(number(&written, 28), 0.4);
    assert_eq!(number(&written, 32), 4.0);
    assert_eq!(
        number(&written, 44),
        7.0,
        "a number packs into the lane a triple leaves free"
    );
    assert_eq!(number(&written, 48), 8.0, "and a matrix reads column first");
    assert_eq!(number(&written, 52), 0.0);
    assert_eq!(count(&written, 112), 9, "a count reads its own four bytes");
}

#[test]
fn the_values_a_style_reads_before_a_frame_writes_any_are_the_defaults() {
    let written = written(&Ripples::default());

    assert_eq!(written.len(), 128);
    assert!(
        written[..48].iter().all(|&byte| byte == 0),
        "every number of them reads zero"
    );
    assert_eq!(
        (number(&written, 48), number(&written, 108)),
        (1.0, 1.0),
        "and the matrix is the one that moves nothing"
    );
}

#[test]
fn a_type_with_no_fields_declares_nothing_and_writes_nothing() {
    let mut written = Vec::new();
    Grain.write(&mut written);

    assert_eq!(Grain::DECLARATION, "");
    assert!(written.is_empty());
}

#[test]
fn a_game_names_the_styles_and_the_effects_it_draws_with() {
    let mut sea = Sea;
    let _ = &mut sea;

    assert_eq!(<Ripples as SurfaceStyle>::PASS, DrawPass::Translucent);
    assert_eq!(<Grain as PostEffect>::STAGE, EffectStage::ToneMapped);
    assert_eq!(
        (
            Looks::from(Ripples::default()).seat(),
            Over::from(Grain).seat()
        ),
        (0, 0),
        "each at the one seat its own set holds it at"
    );
}