Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ByteAddressableBuffer: allow reading from read-only buffer #17

Merged
merged 7 commits into from
Oct 10, 2024

Conversation

Firestar99
Copy link
Contributor

ByteAddressableBuffer currently has the major limitation of requiring a &mut [u32] even if it's only used for reading data. This PR should serve as a place for discussion of how to resolve this issue.

My currently proposed solution splits up ByteAddressableBuffer into the read-only variant ByteAddressableBuffer and the mutable variant MutByteAddressableBuffer. This is obviously a breaking change and the names are up for discussion.

I would also like to mention @eddyb's comment in EmbarkStudios/rust-gpu#1014 about adding TypedBuffer:

TODO(@eddyb): add changelog, tests, discuss UntypedBuffer (TypedBuffer<[u32]> wrapper), maybe deprecate ByteAddressibleBuffer (to rename it to ByteAddressibleView or something? unsure what's best here)

@schell
Copy link
Contributor

schell commented Sep 25, 2024

Does this have any effect on the shader side of things? It seems like a CPU-only change, as from my reading ByteAddressableBuffer is a type used to help collect and marshal data over to the GPU. My guess is that a shader wouldn't use this type directly?

@Firestar99
Copy link
Contributor Author

Not at all.
ByteAddressableBuffer is a shader-only thing and allows you to read and write arbitrary types at arbirary offsets from &[u32] slices, usually backed by storage buffers. Now you can't directly declare a ByteAddressableBuffer as a storage buffer (afaik), so you instead declare a &[u32] as a storage buffer and use the ByteAddressableBuffer to view it and do your reads/writes.
Note however that you can only read and write structs with a minimum alignment of 4 bytes (both as struct alignment and offset), so technically it's a WordAddressableBuffer, but ByteAddressableBuffer is what HLSL calls them.

@schell
Copy link
Contributor

schell commented Sep 27, 2024

Got it. So then I am interested in this for crabslab, though I'm not sure if crabslab could benefit from switching to using ByteAddressableBuffer. It currently uses a series of index_unchecked into the &[u32], driven by a derivable trait.

@schell
Copy link
Contributor

schell commented Sep 27, 2024

Just spit-balling here, but if we're going to break backwards compatibility, we could define the type as:

pub struct ByteAddressableBuffer<T> {
    inner: T
}

impl<'a> ByteAddressableBuffer<&'a [u32]> {
    pub fn from_slice(s: &'a [u32]) -> Self {
        Self{inner: s}
    }
    
    pub fn load<T>(&self, byte_index: u32) -> T {
        todo!()    
    }
}

impl<'a> ByteAddressableBuffer<&'a mut [u32]> {
    pub fn from_mut_slice(s: &'a mut [u32]) -> Self {
        Self{inner: s}
    }
    pub fn store<T>(&mut self, byte_index: u32, t: T) {
        todo!()
    }
}

That would cut out the new type and make fixing the callsites a bit easier, but it also makes discovering what ByteAddressableBuffer does if you're uninitiated (like me)...

@Firestar99
Copy link
Contributor Author

Firestar99 commented Sep 27, 2024

I've actually thought about such a design already, but didn't knew how to express it properly in rust. Got stuck on trying to express it as ByteAddressableBuffer<[u32]> and ByteAddressableBuffer<mut [u32]>, but using the reference and lifetime like that is really nice!
The only disadvantage is duplicating the load and load_unchecked methods, but my suggestion is doing that too. Makes it unfortunately difficult to pass a ByteAddressableBuffer<&mut [u32]> into a method only needing to read, thus accepting only ByteAddressableBuffer<&[u32]>.

@Firestar99 Firestar99 changed the title ByteAddressableBuffer: split up into read-only and mutable variants ByteAddressableBuffer: allow reading from read-only buffer Sep 29, 2024
@schell
Copy link
Contributor

schell commented Sep 29, 2024

@Firestar99 Yes, those functions would have to be defined in both impls. But it might be nice then to add an as_ref function to ByteAddressableBuffer<&mut [u32]> to make proxying to the non-mutable buffer easier:

pub struct ByteAddressableBuffer<T> {
    inner: T,
}

impl<'a> ByteAddressableBuffer<&'a [u32]> {
    pub fn from_slice(s: &'a [u32]) -> Self {
        Self { inner: s }
    }

    pub fn load<T>(&self, byte_index: u32) -> T {
        todo!()
    }
}

impl<'a> ByteAddressableBuffer<&'a mut [u32]> {
    pub fn from_mut_slice(s: &'a mut [u32]) -> Self {
        Self { inner: s }
    }

    pub fn store<T>(&mut self, byte_index: u32, t: T) {
        todo!()
    }

    /// Create a non-mutable `ByteAddressableBuffer` from this mutable one.
    pub fn as_ref(&self) -> ByteAddressableBuffer<&[u32]> {
        let buffer: ByteAddressableBuffer<&[u32]> = ByteAddressableBuffer { inner: self.inner };
        buffer
    }

    pub fn load<T>(&self, byte_index: u32) -> T {
        self.as_ref().load(byte_index)
    }
}

#[cfg(test)]
mod test_buffer {
    use super::*;

    #[test]
    fn buffer() {
        let mut slab = [0u32; 36];
        let mut buffer_mut = ByteAddressableBuffer::from_mut_slice(&mut slab);
        buffer_mut.store(0, Some(123u64));
        let _maybe_u64: Option<u64> = buffer_mut.load(0);
    }
}

It may be acceptable to add as_ref and then not define load and load_unchecked on the mutable buffer, since it's so easy to go from mutable to non-mutable?

@Firestar99 Firestar99 marked this pull request as ready for review September 30, 2024 11:21
@Firestar99
Copy link
Contributor Author

Firestar99 commented Sep 30, 2024

Added ❤️
This makes it pretty much feature complete, and ready for review. Changelog is already added as well.

@Firestar99 Firestar99 merged commit 18b9f75 into Rust-GPU:main Oct 10, 2024
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants