Skip to main content

mujoco_rs/
error.rs

1//! Error types for MuJoCo-rs operations.
2//!
3//! - [`MjDataError`] - physics data, model-compatibility, and Jacobian operations.
4//! - [`MjSceneError`] - 3-D scene and visualization operations (`MjvScene`).
5//! - [`MjrContextError`] - GPU rendering-context operations (`MjrContext`).
6//! - [`MjEditError`] - model-specification editing operations (`MjSpec`).
7//! - [`MjModelError`] - model loading, saving, and state operations (`MjModel`).
8//! - [`MjVfsError`] - virtual file system operations (`MjVfs`).
9//! - [`MjPluginError`] - plugin library loading operations.
10//! - [`MjBatchError`] - batched simulation operations ([`MjBatch`](crate::wrappers::MjBatch)).
11//! - [`GlInitError`] - OpenGL / window initialization (feature-gated).
12use std::path::PathBuf;
13use std::sync::Arc;
14use std::fmt;
15use std::io;
16
17use crate::wrappers::mj_model::{MjModelLayout, MjtObjBase};
18
19/// Errors that can occur in [`MjData`](crate::wrappers::MjData) physics data
20/// and Jacobian operations.
21#[derive(Debug, Clone, PartialEq, Eq)]
22#[non_exhaustive]
23pub enum MjDataError {
24    /// A provided object or body index was out of the valid range.
25    IndexOutOfBounds {
26        /// Name of the index parameter, e.g. `"body_id"` or `"geom_id"`.
27        kind: &'static str,
28        /// The index value that was passed.
29        id: usize,
30        /// Exclusive upper bound of the valid range.
31        upper: usize,
32    },
33    /// The provided MuJoCo object type is not supported by this operation.
34    UnsupportedObjectType(MjtObjBase),
35    /// MuJoCo failed to allocate the requested structure.
36    AllocationFailed,
37    /// A buffer passed to the operation is too small for the required data.
38    BufferTooSmall {
39        /// Descriptive name of the buffer (e.g. `"destination"`, `"state"`).
40        name: &'static str,
41        /// Actual length of the buffer that was provided.
42        got: usize,
43        /// Minimum length the buffer must have.
44        needed: usize,
45    },
46    /// A slice or array parameter has the wrong length for the operation.
47    LengthMismatch {
48        /// Descriptive name of the parameter.
49        name: &'static str,
50        /// Expected length.
51        expected: usize,
52        /// Actual length that was provided.
53        got: usize,
54    },
55    /// Two model-bound objects were created from models that are not compatible.
56    ///
57    /// This is returned by APIs that require a matching model memory layout,
58    /// including data-copy operations and info-view accessors.
59    IncompatibleModel {
60        /// Layout of the source object's model.
61        source: Arc<MjModelLayout>,
62        /// Layout of the destination object's model.
63        destination: Arc<MjModelLayout>,
64    },
65    /// The specified actuator or sensor has no associated history buffer.
66    NoHistoryBuffer {
67        /// `"actuator"` or `"sensor"`.
68        kind: &'static str,
69        /// The zero-based index of the actuator or sensor.
70        id: usize,
71    },
72    /// A history buffer cursor in the written state is outside its valid range.
73    ///
74    /// [`MjData::set_state`](crate::wrappers::MjData::set_state) rejects the write and leaves
75    /// the history buffer unchanged.
76    InvalidHistoryCursor {
77        /// `"actuator"` or `"sensor"`.
78        kind: &'static str,
79        /// The zero-based index of the actuator or sensor.
80        id: usize,
81        /// Number of samples in that history buffer; a valid cursor lies in `0..nsample`.
82        nsample: usize,
83    },
84    /// The contact buffer is full; no more contacts can be added.
85    ContactBufferFull,
86    /// A filesystem path argument contains invalid UTF-8.
87    InvalidUtf8Path,
88    /// A file I/O operation failed.
89    IoError {
90        /// Path of the file.
91        path: PathBuf,
92        /// Kind of the I/O failure.
93        kind: io::ErrorKind,
94    },
95}
96
97impl fmt::Display for MjDataError {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        match self {
100            Self::IndexOutOfBounds { kind, id, upper } => {
101                write!(f, "{kind} {id} is out of bounds [0, {upper})")
102            }
103            Self::UnsupportedObjectType(obj_type) => {
104                write!(f, "object type {obj_type:?} is not supported by this operation")
105            }
106            Self::AllocationFailed => {
107                write!(f, "MuJoCo allocation failed")
108            }
109            Self::BufferTooSmall { name, got, needed } => {
110                write!(
111                    f,
112                    "{name} buffer is too small: got {got} elements, \
113                     but need at least {needed}"
114                )
115            }
116            Self::IncompatibleModel { .. } => write!(f, "incompatible model"),
117            Self::LengthMismatch { name, expected, got } => {
118                write!(
119                    f,
120                    "{name} has wrong length: expected {expected}, got {got}"
121                )
122            }
123            Self::NoHistoryBuffer { kind, id } => {
124                write!(f, "{kind} {id} has no history buffer")
125            }
126            Self::InvalidHistoryCursor { kind, id, nsample } => {
127                write!(f, "{kind} {id} history cursor is out of bounds [0, {nsample})")
128            }
129            Self::ContactBufferFull => {
130                write!(f, "contact buffer is full")
131            }
132            Self::InvalidUtf8Path => {
133                write!(f, "path contains invalid UTF-8")
134            }
135            Self::IoError { path, kind } => {
136                write!(f, "file I/O failed for {}: {kind}", path.display())
137            }
138        }
139    }
140}
141
142impl std::error::Error for MjDataError {}
143
144
145/// Errors that can occur in 3-D scene and visualization operations
146/// ([`MjvScene`](crate::wrappers::MjvScene)).
147#[derive(Debug, Clone, PartialEq, Eq)]
148#[non_exhaustive]
149pub enum MjSceneError {
150    /// No more space is available for new geoms in the scene.
151    ///
152    /// Increase the `max_geom` capacity passed to
153    /// [`MjvScene::new`](crate::wrappers::MjvScene::new).
154    SceneFull {
155        /// The current maximum geom capacity of the scene.
156        capacity: i32,
157    },
158    /// A string label exceeds the fixed-size label buffer of an [`MjvGeom`](crate::wrappers::MjvGeom).
159    LabelTooLong {
160        /// Length of the label in bytes.
161        len: usize,
162        /// Maximum number of bytes (excluding the NUL terminator) the buffer can hold.
163        capacity: usize,
164    },
165    /// The figure's line-data buffer for a given plot is full.
166    FigureBufferFull {
167        /// Index of the plot whose buffer is full.
168        plot_index: usize,
169        /// Maximum number of data points the buffer can hold.
170        capacity: usize,
171    },
172    /// A point index is out of range for the current data in a figure plot.
173    FigureIndexOutOfBounds {
174        /// Index of the plot.
175        plot_index: usize,
176        /// The point index that was provided.
177        point_index: usize,
178        /// Current number of data points in the plot.
179        current_len: usize,
180    },
181    /// A plot index is out of the valid range `[0, mjMAXLINE)`.
182    InvalidPlotIndex {
183        /// The out-of-range plot index that was provided.
184        plot_index: usize,
185        /// Maximum number of plots (`mjMAXLINE`).
186        max_plots: usize,
187    },
188    /// A geom label string contains non-ASCII bytes.
189    ///
190    /// MuJoCo's renderer treats the label buffer as ASCII; multi-byte UTF-8
191    /// sequences would be rendered as garbage characters.
192    NonAsciiLabel,
193    /// An integer value does not correspond to any known camera type variant.
194    InvalidCameraType(i32),
195}
196
197impl fmt::Display for MjSceneError {
198    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199        match self {
200            Self::SceneFull { capacity } => {
201                write!(
202                    f,
203                    "scene geom buffer is full (capacity = {capacity}); \
204                     increase max_geom when constructing MjvScene"
205                )
206            }
207            Self::LabelTooLong { len, capacity } => {
208                write!(
209                    f,
210                    "label of {len} bytes exceeds the fixed buffer capacity of {capacity} bytes"
211                )
212            }
213            Self::FigureBufferFull { plot_index, capacity } => {
214                write!(
215                    f,
216                    "figure plot {plot_index} buffer is full \
217                     (capacity = {capacity} data points)"
218                )
219            }
220            Self::FigureIndexOutOfBounds { plot_index, point_index, current_len } => {
221                write!(
222                    f,
223                    "point index {point_index} is out of bounds for plot {plot_index} \
224                     (current length = {current_len})"
225                )
226            }
227            Self::InvalidPlotIndex { plot_index, max_plots } => {
228                write!(
229                    f,
230                    "plot index {plot_index} is out of range [0, {max_plots})"
231                )
232            }
233            Self::NonAsciiLabel => {
234                write!(f, "label contains non-ASCII characters")
235            }
236            Self::InvalidCameraType(raw) => {
237                write!(f, "unknown camera type {raw}")
238            }
239        }
240    }
241}
242
243impl std::error::Error for MjSceneError {}
244
245
246/// Errors that can occur in GPU rendering-context ([`MjrContext`](crate::wrappers::MjrContext)) operations.
247#[derive(Debug, Clone, PartialEq, Eq)]
248#[non_exhaustive]
249pub enum MjrContextError {
250    /// An index passed to a rendering-context operation is out of the valid range.
251    IndexOutOfBounds {
252        /// The index that was supplied.
253        id: usize,
254        /// Exclusive upper bound of the valid range.
255        len: usize,
256    },
257    /// A viewport has invalid (negative) dimensions.
258    InvalidViewport {
259        /// The viewport width that was provided.
260        width: i32,
261        /// The viewport height that was provided.
262        height: i32,
263    },
264    /// A pixel buffer passed to a rendering-context operation is too small.
265    BufferTooSmall {
266        /// Descriptive name of the buffer (e.g. `"rgb"`, `"depth"`).
267        name: &'static str,
268        /// Actual length of the buffer that was provided.
269        got: usize,
270        /// Minimum length the buffer must have.
271        needed: usize,
272    },
273}
274
275impl fmt::Display for MjrContextError {
276    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277        match self {
278            Self::IndexOutOfBounds { id, len } => {
279                write!(f, "index {id} is out of range [0, {len})")
280            }
281            Self::InvalidViewport { width, height } => {
282                write!(
283                    f,
284                    "viewport dimensions must be non-negative, got {width}x{height}"
285                )
286            }
287            Self::BufferTooSmall { name, got, needed } => {
288                write!(
289                    f,
290                    "{name} buffer is too small: got {got} elements, \
291                     but need at least {needed}"
292                )
293            }
294        }
295    }
296}
297
298impl std::error::Error for MjrContextError {}
299
300
301/// Errors that can occur in model-specification editing operations
302/// ([`MjSpec`](crate::wrappers::mj_editing::MjSpec) and related types).
303#[derive(Debug, Clone, PartialEq, Eq)]
304#[non_exhaustive]
305pub enum MjEditError {
306    /// MuJoCo failed to allocate the requested spec or model element.
307    AllocationFailed,
308    /// A filesystem path argument contains invalid UTF-8.
309    InvalidUtf8Path,
310    /// MuJoCo failed to parse the XML (or other format) input.
311    ParseFailed(String),
312    /// Compiling the spec into a model failed. Carries MuJoCo's error message, or the reason the
313    /// wrapper rejected the spec before the compile step.
314    CompileFailed(String),
315    /// MuJoCo failed to save the spec to XML.
316    SaveFailed(String),
317    /// Some operation on [`MjSpec`](crate::wrappers::mj_editing::MjSpec) failed in MuJoCo.
318    OperationFailed(String),
319    /// A referenced element (e.g. parent default class) was not found.
320    NotFound,
321    /// An element with the same name already exists.
322    AlreadyExists,
323    /// This operation is not supported for the current element.
324    UnsupportedOperation,
325    /// Deleting the element failed. Carries MuJoCo's error message, or the reason the wrapper
326    /// rejected the element.
327    DeleteFailed(String),
328    /// Attaching the child element failed. Carries MuJoCo's error message.
329    AttachFailed(String),
330    /// The output buffer passed to [`MjSpec::save_xml_string`](crate::wrappers::mj_editing::MjSpec::save_xml_string)
331    /// was too small to hold the XML.
332    ///
333    /// `required_size` follows `snprintf`-style semantics: it is the number of bytes MuJoCo would
334    /// write, **not** counting the NUL terminator.  To retry successfully, pass a buffer of at
335    /// least `required_size + 1` bytes.
336    XmlBufferTooSmall {
337        /// Number of bytes MuJoCo would write (excluding the NUL terminator).
338        /// Pass a buffer of at least `required_size + 1` bytes to retry.
339        required_size: usize,
340    },
341    /// Returned when the parameters to the given method/function are invalid.
342    InvalidParameter(String),
343    /// The given index is out of range.
344    IndexOutOfBounds {
345        /// The index that was supplied.
346        id: usize,
347        /// Exclusive upper bound of the valid range.
348        len: usize,
349    },
350    /// The frame belongs to a body different from the one where a frame is being set.
351    FrameParentMismatch,
352}
353
354impl fmt::Display for MjEditError {
355    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
356        match self {
357            Self::AllocationFailed => write!(f, "MuJoCo failed to allocate the spec or model element"),
358            Self::InvalidUtf8Path => write!(f, "path contains invalid UTF-8"),
359            Self::ParseFailed(msg) => write!(f, "parse failed: {msg}"),
360            Self::CompileFailed(msg) => write!(f, "compilation failed: {msg}"),
361            Self::SaveFailed(msg) => write!(f, "save failed: {msg}"),
362            Self::OperationFailed(msg) => write!(f, "operation failed: {msg}"),
363            Self::NotFound => write!(f, "referenced element not found"),
364            Self::AlreadyExists => write!(f, "element with the same name already exists"),
365            Self::UnsupportedOperation => write!(f, "this operation is not supported"),
366            Self::DeleteFailed(msg) => write!(f, "delete failed: {msg}"),
367            Self::AttachFailed(msg) => write!(f, "attach failed: {msg}"),
368            Self::XmlBufferTooSmall { required_size } => write!(
369                f,
370                "XML output buffer too small; retry with at least {} bytes",
371                required_size + 1
372            ),
373            Self::InvalidParameter(msg) => write!(f, "{msg}"),
374            Self::IndexOutOfBounds { id, len } => write!(
375                f,
376                "index {id} is out of bounds (length is {len})"
377            ),
378            Self::FrameParentMismatch => write!(f, "frame belongs to a different body"),
379        }
380    }
381}
382
383impl std::error::Error for MjEditError {}
384
385
386/// Errors that can occur in [`MjModel`](crate::wrappers::MjModel) operations.
387#[derive(Debug, Clone, PartialEq, Eq)]
388#[non_exhaustive]
389pub enum MjModelError {
390    /// A filesystem path argument contains invalid UTF-8.
391    InvalidUtf8Path,
392    /// MuJoCo failed to load the model from a file, string, or buffer.
393    LoadFailed(String),
394    /// MuJoCo failed to load an asset of an already constructed [`MjModel`](crate::wrappers::MjModel).
395    AssetLoadFailed(String),
396    /// MuJoCo failed to save the model XML.
397    SaveFailed(String),
398    /// MuJoCo failed to allocate the requested structure.
399    AllocationFailed,
400    /// The state source slice has the wrong length for the given spec.
401    StateSliceLengthMismatch {
402        /// Expected length.
403        expected: usize,
404        /// Actual length.
405        got: usize,
406    },
407    /// A vector has the wrong length for the model layout.
408    LengthMismatch {
409        /// Descriptive name of the parameter.
410        name: &'static str,
411        /// Expected length.
412        expected: usize,
413        /// Actual length that was provided.
414        got: usize,
415    },
416    /// The destination spec is not a subset of the source spec.
417    SpecNotSubset,
418    /// A destination buffer is too small for the operation.
419    BufferTooSmall {
420        /// Minimum number of elements required.
421        needed: usize,
422        /// Actual number of elements available.
423        available: usize,
424    },
425    /// Two model-bound objects were created from models that are not compatible.
426    IncompatibleModel {
427        /// Layout of the source object's model.
428        source: Arc<MjModelLayout>,
429        /// Layout of the destination object's model.
430        destination: Arc<MjModelLayout>,
431    },
432    /// A virtual-file-system operation failed while loading a model from a string.
433    VfsError(MjVfsError),
434    /// The given index is out of range.
435    IndexOutOfBounds {
436        /// The index that was supplied.
437        id: usize,
438        /// Exclusive upper bound of the valid range.
439        len: usize,
440    },
441    /// A file I/O operation failed.
442    IoError {
443        /// Path of the file.
444        path: PathBuf,
445        /// Kind of the I/O failure.
446        kind: io::ErrorKind,
447    },
448}
449
450impl fmt::Display for MjModelError {
451    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
452        match self {
453            Self::InvalidUtf8Path => write!(f, "path contains invalid UTF-8"),
454            Self::LoadFailed(msg) => write!(f, "model load failed: {msg}"),
455            Self::SaveFailed(msg) => write!(f, "model save failed: {msg}"),
456            Self::AssetLoadFailed(msg) => write!(f, "asset load failed: {msg}"),
457            Self::AllocationFailed => write!(f, "MuJoCo failed to allocate the requested structure"),
458            Self::StateSliceLengthMismatch { expected, got } => {
459                write!(f, "state slice length mismatch: expected {expected}, got {got}")
460            }
461            Self::LengthMismatch { name, expected, got } => {
462                write!(f, "{name} has wrong length: expected {expected}, got {got}")
463            }
464            Self::SpecNotSubset => {
465                write!(f, "dst_spec must be a subset of src_spec")
466            }
467            Self::BufferTooSmall { needed, available } => {
468                write!(
469                    f,
470                    "buffer is too small: got {available} elements, \
471                     but need at least {needed}"
472                )
473            }
474            Self::IncompatibleModel { .. } => write!(f, "incompatible model"),
475            Self::VfsError(_) => write!(f, "VFS operation failed"),
476            Self::IndexOutOfBounds { id, len } => write!(
477                f,
478                "index {id} is out of bounds (length is {len})"
479            ),
480            Self::IoError { path, kind } => {
481                write!(f, "file I/O failed for {}: {kind}", path.display())
482            }
483        }
484    }
485}
486
487impl std::error::Error for MjModelError {
488    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
489        match self {
490            Self::VfsError(e) => Some(e),
491            _ => None,
492        }
493    }
494}
495
496impl From<MjVfsError> for MjModelError {
497    fn from(e: MjVfsError) -> Self {
498        Self::VfsError(e)
499    }
500}
501
502
503/// Errors that can occur in virtual file system ([`MjVfs`](crate::wrappers::MjVfs)) operations.
504#[derive(Debug, Clone, PartialEq, Eq)]
505#[non_exhaustive]
506pub enum MjVfsError {
507    /// A file with the same name already exists in the VFS.
508    AlreadyExists,
509    /// MuJoCo failed to load the file or register the buffer.
510    LoadFailed,
511    /// The specified file was not found in the VFS.
512    NotFound,
513    /// The provided path contains invalid UTF-8.
514    InvalidUtf8Path,
515    /// The buffer length exceeds `i32::MAX` bytes.
516    BufferTooLarge,
517    /// An unrecognized MuJoCo return code.
518    Unknown(i32),
519}
520
521impl fmt::Display for MjVfsError {
522    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
523        match self {
524            Self::AlreadyExists => write!(f, "file already exists in VFS"),
525            Self::LoadFailed => write!(f, "failed to load file into VFS"),
526            Self::NotFound => write!(f, "file not found in VFS"),
527            Self::InvalidUtf8Path => write!(f, "path contains invalid UTF-8"),
528            Self::BufferTooLarge => write!(f, "buffer length exceeds i32::MAX bytes"),
529            Self::Unknown(code) => write!(f, "unknown VFS error (code {code})"),
530        }
531    }
532}
533
534impl std::error::Error for MjVfsError {}
535
536
537/// Errors that can occur when loading MuJoCo plugin libraries.
538#[derive(Debug, Clone, PartialEq, Eq)]
539#[non_exhaustive]
540pub enum MjPluginError {
541    /// The provided path contains invalid UTF-8.
542    InvalidUtf8Path,
543    /// The path string contains an interior null byte.
544    NullBytePath,
545}
546
547impl fmt::Display for MjPluginError {
548    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
549        match self {
550            Self::InvalidUtf8Path => write!(f, "path contains invalid UTF-8"),
551            Self::NullBytePath => write!(f, "path contains an interior null byte"),
552        }
553    }
554}
555
556impl std::error::Error for MjPluginError {}
557
558/// Errors that can occur in batched simulation ([`MjBatch`](crate::wrappers::MjBatch)) operations.
559#[derive(Debug, Clone, PartialEq, Eq)]
560#[non_exhaustive]
561pub enum MjBatchError {
562    /// MuJoCo failed to create the batch. Carries MuJoCo's error message.
563    MakeFailed(String),
564    /// The data was created from a model that is not compatible with the batch's model.
565    IncompatibleModel {
566        /// Layout of the batch's model.
567        source: Arc<MjModelLayout>,
568        /// Layout of the data's model.
569        destination: Arc<MjModelLayout>,
570    },
571    /// The name is not an [`MjData`](crate::wrappers::MjData) field that the batch can output.
572    /// Carries the name.
573    InvalidOutput(String),
574    /// The name is not an [`MjModel`](crate::wrappers::MjModel) field that the batch can expand.
575    /// Carries the name.
576    InvalidExpand(String),
577    /// The name is not an expanded [`MjModel`](crate::wrappers::MjModel) field of the batch.
578    /// Carries the name.
579    NotExpanded(String),
580    /// The requested element type does not match the elements of the field.
581    FieldTypeMismatch {
582        /// Name of the field.
583        name: String,
584        /// Size of one element of the field, in bytes.
585        element_size: usize,
586    },
587}
588
589impl fmt::Display for MjBatchError {
590    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
591        match self {
592            Self::MakeFailed(msg) => write!(f, "batch creation failed: {msg}"),
593            Self::IncompatibleModel { .. } => write!(f, "incompatible model"),
594            Self::InvalidOutput(name) => write!(f, "invalid batch output field '{name}'"),
595            Self::InvalidExpand(name) => write!(f, "invalid batch expand field '{name}'"),
596            Self::NotExpanded(name) => write!(f, "batch field '{name}' is not expanded"),
597            Self::FieldTypeMismatch { name, element_size } => write!(
598                f, "element type does not match field '{name}' ({element_size}-byte elements)"
599            ),
600        }
601    }
602}
603
604impl std::error::Error for MjBatchError {}
605
606/// Errors that can occur during OpenGL / window initialization.
607///
608/// Each variant represents a distinct step in the initialization pipeline.
609#[cfg(any(feature = "viewer", feature = "renderer-winit-fallback"))]
610#[derive(Debug, Clone)]
611#[non_exhaustive]
612pub enum GlInitError {
613    /// The windowing / display builder failed to initialize.
614    DisplayBuild(String),
615    /// No window was produced: the display builder returned no window, or the event loop never
616    /// resumed.
617    NoWindow,
618    /// Failed to obtain the native window handle.
619    WindowHandle(String),
620    /// OpenGL context creation failed. Wraps [`glutin::error::Error`].
621    ContextCreation(glutin::error::Error),
622    /// Window surface attributes could not be constructed.
623    SurfaceAttributes(String),
624    /// Rendering surface creation failed. Wraps [`glutin::error::Error`].
625    SurfaceCreation(glutin::error::Error),
626    /// Making the GL context current on the surface failed. Wraps [`glutin::error::Error`].
627    MakeCurrent(glutin::error::Error),
628}
629
630#[cfg(any(feature = "viewer", feature = "renderer-winit-fallback"))]
631impl fmt::Display for GlInitError {
632    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
633        match self {
634            Self::DisplayBuild(e) => write!(f, "display build failed: {e}"),
635            Self::NoWindow => write!(f, "display builder did not create a window"),
636            Self::WindowHandle(e) => write!(f, "failed to obtain window handle: {e}"),
637            Self::ContextCreation(_) => write!(f, "GL context creation failed"),
638            Self::SurfaceAttributes(e) => write!(f, "failed to build surface attributes: {e}"),
639            Self::SurfaceCreation(_) => write!(f, "window surface creation failed"),
640            Self::MakeCurrent(_) => write!(f, "failed to make GL context current"),
641        }
642    }
643}
644
645#[cfg(any(feature = "viewer", feature = "renderer-winit-fallback"))]
646impl std::error::Error for GlInitError {
647    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
648        match self {
649            Self::ContextCreation(e) |
650            Self::SurfaceCreation(e) |
651            Self::MakeCurrent(e) => Some(e),
652            _ => None,
653        }
654    }
655}