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>;