mirage-engine 0.2.0

Mirage, an immediate-mode 3D engine for simple games on desktop and the browser
Documentation
use crate::assets::Textures;
use crate::renderer::draw_list::{Batch, Casters};
use crate::renderer::mesh_cache::GpuMeshes;
use crate::renderer::pipelines::{FrameBindings, Pipelines, Placing, Styles};
use crate::renderer::post::{NOTHING, SceneTarget};
use crate::renderer::shadows::Shadows;
use crate::renderer::skybox::GpuSky;
use crate::surface_style::GROUP;

/// The dynamic offset the forward pass reads the camera's own viewpoint
/// through.
const CAMERA: [wgpu::DynamicOffset; 1] = [0];

/// The one triangle the sky covers the frame with.
const SKY: core::ops::Range<u32> = 0..3;

/// The slot the stream of what each corner takes of its joints is drawn
/// from, which `pipelines.rs` lays the skinned pipelines out over.
pub(crate) const SKIN: u32 = 2;

/// Everything a pass over the frame draws from, resolved before it is
/// encoded.
pub(crate) struct Scene<'a> {
    pub(crate) opaque: &'a [Batch],
    pub(crate) cutout: &'a [Batch],
    pub(crate) translucent: &'a [Batch],
    pub(crate) additive: &'a [Batch],
    pub(crate) casters: Casters<'a>,
    pub(crate) meshes: &'a GpuMeshes,
    pub(crate) instances: &'a wgpu::Buffer,
    pub(crate) pipelines: &'a Pipelines,
    pub(crate) styles: &'a Styles,
    pub(crate) frame: &'a FrameBindings,
    pub(crate) textures: &'a Textures,
    pub(crate) shadows: &'a Shadows,
    pub(crate) sky: &'a GpuSky,
}

/// Draws the frame's opaque and cutout batches keeping only the nearest
/// surface, the sky where nothing nearer was drawn, then the translucent
/// ones blended over both, and the additive ones last.
///
/// One pass, so the samples resolve once, after all of it.
pub(crate) fn forward(
    encoder: &mut wgpu::CommandEncoder,
    target: &SceneTarget<'_>,
    scene: &Scene<'_>,
) {
    let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
        label: Some("mirage-engine forward"),
        color_attachments: &[Some(wgpu::RenderPassColorAttachment {
            view: target.color,
            depth_slice: None,
            resolve_target: target.resolve,
            ops: wgpu::Operations {
                load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
                store: wgpu::StoreOp::Store,
            },
        })],
        depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
            view: target.depth,
            depth_ops: Some(wgpu::Operations {
                load: wgpu::LoadOp::Clear(NOTHING),
                store: wgpu::StoreOp::Store,
            }),
            stencil_ops: None,
        }),
        timestamp_writes: None,
        occlusion_query_set: None,
        multiview_mask: None,
    });

    draw(&mut pass, scene.pipelines.opaque(), scene.opaque, scene);
    draw(&mut pass, scene.pipelines.cutout(), scene.cutout, scene);
    sky(&mut pass, scene);
    draw(
        &mut pass,
        scene.pipelines.transparent(),
        scene.translucent,
        scene,
    );
    draw(&mut pass, scene.pipelines.additive(), scene.additive, scene);
}

/// Draws the frame's sky over every pixel nothing was drawn over.
fn sky(pass: &mut wgpu::RenderPass<'_>, scene: &Scene<'_>) {
    pass.set_pipeline(scene.pipelines.sky());
    pass.set_bind_group(0, scene.sky.frame(), &CAMERA);
    pass.draw(SKY, 0..1);
}

/// Draws `batches` with `drawn` — or, for a run of batches drawn with a
/// style, with what that style compiled to — reading what the scene is
/// drawn against.
///
/// A run shares one pipeline: its style, and the turn the viewpoint placed
/// its draws by.
fn draw(pass: &mut wgpu::RenderPass<'_>, drawn: &Placing, batches: &[Batch], scene: &Scene<'_>) {
    if batches.is_empty() {
        return;
    }

    pass.set_bind_group(0, scene.sky.frame(), &CAMERA);
    pass.set_bind_group(2, scene.shadows.bindings(), &[]);
    pass.set_vertex_buffer(1, scene.instances.slice(..));

    for run in batches.chunk_by(|batch, next| {
        (batch.style, batch.turn, batch.skinned) == (next.style, next.turn, next.skinned)
    }) {
        let Some(first) = run.first() else {
            continue;
        };
        match first.style {
            None => pass.set_pipeline(drawn.of(first.turn, first.skinned)),
            Some(style) => {
                let Some((styled, values)) =
                    scene.styles.drawn_with(style, first.turn, first.skinned)
                else {
                    continue;
                };
                pass.set_pipeline(styled);
                pass.set_bind_group(GROUP, values, &[]);
            }
        }
        for batch in run {
            instanced(pass, batch, scene);
        }
    }
}

/// Draws one batch of instances with whatever the pass was last set to.
fn instanced(pass: &mut wgpu::RenderPass<'_>, batch: &Batch, scene: &Scene<'_>) {
    let Some(mesh) = scene.meshes.uploaded(batch.mesh) else {
        return;
    };
    let sampled = mesh
        .part_texture(batch.part)
        .unwrap_or_else(|| scene.textures.fallback());

    pass.set_bind_group(1, sampled, &[]);
    pass.set_vertex_buffer(0, mesh.vertices().slice(..));
    if let Some(skin) = mesh.skin() {
        pass.set_vertex_buffer(SKIN, skin.slice(..));
    }
    pass.set_index_buffer(mesh.indices().slice(..), wgpu::IndexFormat::Uint32);
    pass.draw_indexed(batch.indices.clone(), 0, batch.instances.clone());
}