wgpu/api/
blas.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
#[cfg(wgpu_core)]
use core::ops::Deref;

use alloc::{boxed::Box, vec::Vec};

use wgt::{WasmNotSend, WasmNotSendSync};

use crate::dispatch;
use crate::{Buffer, Label};

/// Descriptor for the size defining attributes of a triangle geometry, for a bottom level acceleration structure.
pub type BlasTriangleGeometrySizeDescriptor = wgt::BlasTriangleGeometrySizeDescriptor;
static_assertions::assert_impl_all!(BlasTriangleGeometrySizeDescriptor: Send, Sync);

/// Descriptor for the size defining attributes, for a bottom level acceleration structure.
pub type BlasGeometrySizeDescriptors = wgt::BlasGeometrySizeDescriptors;
static_assertions::assert_impl_all!(BlasGeometrySizeDescriptors: Send, Sync);

/// Flags for an acceleration structure.
pub type AccelerationStructureFlags = wgt::AccelerationStructureFlags;
static_assertions::assert_impl_all!(AccelerationStructureFlags: Send, Sync);

/// Flags for a geometry inside a bottom level acceleration structure.
pub type AccelerationStructureGeometryFlags = wgt::AccelerationStructureGeometryFlags;
static_assertions::assert_impl_all!(AccelerationStructureGeometryFlags: Send, Sync);

/// Update mode for acceleration structure builds.
pub type AccelerationStructureUpdateMode = wgt::AccelerationStructureUpdateMode;
static_assertions::assert_impl_all!(AccelerationStructureUpdateMode: Send, Sync);

