From a990d1c9c8f2107fc234775aa2d3ca3c9de9802f Mon Sep 17 00:00:00 2001 From: Chance Date: Tue, 1 Apr 2025 18:05:07 -0400 Subject: [PATCH 1/2] cross compile for windows and macOS --- default.nix | 91 ++++++++++++++++++++++++++++++++--------------------- flake.nix | 6 ++++ 2 files changed, 61 insertions(+), 36 deletions(-) diff --git a/default.nix b/default.nix index 4b49658..2a287bb 100644 --- a/default.nix +++ b/default.nix @@ -1,5 +1,4 @@ -{ - lib, +{ lib, rustPlatform, nix-gitignore, bash, @@ -11,43 +10,63 @@ pkg-config, libxkbcommon, pkgs, + stdenv, + targetPackages ? pkgs, }: let version = (builtins.fromTOML (builtins.readFile ./engine/Cargo.toml)).package.version; src = nix-gitignore.gitignoreSource [] ./.; in - rustPlatform.buildRustPackage rec { - pname = "zenyx"; - inherit src version; - cargoLock.lockFile = ./Cargo.lock; - nativeBuildInputs = [ - makeWrapper - pkg-config - ]; - buildInputs = with pkgs; [ - wayland - vulkan-loader - libxkbcommon - libGL - libxkbcommon - xorg.libXcursor - xorg.libXrandr - xorg.libXi - xorg.libX11 - xorg.libxcb - bash - dav1d - ]; - doCheck = false; - LD_LIBRARY_PATH = "${pkgs.lib.makeLibraryPath buildInputs}"; +rustPlatform.buildRustPackage rec { + pname = "zenyx"; + inherit src version; + cargoLock.lockFile = ./Cargo.lock; - fixupPhase = '' - wrapProgram $out/bin/${pname} --set PATH ${bash}/bin:\$PATH --set LD_LIBRARY_PATH ${pkgs.lib.makeLibraryPath buildInputs} - ''; + nativeBuildInputs = [ + pkg-config + ] ++ lib.optionals stdenv.targetPlatform.isDarwin [ + targetPackages.darwin.apple_sdk.frameworks.CoreServices + ]; - meta = { - description = "Test"; - license = lib.licenses.mit; - platforms = lib.platforms.linux; - mainProgram = "zenyx"; - }; - } + buildInputs = with targetPackages; [ + dav1d + ] ++ lib.optionals (stdenv.targetPlatform.isLinux || stdenv.targetPlatform.isWindows) [ + vulkan-loader + ] ++ lib.optionals stdenv.targetPlatform.isLinux [ + makeWrapper + wayland + libxkbcommon + libGL + xorg.libXcursor + xorg.libXrandr + xorg.libXi + xorg.libX11 + xorg.libxcb + bash + ] ++ lib.optionals stdenv.targetPlatform.isDarwin [ + makeWrapper + darwin.apple_sdk.frameworks.Cocoa + darwin.apple_sdk.frameworks.Metal + darwin.apple_sdk.frameworks.CoreVideo + darwin.apple_sdk.frameworks.QuartzCore + ]; + + CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER = + lib.optionalString stdenv.targetPlatform.isWindows "${stdenv.cc.targetPrefix}gcc"; + + NIX_LDFLAGS = lib.optionalString stdenv.targetPlatform.isDarwin "-framework CoreFoundation"; + + postInstall = lib.optionalString stdenv.targetPlatform.isLinux '' + wrapProgram $out/bin/${pname} \ + --prefix PATH : ${lib.makeBinPath [ bash ]} \ + --set LD_LIBRARY_PATH ${lib.makeLibraryPath buildInputs} + ''; + + doCheck = false; + + meta = { + description = "Cross-platform WSYWIG Game Engine"; + license = lib.licenses.mit; + platforms = lib.platforms.all; + mainProgram = "zenyx"; + }; +} \ No newline at end of file diff --git a/flake.nix b/flake.nix index b51f286..99f0a07 100644 --- a/flake.nix +++ b/flake.nix @@ -48,6 +48,12 @@ packages = { inherit (pkgs) zenyx; default = pkgs.zenyx; + windows = let + pkgsCross = import nixpkgs { + system = "x86_64-linux"; + crossSystem = nixpkgs.lib.systems.examples.mingwW64; + }; + in pkgsCross.callPackage ./default.nix {}; }; devShells.default = pkgs.mkShell { From 6eae5364787846529886968cd5a17931b4a9a4e6 Mon Sep 17 00:00:00 2001 From: Chance Date: Tue, 1 Apr 2025 18:05:17 -0400 Subject: [PATCH 2/2] improve error handling --- engine/src/core/ecs/mod.rs | 6 +-- engine/src/core/panic.rs | 40 +++++++++++--------- engine/src/core/render/ctx.rs | 27 +++++++++----- engine/src/core/render/mod.rs | 70 ++++++++++++++++++++++++++--------- engine/src/main.rs | 23 +++++++++--- 5 files changed, 112 insertions(+), 54 deletions(-) diff --git a/engine/src/core/ecs/mod.rs b/engine/src/core/ecs/mod.rs index ae72ce5..37b0ab7 100644 --- a/engine/src/core/ecs/mod.rs +++ b/engine/src/core/ecs/mod.rs @@ -81,7 +81,8 @@ impl Entity for EntityImpl { fn get_component(&self) -> Option<&C> { let type_id = TypeId::of::(); if let Some(&bit) = COMPONENT_REGISTRY.lock().unwrap().get(&type_id) { - self.components.get(&bit) + self.components + .get(&bit) .and_then(|boxed| boxed.as_any().downcast_ref::()) } else { None @@ -107,7 +108,6 @@ impl Entity for EntityImpl { components: HashMap::new(), } } - } pub struct ECS { @@ -167,4 +167,4 @@ impl ECS { } self.next_entity_id = count; } -} \ No newline at end of file +} diff --git a/engine/src/core/panic.rs b/engine/src/core/panic.rs index 25dba4d..7f9732c 100644 --- a/engine/src/core/panic.rs +++ b/engine/src/core/panic.rs @@ -48,33 +48,39 @@ fn process_panic(info: &std::panic::PanicHookInfo<'_>) -> Result<(), Box String { const HEX_WIDTH: usize = mem::size_of::() * 2 + 2; const NEXT_SYMBOL_PADDING: usize = HEX_WIDTH + 6; diff --git a/engine/src/core/render/ctx.rs b/engine/src/core/render/ctx.rs index 7024e0f..fcb667b 100644 --- a/engine/src/core/render/ctx.rs +++ b/engine/src/core/render/ctx.rs @@ -6,7 +6,7 @@ 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; @@ -333,7 +333,7 @@ struct FontState { impl<'window> Renderer<'window> { pub async fn new(window: Arc) -> Result { let instance = wgpu::Instance::new(&InstanceDescriptor { - backends: Backends::from_comma_list("dx12,metal,opengl,webgpu"), + backends: Backends::from_comma_list("dx12,metal,opengl,webgpu,vulkan"), ..Default::default() }); @@ -458,23 +458,31 @@ impl<'window> Renderer<'window> { let camera = Camera::new(&device, &camera_bind_group_layout, width, height); let surface_caps = surface.get_capabilities(&adapter); + let present_mode = [ + wgpu::PresentMode::Immediate, + wgpu::PresentMode::Mailbox, + wgpu::PresentMode::AutoNoVsync, + ] + .iter() + .copied() + .find(|mode| surface_caps.present_modes.contains(mode)) + .unwrap_or(wgpu::PresentMode::Fifo); + + debug!("Using {:#?} present mode.", present_mode); + let surface_config = wgpu::SurfaceConfiguration { width, height, format: surface_caps.formats[0], - present_mode: wgpu::PresentMode::AutoNoVsync, + present_mode, alpha_mode: wgpu::CompositeAlphaMode::Auto, view_formats: vec![], usage: TextureUsages::RENDER_ATTACHMENT, desired_maximum_frame_latency: 3, }; surface.configure(&device, &surface_config); - let (depth_texture, depth_texture_view) = create_depth_texture( - &device, - surface_config.width, - surface_config.height, - // surface_config.format, - ); + let (depth_texture, depth_texture_view) = + create_depth_texture(&device, surface_config.width, surface_config.height); let font_bytes = include_bytes!("DejaVuSans.ttf"); let font = FontRef::try_from_slice(font_bytes).map_err(|e| { @@ -706,7 +714,6 @@ fn create_depth_texture( device: &wgpu::Device, width: u32, height: u32, - // format: wgpu::TextureFormat, ) -> (wgpu::Texture, wgpu::TextureView) { let size = wgpu::Extent3d { width, diff --git a/engine/src/core/render/mod.rs b/engine/src/core/render/mod.rs index b6e54e1..7c4186e 100644 --- a/engine/src/core/render/mod.rs +++ b/engine/src/core/render/mod.rs @@ -5,12 +5,15 @@ use ctx::{Renderer, Vertex}; use std::env; use std::fs; use std::path::PathBuf; +use tobj::Mesh; use tobj::{LoadOptions, Model}; use tracing::{debug, error, info, trace, warn}; use wgpu::rwh::HasWindowHandle; use winit::application::ApplicationHandler; use winit::event::{KeyEvent, WindowEvent}; use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; +use winit::monitor::MonitorHandle; +use winit::window::Fullscreen; use winit::window::Window; use winit::window::WindowId; @@ -42,16 +45,16 @@ pub struct App<'window> { static CUBE_OBJ: &str = " # Blender 4.2.3 LTS # www.blender.org -mtllib cube.mtl +mtllib untitled.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 +v 0.645975 0.645975 -0.645975 +v 0.645975 -0.645975 -0.645975 +v 0.645975 0.645975 0.645975 +v 0.645975 -0.645975 0.645975 +v -0.645975 0.645975 -0.645975 +v -0.645975 -0.645975 -0.645975 +v -0.645975 0.645975 0.645975 +v -0.645975 -0.645975 0.645975 vn -0.0000 1.0000 -0.0000 vn -0.0000 -0.0000 1.0000 vn -1.0000 -0.0000 -0.0000 @@ -80,6 +83,7 @@ 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<'_> { @@ -101,10 +105,22 @@ impl App<'_> { ) { Ok(obj) => obj, Err(e) => { - error!("{e}"); - panic!() + error!("Failed to load Pumpkin.obj: {e}"); + // Fallback to CUBE_OBJ + let fallback_obj = CUBE_OBJ.to_string(); + tobj::load_obj_buf( + &mut fallback_obj.as_bytes(), + &LoadOptions { + triangulate: true, + single_index: true, + ..Default::default() + }, + |_| Ok(Default::default()), + ) + .expect("Failed to load fallback CUBE_OBJ") } }; + let (combined_vertices, combined_indices) = parse_obj(&obj.0); wgpu_ctx.add_model(&combined_vertices, &combined_indices); @@ -119,10 +135,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), } } @@ -151,6 +167,7 @@ impl App<'_> { winit::keyboard::KeyCode::Escape => { self.spawn_child_window(event_loop); } + winit::keyboard::KeyCode::F11 => self.toggle_fullscreen(window_id), other => error!("Unimplemented keycode: {:?}", other), }, _ => debug!("Received a keyboard event with no physical key"), @@ -180,6 +197,23 @@ impl App<'_> { warn!("No window context for toggling background: {:?}", window_id); } } + fn toggle_fullscreen(&mut self, window_id: WindowId) { + if let Some(ctx) = self.windows.get_mut(&window_id) { + let is_fullscreen = ctx.window.fullscreen().is_some(); + let fullscreen_mode = if is_fullscreen { + None + } else { + ctx.window + .current_monitor() + .map(|monitor| Fullscreen::Borderless(Some(monitor))) + }; + + ctx.window.set_fullscreen(fullscreen_mode); + debug!("Fullscreen toggled for window: {:?}", window_id); + } else { + warn!("No window found for fullscreen toggle: {:?}", 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()) { @@ -202,7 +236,7 @@ impl App<'_> { 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); + error!("Failed to write cube OBJ to temp: {}", e); } let load_options = tobj::LoadOptions { @@ -217,7 +251,7 @@ impl App<'_> { wgpu_ctx.add_model(&cube_vertices, &cube_indices); } Err(e) => { - error!("Failed to load cube OBJ from temp file: {:?}", e) + error!("Failed to load cube OBJ from temp file: {:#}", e) } } } @@ -231,10 +265,10 @@ impl App<'_> { ); 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 WGPU context for child window: {}", e), } } - Err(e) => error!("Failed to create child window: {:?}", e), + Err(e) => error!("Failed to create child window: {}", e), } } else { error!("No main window found. Cannot spawn a child window."); @@ -350,6 +384,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); } } diff --git a/engine/src/main.rs b/engine/src/main.rs index aaf5154..cdf51bc 100644 --- a/engine/src/main.rs +++ b/engine/src/main.rs @@ -33,18 +33,29 @@ async fn main() -> anyhow::Result<()> { setup(); let repl_thread = std::thread::spawn(|| { - let rt = runtime::Builder::new_current_thread() + let rt = match runtime::Builder::new_current_thread() .enable_all() - .build() - .unwrap(); + .build() { + Ok(rt) => rt, + Err(e) => { + error!("A fatal error has occured: {e}"); + std::process::exit(1) + }, + }; rt.block_on(core::repl::input::handle_repl()) }); - - let event_loop = EventLoop::new().unwrap(); splash::print_splash(); info!("Type 'help' for a list of commands."); - core::render::init_renderer(event_loop); + match EventLoop::new() { + Ok(event_loop) => { + core::render::init_renderer(event_loop); + } + Err(e) => { + error!("{e}") + } + }; + if let Err(_) = repl_thread.join() { eprintln!("REPL thread panicked"); }