khora_core/renderer/
error.rs

1// Copyright 2025 eraflo
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Defines the hierarchy of error types for the rendering subsystem.
16
17use crate::renderer::api::core::ShaderModuleId;
18use crate::renderer::api::pipeline::RenderPipelineId;
19use std::fmt;
20
21/// An error related to the creation, loading, or compilation of a shader module.
22#[derive(Debug)]
23pub enum ShaderError {
24    /// An error occurred while trying to load the shader source from a path.
25    LoadError {
26        /// The path of the file that failed to load.
27        path: String,
28        /// The underlying I/O or source error.
29        source_error: String,
30    },
31    /// The shader source failed to compile into a backend-specific module.
32    CompilationError {
33        /// A descriptive label for the shader, if available.
34        label: String,
35        /// Detailed error messages from the shader compiler.
36        details: String,
37    },
38    /// The requested shader module could not be found.
39    NotFound {
40        /// The ID of the shader module that was not found.
41        id: ShaderModuleId,
42    },
43    /// The specified entry point (e.g., `vs_main`) is not valid for the shader module.
44    InvalidEntryPoint {
45        /// The ID of the shader module.
46        id: ShaderModuleId,
47        /// The entry point name that was not found.
48        entry_point: String,
49    },
50}
51
52impl fmt::Display for ShaderError {
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        match self {
55            ShaderError::LoadError { path, source_error } => {
56                write!(
57                    f,
58                    "Failed to load shader source from '{path}': {source_error}"
59                )
60            }
61            ShaderError::CompilationError { label, details } => {
62                write!(f, "Shader compilation failed for '{label}': {details}")
63            }
64            ShaderError::NotFound { id } => {
65                write!(f, "Shader module not found for ID: {id:?}")
66            }
67            ShaderError::InvalidEntryPoint { id, entry_point } => {
68                write!(
69                    f,
70                    "Invalid entry point '{entry_point}' for shader module {id:?}"
71                )
72            }
73        }
74    }
75}
76
77impl std::error::Error for ShaderError {}
78
79/// An error related to the creation or management of a graphics pipeline.
80#[derive(Debug)]
81pub enum PipelineError {
82    /// Failed to create a pipeline layout from the provided shader reflection data.
83    LayoutCreationFailed(String),
84    /// The graphics backend failed to compile the full pipeline state object.
85    CompilationFailed {
86        /// A descriptive label for the pipeline, if available.
87        label: Option<String>,
88        /// Detailed error messages from the backend.
89        details: String,
90    },
91    /// A shader module provided for the pipeline was invalid or missing.
92    InvalidShaderModuleForPipeline {
93        /// The ID of the invalid shader module.
94        id: ShaderModuleId,
95        /// The label of the pipeline being created.
96        pipeline_label: Option<String>,
97    },
98    /// The specified render pipeline ID is not valid.
99    InvalidRenderPipeline {
100        /// The ID of the invalid render pipeline.
101        id: RenderPipelineId,
102    },
103    /// The fragment shader stage is present but no entry point was specified.
104    MissingEntryPointForFragmentShader {
105        /// The label of the pipeline being created.
106        pipeline_label: Option<String>,
107        /// The ID of the fragment shader module.
108        shader_id: ShaderModuleId,
109    },
110    /// The color target format is not compatible with the pipeline or device.
111    IncompatibleColorTarget(String),
112    /// The depth/stencil format is not compatible with the pipeline or device.
113    IncompatibleDepthStencilFormat(String),
114    /// A required graphics feature is not supported by the device.
115    FeatureNotSupported(String),
116}
117
118impl fmt::Display for PipelineError {
119    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120        match self {
121            PipelineError::LayoutCreationFailed(msg) => {
122                write!(f, "Pipeline layout creation failed: {msg}")
123            }
124            PipelineError::CompilationFailed { label, details } => {
125                write!(
126                    f,
127                    "Pipeline compilation failed for '{}': {}",
128                    label.as_deref().unwrap_or("Unknown"),
129                    details
130                )
131            }
132            PipelineError::InvalidShaderModuleForPipeline { id, pipeline_label } => {
133                write!(
134                    f,
135                    "Invalid shader module {:?} for pipeline '{}'",
136                    id,
137                    pipeline_label.as_deref().unwrap_or("Unknown")
138                )
139            }
140            PipelineError::InvalidRenderPipeline { id } => {
141                write!(f, "Invalid render pipeline ID: {id:?}")
142            }
143            PipelineError::MissingEntryPointForFragmentShader {
144                pipeline_label,
145                shader_id,
146            } => {
147                write!(
148                    f,
149                    "Missing entry point for fragment shader in pipeline '{}', shader ID: {:?}",
150                    pipeline_label.as_deref().unwrap_or("Unknown"),
151                    shader_id
152                )
153            }
154            PipelineError::IncompatibleColorTarget(msg) => {
155                write!(f, "Incompatible color target format: {msg}")
156            }
157            PipelineError::IncompatibleDepthStencilFormat(msg) => {
158                write!(f, "Incompatible depth/stencil format: {msg}")
159            }
160            PipelineError::FeatureNotSupported(msg) => {
161                write!(f, "Feature not supported: {msg}")
162            }
163        }
164    }
165}
166
167impl std::error::Error for PipelineError {}
168
169/// An error related to the creation or use of a GPU resource (buffers, textures, etc.).
170#[derive(Debug)]
171pub enum ResourceError {
172    /// A shader-specific error occurred.
173    Shader(ShaderError),
174    /// A pipeline-specific error occurred.
175    Pipeline(PipelineError),
176    /// A generic resource could not be found.
177    NotFound,
178    /// The handle or ID used to reference a resource is invalid.
179    InvalidHandle,
180    /// An error originating from the specific graphics backend implementation.
181    BackendError(String),
182    /// An attempt was made to access a resource out of its bounds (e.g., in a buffer).
183    OutOfBounds,
184}
185
186impl fmt::Display for ResourceError {
187    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188        match self {
189            ResourceError::Shader(err) => write!(f, "Shader resource error: {err}"),
190            ResourceError::Pipeline(err) => write!(f, "Pipeline resource error: {err}"),
191            ResourceError::NotFound => write!(f, "Resource not found with ID."),
192            ResourceError::InvalidHandle => write!(f, "Invalid resource handle or ID."),
193            ResourceError::BackendError(msg) => {
194                write!(f, "Backend-specific resource error: {msg}")
195            }
196            ResourceError::OutOfBounds => {
197                write!(f, "Resource access out of bounds.")
198            }
199        }
200    }
201}
202
203impl std::error::Error for ResourceError {
204    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
205        match self {
206            ResourceError::Shader(err) => Some(err),
207            _ => None,
208        }
209    }
210}
211
212impl From<ShaderError> for ResourceError {
213    fn from(err: ShaderError) -> Self {
214        ResourceError::Shader(err)
215    }
216}
217
218impl From<PipelineError> for ResourceError {
219    fn from(err: PipelineError) -> Self {
220        ResourceError::Pipeline(err)
221    }
222}
223
224/// A high-level error that can occur within the main rendering system or graphics device.
225#[derive(Debug)]
226pub enum RenderError {
227    /// An operation was attempted before the rendering system was initialized.
228    NotInitialized,
229    /// A failure occurred during the initialization of the graphics backend.
230    InitializationFailed(String),
231    /// Failed to acquire the next frame from the swapchain/surface for rendering.
232    SurfaceAcquisitionFailed(String),
233    /// A critical, unrecoverable rendering operation failed.
234    RenderingFailed(String),
235    /// An error occurred while managing a GPU resource.
236    ResourceError(ResourceError),
237    /// The graphics device was lost (e.g., GPU driver crashed or was updated).
238    /// This is a catastrophic error that typically requires reinitialization.
239    DeviceLost,
240    /// An unexpected or internal error occurred.
241    Internal(String),
242}
243
244impl fmt::Display for RenderError {
245    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
246        match self {
247            RenderError::NotInitialized => {
248                write!(f, "The rendering system is not initialized.")
249            }
250            RenderError::InitializationFailed(msg) => {
251                write!(f, "Failed to initialize graphics backend: {msg}")
252            }
253            RenderError::SurfaceAcquisitionFailed(msg) => {
254                write!(f, "Failed to acquire surface for rendering: {msg}")
255            }
256            RenderError::RenderingFailed(msg) => {
257                write!(f, "A critical rendering operation failed: {msg}")
258            }
259            RenderError::ResourceError(err) => {
260                write!(f, "Graphics resource operation failed: {err}")
261            }
262            RenderError::DeviceLost => write!(
263                f,
264                "The graphics device was lost and needs to be reinitialized."
265            ),
266            RenderError::Internal(msg) => {
267                write!(f, "An internal or unexpected error occurred: {msg}")
268            }
269        }
270    }
271}
272
273impl std::error::Error for RenderError {
274    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
275        match self {
276            RenderError::ResourceError(err) => Some(err),
277            _ => None,
278        }
279    }
280}
281
282impl From<ResourceError> for RenderError {
283    fn from(err: ResourceError) -> Self {
284        RenderError::ResourceError(err)
285    }
286}
287
288#[cfg(test)]
289mod tests {
290    use std::error::Error;
291
292    use super::*;
293    use crate::renderer::api::core::ShaderModuleId;
294
295    #[test]
296    fn shader_error_display() {
297        let err = ShaderError::LoadError {
298            path: "path/to/shader.wgsl".to_string(),
299            source_error: "File not found".to_string(),
300        };
301        assert_eq!(
302            format!("{err}"),
303            "Failed to load shader source from 'path/to/shader.wgsl': File not found"
304        );
305
306        let err_comp = ShaderError::CompilationError {
307            label: "MyShader".to_string(),
308            details: "Syntax error at line 5".to_string(),
309        };
310        assert_eq!(
311            format!("{err_comp}"),
312            "Shader compilation failed for 'MyShader': Syntax error at line 5"
313        );
314    }
315
316    #[test]
317    fn resource_error_display_wrapping_shader_error() {
318        let shader_err = ShaderError::NotFound {
319            id: ShaderModuleId(42),
320        };
321        let res_err: ResourceError = shader_err.into();
322        assert_eq!(
323            format!("{res_err}"),
324            "Shader resource error: Shader module not found for ID: ShaderModuleId(42)"
325        );
326        assert!(res_err.source().is_some());
327    }
328
329    #[test]
330    fn render_error_display_wrapping_resource_error() {
331        let shader_err = ShaderError::NotFound {
332            id: ShaderModuleId(101),
333        };
334        let res_err: ResourceError = shader_err.into();
335        let render_err: RenderError = res_err.into();
336        assert_eq!(
337            format!("{render_err}"),
338            "Graphics resource operation failed: Shader resource error: Shader module not found for ID: ShaderModuleId(101)"
339        );
340        assert!(render_err.source().is_some());
341        assert!(render_err.source().unwrap().source().is_some());
342    }
343}