proper 3d projection

This commit is contained in:
Chance 2025-03-22 18:19:01 -04:00 committed by BitSyndicate
parent 0f7f67803a
commit ec42d4b6ee
Signed by: bitsyndicate
GPG key ID: 443E4198D6BBA6DE
14 changed files with 3761 additions and 209 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
use nix

5
.gitignore vendored
View file

@ -1,4 +1,5 @@
/target /target
.idea .idea
Cargo.lock # Cargo.lock
*.log *.log
.direnv

3258
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

30
build.nix Normal file
View file

@ -0,0 +1,30 @@
{ lib, rustPlatform, nix-gitignore, bash, makeWrapper, dav1d, pkg-config }:
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 = [
bash
dav1d
];
doCheck = false;
fixupPhase = ''
wrapProgram $out/bin/${pname} --set PATH ${bash}/bin:\$PATH
'';
meta = {
description = "Test";
license = lib.licenses.mit;
platforms = lib.platforms.linux;
mainProgram = "zenyx";
};
}

View file

@ -24,6 +24,7 @@ wgpu = "24.0.1"
winit = "0.30.8" winit = "0.30.8"
bytemuck = "1.21.0" bytemuck = "1.21.0"
futures = "0.3.31" futures = "0.3.31"
cgmath = "0.18.0"
[profile.dev] [profile.dev]

View file

