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