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