@ -1,9 +1,7 @@
pub trait Component: Sized + 'static { pub trait Component: Sized + 'static {
fn update(&mut self, delta_time: f32); fn update(&mut self, delta_time: f32);
fn serialize(&self) -> Vec<u8>; fn serialize(&self) -> Vec<u8>;
fn deserialize(data: &[u8;6]) -> Self; fn deserialize(data: &[u8; 6]) -> Self;
} }
pub trait Entity: Sized { pub trait Entity: Sized {
@ -11,5 +9,5 @@ pub trait Entity: Sized {
fn remove_component<C: Component>(&mut self); fn remove_component<C: Component>(&mut self);
fn get_component<C: Component>(&self) -> Option<&C>; fn get_component<C: Component>(&self) -> Option<&C>;
fn serialize(&self) -> Vec<u8>; fn serialize(&self) -> Vec<u8>;
fn deserialize(data: &[u8;6]) -> Self; fn deserialize(data: &[u8; 6]) -> Self;
} }

View file

@ -5,4 +5,4 @@ pub mod repl;
pub mod splash; pub mod splash;
pub mod workspace; pub mod workspace;
pub mod render; pub mod render;

View file

@ -7,13 +7,13 @@ use regex::Regex;
static INIT: parking_lot::Once = Once::new(); static INIT: parking_lot::Once = Once::new();
//#[cfg(not(debug_assertions))]
pub fn set_panic_hook() { pub fn set_panic_hook() {
use std::io::Write; use std::io::Write;
use colored::Colorize; use colored::Colorize;
use crate::workspace; use crate::workspace;
INIT.call_once(|| { INIT.call_once(|| {
let default_hook = std::panic::take_hook(); let default_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| { std::panic::set_hook(Box::new(move |info| {
@ -25,23 +25,26 @@ pub fn set_panic_hook() {
std::fs::create_dir_all(&log_path).unwrap_or_else(|_| { std::fs::create_dir_all(&log_path).unwrap_or_else(|_| {
default_hook(info); default_hook(info);
std::process::exit(0); std::process::exit(0);
}) });
} }
let log_path = log_path.join("panic.log"); let log_path = log_path.join("panic.log");
// human_panic::print_msg::<PathBuf>(Some(log_path), &human_panic::Metadata::new("Zenyx", env!("CARGO_PKG_VERSION"))
// .support("https://github.com/Zenyx-Engine/Zenyx/issues")
// .authors("Zenyx community <https://github.com/Zenyx-Engine>")).unwrap();
// // Call the default hook for any additional actions
let mut file = std::fs::File::create(&log_path).unwrap_or_else(|_| { let mut file = std::fs::File::create(&log_path).unwrap_or_else(|_| {
default_hook(info); default_hook(info);
std::process::exit(0); std::process::exit(0);
}); });
writeln!(file, "{}", info.payload_as_str().unwrap_or_else(|| {
default_hook(info); // Instead of using payload_as_str(), downcast the panic payload:
std::process::exit(0); let payload = info.payload();
})).unwrap_or_else(|_| { 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).unwrap_or_else(|_| {
default_hook(info); default_hook(info);
std::process::exit(0); std::process::exit(0);
}); });
@ -49,6 +52,7 @@ pub fn set_panic_hook() {
default_hook(info); default_hook(info);
std::process::exit(0); std::process::exit(0);
}); });
let panic_msg = format!( let panic_msg = format!(
"Zenyx had a problem and crashed. To help us diagnose the problem you can send us a crash report. "Zenyx had a problem and crashed. To help us diagnose the problem you can send us a crash report.
@ -60,34 +64,20 @@ 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. 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()); Thank you kindly!", log_path.display());
println!("{}",panic_msg.red().bold());
println!("\nFor future reference, the error summary is as follows:\n{}",info.payload_as_str().unwrap_or_else(||{ println!("{}", panic_msg.red().bold());
default_hook(info); println!("\nFor future reference, the error summary is as follows:\n{}", payload_str.red().bold());
std::process::exit(0); std::process::exit(0);
}).red().bold());
std::process::exit(0); // There is nothing to be done at this point, it looks cleaner to exit instead of doing a natural panic
})); }));
}); });
} }
// THIS SNIPPET IS LICENSED UNDER THE APACHE LICENSE, VERSION 2.0
// https://github.com/rust-cli/human-panic
// No changes were made to the original snippet
fn render_backtrace() -> String { fn render_backtrace() -> String {
//We take padding for address and extra two letters
//to pad after index.
#[allow(unused_qualifications)] // needed for pre-1.80 MSRV
const HEX_WIDTH: usize = mem::size_of::<usize>() * 2 + 2; const HEX_WIDTH: usize = mem::size_of::<usize>() * 2 + 2;
//Padding for next lines after frame's address
const NEXT_SYMBOL_PADDING: usize = HEX_WIDTH + 6; const NEXT_SYMBOL_PADDING: usize = HEX_WIDTH + 6;
let mut backtrace = String::new(); let mut backtrace = String::new();
//Here we iterate over backtrace frames
//(each corresponds to function's stack)
//We need to print its address
//and symbol(e.g. function name),
//if it is available
let bt = Backtrace::new(); let bt = Backtrace::new();
let symbols = bt let symbols = bt
.frames() .frames()
@ -121,7 +111,6 @@ fn render_backtrace() -> String {
let ip = frame.ip(); let ip = frame.ip();
let _ = writeln!(backtrace, "{entry_idx:4}: {ip:HEX_WIDTH$?} - {name}"); let _ = writeln!(backtrace, "{entry_idx:4}: {ip:HEX_WIDTH$?} - {name}");
if let Some(symbol) = symbol { if let Some(symbol) = symbol {
//See if there is debug information with file name and line
if let (Some(file), Some(line)) = (symbol.filename(), symbol.lineno()) { if let (Some(file), Some(line)) = (symbol.filename(), symbol.lineno()) {
let _ = writeln!( let _ = writeln!(
backtrace, backtrace,
@ -134,7 +123,6 @@ fn render_backtrace() -> String {
} }
} }
} }
backtrace backtrace
} }
@ -146,7 +134,6 @@ impl Sanitize for str {
fn sanitize_path(&self) -> String { fn sanitize_path(&self) -> String {
let username_pattern = r"(?i)(/home/|/Users/|\\Users\\)([^/\\]+)"; 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 sanitize path, aborting operation");
re.replace_all(self, "${1}<USER>").to_string() re.replace_all(self, "${1}<USER>").to_string()
} }
} }

View file

@ -1,118 +1,230 @@
 use std::borrow::Cow;
use std::borrow::Cow;
use std::sync::Arc; use std::sync::Arc;
use std::time::Instant; use std::time::Instant;
use thiserror::Error;
use winit::window::Window; use cgmath::{Matrix4, Point3, Rad, Vector3, perspective};
use futures::executor::block_on; use futures::executor::block_on;
use thiserror::Error;
use wgpu::util::DeviceExt;
use winit::window::Window;
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum ContextError { pub enum ContextError {
#[error("Failed to create WGPU surface: {0}")] #[error("Failed to create WGPU surface: {0}")]
SurfaceCreationFailure(#[from] wgpu::CreateSurfaceError), SurfaceCreationFailure(#[from] wgpu::CreateSurfaceError),
} }
/// This WGSL shader generates a cube procedurally and rotates it around the Y axis.
/// A uniform (u.time) is used as the rotation angle. After rotation, a simple
/// perspective projection is applied (dividing x,y by z) to produce clip-space coordinates.
const CUBE_SHADER: &str = r#" const CUBE_SHADER: &str = r#"
struct Uniforms { struct Uniforms {
time: f32, mvp: mat4x4<f32>,
// pad to 16 bytes (uniforms require 16-byte alignment)
padding0: f32,
padding1: f32,
padding2: f32,
}; };
@group(0) @binding(0) @group(0) @binding(0)
var<uniform> u: Uniforms; var<uniform> u: Uniforms;
// Returns a rotation matrix about the Y axis. struct VertexInput {
fn rotationY(angle: f32) -> mat3x3<f32> { @location(0) position: vec3<f32>,
let c = cos(angle); @location(1) normal: vec3<f32>,
let s = sin(angle); };
return mat3x3<f32>(
vec3<f32>( c, 0.0, s), struct VertexOutput {
vec3<f32>(0.0, 1.0, 0.0), @builtin(position) clip_position: vec4<f32>,
vec3<f32>(-s, 0.0, c) @location(0) normal: vec3<f32>,
); };
}
@vertex @vertex
fn vs_main(@builtin(vertex_index) vid: u32) -> @builtin(position) vec4<f32> { fn vs_main(input: VertexInput) -> VertexOutput {
// We generate 36 vertices (6 faces * 6 vertices per face) var output: VertexOutput;
let face: u32 = vid / 6u; // which face (0..5) output.clip_position = u.mvp * vec4<f32>(input.position, 1.0);
let corner: u32 = vid % 6u; // which corner within that face output.normal = input.normal;
return output;
// Offsets for the two triangles that make up a face:
// (these are in a 2D space, later used to compute positions on the face)
var offsets = array<vec2<f32>, 6>(
vec2<f32>(-1.0, -1.0),
vec2<f32>( 1.0, -1.0),
vec2<f32>( 1.0, 1.0),
vec2<f32>( 1.0, 1.0),
vec2<f32>(-1.0, 1.0),
vec2<f32>(-1.0, -1.0)
);
var center: vec3<f32>;
var uvec: vec3<f32>;
var vvec: vec3<f32>;
// Define each face of the cube (cube of side length 1 centered at origin)
if (face == 0u) {
// Front face (z = +0.5)
center = vec3<f32>(0.0, 0.0, 0.5);
uvec = vec3<f32>(0.5, 0.0, 0.0);
vvec = vec3<f32>(0.0, 0.5, 0.0);
} else if (face == 1u) {
// Back face (z = -0.5)
center = vec3<f32>(0.0, 0.0, -0.5);
uvec = vec3<f32>(-0.5, 0.0, 0.0);
vvec = vec3<f32>(0.0, 0.5, 0.0);
} else if (face == 2u) {
// Right face (x = +0.5)
center = vec3<f32>(0.5, 0.0, 0.0);
uvec = vec3<f32>(0.0, 0.0, -0.5);
vvec = vec3<f32>(0.0, 0.5, 0.0);
} else if (face == 3u) {
// Left face (x = -0.5)
center = vec3<f32>(-0.5, 0.0, 0.0);
uvec = vec3<f32>(0.0, 0.0, 0.5);
vvec = vec3<f32>(0.0, 0.5, 0.0);
} else if (face == 4u) {
// Top face (y = +0.5)
center = vec3<f32>(0.0, 0.5, 0.0);
uvec = vec3<f32>(0.5, 0.0, 0.0);
vvec = vec3<f32>(0.0, 0.0, -0.5);
} else {
// Bottom face (y = -0.5)
center = vec3<f32>(0.0, -0.5, 0.0);
uvec = vec3<f32>(0.5, 0.0, 0.0);
vvec = vec3<f32>(0.0, 0.0, 0.5);
}
let off = offsets[corner];
var pos = center + off.x * uvec + off.y * vvec;
// Apply a rotation about the Y axis using the uniform time.
let rot = rotationY(u.time);
pos = rot * pos;
// Translate the cube so it is in front of the camera.
pos = pos + vec3<f32>(0.0, 0.0, 2.0);
// Simple perspective projection: divide x and y by z.
let projected = vec2<f32>(pos.x / pos.z, pos.y / pos.z);
return vec4<f32>(projected, 0.0, 1.0);
} }
@fragment @fragment
fn fs_main() -> @location(0) vec4<f32> { fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
// Output a fixed color. let light_dir = normalize(vec3<f32>(0.5, 1.0, 0.5));
return vec4<f32>(0.7, 0.7, 0.9, 1.0); let brightness = clamp(dot(normalize(input.normal), light_dir), 0.0, 1.0);
return vec4<f32>(0.7 * brightness, 0.7 * brightness, 0.9 * brightness, 1.0);
} }
"#; "#;
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct Vertex {
position: [f32; 3],
normal: [f32; 3],
}
impl Vertex {
const ATTRIBS: [wgpu::VertexAttribute; 2] = [
wgpu::VertexAttribute {
offset: 0,
shader_location: 0,
format: wgpu::VertexFormat::Float32x3,
},
wgpu::VertexAttribute {
offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
shader_location: 1,
format: wgpu::VertexFormat::Float32x3,
},
];
fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &Self::ATTRIBS,
}
}
}
const CUBE_VERTICES: &[Vertex] = &[
Vertex {
position: [-0.5, -0.5, 0.5],
normal: [0.0, 0.0, 1.0],
},
Vertex {
position: [0.5, -0.5, 0.5],
normal: [0.0, 0.0, 1.0],
},
Vertex {
position: [0.5, 0.5, 0.5],
normal: [0.0, 0.0, 1.0],
},
Vertex {
position: [0.5, 0.5, 0.5],
normal: [0.0, 0.0, 1.0],
},
Vertex {
position: [-0.5, 0.5, 0.5],
normal: [0.0, 0.0, 1.0],
},
Vertex {
position: [-0.5, -0.5, 0.5],
normal: [0.0, 0.0, 1.0],
},
Vertex {
position: [0.5, -0.5, -0.5],
normal: [0.0, 0.0, -1.0],
},
Vertex {
position: [-0.5, -0.5, -0.5],
normal: [0.0, 0.0, -1.0],
},
Vertex {
position: [-0.5, 0.5, -0.5],
normal: [0.0, 0.0, -1.0],
},
Vertex {
position: [-0.5, 0.5, -0.5],
normal: [0.0, 0.0, -1.0],
},
Vertex {
position: [0.5, 0.5, -0.5],
normal: [0.0, 0.0, -1.0],
},
Vertex {
position: [0.5, -0.5, -0.5],
normal: [0.0, 0.0, -1.0],
},
Vertex {
position: [0.5, -0.5, 0.5],
normal: [1.0, 0.0, 0.0],
},
Vertex {
position: [0.5, -0.5, -0.5],
normal: [1.0, 0.0, 0.0],
},
Vertex {
position: [0.5, 0.5, -0.5],
normal: [1.0, 0.0, 0.0],
},
Vertex {
position: [0.5, 0.5, -0.5],
normal: [1.0, 0.0, 0.0],
},
Vertex {
position: [0.5, 0.5, 0.5],
normal: [1.0, 0.0, 0.0],
},
Vertex {
position: [0.5, -0.5, 0.5],
normal: [1.0, 0.0, 0.0],
},
Vertex {
position: [-0.5, -0.5, -0.5],
normal: [-1.0, 0.0, 0.0],
},
Vertex {
position: [-0.5, -0.5, 0.5],
normal: [-1.0, 0.0, 0.0],
},
Vertex {
position: [-0.5, 0.5, 0.5],
normal: [-1.0, 0.0, 0.0],
},
Vertex {
position: [-0.5, 0.5, 0.5],
normal: [-1.0, 0.0, 0.0],
},
Vertex {
position: [-0.5, 0.5, -0.5],
normal: [-1.0, 0.0, 0.0],
},
Vertex {
position: [-0.5, -0.5, -0.5],
normal: [-1.0, 0.0, 0.0],
},
Vertex {
position: [-0.5, 0.5, 0.5],
normal: [0.0, 1.0, 0.0],
},
Vertex {
position: [0.5, 0.5, 0.5],
normal: [0.0, 1.0, 0.0],
},
Vertex {
position: [0.5, 0.5, -0.5],
normal: [0.0, 1.0, 0.0],
},
Vertex {
position: [0.5, 0.5, -0.5],
normal: [0.0, 1.0, 0.0],
},
Vertex {
position: [-0.5, 0.5, -0.5],
normal: [0.0, 1.0, 0.0],
},
Vertex {
position: [-0.5, 0.5, 0.5],
normal: [0.0, 1.0, 0.0],
},
Vertex {
position: [-0.5, -0.5, -0.5],
normal: [0.0, -1.0, 0.0],
},
Vertex {
position: [0.5, -0.5, -0.5],
normal: [0.0, -1.0, 0.0],
},
Vertex {
position: [0.5, -0.5, 0.5],
normal: [0.0, -1.0, 0.0],
},
Vertex {
position: [0.5, -0.5, 0.5],
normal: [0.0, -1.0, 0.0],
},
Vertex {
position: [-0.5, -0.5, 0.5],
normal: [0.0, -1.0, 0.0],
},
Vertex {
position: [-0.5, -0.5, -0.5],
normal: [0.0, -1.0, 0.0],
},
];
pub struct WgpuCtx<'window> { pub struct WgpuCtx<'window> {
device: wgpu::Device, device: wgpu::Device,
queue: wgpu::Queue, queue: wgpu::Queue,
@ -121,6 +233,7 @@ pub struct WgpuCtx<'window> {
adapter: wgpu::Adapter, adapter: wgpu::Adapter,
render_pipeline: wgpu::RenderPipeline, render_pipeline: wgpu::RenderPipeline,
uniform_buffer: wgpu::Buffer, uniform_buffer: wgpu::Buffer,
vertex_buffer: wgpu::Buffer,
start_time: Instant, start_time: Instant,
} }
@ -149,28 +262,26 @@ impl<'window> WgpuCtx<'window> {
) )
.await .await
.expect("Failed to create rendering device"); .expect("Failed to create rendering device");
let size = window.inner_size(); let size = window.inner_size();
let width = size.width.max(1); let width = size.width.max(1);
let height = size.height.max(1); let height = size.height.max(1);
let surface_config = surface.get_default_config(&adapter, width, height).unwrap(); let surface_config = surface.get_default_config(&adapter, width, height).unwrap();
surface.configure(&device, &surface_config); surface.configure(&device, &surface_config);
// Create a uniform buffer (16 bytes to satisfy alignment requirements)
let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor { let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Uniform Buffer"), label: Some("Uniform Buffer"),
size: 16, size: 64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false, mapped_at_creation: false,
}); });
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
// Create the shader module from the inline WGSL shader. label: Some("Cube Vertex Buffer"),
contents: bytemuck::cast_slice(CUBE_VERTICES),
usage: wgpu::BufferUsages::VERTEX,
});
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Cube Shader"), label: Some("Cube Shader"),
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(CUBE_SHADER)), source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(CUBE_SHADER)),
}); });
// Create a bind group layout for the uniform.
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Uniform Bind Group Layout"), label: Some("Uniform Bind Group Layout"),
entries: &[wgpu::BindGroupLayoutEntry { entries: &[wgpu::BindGroupLayoutEntry {
@ -179,27 +290,23 @@ impl<'window> WgpuCtx<'window> {
ty: wgpu::BindingType::Buffer { ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform, ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false, has_dynamic_offset: false,
min_binding_size: wgpu::BufferSize::new(16), min_binding_size: wgpu::BufferSize::new(64),
}, },
count: None, count: None,
}], }],
}); });
// Create the pipeline layout.
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Cube Pipeline Layout"), label: Some("Cube Pipeline Layout"),
bind_group_layouts: &[&bind_group_layout], bind_group_layouts: &[&bind_group_layout],
push_constant_ranges: &[], push_constant_ranges: &[],
}); });
// TODO: add proper vertex buffer
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Cube Render Pipeline"), label: Some("Cube Render Pipeline"),
layout: Some(&pipeline_layout), layout: Some(&pipeline_layout),
vertex: wgpu::VertexState { vertex: wgpu::VertexState {
module: &shader, module: &shader,
entry_point: Some("vs_main"), entry_point: Some("vs_main"),
buffers: &[], buffers: &[Vertex::desc()],
compilation_options: wgpu::PipelineCompilationOptions::default(), compilation_options: wgpu::PipelineCompilationOptions::default(),
}, },
fragment: Some(wgpu::FragmentState { fragment: Some(wgpu::FragmentState {
@ -207,7 +314,7 @@ impl<'window> WgpuCtx<'window> {
entry_point: Some("fs_main"), entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState { targets: &[Some(wgpu::ColorTargetState {
format: surface_config.format, format: surface_config.format,
blend: Some(wgpu::BlendState::REPLACE), blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL, write_mask: wgpu::ColorWrites::ALL,
})], })],
compilation_options: wgpu::PipelineCompilationOptions::default(), compilation_options: wgpu::PipelineCompilationOptions::default(),
@ -216,7 +323,7 @@ impl<'window> WgpuCtx<'window> {
topology: wgpu::PrimitiveTopology::TriangleList, topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None, strip_index_format: None,
front_face: wgpu::FrontFace::Ccw, front_face: wgpu::FrontFace::Ccw,
cull_mode: None, cull_mode: Some(wgpu::Face::Back),
polygon_mode: wgpu::PolygonMode::Fill, polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false, unclipped_depth: false,
conservative: false, conservative: false,
@ -230,7 +337,6 @@ impl<'window> WgpuCtx<'window> {
multiview: None, multiview: None,
cache: None, cache: None,
}); });
Ok(WgpuCtx { Ok(WgpuCtx {
device, device,
queue, queue,
@ -239,6 +345,7 @@ impl<'window> WgpuCtx<'window> {
adapter, adapter,
render_pipeline, render_pipeline,
uniform_buffer, uniform_buffer,
vertex_buffer,
start_time: Instant::now(), start_time: Instant::now(),
}) })
} }
@ -255,37 +362,47 @@ impl<'window> WgpuCtx<'window> {
} }
pub fn draw(&mut self) { pub fn draw(&mut self) {
// Update the uniform buffer with the elapsed time.
let elapsed = self.start_time.elapsed().as_secs_f32(); let elapsed = self.start_time.elapsed().as_secs_f32();
// Pack into 4 floats (pad to 16 bytes) let model = Matrix4::from_angle_x(Rad(elapsed)) * Matrix4::from_angle_y(Rad(elapsed));
let time_data = [elapsed, 0.0, 0.0, 0.0]; let view = Matrix4::look_at_rh(
self.queue.write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&time_data)); Point3::new(0.0, 0.0, 3.0),
Point3::new(0.0, 0.0, 0.0),
Vector3::unit_y(),
);
let aspect = self.surface_config.width as f32 / self.surface_config.height as f32;
let proj = perspective(Rad(std::f32::consts::FRAC_PI_4), aspect, 0.1, 100.0);
let mvp = proj * view * model;
let mvp_array: [[f32; 4]; 4] = [
[mvp.x.x, mvp.x.y, mvp.x.z, mvp.x.w],
[mvp.y.x, mvp.y.y, mvp.y.z, mvp.y.w],
[mvp.z.x, mvp.z.y, mvp.z.z, mvp.z.w],
[mvp.w.x, mvp.w.y, mvp.w.z, mvp.w.w],
];
self.queue
.write_buffer(&self.uniform_buffer, 0, bytemuck::bytes_of(&mvp_array));
let surface_texture = self let surface_texture = self
.surface .surface
.get_current_texture() .get_current_texture()
.expect("Failed to get surface texture"); .expect("Failed to get surface texture");
let view = surface_texture let view_texture = surface_texture
.texture .texture
.create_view(&wgpu::TextureViewDescriptor::default()); .create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder = self
let mut encoder = .device
self.device .create_command_encoder(&wgpu::CommandEncoderDescriptor {
.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("Cube Command Encoder"),
label: Some("Cube Command Encoder"), });
});
{ {
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Cube Render Pass"), label: Some("Cube Render Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment { color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view, view: &view_texture,
resolve_target: None, resolve_target: None,
ops: wgpu::Operations { ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color { load: wgpu::LoadOp::Clear(wgpu::Color {
r: 0.1, r: 0.1,
g: 0.2, g: 0.1,
b: 0.3, b: 0.1,
a: 1.0, a: 1.0,
}), }),
store: wgpu::StoreOp::Store, store: wgpu::StoreOp::Store,
@ -296,9 +413,8 @@ impl<'window> WgpuCtx<'window> {
occlusion_query_set: None, occlusion_query_set: None,
}); });
render_pass.set_pipeline(&self.render_pipeline); render_pass.set_pipeline(&self.render_pipeline);
// Create a bind group on the fly for the uniform.
let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor { let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Uniform Bind Group (per draw)"), label: Some("Uniform Bind Group"),
layout: &self.render_pipeline.get_bind_group_layout(0), layout: &self.render_pipeline.get_bind_group_layout(0),
entries: &[wgpu::BindGroupEntry { entries: &[wgpu::BindGroupEntry {
binding: 0, binding: 0,
@ -306,10 +422,9 @@ impl<'window> WgpuCtx<'window> {
}], }],
}); });
render_pass.set_bind_group(0, &bind_group, &[]); render_pass.set_bind_group(0, &bind_group, &[]);
// Draw 36 vertices (6 faces × 6 vertices) render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
render_pass.draw(0..36, 0..1); render_pass.draw(0..36, 0..1);
} }
self.queue.submit(Some(encoder.finish())); self.queue.submit(Some(encoder.finish()));
surface_texture.present(); surface_texture.present();
} }

