Merge pull request 'Improve rendering and reduce rendering related crashes' from error_handling into main

This commit is contained in:
Caznix 2025-03-30 00:22:56 +00:00 committed by BitSyndicate
commit e09c15a94f
Signed by: bitsyndicate
GPG key ID: 443E4198D6BBA6DE
16 changed files with 20593 additions and 484 deletions

View file

@ -1 +1,4 @@
[alias]
[build]
rustflags = ["-Ctarget-cpu=native"]

243
Cargo.lock generated
View file

@ -43,7 +43,7 @@ dependencies = [
"getrandom",
"once_cell",
"version_check",
"zerocopy",
"zerocopy 0.7.35",
]
[[package]]
@ -112,6 +112,15 @@ dependencies = [
"num-traits",
]
[[package]]
name = "approx"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
dependencies = [
"num-traits",
]
[[package]]
name = "arrayref"
version = "0.3.9"
@ -315,7 +324,7 @@ version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a98d30140e3296250832bbaaff83b27dcd6fa3cc70fb6f1f3e5c9c0023b5317"
dependencies = [
"approx",
"approx 0.4.0",
"num-traits",
]
@ -443,6 +452,34 @@ dependencies = [
"urlencoding",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
@ -523,6 +560,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53"
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "endian-type"
version = "0.1.2"
@ -752,6 +795,44 @@ dependencies = [
"gl_generator",
]
[[package]]
name = "glyph_brush"
version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0060f4ed4ef64a5876d9836d7d6c9ed43a463f3ca431682bec1c326064c8c93e"
dependencies = [
"glyph_brush_draw_cache",
"glyph_brush_layout",
"ordered-float 5.0.0",
"rustc-hash 2.1.1",
"twox-hash",
]
[[package]]
name = "glyph_brush_draw_cache"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bb6c910def52365fef3f439a6b50a4d5c11b28eec4cf6c191f6dfea18e88d7f"
dependencies = [
"ab_glyph",
"crossbeam-channel",
"crossbeam-deque",
"linked-hash-map",
"rayon",
"rustc-hash 2.1.1",
]
[[package]]
name = "glyph_brush_layout"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b1e288bfd2f6c0313f78bf5aa538356ad481a3bb97e9b7f93220ab0066c5992"
dependencies = [
"ab_glyph",
"approx 0.5.1",
"xi-unicode",
]
[[package]]
name = "gpu-alloc"
version = "0.6.0"
@ -1111,6 +1192,12 @@ dependencies = [
"redox_syscall 0.5.10",
]
[[package]]
name = "linked-hash-map"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "linux-raw-sys"
version = "0.4.15"
@ -1213,7 +1300,7 @@ dependencies = [
"hexf-parse",
"indexmap",
"log",
"rustc-hash",
"rustc-hash 1.1.0",
"spirv",
"strum",
"termcolor",
@ -1566,6 +1653,15 @@ dependencies = [
"num-traits",
]
[[package]]
name = "ordered-float"
version = "5.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2c1f9f56e534ac6a9b8a4600bdf0f530fb393b5f393e7b4d03489c3cf0c3f01"
dependencies = [
"num-traits",
]
[[package]]
name = "overload"
version = "0.1.1"
@ -1669,6 +1765,15 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy 0.8.24",
]
[[package]]
name = "presser"
version = "0.3.1"
@ -1727,6 +1832,36 @@ dependencies = [
"nibble_vec",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "range-alloc"
version = "0.1.4"
@ -1739,6 +1874,26 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
[[package]]
name = "rayon"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]]
name = "redox_syscall"
version = "0.4.1"
@ -1815,6 +1970,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc-hash"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustix"
version = "0.38.44"
@ -2188,6 +2349,16 @@ dependencies = [
"zerovec",
]
[[package]]
name = "tobj"
version = "4.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04aca6092e5978e708ee784e8ab9b5cf3cdb598b28f99a2f257446e7081a7025"
dependencies = [
"ahash",
"tokio",
]
[[package]]
name = "tokio"
version = "1.44.1"
@ -2195,6 +2366,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a"
dependencies = [
"backtrace",
"bytes",
"parking_lot",
"pin-project-lite",
"tokio-macros",
@ -2291,6 +2463,21 @@ version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
[[package]]
name = "twox-hash"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7b17f197b3050ba473acf9181f7b1d3b66d1cf7356c6cc57886662276e65908"
dependencies = [
"rand",
]
[[package]]
name = "typenum"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
[[package]]
name = "unicode-ident"
version = "1.0.18"
@ -2628,7 +2815,7 @@ dependencies = [
"parking_lot",
"profiling",
"raw-window-handle",
"rustc-hash",
"rustc-hash 1.1.0",
"smallvec",
"thiserror 2.0.12",
"wgpu-hal",
@ -2665,13 +2852,13 @@ dependencies = [
"ndk-sys 0.5.0+25.2.9519653",
"objc",
"once_cell",
"ordered-float",
"ordered-float 4.6.0",
"parking_lot",
"profiling",
"range-alloc",
"raw-window-handle",
"renderdoc-sys",
"rustc-hash",
"rustc-hash 1.1.0",
"smallvec",
"thiserror 2.0.12",
"wasm-bindgen",
@ -2693,6 +2880,18 @@ dependencies = [
"web-sys",
]
[[package]]
name = "wgpu_text"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccd192487eb81eb51f8f0eb82fea0865e71ab4f002f7942bee0bba04fc2a0b8c"
dependencies = [
"bytemuck",
"glyph_brush",
"log",
"wgpu",
]
[[package]]
name = "winapi"
version = "0.3.9"
@ -3119,6 +3318,12 @@ version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61"
[[package]]
name = "xi-unicode"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a67300977d3dc3f8034dae89778f502b6ba20b269527b3223ba59c0cf393bb8a"
[[package]]
name = "xkbcommon-dl"
version = "0.4.2"
@ -3181,6 +3386,7 @@ dependencies = [
name = "zenyx"
version = "0.1.0"
dependencies = [
"ahash",
"anyhow",
"backtrace",
"bytemuck",
@ -3196,10 +3402,13 @@ dependencies = [
"regex",
"rustyline",
"thiserror 2.0.12",
"tobj",
"tokio",
"tracing",
"tracing-subscriber",
"typenum",
"wgpu",
"wgpu_text",
"winit",
]
@ -3209,7 +3418,16 @@ version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"zerocopy-derive",
"zerocopy-derive 0.7.35",
]
[[package]]
name = "zerocopy"
version = "0.8.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879"
dependencies = [
"zerocopy-derive 0.8.24",
]
[[package]]
@ -3223,6 +3441,17 @@ dependencies = [
"syn",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zerofrom"
version = "0.1.6"

View file

@ -2,41 +2,16 @@
resolver = "2"
members = ["engine","subcrates/zen_core"]
[profile.dev]
rpath = false
panic = "abort"
lto = "off"
opt-level = 0
debug = false
overflow-checks = false
incremental = true
codegen-units = 512
strip = "symbols"
debug-assertions = true
[profile.dev.package."*"]
opt-level = 0
debug = false
overflow-checks = false
incremental = true
codegen-units = 512
strip = "symbols"
debug-assertions = true
[profile.dev.build-override]
opt-level = 0
debug = false
overflow-checks = false
incremental = true
codegen-units = 512
[workspace.dependencies]
lazy_static = "1.5.0"
parking_lot = "0.12.3"
[profile.release]
debug-assertions = false
lto = true
codegen-units = 1
panic = "abort"
split-debuginfo = "off"
[profile.dev]
debug = 0

22
Pumpkin.mtl Normal file
View file

@ -0,0 +1,22 @@
# Blender 4.2.3 LTS MTL File: 'None'
# www.blender.org
newmtl Material.001
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.000000 0.000000 0.000000
Ke 0.000000 0.000000 0.000000
Ni 1.500000
d 1.000000
illum 1
newmtl Material.003
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.000000 0.000000 0.000000
Ke 0.000000 0.000000 0.000000
Ni 1.500000
d 1.000000
illum 1

19331
Pumpkin.obj Normal file

File diff suppressed because it is too large Load diff

View file

@ -40,7 +40,6 @@ in
doCheck = false;
LD_LIBRARY_PATH = "${pkgs.lib.makeLibraryPath buildInputs}";
fixupPhase = ''
wrapProgram $out/bin/${pname} --set PATH ${bash}/bin:\$PATH --set LD_LIBRARY_PATH ${pkgs.lib.makeLibraryPath buildInputs}
'';

View file

@ -3,32 +3,42 @@ name = "zenyx"
version = "0.1.0"
edition = "2024"
repository = "https://github.com/Zenyx-Engine/Zenyx"
[dependencies]
# TBR
anyhow = "1.0.94"
# TBR (if possible)
backtrace = "0.3.74"
# TBR (if possible)
chrono = "0.4.39"
colored = "3.0.0"
# TBR (if possible)
crashreport = "1.0.1"
# TBR
dirs-next = "2.0.0"
# TBR == (To be removed)
# TBR
lazy_static.workspace = true
# TBR
once_cell = "1.21.1"
parking_lot.workspace = true
# TBR (if possible)
regex = "1.11.1"
# TBR (if possible)
rustyline = { version = "15.0.0", features = ["derive", "rustyline-derive"] }
thiserror = "2.0.11"
tokio = { version = "1.44.1", features = ["macros", "parking_lot","rt-multi-thread"] }
# Tokio is heavy but so far its the best option, we should make better use of it or switch to another runtime.
tokio = { version = "1.44.1", features = ["fs", "macros", "parking_lot", "rt-multi-thread"] }
wgpu = "24.0.3"
winit = "0.30.9"
bytemuck = "1.21.0"
# TBR (if possible)
futures = "0.3.31"
cgmath = "0.18.0"
tracing = "0.1.41"
tracing-subscriber = "0.3.19"
[profile.dev]
debug-assertions = true
[profile.release]
debug-assertions = false
# TBR
typenum = { version = "1.18.0", features = ["const-generics"] }
tobj = { version = "4.0.3", features = ["tokio"] }
ahash = "0.8.11"
wgpu_text = "0.9.2"

View file

@ -1,3 +1,4 @@
use std::error::Error;
use std::fmt::Write as FmtWrite;
use std::mem;
@ -8,50 +9,43 @@ use regex::Regex;
static INIT: parking_lot::Once = Once::new();
pub fn set_panic_hook() {
use std::io::Write;
use colored::Colorize;
use crate::workspace;
INIT.call_once(|| {
let default_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
let log_path = workspace::get_working_dir().unwrap_or_else(|_| {
if let Err(e) = process_panic(info) {
eprintln!("Error in panic hook: {}", e);
default_hook(info);
std::process::exit(0);
});
if !log_path.exists() {
std::fs::create_dir_all(&log_path).unwrap_or_else(|_| {
default_hook(info);
std::process::exit(0);
});
}
let log_path = log_path.join("panic.log");
std::process::exit(0);
}));
});
}
let mut file = std::fs::File::create(&log_path).unwrap_or_else(|_| {
default_hook(info);
std::process::exit(0);
});
let payload = info.payload();
let payload_str = if let Some(s) = payload.downcast_ref::<&str>() {
*s
} else if let Some(s) = payload.downcast_ref::<String>() {
s
} else {
"<non-string panic payload>"
};
fn process_panic(info: &std::panic::PanicHookInfo<'_>) -> Result<(), Box<dyn Error>> {
use crate::workspace;
use colored::Colorize;
use std::io::Write;
writeln!(file, "{}", payload_str).unwrap_or_else(|_| {
default_hook(info);
std::process::exit(0);
});
writeln!(file, "{}", render_backtrace().sanitize_path()).unwrap_or_else(|_| {
default_hook(info);
std::process::exit(0);
});
let log_dir = workspace::get_working_dir()?;
if !log_dir.exists() {
std::fs::create_dir_all(&log_dir)?;
}
let log_path = log_dir.join("panic.log");
let panic_msg = format!(
let mut file = std::fs::File::create(&log_path)?;
let payload = info.payload();
let payload_str = if let Some(s) = payload.downcast_ref::<&str>() {
*s
} else if let Some(s) = payload.downcast_ref::<String>() {
s
} else {
"<non-string panic payload>"
};
writeln!(file, "{}", payload_str)?;
writeln!(file, "{}", render_backtrace().sanitize_path())?;
let panic_msg = format!(
"Zenyx had a problem and crashed. To help us diagnose the problem you can send us a crash report.
We have generated a report file at \"{}\". Submit an issue or email with the subject of \"Zenyx Crash Report\" and include the report as an attachment.
@ -63,11 +57,12 @@ https://github.com/Zenyx-Engine/Zenyx/issues
We take privacy seriously, and do not perform any automated error collection. In order to improve the software, we rely on people to submit reports.
Thank you kindly!", log_path.display());
println!("{}", panic_msg.red().bold());
println!("\nFor future reference, the error summary is as follows:\n{}", payload_str.red().bold());
std::process::exit(0);
}));
});
println!("{}", panic_msg.red().bold());
println!(
"\nFor future reference, the error summary is as follows:\n{}",
payload_str.red().bold()
);
Ok(())
}
fn render_backtrace() -> String {
@ -130,7 +125,7 @@ trait Sanitize {
impl Sanitize for str {
fn sanitize_path(&self) -> String {
let username_pattern = r"(?i)(/home/|/Users/|\\Users\\)([^/\\]+)";
let re = Regex::new(username_pattern).expect("Failed to sanitize path, aborting operation");
let re = Regex::new(username_pattern).expect("Failed to compile regex for sanitization");
re.replace_all(self, "${1}<USER>").to_string()
}
}

Binary file not shown.

File diff suppressed because it is too large Load diff

View file

@ -1,19 +1,32 @@
use std::collections::HashMap;
use std::ops::Deref;
use std::sync::Arc;
use ctx::WgpuCtx;
use ctx::{Renderer, Vertex};
use std::env;
use std::fs;
use std::path::PathBuf;
use tobj::{LoadOptions, Model};
use tracing::{debug, error, info, trace, warn};
use wgpu::rwh::HasWindowHandle;
use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::ControlFlow;
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::window::{Window, WindowId};
use winit::event::{KeyEvent, WindowEvent};
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::window::Window;
use winit::window::WindowId;
pub mod ctx;
struct WindowContext<'window> {
window: Arc<Window>,
ctx: WgpuCtx<'window>,
main_window: bool
ctx: Renderer<'window>,
main_window: bool,
}
impl Deref for WindowContext<'_> {
type Target = winit::window::Window;
fn deref(&self) -> &Self::Target {
self.window.as_ref()
}
}
impl WindowContext<'_> {
pub fn is_main_window(&self) -> bool {
@ -23,28 +36,284 @@ impl WindowContext<'_> {
#[derive(Default)]
pub struct App<'window> {
windows: HashMap<WindowId, WindowContext<'window>>,
windows: ahash::AHashMap<WindowId, WindowContext<'window>>,
}
static CUBE_OBJ: &str = "
# Blender 4.2.3 LTS
# www.blender.org
mtllib cube.mtl
o Cube
v 1.000000 1.000000 -1.000000
v 1.000000 -1.000000 -1.000000
v 1.000000 1.000000 1.000000
v 1.000000 -1.000000 1.000000
v -1.000000 1.000000 -1.000000
v -1.000000 -1.000000 -1.000000
v -1.000000 1.000000 1.000000
v -1.000000 -1.000000 1.000000
vn -0.0000 1.0000 -0.0000
vn -0.0000 -0.0000 1.0000
vn -1.0000 -0.0000 -0.0000
vn -0.0000 -1.0000 -0.0000
vn 1.0000 -0.0000 -0.0000
vn -0.0000 -0.0000 -1.0000
vt 0.625000 0.500000
vt 0.875000 0.500000
vt 0.875000 0.750000
vt 0.625000 0.750000
vt 0.375000 0.750000
vt 0.625000 1.000000
vt 0.375000 1.000000
vt 0.375000 0.000000
vt 0.625000 0.000000
vt 0.625000 0.250000
vt 0.375000 0.250000
vt 0.125000 0.500000
vt 0.375000 0.500000
vt 0.125000 0.750000
s 0
usemtl Material
f 1/1/1 5/2/1 7/3/1 3/4/1
f 4/5/2 3/4/2 7/6/2 8/7/2
f 8/8/3 7/9/3 5/10/3 6/11/3
f 6/12/4 2/13/4 4/5/4 8/14/4
f 2/13/5 1/1/5 3/4/5 4/5/5
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");
match event_loop.create_window(win_attr) {
Ok(window) => {
let window = Arc::new(window);
let window_id = window.id();
match Renderer::new_blocking(window.clone()) {
Ok(mut wgpu_ctx) => {
let obj = match tobj::load_obj(
"Pumpkin.obj",
&LoadOptions {
triangulate: true,
single_index: true,
..Default::default()
},
) {
Ok(obj) => obj,
Err(e) => {
error!("{e}");
panic!()
}
};
let (combined_vertices, combined_indices) = parse_obj(&obj.0);
wgpu_ctx.add_model(&combined_vertices, &combined_indices);
self.windows.insert(
window_id,
WindowContext {
window,
ctx: wgpu_ctx,
main_window: true,
},
);
info!("Main window created: {:?}", window_id);
}
Err(e) => error!("Failed to create WGPU context: {:?}", e),
}
}
Err(e) => error!("Failed to create main window: {:?}", e),
}
}
fn handle_close_requested(&mut self, window_id: WindowId) {
if self.windows.remove(&window_id).is_some() {
debug!("Window {:?} closed", window_id);
} else {
warn!("Tried to close non-existent window {:?}", window_id);
}
}
fn handle_keyboard_input(
&mut self,
event_loop: &ActiveEventLoop,
window_id: WindowId,
key_event: KeyEvent,
) {
if !key_event.state.is_pressed() {
return;
}
match key_event.physical_key {
winit::keyboard::PhysicalKey::Code(code) => match code {
winit::keyboard::KeyCode::Space => {
self.toggle_background(window_id);
}
winit::keyboard::KeyCode::Escape => {
self.spawn_child_window(event_loop);
}
other => error!("Unimplemented keycode: {:?}", other),
},
_ => debug!("Received a keyboard event with no physical key"),
}
}
fn toggle_background(&mut self, window_id: WindowId) {
if let Some(window_context) = self.windows.get_mut(&window_id) {
let current_color = window_context.ctx.bg_color();
let new_color = match current_color {
&wgpu::Color::WHITE => wgpu::Color::BLACK,
&wgpu::Color::BLACK => wgpu::Color::WHITE,
_ => wgpu::Color::WHITE,
};
let new_text_color = match window_context.ctx.text_color() {
&wgpu::Color::WHITE => wgpu::Color::BLACK,
&wgpu::Color::BLACK => wgpu::Color::WHITE,
_ => wgpu::Color::WHITE,
};
println!("new text color {new_text_color:#?}");
window_context.ctx.set_bg_color(new_color);
window_context.ctx.set_text_color(new_text_color);
debug!("Toggled background color for window {:?}", window_id);
} else {
warn!("No window context for toggling background: {:?}", window_id);
}
}
fn spawn_child_window(&mut self, event_loop: &ActiveEventLoop) {
if let Some(main_ctx) = self.windows.values().find(|ctx| ctx.is_main_window()) {
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);
match main_ctx.window_handle() {
Ok(handle) => base.with_parent_window(Some(handle.as_raw())),
Err(_) => base,
}
};
match event_loop.create_window(win_attr) {
Ok(window) => {
let window = Arc::new(window);
let window_id = window.id();
match Renderer::new_blocking(window.clone()) {
Ok(mut wgpu_ctx) => {
{
let mut tmp_path: PathBuf = env::temp_dir();
tmp_path.push("cube.obj");
if let Err(e) = fs::write(&tmp_path, CUBE_OBJ) {
error!("Failed to write cube OBJ to temp: {:?}", e);
}
let load_options = tobj::LoadOptions {
triangulate: true,
single_index: true,
..Default::default()
};
match tobj::load_obj(tmp_path.to_str().unwrap(), &load_options) {
Ok(cube_model) => {
let (cube_vertices, cube_indices) =
parse_obj(&cube_model.0);
wgpu_ctx.add_model(&cube_vertices, &cube_indices);
}
Err(e) => {
error!("Failed to load cube OBJ from temp file: {:?}", e)
}
}
}
self.windows.insert(
window_id,
WindowContext {
window,
ctx: wgpu_ctx,
main_window: false,
},
);
debug!("Spawned new child window: {:?}", window_id);
}
Err(e) => error!("Failed to create WGPU context for child window: {:?}", e),
}
}
Err(e) => error!("Failed to create child window: {:?}", e),
}
} else {
error!("No main window found. Cannot spawn a child window.");
}
}
fn handle_redraw_requested(&mut self, window_id: WindowId) {
if let Some(window_context) = self.windows.get_mut(&window_id) {
window_context.ctx.draw();
window_context.request_redraw();
trace!(
"Redrew window {:?} with title: {}",
window_id,
window_context.window.title()
);
} else {
warn!("Received redraw for unknown window {:?}", window_id);
}
}
fn handle_resize(&mut self, window_id: WindowId, new_size: winit::dpi::PhysicalSize<u32>) {
if let Some(window_context) = self.windows.get_mut(&window_id) {
window_context.ctx.resize(new_size.into());
window_context.window.request_redraw();
debug!(
"Resized window {:?} to {}x{}",
window_id, new_size.width, new_size.height
);
} else {
warn!("Received resize for unknown window {:?}", window_id);
}
}
fn handle_destroyed(&mut self, event_loop: &ActiveEventLoop) {
if self.windows.is_empty() || !self.windows.iter().any(|(_, ctx)| ctx.is_main_window()) {
self.windows.clear();
debug!("All main windows are closed. Exiting event loop.");
event_loop.exit();
}
}
}
fn parse_obj(obj: &Vec<Model>) -> (Vec<Vertex>, Vec<u32>) {
let mut combined_vertices = Vec::new();
let mut combined_indices = Vec::new();
let mut vertex_offset = 0;
for object in obj {
let mesh: &_ = &object.mesh;
let vertices: Vec<Vertex> = (0..mesh.positions.len() / 3)
.map(|i| Vertex {
position: [
mesh.positions[i * 3],
mesh.positions[i * 3 + 1],
mesh.positions[i * 3 + 2],
],
normal: if !mesh.normals.is_empty() {
[
mesh.normals[i * 3],
mesh.normals[i * 3 + 1],
mesh.normals[i * 3 + 2],
]
} else {
[0.0; 3]
},
})
.collect();
combined_vertices.extend(vertices);
combined_indices.extend(mesh.indices.iter().map(|&index| index + vertex_offset));
vertex_offset += (mesh.positions.len() as u32) / 3;
}
(combined_vertices, combined_indices)
}
impl ApplicationHandler for App<'_> {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
if self.windows.is_empty() {
let win_attr = Window::default_attributes().with_title("Zenyx");
let window = Arc::new(
event_loop
.create_window(win_attr)
.expect("create window err."),
);
let window_id = window.id();
let wgpu_ctx = WgpuCtx::new_blocking(window.clone()).unwrap();
self.windows.insert(
window_id,
WindowContext {
window,
ctx: wgpu_ctx,
main_window: true,
},
);
self.create_main_window(event_loop);
}
}
@ -56,88 +325,31 @@ impl ApplicationHandler for App<'_> {
) {
match event {
WindowEvent::CloseRequested => {
if let Some(window_context) = self.windows.remove(&window_id) {
drop(window_context);
debug!("Window: {:?} closed, exiting", window_id);
}
self.handle_close_requested(window_id);
}
WindowEvent::KeyboardInput {
device_id,
event,
is_synthetic,
} => match event.physical_key {
winit::keyboard::PhysicalKey::Code(code) => {
if event.state.is_pressed() == false {
return;
}
match code {
winit::keyboard::KeyCode::Space => {
debug!("Space key pressed");
if let Some(window_context) = self.windows.get_mut(&window_id){
match window_context.ctx.bg_color() {
wgpu::Color::WHITE => {
window_context.ctx.change_bg_color(wgpu::Color::BLACK)
}
wgpu::Color::BLACK => {
window_context.ctx.change_bg_color(wgpu::Color::WHITE)
}
_ => window_context.ctx.change_bg_color(wgpu::Color::WHITE),
}
}
}
winit::keyboard::KeyCode::Escape => {
debug!("Escape key pressed, spawning new window");
let win_attr = Window::default_attributes()
.with_title(format!("Zenyx - New Window {}", self.windows.len()));
let new_window = Arc::new(
event_loop
.create_window(win_attr)
.expect("create window err."),
);
let window_id = new_window.id();
let wgpu_ctx = WgpuCtx::new_blocking(new_window.clone()).unwrap();
self.windows.insert(
window_id,
WindowContext {
window: new_window,
ctx: wgpu_ctx,
main_window: false,
},
);
}
_ => info!("Unimplemented keycode: {:?}", code),
}
}
_ => {}
},
WindowEvent::RedrawRequested => {
if let Some(window_context) = self.windows.get_mut(&window_id) {
window_context.ctx.draw();
window_context.window.request_redraw();
}
event: key_event, ..
} => {
self.handle_keyboard_input(event_loop, window_id, key_event);
}
WindowEvent::Resized(size) => {
if let Some(window_context) = self.windows.get_mut(&window_id) {
window_context.ctx.resize(size.into());
window_context.window.request_redraw();
let size_str: String = size.height.to_string() + "x" + &size.width.to_string();
debug!("Window resized to {:?}", size_str);
}
WindowEvent::RedrawRequested => {
self.handle_redraw_requested(window_id);
}
WindowEvent::Resized(new_size) => {
self.handle_resize(window_id, new_size);
}
WindowEvent::Destroyed => {
if !self.windows.iter().any(|(_,ctx)| ctx.is_main_window()) || self.windows.is_empty() {
self.windows.clear();
event_loop.exit();
}
self.handle_destroyed(event_loop);
}
_ => trace!("Unhandled window event"),
_ => trace!("Unhandled window event for window {:?}", window_id),
}
}
}
pub fn init_renderer(event_loop: EventLoop<()>) {
event_loop.set_control_flow(ControlFlow::Poll);
event_loop.set_control_flow(ControlFlow::Wait);
let mut app = App::default();
event_loop.run_app(&mut app).unwrap();
if let Err(e) = event_loop.run_app(&mut app) {
error!("Failed to run application: {:?}", e);
}
}

View file

@ -0,0 +1,42 @@
struct CameraUniform {
view: mat4x4<f32>,
proj: mat4x4<f32>,
};
struct ModelUniform {
model: mat4x4<f32>,
};
@group(0) @binding(0)
var<uniform> camera: CameraUniform;
@group(1) @binding(0)
var<uniform> model: ModelUniform;
struct VertexInput {
@location(0) position: vec3<f32>,
@location(1) normal: vec3<f32>,
};
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) normal: vec3<f32>,
};
@vertex
fn vs_main(input: VertexInput) -> VertexOutput {
var output: VertexOutput;
let model_pos = model.model * vec4<f32>(input.position, 1.0);
output.clip_position = camera.proj * camera.view * model_pos;
output.normal = input.normal;
return output;
}
@fragment
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
let ambient: f32 = 0.2;
let light_dir = normalize(vec3<f32>(0.5, 1.0, 0.5));
let diffuse = clamp(dot(normalize(input.normal), light_dir), 0.0, 1.0);
let brightness = ambient + (1.0 - ambient) * diffuse;
return vec4<f32>(0.7 * brightness, 0.7 * brightness, 0.9 * brightness, 1.0);
}

View file

@ -5,7 +5,6 @@ use tokio::runtime;
#[allow(unused_imports)]
use tracing::{debug, error, info, warn};
use tracing::{level_filters::LevelFilter, subscriber::set_global_default};
use tracing_subscriber::{layer::Filter, util::SubscriberInitExt};
use winit::event_loop::EventLoop;
pub mod core;
@ -45,7 +44,6 @@ async fn main() -> anyhow::Result<()> {
info!("Type 'help' for a list of commands.");
core::render::init_renderer(event_loop);
if let Err(_) = repl_thread.join() {
eprintln!("REPL thread panicked");
}

12
flake.lock generated
View file

@ -77,11 +77,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1742422364,
"narHash": "sha256-mNqIplmEohk5jRkqYqG19GA8MbQ/D4gQSK0Mu4LvfRQ=",
"lastModified": 1743095683,
"narHash": "sha256-gWd4urRoLRe8GLVC/3rYRae1h+xfQzt09xOfb0PaHSk=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "a84ebe20c6bc2ecbcfb000a50776219f48d134cc",
"rev": "5e5402ecbcb27af32284d4a62553c019a3a49ea6",
"type": "github"
},
"original": {
@ -138,11 +138,11 @@
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1742610648,
"narHash": "sha256-9jWi3gw3fEIgEslnFjH/s1I+Iyf1+4t5B1Ed1FOiy8o=",
"lastModified": 1743215516,
"narHash": "sha256-52qbrkG65U1hyrQWltgHTgH4nm0SJL+9TWv2UDCEPNI=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "c60d41987df3c853e2a842de2c63ded40400979b",
"rev": "524463199fdee49338006b049bc376b965a2cfed",
"type": "github"
},
"original": {

View file

@ -52,7 +52,9 @@
devShells.default = pkgs.mkShell {
name = "zenyx";
nativeBuildInputs = with pkgs; [
rust-bin.stable.latest.default
(rust-bin.stable.latest.default.override {
extensions = ["rust-src" "cargo" "rustfmt" "clippy"];
})
pkg-config
];
buildInputs = buildInputs;

2
test.mtl Normal file
View file

@ -0,0 +1,2 @@
# Blender 4.2.3 LTS MTL File: 'None'
# www.blender.org