/// Descriptor to create bottom level acceleration structures.
pub type CreateBlasDescriptor<'a> = wgt::CreateBlasDescriptor<Label<'a>>;
static_assertions::assert_impl_all!(CreateBlasDescriptor<'_>: Send, Sync);

/// Safe instance for a [Tlas].
///
/// A TlasInstance may be made invalid, if a TlasInstance is invalid, any attempt to build a [Tlas] containing an
/// invalid TlasInstance will generate a validation error
///
/// Each one contains:
/// - A reference to a BLAS, this ***must*** be interacted with using [TlasInstance::new] or [TlasInstance::set_blas], a
///   TlasInstance that references a BLAS keeps that BLAS from being dropped
/// - A user accessible transformation matrix
/// - A user accessible mask
/// - A user accessible custom index
///
/// [Tlas]: crate::Tlas
#[derive(Debug, Clone)]
pub struct TlasInstance {
    pub(crate) blas: dispatch::DispatchBlas,
    /// Affine transform matrix 3x4 (rows x columns, row major order).
    pub transform: [f32; 12],
    /// Custom index for the instance used inside the shader.
    ///
    /// This must only use the lower 24 bits, if any bits are outside that range (byte 4 does not equal 0) the TlasInstance becomes
    /// invalid and generates a validation error when built
    pub custom_data: u32,
    /// Mask for the instance used inside the shader to filter instances.
    /// Reports hit only if `(shader_cull_mask & tlas_instance.mask) != 0u`.
    pub mask: u8,
}

impl TlasInstance {
    /// Construct TlasInstance.
    /// - blas: Reference to the bottom level acceleration structure
    /// - transform: Transform buffer offset in bytes (optional, required if transform buffer is present)
    /// - custom_data: Custom index for the instance used inside the shader (max 24 bits)
    /// - mask: Mask for the instance used inside the shader to filter instances
    ///
    /// Note: while one of these contains a reference to a BLAS that BLAS will not be dropped,
    /// but it can still be destroyed. Destroying a BLAS that is referenced by one or more
    /// TlasInstance(s) will immediately make them invalid. If one or more of those invalid
    /// TlasInstances is inside a TlasPackage that is attempted to be built, the build will
    /// generate a validation error.
    pub fn new(blas: &Blas, transform: [f32; 12], custom_data: u32, mask: u8) -> Self {
        Self {
            blas: blas.inner.clone(),
            transform,
            custom_data,
            mask,
        }
    }

    /// Set the bottom level acceleration structure.
    ///
    /// See the note on [TlasInstance] about the
    /// guarantees of keeping a BLAS alive.
    pub fn set_blas(&mut self, blas: &Blas) {
        self.blas = blas.inner.clone();
    }
}

#[derive(Debug)]
/// Definition for a triangle geometry for a Bottom Level Acceleration Structure (BLAS).
///
/// The size must match the rest of the structures fields, otherwise the build will fail.
/// (e.g. if index count is present in the size, the index buffer must be present as well.)
pub struct BlasTriangleGeometry<'a> {
    /// Sub descriptor for the size defining attributes of a triangle geometry.
    pub size: &'a BlasTriangleGeometrySizeDescriptor,
    /// Vertex buffer.
    pub vertex_buffer: &'a Buffer,
    /// Offset into the vertex buffer as a factor of the vertex stride.
    pub first_vertex: u32,
    /// Vertex stride, must be greater than [`wgpu_types::VertexFormat::min_acceleration_structure_vertex_stride`]
    /// of the format and must be a multiple of [`wgpu_types::VertexFormat::acceleration_structure_stride_alignment`].
    pub vertex_stride: wgt::BufferAddress,
    /// Index buffer (optional).
    pub index_buffer: Option<&'a Buffer>,
    /// Number of indexes to skip in the index buffer (optional, required if index buffer is present).
    pub first_index: Option<u32>,
    /// Transform buffer containing 3x4 (rows x columns, row major) affine transform matrices `[f32; 12]` (optional).
    pub transform_buffer: Option<&'a Buffer>,
    /// Transform buffer offset in bytes (optional, required if transform buffer is present).
    pub transform_buffer_offset: Option<wgt::BufferAddress>,
}
static_assertions::assert_impl_all!(BlasTriangleGeometry<'_>: WasmNotSendSync);

/// Contains the sets of geometry that go into a [Blas].
pub enum BlasGeometries<'a> {
    /// Triangle geometry variant.
    TriangleGeometries(Vec<BlasTriangleGeometry<'a>>),
}
static_assertions::assert_impl_all!(BlasGeometries<'_>: WasmNotSendSync);

/// Builds the given sets of geometry into the given [Blas].
pub struct BlasBuildEntry<'a> {
    /// Reference to the acceleration structure.
    pub blas: &'a Blas,
    /// Geometries.
    pub geometry: BlasGeometries<'a>,
}
static_assertions::assert_impl_all!(BlasBuildEntry<'_>: WasmNotSendSync);

#[derive(Debug, Clone)]
/// Bottom Level Acceleration Structure (BLAS).
///
/// A BLAS is a device-specific raytracing acceleration structure that contains geometry data.
///
/// These BLASes are combined with transform in a [TlasInstance] to create a [Tlas].
///
/// [Tlas]: crate::Tlas
pub struct Blas {
    pub(crate) handle: Option<u64>,
    pub(crate) inner: dispatch::DispatchBlas,
}
static_assertions::assert_impl_all!(Blas: WasmNotSendSync);

crate::cmp::impl_eq_ord_hash_proxy!(Blas => .inner);

impl Blas {
    /// Raw handle to the acceleration structure, used inside raw instance buffers.
    pub fn handle(&self) -> Option<u64> {
        self.handle
    }

    /// Get the [`wgpu_hal`] acceleration structure from this `Blas`.
    ///
    /// Find the Api struct corresponding to the active backend in [`wgpu_hal::api`],
    /// and pass that struct to the to the `A` type parameter.
    ///
    /// Returns a guard that dereferences to the type of the hal backend
    /// which implements [`A::AccelerationStructure`].
    ///
    /// # Deadlocks
    ///
    /// - The returned guard holds a read-lock on a device-local "destruction"
    ///   lock, which will cause all calls to `destroy` to block until the
    ///   guard is released.
    ///
    /// # Errors
    ///
    /// This method will return None if:
    /// - The acceleration structure is not from the backend specified by `A`.
    /// - The acceleration structure is from the `webgpu` or `custom` backend.
    ///
    /// # Safety
    ///
    /// - The returned resource must not be destroyed unless the guard
    ///   is the last reference to it and it is not in use by the GPU.
    ///   The guard and handle may be dropped at any time however.
    /// - All the safety requirements of wgpu-hal must be upheld.
    ///
    /// [`A::AccelerationStructure`]: hal::Api::AccelerationStructure
    #[cfg(wgpu_core)]
    pub unsafe fn as_hal<A: wgc::hal_api::HalApi>(
        &mut self,
    ) -> Option<impl Deref<Target = A::AccelerationStructure> + WasmNotSendSync> {
        let blas = self.inner.as_core_opt()?;
        unsafe { blas.context.blas_as_hal::<A>(blas) }
    }

    #[cfg(custom)]
    /// Returns custom implementation of Blas (if custom backend and is internally T)
    pub fn as_custom<T: crate::custom::BlasInterface>(&self) -> Option<&T> {
        self.inner.as_custom()
    }
}

/// Context version of [BlasTriangleGeometry].
pub struct ContextBlasTriangleGeometry<'a> {
    #[expect(dead_code)]
    pub(crate) size: &'a BlasTriangleGeometrySizeDescriptor,
    #[expect(dead_code)]
    pub(crate) vertex_buffer: &'a dispatch::DispatchBuffer,
    #[expect(dead_code)]
    pub(crate) index_buffer: Option<&'a dispatch::DispatchBuffer>,
    #[expect(dead_code)]
    pub(crate) transform_buffer: Option<&'a dispatch::DispatchBuffer>,
    #[expect(dead_code)]
    pub(crate) first_vertex: u32,
    #[expect(dead_code)]
    pub(crate) vertex_stride: wgt::BufferAddress,
    #[expect(dead_code)]
    pub(crate) index_buffer_offset: Option<wgt::BufferAddress>,
    #[expect(dead_code)]
    pub(crate) transform_buffer_offset: Option<wgt::BufferAddress>,
}

/// Context version of [BlasGeometries].
pub enum ContextBlasGeometries<'a> {
    /// Triangle geometries.
    TriangleGeometries(Box<dyn Iterator<Item = ContextBlasTriangleGeometry<'a>> + 'a>),
}

