Skip to content

Commit

Permalink
crabslab docs
Browse files Browse the repository at this point in the history
  • Loading branch information
schell committed Jan 3, 2024
1 parent a61fb1c commit 89fc539
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 76 deletions.
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
[workspace]
members = [
"crates/example",
"crates/example-wasm",
#"crates/example",
#"crates/example-wasm",
"crates/loading-bytes",
"crates/renderling",
"crates/renderling-shader",
"crates/renderling-gpui",
#"crates/renderling-gpui",
"crates/crabslab",
"crates/crabslab-derive",
]
Expand Down
76 changes: 67 additions & 9 deletions crates/crabslab/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,91 @@
<img src="crabslab.png" alt="slabcraft for crabs" width="512" />
</div>

## what
`crabslab` is a slab implementation focused on marshalling data from CPUs to GPUs.
## What

`crabslab` is a slab implementation focused on marshalling data between CPUs and GPUs.

### example
```rust
use crabslab::{CpuSlab, Slab, GrowableSlab, SlabItem, Id};
use glam::{Vec3, Vec4};

#[derive(Debug, Default, SlabItem, PartialEq)]
struct Light {
direction: Vec3,
color: Vec4,
inner_cutoff: f32,
outer_cutoff: f32,
is_on: bool
}

impl Light {
fn standard() -> Self {
Light {
direction: Vec3::NEG_Z, // pointing down
color: Vec4::ONE, // white
inner_cutoff: 0.5,
outer_cutoff: 2.6,
is_on: true
}
}
}

fn cpu_code() -> (Id<Light>, Vec<u32>) {
// Create a new slab on the CPU-side.
// NOTE: For simplicity here we use `Vec<u32>` but if we were using `wgpu`
// we could use `crabslab::WgpuBuffer` instead of `Vec<u32>`.
// The API is the same.
let light = Light::standard();
let mut slab = CpuSlab::new(vec![]);
let id = slab.append(&light);
(id, slab.into_inner())
}

fn shader_code(light_id: Id<Light>, slab: &[u32]) {
let light = slab.read(light_id);
assert_eq!(Light::standard(), light);
}

let (light_id, slab) = cpu_code();
// marshalling your data depends on which GPU library you are using...
shader_code(light_id, &slab);
```

## But Why?
It's hard to get data onto GPUs in the form you expect.

To marshall your data correctly you must know about the alignment and sizes of the underlying representation of your data.
This will often surprise you!

Working with a slab on the other hand, only requires that your types can be written into an array and read from an array.

## why
### Opinion
Working with shaders is much easier using a slab.
Working with _shaders_ is much easier using a slab.

