Skip to main content

viewport_lib/
error.rs

1//! Error types for the viewport library.
2
3/// Errors that can occur during mesh upload and manipulation.
4#[derive(Debug, Clone, thiserror::Error)]
5#[non_exhaustive]
6pub enum ViewportError {
7    /// Mesh data has empty positions or indices.
8    #[error("empty mesh: {positions} positions, {indices} indices")]
9    EmptyMesh {
10        /// Number of positions provided.
11        positions: usize,
12        /// Number of indices provided.
13        indices: usize,
14    },
15
16    /// Positions and normals arrays have different lengths.
17    #[error("mesh length mismatch: {positions} positions vs {normals} normals")]
18    MeshLengthMismatch {
19        /// Number of positions provided.
20        positions: usize,
21        /// Number of normals provided.
22        normals: usize,
23    },
24
25    /// A position/normal override buffer was bound to a mesh whose vertex
26    /// count differs from the buffer's. The shader indexes the override per
27    /// mesh vertex, so a smaller buffer reads out of bounds and a different
28    /// count silently scrambles the geometry.
29    #[error(
30        "override buffer has {override_vertices} vertices but mesh {mesh_id} has {mesh_vertices}"
31    )]
32    OverrideBufferVertexCountMismatch {
33        /// Index of the mesh the override was bound to.
34        mesh_id: usize,
35        /// Vertex count of the mesh.
36        mesh_vertices: usize,
37        /// Vertex count the override buffer holds.
38        override_vertices: usize,
39    },
40
41    /// A handle does not resolve to a live resource: its slot was freed, reused
42    /// at a newer generation, or its index is out of range for the store.
43    #[error("handle {index} does not resolve to a live resource (store holds {count})")]
44    StaleHandle {
45        /// The slot index the handle points at.
46        index: usize,
47        /// The number of live entries currently in the store.
48        count: usize,
49    },
50
51    /// An index buffer entry references a vertex that does not exist.
52    #[error("invalid vertex index {vertex_index} (vertex count: {vertex_count})")]
53    InvalidVertexIndex {
54        /// The offending index value.
55        vertex_index: u32,
56        /// Total number of vertices.
57        vertex_count: usize,
58    },
59
60    /// Volume scalar data length does not match the declared grid dimensions.
61    #[error("volume data length {actual} does not match dims {dims:?} (expected {expected})")]
62    VolumeDataLengthMismatch {
63        /// Number of scalar values provided.
64        actual: usize,
65        /// Expected length, equal to `dims[0] * dims[1] * dims[2]`.
66        expected: usize,
67        /// Grid dimensions `[nx, ny, nz]`.
68        dims: [u32; 3],
69    },
70
71    /// Texture RGBA data has an unexpected size.
72    #[error("invalid texture data: expected {expected} bytes, got {actual}")]
73    InvalidTextureData {
74        /// Expected byte count (width * height * 4).
75        expected: usize,
76        /// Actual byte count provided.
77        actual: usize,
78    },
79
80    /// A compressed-texture upload requested a format the device cannot sample,
81    /// or a format that is not block-compressed.
82    ///
83    /// The device check reads the format's required wgpu feature (for example
84    /// `TEXTURE_COMPRESSION_BC`, `_ASTC`, or `_ETC2`). When this is returned for
85    /// a hardware reason, the caller should fall back to `upload_texture` /
86    /// `upload_normal_map` or ship a variant the device supports.
87    #[error(
88        "unsupported texture format {format:?}: device lacks its required feature, or the format is not block-compressed"
89    )]
90    UnsupportedTextureFormat {
91        /// The format that was rejected.
92        format: wgpu::TextureFormat,
93    },
94
95    /// A compressed-texture upload had base dimensions that are not a multiple
96    /// of the format's block size. Block-compressed textures must be created
97    /// with block-aligned dimensions; pad the image (and adjust UVs) or upload
98    /// it uncompressed.
99    #[error(
100        "compressed texture {width}x{height} is not block-aligned (block {block_width}x{block_height})"
101    )]
102    CompressedTextureNotBlockAligned {
103        /// Base (mip 0) width in texels.
104        width: u32,
105        /// Base (mip 0) height in texels.
106        height: u32,
107        /// Format block width.
108        block_width: u32,
109        /// Format block height.
110        block_height: u32,
111    },
112
113    /// A mip level of a compressed-texture upload had the wrong byte length for
114    /// its block-packed dimensions.
115    #[error(
116        "invalid compressed texture data at mip {level}: expected {expected} bytes, got {actual}"
117    )]
118    InvalidCompressedTextureData {
119        /// The mip level whose data was the wrong size (0 is the base level).
120        level: u32,
121        /// Expected block-packed byte count for this level.
122        expected: usize,
123        /// Actual byte count provided for this level.
124        actual: usize,
125    },
126
127    /// Attempted to access or replace a slot that is empty (its resource was
128    /// previously freed).
129    #[error("slot {index} is empty")]
130    SlotEmpty {
131        /// The slot index that was accessed.
132        index: usize,
133    },
134
135    /// Named scalar attribute not found on the given mesh.
136    #[error("attribute '{name}' not found on mesh {mesh_id}")]
137    AttributeNotFound {
138        /// The mesh index that was accessed.
139        mesh_id: usize,
140        /// The attribute name that was requested.
141        name: String,
142    },
143
144    /// Attribute data length does not match the existing buffer.
145    ///
146    /// `replace_attribute` requires the same vertex count as the original upload.
147    #[error("attribute length mismatch: expected {expected} f32 elements, got {got}")]
148    AttributeLengthMismatch {
149        /// Expected number of f32 elements (original attribute length).
150        expected: usize,
151        /// Actual number of f32 elements provided.
152        got: usize,
153    },
154
155    /// A buffer required for GPU marching cubes upload exceeds the device's
156    /// `max_buffer_size` limit. The caller should fall back to CPU marching cubes.
157    #[error("GPU MC buffer '{buffer}' needs {needed} bytes but device limit is {limit}")]
158    McBufferTooLarge {
159        /// Short name identifying which buffer exceeded the limit (e.g. `"vertex_buf"`).
160        buffer: &'static str,
161        /// Bytes required for the allocation.
162        needed: u64,
163        /// The device's `max_buffer_size` limit.
164        limit: u64,
165    },
166
167    /// Gaussian splat data is malformed or empty.
168    #[error("invalid gaussian splat data: {reason}")]
169    InvalidGaussianSplatData {
170        /// Short description of what is wrong.
171        reason: &'static str,
172    },
173
174    /// A background upload worker panicked or dropped its channel before
175    /// sending a result.
176    #[error("upload worker did not complete: {reason}")]
177    JobWorkerLost {
178        /// Short description of how the worker was lost.
179        reason: &'static str,
180    },
181
182    /// `upload_result_*` was called before the corresponding job reached the
183    /// `Ready` state. Poll `upload_status` and retry on the next frame.
184    #[error("upload job is not yet ready to take its result")]
185    JobNotReady,
186
187    /// `upload_result_*` was called with a `JobId` that does not belong to
188    /// the matching upload type, has already been taken, or was never
189    /// issued.
190    #[error("upload job has no result available: {reason}")]
191    JobResultMissing {
192        /// Short description of why the result is unavailable.
193        reason: &'static str,
194    },
195
196    /// No deformer slots remain. The registry has a fixed budget set by the
197    /// vertex-stage storage-buffer limit; once all slots are taken, callers
198    /// must release one with `unregister_deformer` (when implemented) before
199    /// registering another.
200    #[error("deformer registry exhausted: {max} slots in use")]
201    DeformSlotsExhausted {
202        /// Total number of slots the registry exposes.
203        max: usize,
204    },
205
206    /// A deformer with this name is already registered.
207    #[error("deformer name '{name}' already registered")]
208    DeformNameTaken {
209        /// The duplicate name that was rejected.
210        name: String,
211    },
212
213    /// The composed mesh-family shader failed validation, either because the
214    /// supplied `wgsl_body` is malformed or because composition produced an
215    /// invalid module. The reason carries the underlying validator message
216    /// and identifies which shader failed.
217    #[error("deformer shader invalid: {reason}")]
218    DeformShaderInvalid {
219        /// Validator message prefixed by the shader name that failed.
220        reason: String,
221    },
222
223    /// A LOD group was registered with no levels.
224    #[error("LOD group has no levels")]
225    LodGroupEmpty,
226
227    /// The mesh list and threshold list passed to `register_lod_group` differ
228    /// in length. Each level needs exactly one threshold.
229    #[error("LOD level count mismatch: {meshes} meshes vs {thresholds} thresholds")]
230    LodLevelCountMismatch {
231        /// Number of meshes provided.
232        meshes: usize,
233        /// Number of thresholds provided.
234        thresholds: usize,
235    },
236
237    /// LOD thresholds must strictly decrease from the full level to the crudest,
238    /// since each level applies to a smaller on-screen size than the one before.
239    #[error(
240        "LOD thresholds must strictly decrease (level {level} is not smaller than the previous)"
241    )]
242    LodThresholdsNotDescending {
243        /// The level whose threshold is not smaller than its predecessor's.
244        level: usize,
245    },
246
247    /// A level's mesh is not compatible with the other levels in the group: a
248    /// switch to it would change rendering. Today this means its named
249    /// attribute set or its deformer attachment differs from level 0.
250    #[error("LOD level {level} is incompatible with level 0: {reason}")]
251    LodLevelIncompatible {
252        /// The level that does not match level 0.
253        level: usize,
254        /// What differs (a missing attribute name, or a deform mismatch).
255        reason: String,
256    },
257
258    /// A LOD group id does not refer to a registered group.
259    #[error("LOD group {index} is not registered")]
260    LodGroupNotFound {
261        /// The group index that was looked up.
262        index: usize,
263    },
264}
265
266/// Convenience alias for `Result<T, ViewportError>`.
267pub type ViewportResult<T> = Result<T, ViewportError>;