View file

@ -1,30 +1,29 @@
use std::sync::Arc; use std::sync::Arc;
use ctx::WgpuCtx; use ctx::WgpuCtx;
use log::{debug, trace};
use winit::application::ApplicationHandler; use winit::application::ApplicationHandler;
use winit::event::WindowEvent; use winit::event::WindowEvent;
use log::{debug,trace};
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::event_loop::ControlFlow; use winit::event_loop::ControlFlow;
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::window::{Window, WindowId}; use winit::window::{Window, WindowId};
pub mod ctx; pub mod ctx;
#[derive(Default)] #[derive(Default)]
pub struct App<'window> { pub struct App<'window> {
window: Option<Arc<Window>>, window: Option<Arc<Window>>,
ctx: Option<WgpuCtx<'window>>, ctx: Option<WgpuCtx<'window>>,
} }
impl ApplicationHandler for App<'_> { impl ApplicationHandler for App<'_> {
fn resumed(&mut self, event_loop: &ActiveEventLoop) { fn resumed(&mut self, event_loop: &ActiveEventLoop) {
if self.window.is_none() { if self.window.is_none() {
let win_attr = Window::default_attributes().with_title("Zenyx"); let win_attr = Window::default_attributes().with_title("Zenyx");
let window = Arc::new(event_loop let window = Arc::new(
.create_window(win_attr) event_loop
.expect("create window err.")); .create_window(win_attr)
.expect("create window err."),
);
self.window = Some(window.clone()); self.window = Some(window.clone());
let wgpu_ctx = WgpuCtx::new_blocking(window.clone()).unwrap(); let wgpu_ctx = WgpuCtx::new_blocking(window.clone()).unwrap();
self.ctx = Some(wgpu_ctx) self.ctx = Some(wgpu_ctx)
@ -52,11 +51,12 @@ impl ApplicationHandler for App<'_> {
} }
} }
WindowEvent::Resized(size) => { WindowEvent::Resized(size) => {
if let (Some(wgpu_ctx),Some(window)) = (&mut self.ctx, &self.window) { if let (Some(wgpu_ctx), Some(window)) = (&mut self.ctx, &self.window) {
wgpu_ctx.resize(size.into()); wgpu_ctx.resize(size.into());
window.request_redraw(); window.request_redraw();
let size_str: String = size.height.to_string() + "x" + &size.width.to_string(); let size_str: String = size.height.to_string() + "x" + &size.width.to_string();
//self.window.as_ref().unwrap().set_title(&format!("you reszed the window to {size_str}")); //self.window.as_ref().unwrap().set_title(&format!("you reszed the window to
// {size_str}"));
debug!("Window resized to {:?}", size_str); debug!("Window resized to {:?}", size_str);
} }
} }
@ -69,4 +69,4 @@ pub fn init_renderer(event_loop: EventLoop<()>) {
event_loop.set_control_flow(ControlFlow::Poll); event_loop.set_control_flow(ControlFlow::Poll);
let mut app = App::default(); let mut app = App::default();
event_loop.run_app(&mut app).unwrap(); event_loop.run_app(&mut app).unwrap();
} }