/// Context version see [BlasBuildEntry].
pub struct ContextBlasBuildEntry<'a> {
    #[expect(dead_code)]
    pub(crate) blas: &'a dispatch::DispatchBlas,
    #[expect(dead_code)]
    pub(crate) geometries: ContextBlasGeometries<'a>,
}

/// Error occurred when trying to asynchronously prepare a blas for compaction.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct BlasAsyncError;
static_assertions::assert_impl_all!(BlasAsyncError: Send, Sync);

impl core::fmt::Display for BlasAsyncError {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(
            f,
            "Error occurred when trying to asynchronously prepare a blas for compaction"
        )
    }
}

impl core::error::Error for BlasAsyncError {}

impl Blas {
    /// Asynchronously prepares this BLAS for compaction. The callback is called once all builds
    /// using this BLAS are finished and the BLAS is compactable. This can be checked using
    /// [`Blas::ready_for_compaction`]. Rebuilding this BLAS will reset its compacted state, and it
    /// will need to be prepared again.
    ///
    /// ### Interaction with other functions
    /// On native, `queue.submit(..)` and polling devices (that is calling `instance.poll_all` or
    /// `device.poll`) with [`PollType::Poll`] may call the callback. On native, polling devices with
    /// [`PollType::Wait`] (or [`PollType::WaitForSubmissionIndex`] with a submission index greater
    /// than the last submit the BLAS was used in) will guarantee callback is called.
    ///
    /// [`PollType::Poll`]: wgpu_types::PollType::Poll
    /// [`PollType::Wait`]: wgpu_types::PollType::Wait
    /// [`PollType::WaitForSubmissionIndex`]: wgpu_types::PollType::WaitForSubmissionIndex
    pub fn prepare_compaction_async(
        &self,
        callback: impl FnOnce(Result<(), BlasAsyncError>) + WasmNotSend + 'static,
    ) {
        self.inner.prepare_compact_async(Box::new(callback));
    }

    /// Checks whether this BLAS is ready for compaction. The returned value is `true` if
    /// [`Blas::prepare_compaction_async`]'s callback was called with a non-error value, otherwise
    /// this is `false`.
    pub fn ready_for_compaction(&self) -> bool {
        self.inner.ready_for_compaction()
    }
}