improve error handling and add metadata

This commit is contained in:
Chance 2025-04-03 01:00:24 -04:00
parent 13893e96a9
commit 43fd5966b7
17 changed files with 1162 additions and 787 deletions

View file

@ -5,132 +5,15 @@ use std::time::Instant;
use cgmath::{Deg, Matrix4, Point3, Rad, SquareMatrix, Vector3, perspective};
use futures::executor::block_on;
use thiserror::Error;
use tracing::{error, info, trace};
use tracing::{debug, error, info, trace};
use wgpu::TextureUsages;
use wgpu::{Backends, InstanceDescriptor, util::DeviceExt};
use wgpu_text::glyph_brush::ab_glyph::FontRef;
use wgpu_text::glyph_brush::{HorizontalAlign, Layout, OwnedSection, OwnedText, VerticalAlign};
use wgpu_text::{BrushBuilder, TextBrush};
use winit::window::Window;
#[derive(Debug, Error)]
#[error(transparent)]
pub enum ContextErrorKind {
#[error("Surface creation failed")]
SurfaceCreation,
#[error("Surface configuration failed")]
SurfaceConfiguration,
#[error("Adapter request failed")]
AdapterRequest,
#[error("Device request failed")]
DeviceRequest,
#[error("Surface texture acquisition failed")]
SurfaceTexture,
}
#[derive(Debug, Error)]
pub struct RenderContextError {
kind: ContextErrorKind,
label: Option<Cow<'static, str>>,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
}
impl RenderContextError {
pub fn new(
kind: ContextErrorKind,
label: impl Into<Option<Cow<'static, str>>>,
source: impl Into<Option<Box<dyn std::error::Error + Send + Sync>>>,
) -> Self {
Self {
kind,
label: label.into(),
source: source.into(),
}
}
pub fn with_label(mut self, label: impl Into<Cow<'static, str>>) -> Self {
self.label = Some(label.into());
self
}
}
impl std::fmt::Display for RenderContextError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(label) = &self.label {
writeln!(f, "[{}] {}", label, self.kind)?;
} else {
writeln!(f, "{}", self.kind)?;
}
if let Some(source) = &self.source {
fn fmt_chain(
err: &(dyn std::error::Error + 'static),
indent: usize,
f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
let indent_str = " ".repeat(indent);
writeln!(f, "{}{}", indent_str, err)?;
if let Some(next) = err.source() {
writeln!(f, "{}Caused by:", indent_str)?;
fmt_chain(next, indent + 1, f)?;
}
Ok(())
}
writeln!(f, "Caused by:")?;
fmt_chain(source.as_ref(), 1, f)?;
}
Ok(())
}
}
trait IntoRenderContextError<T> {
fn ctx_err(
self,
kind: ContextErrorKind,
label: impl Into<Cow<'static, str>>,
) -> Result<T, RenderContextError>;
}
impl<T, E> IntoRenderContextError<T> for Result<T, E>
where
E: std::error::Error + Send + Sync + 'static,
{
fn ctx_err(
self,
kind: ContextErrorKind,
label: impl Into<Cow<'static, str>>,
) -> Result<T, RenderContextError> {
self.map_err(|e| {
RenderContextError::new(
kind,
Some(label.into()),
Some(Box::new(e) as Box<dyn std::error::Error + Send + Sync>),
)
})
}
}
impl From<wgpu::CreateSurfaceError> for RenderContextError {
fn from(err: wgpu::CreateSurfaceError) -> Self {
RenderContextError::new(
ContextErrorKind::SurfaceCreation,
Some("Surface creation".into()),
Some(Box::new(err) as Box<dyn std::error::Error + Send + Sync>),
)
}
}
impl From<wgpu::RequestDeviceError> for RenderContextError {
fn from(err: wgpu::RequestDeviceError) -> Self {
RenderContextError::new(
ContextErrorKind::DeviceRequest,
Some("Device setup".into()),
Some(Box::new(err) as Box<dyn std::error::Error + Send + Sync>),
)
}
}
use crate::error::{ZenyxError, ZenyxErrorKind};
use crate::error::Result;
const SHADER_SRC: &str = include_str!("shader.wgsl");
@ -238,7 +121,7 @@ impl Camera {
queue.write_buffer(&self.uniform_buffer, 0, bytemuck::bytes_of(&uniform));
}
}
#[derive(Debug)]
struct Model {
vertex_buffer: wgpu::Buffer,
index_buffer: wgpu::Buffer,
@ -246,6 +129,7 @@ struct Model {
bind_group: wgpu::BindGroup,
index_count: u32,
transform: Matrix4<f32>,
version: u32,
}
impl Model {
@ -289,6 +173,7 @@ impl Model {
bind_group,
index_count: indices.len() as u32,
transform: Matrix4::identity(),
version: 1
}
}
@ -299,8 +184,12 @@ impl Model {
}
fn set_transform(&mut self, transform: Matrix4<f32>) {
self.transform = transform;
if self.transform != transform {
self.transform = transform;
self.version += 1;
}
}
}
pub struct Renderer<'window> {
@ -321,6 +210,7 @@ pub struct Renderer<'window> {
frame_count: u32,
fps: f32,
font_state: FontState,
model_versions: Vec<u32>,
}
struct FontState {
@ -331,15 +221,13 @@ struct FontState {
}
impl<'window> Renderer<'window> {
pub async fn new(window: Arc<Window>) -> Result<Self, RenderContextError> {
pub async fn new(window: Arc<Window>) -> Result<Self> {
let instance = wgpu::Instance::new(&InstanceDescriptor {
backends: Backends::from_comma_list("dx12,metal,opengl,webgpu"),
..Default::default()
});
let surface = instance
.create_surface(Arc::clone(&window))
.ctx_err(ContextErrorKind::SurfaceCreation, "Surface initialization")?;
let surface = instance.create_surface(Arc::clone(&window))?;
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
@ -349,17 +237,15 @@ impl<'window> Renderer<'window> {
})
.await
.ok_or_else(|| {
RenderContextError::new(
ContextErrorKind::AdapterRequest,
Some("Adapter selection".into()),
None,
)
ZenyxError::builder(ZenyxErrorKind::AdapterRequest)
.with_message("No suitable adapter found")
.build()
})?;
let (device, queue) = adapter
.request_device(&wgpu::DeviceDescriptor::default(), None)
.await
.ctx_err(ContextErrorKind::DeviceRequest, "Device configuration")?;
.map_err(ZenyxError::from)?;
let size = window.inner_size();
let width = size.width.max(1);
@ -473,16 +359,13 @@ impl<'window> Renderer<'window> {
&device,
surface_config.width,
surface_config.height,
// surface_config.format,
);
let font_bytes = include_bytes!("DejaVuSans.ttf");
let font = FontRef::try_from_slice(font_bytes).map_err(|e| {
RenderContextError::new(
ContextErrorKind::DeviceRequest,
Some("Font loading".into()),
None,
)
ZenyxError::builder(ZenyxErrorKind::DeviceRequest)
.with_message("Font loading failed")
.build()
})?;
let brush =
@ -492,6 +375,8 @@ impl<'window> Renderer<'window> {
let scale = base_scale * (surface_config.width as f32 / base_width as f32).clamp(0.5, 2.0);
let color = wgpu::Color::WHITE;
let section = OwnedSection::default()
.add_text(OwnedText::new("FPS: 0.00").with_scale(scale).with_color([
color.r as f32,
@ -535,10 +420,11 @@ impl<'window> Renderer<'window> {
scale,
color,
},
model_versions: vec![]
})
}
pub fn new_blocking(window: Arc<Window>) -> Result<Self, RenderContextError> {
pub fn new_blocking(window: Arc<Window>) -> Result<Self> {
block_on(Self::new(window))
}
@ -550,6 +436,7 @@ impl<'window> Renderer<'window> {
&self.model_bind_group_layout,
);
self.models.push(model);
self.model_versions.push(0);
}
pub fn resize(&mut self, new_size: (u32, u32)) {
@ -577,18 +464,30 @@ impl<'window> Renderer<'window> {
for (i, model) in self.models.iter_mut().enumerate() {
let angle = Rad(elapsed * 0.8 + i as f32 * 0.3);
if i % 2 == 0 {
model.set_transform(Matrix4::from_angle_y(angle));
// model.set_transform(Matrix4::from_angle_x(angle) * Matrix4::from_angle_y(angle));
model.update(&self.queue);
} else {
model.set_transform(Matrix4::from_angle_x(angle) * Matrix4::from_angle_y(angle));
}
}
for (i, model) in self.models.iter().enumerate() {
if model.version > self.model_versions[i] {
model.update(&self.queue);
#[cfg(debug_assertions)]
trace!("Updating model: {:#?}",model);
self.model_versions[i] = model.version;
}
}
let surface_texture = self
.surface
.get_current_texture()
.ctx_err(
ContextErrorKind::SurfaceTexture,
"Surface texture acquisition",
)
.map_err(|e| {
ZenyxError::builder(ZenyxErrorKind::SurfaceTexture)
.with_message("Failed to acquire surface texture")
.with_source(e)
.build()
})
.unwrap();
let view = surface_texture
@ -679,7 +578,7 @@ impl<'window> Renderer<'window> {
self.frame_count += 1;
let elapsed_secs = self.last_frame_instant.elapsed().as_secs_f32();
if elapsed_secs >= 1.0 {
if (elapsed_secs >= 1.0) {
let fps = self.frame_count as f32 / elapsed_secs;
// trace!("Renderer FPS: {:.2}", fps);
self.fps = fps;

View file

@ -2,6 +2,8 @@ use std::ops::Deref;
use std::sync::Arc;
use ctx::{Renderer, Vertex};
use winit::dpi::LogicalSize;
use winit::dpi::Size;
use std::env;
use std::fs;
use std::path::PathBuf;
@ -84,7 +86,7 @@ f 6/11/6 5/10/6 1/1/6 2/13/6
impl App<'_> {
fn create_main_window(&mut self, event_loop: &ActiveEventLoop) {
let win_attr = Window::default_attributes().with_title("Zenyx");
let win_attr = Window::default_attributes().with_title("Zenyx").with_min_inner_size(Size::Logical(LogicalSize::new(100.0, 100.0)));
match event_loop.create_window(win_attr) {
Ok(window) => {
let window = Arc::new(window);
@ -119,10 +121,10 @@ impl App<'_> {
);
info!("Main window created: {:?}", window_id);
}
Err(e) => error!("Failed to create WGPU context: {:?}", e),
Err(e) => error!("Failed to create WGPU context: {:#}", e),
}
}
Err(e) => error!("Failed to create main window: {:?}", e),
Err(e) => error!("Failed to create main window: {:#}", e),
}
}
@ -140,9 +142,9 @@ impl App<'_> {
window_id: WindowId,
key_event: KeyEvent,
) {
if !key_event.state.is_pressed() {
if !key_event.state.is_pressed() || key_event.repeat {
return;
}
}
match key_event.physical_key {
winit::keyboard::PhysicalKey::Code(code) => match code {
winit::keyboard::KeyCode::Space => {
@ -186,10 +188,19 @@ impl App<'_> {
let title = format!("Zenyx - New Window {}", self.windows.len());
// TODO: Verify that this is safe instead of matching on it
let win_attr = unsafe {
let base = Window::default_attributes().with_title(title);
let base = Window::default_attributes().with_title(title).with_min_inner_size(Size::Logical(LogicalSize::new(100.0, 100.0)));
match main_ctx.window_handle() {
Ok(handle) => base.with_parent_window(Some(handle.as_raw())),
Err(_) => base,
Ok(handle) => {
if !cfg!(target_os = "windows") {
base.with_parent_window(Some(handle.as_raw()))
} else {
base
}
},
Err(e) => {
error!("{e}");
base
},
}
};
match event_loop.create_window(win_attr) {
@ -350,6 +361,6 @@ pub fn init_renderer(event_loop: EventLoop<()>) {
event_loop.set_control_flow(ControlFlow::Wait);
let mut app = App::default();
if let Err(e) = event_loop.run_app(&mut app) {
error!("Failed to run application: {:?}", e);
error!("Failed to run application: {}", e);
}
}