View file

@ -1,4 +1,3 @@
#![feature(panic_payload_as_str)]
use core::{ use core::{
panic::set_panic_hook, panic::set_panic_hook,
repl::{handler::COMMAND_MANAGER, setup}, repl::{handler::COMMAND_MANAGER, setup},
@ -7,43 +6,31 @@ use core::{
use colored::Colorize; use colored::Colorize;
use log::info; use log::info;
use mlua::Lua;
use parking_lot::Mutex;
use tokio::runtime; use tokio::runtime;
use winit::event_loop::EventLoop; use winit::event_loop::EventLoop;
pub mod core; pub mod core;
fn main() -> anyhow::Result<()> { #[tokio::main(flavor = "current_thread")]
async fn main() -> anyhow::Result<()> {
if !cfg!(debug_assertions) { if !cfg!(debug_assertions) {
println!("{}", "Debug mode disabled".bright_blue()); println!("{}", "Debug mode disabled".bright_blue());
set_panic_hook(); set_panic_hook();
} }
let runtime = runtime::Builder::new_current_thread() setup();
.enable_all() splash::print_splash();
.build()?; info!("Type 'help' for a list of commands.");
runtime.block_on(async { let repl_thread = std::thread::spawn(|| {
setup(); let rt = runtime::Builder::new_current_thread().enable_all().build().unwrap();
splash::print_splash(); rt.block_on(core::repl::input::handle_repl())
info!("Type 'help' for a list of commands."); });
let repl_handle = tokio::spawn(core::repl::input::handle_repl()); let event_loop = EventLoop::new().unwrap();
let event_loop = EventLoop::new().unwrap(); core::render::init_renderer(event_loop);
core::render::init_renderer(event_loop);
// Await the REPL
if let Err(e) = repl_handle.await {
eprintln!("REPL error: {:?}", e);
}
// Wait for the renderer to finish (if needed)
Ok::<(), anyhow::Error>(())
})?;
if let Err(_) = repl_thread.join() {
eprintln!("REPL thread panicked");
}
Ok(()) Ok(())
} }

111
flake.lock generated Normal file
View file

@ -0,0 +1,111 @@
{
"nodes": {
"flake-compat": {
"locked": {
"lastModified": 1733328505,
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
"revCount": 69,
"type": "tarball",
"url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.1.0/01948eb7-9cba-704f-bbf3-3fa956735b52/source.tar.gz"
},
"original": {
"type": "tarball",
"url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1742422364,
"narHash": "sha256-mNqIplmEohk5jRkqYqG19GA8MbQ/D4gQSK0Mu4LvfRQ=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "a84ebe20c6bc2ecbcfb000a50776219f48d134cc",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1736320768,
"narHash": "sha256-nIYdTAiKIGnFNugbomgBJR+Xv5F1ZQU+HfaBqJKroC0=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "4bc9c909d9ac828a039f288cf872d16d38185db8",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-compat": "flake-compat",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay",
"utils": "utils"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1742610648,
"narHash": "sha256-9jWi3gw3fEIgEslnFjH/s1I+Iyf1+4t5B1Ed1FOiy8o=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "c60d41987df3c853e2a842de2c63ded40400979b",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

50
flake.nix Normal file
View file

@ -0,0 +1,50 @@
{
description = "Zenyx - A WSYWIG game engine written in rust ";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
utils.url = "github:numtide/flake-utils";
rust-overlay.url = "github:oxalica/rust-overlay";
flake-compat.url = "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz";
};
outputs = { self, nixpkgs, utils,rust-overlay, ... }: {
overlays.default = final: prev: {
zenyx = final.callPackage ./build.nix {};
};
}
//
utils.lib.eachDefaultSystem (system:
let pkgs = import nixpkgs {
inherit system;
overlays = [self.overlays.default (import rust-overlay) ];
};
in {
packages = {
inherit (pkgs) zenyx;
default = pkgs.zenyx;
};
devShells.default = pkgs.mkShell {
name = "zenyx";
nativeBuildInputs = with pkgs; [
(rust-bin.selectLatestNightlyWith (toolchain: toolchain.default.override {
extensions = [ "cargo" "clippy" ];
# targets = [ "arm-unknown-linux-gnueabihf" ];
}))
pkg-config
];
buildInputs = with pkgs; [
vulkan-loader
wayland
libxkbcommon
xorg.libXcursor
xorg.libXrandr
xorg.libXi
xorg.libX11
xorg.libxcb
pkg-config
];
};
}
);
}

13
shell.nix Normal file
View file

@ -0,0 +1,13 @@
(import
(
let
lock = builtins.fromJSON (builtins.readFile ./flake.lock);
nodeName = lock.nodes.root.inputs.flake-compat;
in
fetchTarball {
url = lock.nodes.${nodeName}.locked.url or "https://github.com/edolstra/flake-compat/archive/${lock.nodes.${nodeName}.locked.rev}.tar.gz";
sha256 = lock.nodes.${nodeName}.locked.narHash;
}
)
{ src = ./.; }
).shellNix