improve error handling and add metadata
This commit is contained in:
parent
13893e96a9
commit
43fd5966b7
17 changed files with 1162 additions and 787 deletions
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue