Skip to main content

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