dynamis-gpu 0.6.0

GPU-driven physics engine
Documentation
mod common;

use common::shared;
use dynamis_gpu::{BufferReadback, GpuBuffer, ReadbackRing, SubmissionEncoder};
use std::sync::Barrier;
use wgpu::BufferUsages;

fn source() -> GpuBuffer {
    GpuBuffer::new(
        shared().device(),
        "readback source",
        32,
        BufferUsages::COPY_SRC | BufferUsages::COPY_DST,
    )
}

#[test]
fn recording_and_polling_never_maps_an_unsubmitted_copy() {
    let context = shared();
    let source = source();
    let expected = [11u8; 8];
    source.write_at(context.queue(), 4, &expected);
    let mut ring = ReadbackRing::new(context.device(), "deferred mapping", 8);
    let mut encoder = SubmissionEncoder::new(context.device(), "deferred copy");
    assert!(
        ring.enqueue(&mut encoder, source.buffer(), 4, 8, 0)
            .is_none()
    );
    context.poll();
    assert!(ring.collect().is_empty());
    encoder.submit(context.queue());
    assert_eq!(ring.drain(), vec![(0, expected.to_vec())]);
}

#[test]
fn readbacks_retain_their_own_submission_and_retire_in_sequence_order() {
    let context = shared();
    let first = source();
    let second = source();
    first.write(context.queue(), &[1u8; 8]);
    second.write(context.queue(), &[2u8; 8]);
    let mut ring = ReadbackRing::new(context.device(), "ordered", 8);
    let mut earlier = SubmissionEncoder::new(context.device(), "earlier sequence");
    let mut later = SubmissionEncoder::new(context.device(), "later sequence");
    ring.enqueue(&mut earlier, first.buffer(), 0, 8, 0);
    ring.enqueue(&mut later, second.buffer(), 0, 8, 1);
    later.submit(context.queue());
    let mut fence = BufferReadback::new(context.device(), "later fence", 8);
    assert_eq!(fence.read(context.queue(), second.buffer(), 0, 8), [2u8; 8]);
    assert!(ring.collect().is_empty());
    earlier.submit(context.queue());
    assert_eq!(ring.drain(), vec![(0, vec![1u8; 8]), (1, vec![2u8; 8])]);
}

#[test]
fn a_batch_larger_than_the_pool_grows_the_pool_without_waiting_on_unsubmitted_slots() {
    let context = shared();
    let source = source();
    let expected = [7u8; 4];
    source.write(context.queue(), &expected);
    let mut ring = ReadbackRing::new(context.device(), "burst ring", 4);
    let mut encoder = SubmissionEncoder::new(context.device(), "burst copies");
    let reads = ReadbackRing::DEPTH as u64 + 2;
    for sequence in 0..reads {
        assert!(
            ring.enqueue(&mut encoder, source.buffer(), 0, 4, sequence)
                .is_none(),
            "a slot whose encoder is still being recorded must never be waited on"
        );
    }
    encoder.submit(context.queue());
    assert_eq!(
        ring.drain(),
        (0..reads)
            .map(|sequence| (sequence, expected.to_vec()))
            .collect::<Vec<_>>()
    );

    let mut reused = SubmissionEncoder::new(context.device(), "reused copies");
    ring.enqueue(&mut reused, source.buffer(), 0, 4, reads);
    reused.submit(context.queue());
    assert_eq!(ring.drain(), vec![(reads, expected.to_vec())]);
}

#[test]
#[should_panic(expected = "must be submitted before waiting or reusing its slot")]
fn waiting_on_a_discarded_encoder_fails_fast() {
    let context = shared();
    let source = source();
    let mut ring = ReadbackRing::new(context.device(), "discarded ring", 4);
    let mut encoder = SubmissionEncoder::new(context.device(), "discarded copy");
    ring.enqueue(&mut encoder, source.buffer(), 0, 4, 0);
    drop(encoder);
    ring.drain();
}

#[test]
fn parallel_submissions_recycle_slots_without_losing_or_mixing_results() {
    const WORKERS: usize = 8;
    let context = shared();
    let start = Barrier::new(WORKERS);
    std::thread::scope(|scope| {
        for worker in 0..WORKERS {
            let start = &start;
            scope.spawn(move || {
                let source = source();
                let mut ring = ReadbackRing::new(context.device(), "parallel ring", 16);
                let mut synchronous = BufferReadback::new(context.device(), "parallel sync", 16);
                let mut expected = Vec::new();
                let mut received = Vec::new();
                start.wait();
                for sequence in 0..24u64 {
                    let bytes = 4 * (1 + sequence as usize % 4);
                    let data = vec![(worker * 24 + sequence as usize) as u8; bytes];
                    source.write_at(context.queue(), 4, &data);
                    let mut encoder = SubmissionEncoder::new(context.device(), "parallel copy");
                    if let Some(displaced) =
                        ring.enqueue(&mut encoder, source.buffer(), 4, bytes as u64, sequence)
                    {
                        received.push(displaced);
                    }
                    encoder.submit(context.queue());
                    if sequence % 7 == 0 {
                        assert_eq!(
                            synchronous.read(context.queue(), source.buffer(), 4, bytes as u64),
                            data
                        );
                        context.poll();
                        received.extend(ring.collect());
                    }
                    expected.push((sequence, data));
                }
                received.extend(ring.drain());
                assert_eq!(received, expected);
                assert!(ring.collect().is_empty());
                assert!(ring.drain().is_empty());
            });
        }
    });
}