Shader code can be written in Rust with [`rust-gpu`](https://github.com/EmbarkStudios/rust-gpu),
which will enable you to use this crate on both CPU and GPU code.

### rust-gpu
This crate was made to work with [`rust-gpu`](https://github.com/EmbarkStudios/rust-gpu/).
Specifically, using this crate it is possible to pack your types into a buffer on the CPU
Specifically, with this crate it is possible to pack your types into a buffer on the CPU
and then read your types from the slab on the GPU (in Rust).

### Other no-std platforms
Even though this crate was written with `rust-gpu` in mind, it should work in other `no-std`
contexts.

## how
## And How
`crabslab` includes:
* a few traits:
- `Slab`
- `GrowableSlab`
- `SlabItem`
* a derive macro for `SlabItem`
* a few structs for working with various slabs
* a derive macro for `SlabItem` for your structs
* a few new structs for working with slabs
- `Id`
- `Array`
- `Offset`
* a helper struct `CpuSlab`
* a helper struct `CpuSlab` which wraps `Vec<u32>` or `WgpuBuffer`
* a feature-gated helper for using slabs with `wgpu` - `WgpuBuffer`
- [example](src/wgpu_slab.rs#L344)
1 change: 1 addition & 0 deletions crates/crabslab/src/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use core::marker::PhantomData;

use crate::{id::Id, slab::SlabItem};

/// Iterator over [`Id`]s in an [`Array`].
#[derive(Clone, Copy)]
pub struct ArrayIter<T> {
array: Array<T>,
Expand Down
5 changes: 3 additions & 2 deletions crates/crabslab/src/id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use core::marker::PhantomData;

use crate::{self as crabslab, slab::SlabItem};

/// `u32` value of an [`Id`] that does not point to any item.
pub const ID_NONE: u32 = u32::MAX;

/// An identifier that can be used to read or write a type from/into the slab.
Expand Down Expand Up @@ -144,9 +145,9 @@ impl<T> Id<T> {
}
}

/// The offset of a field relative a parent's `Id`.
/// The slab offset of a struct's field.
///
/// Offset functions are automatically derived for `SlabItem` structs.
/// Offset functions are automatically derived for [`SlabItem`] structs.
///
/// ```rust
/// use crabslab::{Id, Offset, Slab, SlabItem};
Expand Down
7 changes: 7 additions & 0 deletions crates/crabslab/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#![cfg_attr(target_arch = "spirv", no_std)]
//! Creating and crafting a tasty slab of memory.
#![doc = include_str!("../README.md")]

mod array;
pub use array::*;
Expand All @@ -16,3 +17,9 @@ mod wgpu_slab;
pub use wgpu_slab::*;

pub use crabslab_derive::SlabItem;

#[cfg(doctest)]
pub mod doctest {
#[doc = include_str!("../README.md")]
pub struct ReadmeDoctests;
}
11 changes: 8 additions & 3 deletions crates/crabslab/src/slab.rs
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@ pub trait GrowableSlab: Slab {
/// Preallocate space for one `T` element, but don't write anything to the
/// buffer.
///
/// The returned `Id` can be used to write later with [`Self::write`].
/// The returned `Id` can be used to write later with [`Slab::write`].
///
/// NOTE: This changes the next available buffer index and may change the
/// buffer capacity.
Expand All @@ -494,7 +494,7 @@ pub trait GrowableSlab: Slab {
/// the buffer.
///
/// This can be used to allocate space for a bunch of elements that get
/// written later with [`Self::write_array`].
/// written later with [`Slab::write_array`].
///
/// NOTE: This changes the length of the buffer and may change the capacity.
fn allocate_array<T: SlabItem>(&mut self, len: usize) -> Array<T> {
Expand Down Expand Up @@ -528,7 +528,7 @@ pub trait GrowableSlab: Slab {
}
}

/// A wrapper around a `GrowableSlab` that provides convenience methods for
/// A wrapper around a [`GrowableSlab`] that provides convenience methods for
/// working with CPU-side slabs.
///
/// Working with slabs on the CPU is much more convenient because the underlying
Expand Down Expand Up @@ -588,6 +588,11 @@ impl<B: GrowableSlab> CpuSlab<B> {
pub fn new(slab: B) -> Self {
Self { slab }
}

/// Consume the [`CpuSlab`], converting it into the underlying buffer.
pub fn into_inner(self) -> B {
self.slab
}
}

#[cfg(not(target_arch = "spirv"))]
Expand Down
33 changes: 27 additions & 6 deletions crates/crabslab/src/wgpu_slab.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ use std::{
use crate::{GrowableSlab, Id, Slab, SlabItem};
use snafu::{IntoError, ResultExt, Snafu};

/// Errors that can occur when using a [`WgpuBuffer`] as slab storage.
#[derive(Debug, Snafu)]
pub enum SlabError {
pub enum WgpuSlabError {
#[snafu(display(
"Out of capacity. Tried to write {type_is}(slab size={slab_size}) at {index} but capacity \
is {capacity}",
Expand Down Expand Up @@ -49,8 +50,28 @@ pub enum SlabError {
Async { source: wgpu::BufferAsyncError },
}

pub fn print_slab(slab: &[u32], starting_index: usize) {
for (u, i) in slab.iter().zip(starting_index..) {
/// Print the slab's index, binary representation, integer value, and float
/// value.
///
/// ```rust
/// use crabslab::{print_slab, CpuSlab, GrowableSlab, Slab, SlabItem};
///
/// let mut slab = CpuSlab::new(vec![]);
/// slab.append(&42u32);
/// slab.append(&42.0f32);
/// slab.append_array(&[0.0f32, 1.0f32, 2.0f32]);
///
/// print_slab(slab.as_ref().as_slice(), 0..5);
/// /* stdout:
/// 00: 00000000000000000000000000101010 0000000042 5.9e-44
/// 01: 01000010001010000000000000000000 1109917696 42.0
/// 02: 00000000000000000000000000000000 0000000000 0.0
/// 03: 00111111100000000000000000000000 1065353216 1.0
/// 04: 01000000000000000000000000000000 1073741824 2.0
/// */
/// ```
pub fn print_slab(slab: &[u32], indices: impl IntoIterator<Item = usize>) {
for (u, i) in slab.iter().zip(indices.into_iter()) {
println!("{i:02}: {u:032b} {u:010} {:?}", f32::from_bits(*u));
}
}
Expand Down Expand Up @@ -226,12 +247,12 @@ impl WgpuBuffer {

#[cfg(feature = "futures-lite")]
/// Read from the slab buffer synchronously.
pub fn block_on_read_raw(&self, start: usize, len: usize) -> Result<Vec<u32>, SlabError> {
pub fn block_on_read_raw(&self, start: usize, len: usize) -> Result<Vec<u32>, WgpuSlabError> {
futures_lite::future::block_on(self.read_raw(start, len))
}

/// Read from the slab buffer.
pub async fn read_raw(&self, start: usize, len: usize) -> Result<Vec<u32>, SlabError> {
pub async fn read_raw(&self, start: usize, len: usize) -> Result<Vec<u32>, WgpuSlabError> {
let byte_offset = start * std::mem::size_of::<u32>();
let length = len * std::mem::size_of::<u32>();
let output_buffer_size = length as u64;
Expand Down Expand Up @@ -271,7 +292,7 @@ impl WgpuBuffer {
}

/// Read from the slab buffer.
pub async fn read_async<T: SlabItem + Default>(&self, id: Id<T>) -> Result<T, SlabError> {
pub async fn read_async<T: SlabItem + Default>(&self, id: Id<T>) -> Result<T, WgpuSlabError> {
let vec = self.read_raw(id.index(), T::slab_size()).await?;
let t = Slab::read(vec.as_slice(), Id::<T>::new(0));
Ok(t)
Expand Down
12 changes: 6 additions & 6 deletions crates/renderling/src/stage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use snafu::Snafu;

use crate::{
Atlas, AtlasError, AtlasImage, AtlasImageError, DepthTexture, Device, HdrSurface, Queue,
Skybox, SlabError,
Skybox, WgpuSlabError,
};

#[cfg(feature = "gltf")]
Expand All @@ -33,7 +33,7 @@ pub enum StageError {
Atlas { source: AtlasError },

#[snafu(display("{source}"))]
Slab { source: SlabError },
Slab { source: WgpuSlabError },
}

impl From<AtlasError> for StageError {
Expand All @@ -42,8 +42,8 @@ impl From<AtlasError> for StageError {
}
}

impl From<SlabError> for StageError {
fn from(source: SlabError) -> Self {
impl From<WgpuSlabError> for StageError {
fn from(source: WgpuSlabError) -> Self {
Self::Slab { source }
}
}
Expand Down Expand Up @@ -652,7 +652,7 @@ impl Stage {
/// into a vector.
///
/// This is primarily used for debugging.
pub fn read_slab(&self) -> Result<Vec<u32>, SlabError> {
pub fn read_slab(&self) -> Result<Vec<u32>, WgpuSlabError> {
// UNWRAP: if we can't acquire the lock we want to panic.
self.slab
.read()
Expand Down Expand Up @@ -694,7 +694,7 @@ pub(crate) enum StageDrawStrategy {
/// Render the stage.
pub fn stage_render(
(stage, hdr_frame, depth): (ViewMut<Stage>, View<HdrSurface>, View<DepthTexture>),
) -> Result<(), SlabError> {
) -> Result<(), WgpuSlabError> {
let label = Some("stage render");
let pipeline = stage.get_pipeline();
let slab_buffers_bindgroup = stage.get_slab_buffers_bindgroup();
Expand Down
49 changes: 3 additions & 46 deletions crates/renderling/src/stage/gltf_support.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@ pub enum StageGltfError {
MissingCamera { index: usize },

#[snafu(display("{source}"))]
Slab { source: crabslab::SlabError },
Slab { source: crabslab::WgpuSlabError },
}

impl From<crabslab::SlabError> for StageGltfError {
fn from(source: crabslab::SlabError) -> Self {
impl From<crabslab::WgpuSlabError> for StageGltfError {
fn from(source: crabslab::WgpuSlabError) -> Self {
Self::Slab { source }
}
}
Expand Down Expand Up @@ -1309,49 +1309,6 @@ impl<'a> GltfMeshBuilder<'a> {
}
}

/// Convenience builder for creating a [`GltfDocument`] without having a
/// [`gltf::Document`].
///
/// This is useful if you have non-GLTF assets that you want to render.
pub struct GltfDocumentBuilder<'a> {
stage: &'a mut Stage,
}

impl<'a> GltfDocumentBuilder<'a> {
pub fn new(stage: &'a mut Stage) -> Self {
Self { stage }
}

pub fn build(self) -> GltfDocument {
let accessors = Array::default();
let animations = Array::default();
let buffers = Array::default();
let cameras = Array::default();
let materials = Array::default();
let default_material = Id::NONE;
let meshes = Array::default();
let nodes = Array::default();
let scenes = Array::default();
let skins = Array::default();
let textures = Array::default();
let views = Array::default();
GltfDocument {
accessors,
animations,
buffers,
cameras,
materials,
default_material,
meshes,
nodes,
scenes,
skins,
textures,
views,
}
}
}

#[cfg(test)]
mod test {
use crate::{
Expand Down
2 changes: 1 addition & 1 deletion crates/renderling/src/tonemapping.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use moongraph::{GraphError, Move, View};
use moongraph::{GraphError, View};

use crate::{frame::FrameTextureView, Device, HdrSurface, Queue};

Expand Down

0 comments on commit 89fc539

Please sign in to comment.