dynamis-gpu 0.7.0

wgpu device, buffer, stream, pipeline, and readback runtime
Documentation
mod common;

use common::shared;
use dynamis_gpu::{GpuBuffer, Readback, SubmissionEncoder, read_regions};
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 readback = Readback::new(context.device(), "deferred mapping", 8, Readback::DEPTH);
    let mut encoder = SubmissionEncoder::new(context.device(), "deferred copy");
    assert!(
        readback
            .enqueue(&mut encoder, source.buffer(), 4, 8, 0)
            .is_none()
    );
    context.poll();
    assert!(readback.collect().is_empty());
    encoder.submit(context.queue());
    assert_eq!(readback.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 readback = Readback::new(context.device(), "ordered", 8, Readback::DEPTH);
    let mut earlier = SubmissionEncoder::new(context.device(), "earlier sequence");
    let mut later = SubmissionEncoder::new(context.device(), "later sequence");
    readback.enqueue(&mut earlier, first.buffer(), 0, 8, 0);
    readback.enqueue(&mut later, second.buffer(), 0, 8, 1);
    later.submit(context.queue());
    assert_eq!(
        read_regions(
            context.device(),
            context.queue(),
            "ordered fence",
            &[(second.buffer(), 0, 8)]
        ),
        [2u8; 8]
    );
    assert!(readback.collect().is_empty());
    earlier.submit(context.queue());
    assert_eq!(readback.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 readback = Readback::new(context.device(), "burst ring", 4, 2);
    let mut encoder = SubmissionEncoder::new(context.device(), "burst copies");
    let reads = Readback::DEPTH as u64 + 2;
    for sequence in 0..reads {
        assert!(
            readback
                .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!(
        readback.drain(),
        (0..reads)
            .map(|sequence| (sequence, expected.to_vec()))
            .collect::<Vec<_>>()
    );

    let mut reused = SubmissionEncoder::new(context.device(), "reused copies");
    readback.enqueue(&mut reused, source.buffer(), 0, 4, reads);
    reused.submit(context.queue());
    assert_eq!(readback.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 readback = Readback::new(context.device(), "discarded ring", 4, Readback::DEPTH);
    let mut encoder = SubmissionEncoder::new(context.device(), "discarded copy");
    readback.enqueue(&mut encoder, source.buffer(), 0, 4, 0);
    drop(encoder);
    readback.drain();
}

#[test]
#[should_panic(expected = "sequences must increase")]
fn a_readback_rejects_a_sequence_that_does_not_advance() {
    let context = shared();
    let source = source();
    source.write(context.queue(), &[5u8; 4]);
    let mut readback = Readback::new(context.device(), "stalled sequence", 4, Readback::DEPTH);
    let mut encoder = SubmissionEncoder::new(context.device(), "stalled copies");
    readback.enqueue(&mut encoder, source.buffer(), 0, 4, 3);
    readback.enqueue(&mut encoder, source.buffer(), 0, 4, 3);
}

#[test]
fn read_regions_concatenates_every_region_in_one_submission() {
    let context = shared();
    let source = source();
    source.write(context.queue(), &[1u8; 32]);
    assert_eq!(
        read_regions(
            context.device(),
            context.queue(),
            "concatenated probe",
            &[(source.buffer(), 8, 4), (source.buffer(), 0, 8)]
        ),
        vec![1u8; 12]
    );
}

#[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 readback = Readback::new(context.device(), "parallel ring", 16, 4);
                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) =
                        readback.enqueue(&mut encoder, source.buffer(), 4, bytes as u64, sequence)
                    {
                        received.push(displaced);
                    }
                    encoder.submit(context.queue());
                    if sequence % 7 == 0 {
                        assert_eq!(
                            read_regions(
                                context.device(),
                                context.queue(),
                                "parallel probe",
                                &[(source.buffer(), 4, bytes as u64)]
                            ),
                            data
                        );
                        context.poll();
                        received.extend(readback.collect());
                    }
                    expected.push((sequence, data));
                }
                received.extend(readback.drain());
                assert_eq!(received, expected);
                assert!(readback.collect().is_empty());
                assert!(readback.drain().is_empty());
            });
        }
    });
}