wgpu_core/
ray_tracing.rs

1// Ray tracing
2// Major missing optimizations (no api surface changes needed):
3// - use custom tracker to track build state
4// - no forced rebuilt (build mode deduction)
5// - lazy instance buffer allocation
6// - maybe share scratch and instance staging buffer allocation
7// - partial instance buffer uploads (api surface already designed with this in mind)
8// - Batch BLAS read-backs (if it shows up in performance).
9// - ([non performance] extract function in build (rust function extraction with guards is a pain))
10
11use alloc::{boxed::Box, sync::Arc, vec::Vec};
12
13#[cfg(feature = "serde")]
14use macro_rules_attribute::apply;
15use thiserror::Error;
16use wgt::{
17    error::{ErrorType, WebGpuError},
18    AccelerationStructureGeometryFlags, BufferAddress, IndexFormat, VertexFormat,
19};
20
21#[cfg(feature = "serde")]
22use crate::command::serde_object_reference_struct;
23use crate::{
24    command::{ArcReferences, EncoderStateError, ReferenceType},
25    device::{DeviceError, MissingFeatures},
26    resource::{
27        self, Blas, BlasCompactCallback, BlasPrepareCompactResult, DestroyedResourceError,
28        InvalidResourceError, MissingBufferUsageError, ResourceErrorIdent, Tlas,
29    },
30};
31
32#[derive(Clone, Debug, Error)]
33pub enum CreateBlasError {
34    #[error(transparent)]
35    Device(#[from] DeviceError),
36    #[error(transparent)]
37    MissingFeatures(#[from] MissingFeatures),
38    #[error(
39        "Only one of 'index_count' and 'index_format' was provided (either provide both or none)"
40    )]
41    MissingIndexData,
42    #[error("Provided format was not within allowed formats. Provided format: {0:?}. Allowed formats: {1:?}")]
43    InvalidVertexFormat(VertexFormat, Vec<VertexFormat>),
44    #[error("Limit `max_blas_geometry_count` is {0}, but the BLAS had {1} geometries")]
45    TooManyGeometries(u32, u32),
46    #[error(
47        "Limit `max_blas_primitive_count` is {0}, but the BLAS had a maximum of {1} primitives"
48    )]
49    TooManyPrimitives(u32, u32),
50}
51
52impl WebGpuError for CreateBlasError {
53    fn webgpu_error_type(&self) -> ErrorType {
54        match self {
55            Self::Device(e) => e.webgpu_error_type(),
56            Self::MissingFeatures(e) => e.webgpu_error_type(),
57            Self::MissingIndexData
58            | Self::InvalidVertexFormat(..)
59            | Self::TooManyGeometries(..)
60            | Self::TooManyPrimitives(..) => ErrorType::Validation,
61        }
62    }
63}
64
65#[derive(Clone, Debug, Error)]
66pub enum CreateTlasError {
67    #[error(transparent)]
68    Device(#[from] DeviceError),
69    #[error(transparent)]
70    MissingFeatures(#[from] MissingFeatures),
71    #[error("Flag {0:?} is not allowed on a TLAS")]
72    DisallowedFlag(wgt::AccelerationStructureFlags),
73    #[error("Limit `max_tlas_instance_count` is {0}, but the TLAS had a maximum of {1} instances")]
74    TooManyInstances(u32, u32),
75}
76
77impl WebGpuError for CreateTlasError {
78    fn webgpu_error_type(&self) -> ErrorType {
79        match self {
80            Self::Device(e) => e.webgpu_error_type(),
81            Self::MissingFeatures(e) => e.webgpu_error_type(),
82            Self::DisallowedFlag(..) | Self::TooManyInstances(..) => ErrorType::Validation,
83        }
84    }
85}
86
87/// Error encountered while attempting to do a copy on a command encoder.
88#[derive(Clone, Debug, Error)]
89pub enum BuildAccelerationStructureError {
90    #[error(transparent)]
91    EncoderState(#[from] EncoderStateError),
92
93    #[error(transparent)]
94    Device(#[from] DeviceError),
95
96    #[error(transparent)]
97    InvalidResource(#[from] InvalidResourceError),
98
99    #[error(transparent)]
100    DestroyedResource(#[from] DestroyedResourceError),
101
102    #[error(transparent)]
103    MissingBufferUsage(#[from] MissingBufferUsageError),
104
105    #[error(transparent)]
106    MissingFeatures(#[from] MissingFeatures),
107
108    #[error(
109        "Data range of {region_size} B starting at offset {offset} would overrun the size {buffer_size} of buffer {buffer_ident:?}"
110    )]
111    InsufficientBufferSize {
112        buffer_ident: ResourceErrorIdent,
113        offset: BufferAddress,
114        region_size: BufferAddress,
115        buffer_size: BufferAddress,
116    },
117
118    #[error(
119        "Offset {offset}, computed as {count} times {stride} B, exceeds the maximum addressable offset 2^32 - 1 within buffer {buffer_ident:?}"
120    )]
121    OffsetLimitedTo4GB {
122        buffer_ident: ResourceErrorIdent,
123        offset: BufferAddress,
124        count: BufferAddress,
125        stride: BufferAddress,
126    },
127
128    #[error("Buffer {0:?} associated offset doesn't align with the index type")]
129    UnalignedIndexBufferOffset(ResourceErrorIdent),
130
131    #[error("Buffer {0:?} associated offset is unaligned")]
132    UnalignedTransformBufferOffset(ResourceErrorIdent),
133
134    #[error("Buffer {0:?} associated index count not divisible by 3 (count: {1}")]
135    InvalidIndexCount(ResourceErrorIdent, u32),
136
137    #[error("Buffer {0:?} associated data contains None")]
138    MissingAssociatedData(ResourceErrorIdent),
139
140    #[error(
141        "Blas {0:?} build sizes to may be greater than the descriptor at build time specified"
142    )]
143    IncompatibleBlasBuildSizes(ResourceErrorIdent),
144
145    #[error("Blas {0:?} flags are different, creation flags: {1:?}, provided: {2:?}")]
146    IncompatibleBlasFlags(
147        ResourceErrorIdent,
148        AccelerationStructureGeometryFlags,
149        AccelerationStructureGeometryFlags,
150    ),
151
152    #[error("Blas {0:?} build vertex count is greater than creation count (needs to be less than or equal to), creation: {1:?}, build: {2:?}")]
153    IncompatibleBlasVertexCount(ResourceErrorIdent, u32, u32),
154
155    #[error("Blas {0:?} vertex formats are different, creation format: {1:?}, provided: {2:?}")]
156    DifferentBlasVertexFormats(ResourceErrorIdent, VertexFormat, VertexFormat),
157
158    #[error("Blas {0:?} stride was required to be at least {1} but stride given was {2}")]
159    VertexStrideTooSmall(ResourceErrorIdent, u64, u64),
160
161    #[error("Blas {0:?} stride was required to be a multiple of {1} but stride given was {2}")]
162    VertexStrideUnaligned(ResourceErrorIdent, u64, u64),
163
164    #[error("Blas {0:?} index count was provided at creation or building, but not the other")]
165    BlasIndexCountProvidedMismatch(ResourceErrorIdent),
166
167    #[error("Blas {0:?} build index count is greater than creation count (needs to be less than or equal to), creation: {1:?}, build: {2:?}")]
168    IncompatibleBlasIndexCount(ResourceErrorIdent, u32, u32),
169
170    #[error("Blas {0:?} index formats are different, creation format: {1:?}, provided: {2:?}")]
171    DifferentBlasIndexFormats(ResourceErrorIdent, Option<IndexFormat>, Option<IndexFormat>),
172
173    #[error("Blas {0:?} is compacted and so cannot be built")]
174    CompactedBlas(ResourceErrorIdent),
175
176    #[error("Blas {0:?} build sizes require index buffer but none was provided")]
177    MissingIndexBuffer(ResourceErrorIdent),
178
179    #[error(
180        "Tlas {0:?} an associated instances contains an invalid custom index (more than 24bits)"
181    )]
182    TlasInvalidCustomIndex(ResourceErrorIdent),
183
184    #[error(
185        "Tlas {0:?} has {1} active instances but only {2} are allowed as specified by the descriptor at creation"
186    )]
187    TlasInstanceCountExceeded(ResourceErrorIdent, u32, u32),
188
189    #[error("Blas {0:?} has flag USE_TRANSFORM but the transform buffer is missing")]
190    TransformMissing(ResourceErrorIdent),
191
192    #[error("Blas {0:?} is missing the flag USE_TRANSFORM but the transform buffer is set")]
193    UseTransformMissing(ResourceErrorIdent),
194    #[error(
195        "Tlas {0:?} dependent {1:?} is missing AccelerationStructureFlags::ALLOW_RAY_HIT_VERTEX_RETURN"
196    )]
197    TlasDependentMissingVertexReturn(ResourceErrorIdent, ResourceErrorIdent),
198
199    #[error("Blas {0:?} geometry kind at creation does not match build (triangles vs AABBs)")]
200    BlasGeometryKindMismatch(ResourceErrorIdent),
201
202    #[error(
203        "Blas {0:?} build AABB primitive count is greater than creation count (creation: {1}, build: {2})"
204    )]
205    IncompatibleBlasAabbPrimitiveCount(ResourceErrorIdent, u32, u32),
206
207    #[error("Blas {0:?} AABB primitive offset must be a multiple of 8")]
208    UnalignedAabbPrimitiveOffset(ResourceErrorIdent),
209
210    #[error("Blas {0:?} AABB stride is invalid (must be >= {1} and a multiple of 8)")]
211    InvalidAabbStride(ResourceErrorIdent, BufferAddress),
212}
213
214impl WebGpuError for BuildAccelerationStructureError {
215    fn webgpu_error_type(&self) -> ErrorType {
216        match self {
217            Self::EncoderState(e) => e.webgpu_error_type(),
218            Self::Device(e) => e.webgpu_error_type(),
219            Self::InvalidResource(e) => e.webgpu_error_type(),
220            Self::DestroyedResource(e) => e.webgpu_error_type(),
221            Self::MissingBufferUsage(e) => e.webgpu_error_type(),
222            Self::MissingFeatures(e) => e.webgpu_error_type(),
223            Self::InsufficientBufferSize { .. }
224            | Self::OffsetLimitedTo4GB { .. }
225            | Self::UnalignedIndexBufferOffset(..)
226            | Self::UnalignedTransformBufferOffset(..)
227            | Self::InvalidIndexCount(..)
228            | Self::MissingAssociatedData(..)
229            | Self::IncompatibleBlasBuildSizes(..)
230            | Self::IncompatibleBlasFlags(..)
231            | Self::IncompatibleBlasVertexCount(..)
232            | Self::DifferentBlasVertexFormats(..)
233            | Self::VertexStrideTooSmall(..)
234            | Self::VertexStrideUnaligned(..)
235            | Self::BlasIndexCountProvidedMismatch(..)
236            | Self::IncompatibleBlasIndexCount(..)
237            | Self::DifferentBlasIndexFormats(..)
238            | Self::CompactedBlas(..)
239            | Self::MissingIndexBuffer(..)
240            | Self::TlasInvalidCustomIndex(..)
241            | Self::TlasInstanceCountExceeded(..)
242            | Self::TransformMissing(..)
243            | Self::UseTransformMissing(..)
244            | Self::TlasDependentMissingVertexReturn(..)
245            | Self::BlasGeometryKindMismatch(..)
246            | Self::IncompatibleBlasAabbPrimitiveCount(..)
247            | Self::UnalignedAabbPrimitiveOffset(..)
248            | Self::InvalidAabbStride(..) => ErrorType::Validation,
249        }
250    }
251}
252
253#[derive(Clone, Debug, Error)]
254pub enum ValidateAsActionsError {
255    #[error(transparent)]
256    DestroyedResource(#[from] DestroyedResourceError),
257
258    #[error("Tlas {0:?} is used before it is built")]
259    UsedUnbuiltTlas(ResourceErrorIdent),
260
261    #[error("Blas {0:?} is used before it is built (in Tlas {1:?})")]
262    UsedUnbuiltBlas(ResourceErrorIdent, ResourceErrorIdent),
263
264    #[error("Blas {0:?} is newer than the containing Tlas {1:?}")]
265    BlasNewerThenTlas(ResourceErrorIdent, ResourceErrorIdent),
266}
267
268impl WebGpuError for ValidateAsActionsError {
269    fn webgpu_error_type(&self) -> ErrorType {
270        match self {
271            Self::DestroyedResource(e) => e.webgpu_error_type(),
272            Self::UsedUnbuiltTlas(..) | Self::UsedUnbuiltBlas(..) | Self::BlasNewerThenTlas(..) => {
273                ErrorType::Validation
274            }
275        }
276    }
277}
278
279#[derive(Debug)]
280pub struct BlasTriangleGeometry<'a, Buffer = Arc<resource::Buffer>> {
281    pub size: &'a wgt::BlasTriangleGeometrySizeDescriptor,
282    pub vertex_buffer: Buffer,
283    pub index_buffer: Option<Buffer>,
284    pub transform_buffer: Option<Buffer>,
285    pub first_vertex: u32,
286    pub vertex_stride: BufferAddress,
287    pub first_index: Option<u32>,
288    pub transform_buffer_offset: Option<BufferAddress>,
289}
290
291#[derive(Debug)]
292pub struct BlasAabbGeometry<'a, Buffer = Arc<resource::Buffer>> {
293    pub size: &'a wgt::BlasAABBGeometrySizeDescriptor,
294    pub stride: BufferAddress,
295    pub aabb_buffer: Buffer,
296    pub primitive_offset: u32,
297}
298
299pub enum BlasGeometries<'a, Buffer = Arc<resource::Buffer>> {
300    TriangleGeometries(Box<dyn Iterator<Item = BlasTriangleGeometry<'a, Buffer>> + 'a>),
301    AabbGeometries(Box<dyn Iterator<Item = BlasAabbGeometry<'a, Buffer>> + 'a>),
302}
303
304pub struct BlasBuildEntry<'a, Blas = Arc<resource::Blas>, Buffer = Arc<resource::Buffer>> {
305    pub blas: Blas,
306    pub geometries: BlasGeometries<'a, Buffer>,
307}
308
309#[derive(Debug, Clone)]
310#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
311pub struct TlasBuildEntry<Tlas = Arc<resource::Tlas>, Buffer = Arc<resource::Buffer>> {
312    pub tlas: Tlas,
313    pub instance_buffer: Buffer,
314    pub instance_count: u32,
315}
316
317#[derive(Debug)]
318pub struct TlasInstance<'a, Blas = Arc<resource::Blas>> {
319    pub blas: Blas,
320    pub transform: &'a [f32; 12],
321    pub custom_data: u32,
322    pub mask: u8,
323}
324
325pub struct TlasPackage<'a, Tlas = Arc<resource::Tlas>, Blas = Arc<resource::Blas>> {
326    pub tlas: Tlas,
327    pub instances: Box<dyn Iterator<Item = Option<TlasInstance<'a, Blas>>> + 'a>,
328    pub lowest_unmodified: u32,
329}
330
331#[derive(Debug, Clone)]
332pub(crate) struct TlasBuild {
333    pub tlas: Arc<Tlas>,
334    pub dependencies: Vec<Arc<Blas>>,
335}
336
337#[derive(Debug, Clone, Default)]
338pub(crate) struct AsBuild {
339    pub blas_s_built: Vec<Arc<Blas>>,
340    pub tlas_s_built: Vec<TlasBuild>,
341}
342
343impl AsBuild {
344    pub(crate) fn with_capacity(blas: usize, tlas: usize) -> Self {
345        Self {
346            blas_s_built: Vec::with_capacity(blas),
347            tlas_s_built: Vec::with_capacity(tlas),
348        }
349    }
350}
351
352#[derive(Debug, Clone)]
353pub(crate) enum AsAction {
354    Build(AsBuild),
355    UseTlas(Arc<Tlas>),
356}
357
358/// Like [`BlasTriangleGeometry`], but with owned data.
359#[derive(Debug, Clone)]
360#[cfg_attr(feature = "serde", apply(serde_object_reference_struct))]
361pub struct OwnedBlasTriangleGeometry<R: ReferenceType> {
362    pub size: wgt::BlasTriangleGeometrySizeDescriptor,
363    pub vertex_buffer: R::Buffer,
364    pub index_buffer: Option<R::Buffer>,
365    pub transform_buffer: Option<R::Buffer>,
366    pub first_vertex: u32,
367    pub vertex_stride: BufferAddress,
368    pub first_index: Option<u32>,
369    pub transform_buffer_offset: Option<BufferAddress>,
370}
371
372pub type ArcBlasTriangleGeometry = OwnedBlasTriangleGeometry<ArcReferences>;
373
374#[derive(Debug, Clone)]
375#[cfg_attr(feature = "serde", apply(serde_object_reference_struct))]
376pub struct OwnedBlasAabbGeometry<R: ReferenceType> {
377    pub size: wgt::BlasAABBGeometrySizeDescriptor,
378    pub stride: BufferAddress,
379    pub aabb_buffer: R::Buffer,
380    pub primitive_offset: u32,
381}
382
383pub type ArcBlasAabbGeometry = OwnedBlasAabbGeometry<ArcReferences>;
384
385#[derive(Debug, Clone)]
386#[cfg_attr(feature = "serde", apply(serde_object_reference_struct))]
387pub enum OwnedBlasGeometries<R: ReferenceType> {
388    TriangleGeometries(Vec<OwnedBlasTriangleGeometry<R>>),
389    AabbGeometries(Vec<OwnedBlasAabbGeometry<R>>),
390}
391
392pub type ArcBlasGeometries = OwnedBlasGeometries<ArcReferences>;
393
394#[derive(Debug, Clone)]
395#[cfg_attr(feature = "serde", apply(serde_object_reference_struct))]
396pub struct OwnedBlasBuildEntry<R: ReferenceType> {
397    pub blas: R::Blas,
398    pub geometries: OwnedBlasGeometries<R>,
399}
400
401pub type ArcBlasBuildEntry = OwnedBlasBuildEntry<ArcReferences>;
402
403#[derive(Debug, Clone)]
404#[cfg_attr(feature = "serde", apply(serde_object_reference_struct))]
405pub struct OwnedTlasInstance<R: ReferenceType> {
406    pub blas: R::Blas,
407    pub transform: [f32; 12],
408    pub custom_data: u32,
409    pub mask: u8,
410}
411
412pub type ArcTlasInstance = OwnedTlasInstance<ArcReferences>;
413
414#[derive(Debug, Clone)]
415#[cfg_attr(feature = "serde", apply(serde_object_reference_struct))]
416pub struct OwnedTlasPackage<R: ReferenceType> {
417    pub tlas: R::Tlas,
418    pub instances: Vec<Option<OwnedTlasInstance<R>>>,
419    pub lowest_unmodified: u32,
420}
421
422pub type ArcTlasPackage = OwnedTlasPackage<ArcReferences>;
423
424/// [`BlasTriangleGeometry`], without the resources.
425#[derive(Debug, Clone)]
426pub struct BlasTriangleGeometryInfo {
427    pub size: wgt::BlasTriangleGeometrySizeDescriptor,
428    pub first_vertex: u32,
429    pub vertex_stride: BufferAddress,
430    pub first_index: Option<u32>,
431    pub transform_buffer_offset: Option<BufferAddress>,
432}
433
434#[derive(Clone, Debug, Error)]
435pub enum BlasPrepareCompactError {
436    #[error(transparent)]
437    Device(#[from] DeviceError),
438    #[error(transparent)]
439    InvalidResource(#[from] InvalidResourceError),
440    #[error("Compaction is already being prepared")]
441    CompactionPreparingAlready,
442    #[error("Cannot compact an already compacted BLAS")]
443    DoubleCompaction,
444    #[error("BLAS is not yet built")]
445    NotBuilt,
446    #[error("BLAS does not support compaction (is AccelerationStructureFlags::ALLOW_COMPACTION missing?)")]
447    CompactionUnsupported,
448}
449
450impl WebGpuError for BlasPrepareCompactError {
451    fn webgpu_error_type(&self) -> ErrorType {
452        match self {
453            Self::Device(e) => e.webgpu_error_type(),
454            Self::InvalidResource(e) => e.webgpu_error_type(),
455            Self::CompactionPreparingAlready
456            | Self::DoubleCompaction
457            | Self::NotBuilt
458            | Self::CompactionUnsupported => ErrorType::Validation,
459        }
460    }
461}
462
463#[derive(Clone, Debug, Error)]
464pub enum CompactBlasError {
465    #[error(transparent)]
466    Encoder(#[from] EncoderStateError),
467
468    #[error(transparent)]
469    Device(#[from] DeviceError),
470
471    #[error(transparent)]
472    InvalidResource(#[from] InvalidResourceError),
473
474    #[error(transparent)]
475    DestroyedResource(#[from] DestroyedResourceError),
476
477    #[error(transparent)]
478    MissingFeatures(#[from] MissingFeatures),
479
480    #[error("BLAS was not prepared for compaction")]
481    BlasNotReady,
482}
483
484impl WebGpuError for CompactBlasError {
485    fn webgpu_error_type(&self) -> ErrorType {
486        match self {
487            Self::Encoder(e) => e.webgpu_error_type(),
488            Self::Device(e) => e.webgpu_error_type(),
489            Self::InvalidResource(e) => e.webgpu_error_type(),
490            Self::DestroyedResource(e) => e.webgpu_error_type(),
491            Self::MissingFeatures(e) => e.webgpu_error_type(),
492            Self::BlasNotReady => ErrorType::Validation,
493        }
494    }
495}
496
497pub type BlasCompactReadyPendingClosure = (Option<BlasCompactCallback>, BlasPrepareCompactResult);