feat: basic triangle rendering

This commit is contained in:
Chance 2025-04-16 01:24:10 +00:00 committed by BitSyndicate
parent b10568484e
commit d30b0a2d2a
11 changed files with 1828 additions and 67 deletions

1487
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -12,7 +12,7 @@ repository = "https://codeberg.org/Caznix/Zenyx"
[workspace] [workspace]
resolver = "2" resolver = "2"
members = ["subcrates/zlog"] members = [ "subcrates/renderer","subcrates/zlog"]
[workspace.dependencies] [workspace.dependencies]
zlog = { path = "subcrates/zlog" } zlog = { path = "subcrates/zlog" }
@ -37,10 +37,14 @@ incremental = false
panic = "abort" panic = "abort"
[dependencies] [dependencies]
glm = "0.2.3"
smol = "2.0.2"
terminator = "0.3.2" terminator = "0.3.2"
thiserror = "2.0.12" thiserror = "2.0.12"
tracing = "0.1.41" tracing = "0.1.41"
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
vulkano = "0.35.1"
wgpu = { version = "25.0.0", features = ["spirv"] }
winit = "0.30.9" winit = "0.30.9"
zlog.workspace = true zlog.workspace = true

26
build.rs Normal file
View file

@ -0,0 +1,26 @@
use std::{env, process::Command};
fn main() {
println!("cargo::rerun-if-changed=shaders");
let outdir = env::var("OUT_DIR").unwrap();
let vert = Command::new("glslc")
.args(["shaders/shader.vert", "-o", &format!("{outdir}/vert.spv")])
.output()
.expect("Failed to execute 'glslc'");
let frag = Command::new("glslc")
.args(["shaders/shader.frag", "-o", &format!("{outdir}/frag.spv")])
.output()
.expect("Failed to execute 'glslc'");
if !vert.status.success() {
panic!(
"Failed to compile vertex shader: {}",
String::from_utf8(vert.stderr).unwrap()
)
}
if !frag.status.success() {
panic!(
"Failed to compile fragment shader: {}",
String::from_utf8(frag.stderr).unwrap()
)
}
}

8
shaders/shader.frag Normal file
View file

@ -0,0 +1,8 @@
#version 450
layout(location = 0) in vec3 color;
layout(location = 0) out vec4 out_color;
void main() {
out_color = vec4(color, 1.0);
}

10
shaders/shader.vert Normal file
View file

@ -0,0 +1,10 @@
#version 450
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 color;
layout(location = 0) out vec3 out_color;
void main() {
gl_Position = vec4(position, 1.0);
out_color = color;
}

View file

@ -1,7 +1,327 @@
use tracing::info; use std::collections::BTreeMap;
use zlog::config::LoggerConfig; use std::sync::LazyLock;
use std::{collections::HashMap, sync::Arc};
fn main() { use terminator::Terminator;
use vulkano::buffer::BufferUsage;
use wgpu::util::{DeviceExt, RenderEncoder};
use wgpu::{
Backends, BufferSlice, BufferUsages, FragmentState, IndexFormat, Instance, InstanceDescriptor,
PipelineCompilationOptions, Surface,
};
use winit::application::ApplicationHandler;
use winit::event_loop::EventLoop;
use winit::window::{Window, WindowAttributes, WindowId};
use zlog::{config::LoggerConfig, query::LogQuery};
struct WindowContext<'window> {
window: Arc<Window>,
renderer: WgpuRenderer<'window>,
}
impl std::ops::Deref for WindowContext<'_> {
type Target = winit::window::Window;
fn deref(&self) -> &Self::Target {
self.window.as_ref()
}
}
struct WgpuRenderer<'surface> {
device: wgpu::Device,
queue: wgpu::Queue,
surface: wgpu::Surface<'surface>,
surface_config: wgpu::SurfaceConfiguration,
render_pipeline: wgpu::RenderPipeline,
}
// impl WgpuRenderer {
// pub fn new(window: Arc<Window>) {}
struct App<'window> {
state: WgpuState,
windows: BTreeMap<WindowId, WindowContext<'window>>,
}
impl App<'_> {
fn new() -> Self {
Self {
state: WgpuState::new(),
windows: BTreeMap::new(),
}
}
}
struct WgpuState {
instance: wgpu::Instance,
}
#[repr(C)]
struct Vertex {
position: glm::Vec3,
color: glm::Vec3,
}
impl Vertex {
const ATTRIBS: [wgpu::VertexAttribute; 2] = [
wgpu::VertexAttribute {
offset: 0,
shader_location: 0,
format: wgpu::VertexFormat::Float32x3,
},
wgpu::VertexAttribute {
offset: std::mem::offset_of!(Vertex, color) as u64,
shader_location: 1,
format: wgpu::VertexFormat::Float32x3,
},
];
const 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,
}
}
}
impl WgpuState {
fn new() -> Self {
let backends = Backends::PRIMARY;
let instance_descriptor = InstanceDescriptor {
backends,
..Default::default()
};
let instance = Instance::new(&instance_descriptor);
Self { instance }
}
async fn create_renderer<'surface>(&self, window: Arc<Window>) -> WgpuRenderer<'surface> {
let surface = self.instance.create_surface(window.clone()).unwrap();
let adapter = self
.instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
compatible_surface: Some(&surface),
..Default::default()
})
.await
.unwrap();
let device_descriptor = wgpu::DeviceDescriptor::default();
let (device, queue) = adapter.request_device(&device_descriptor).await.unwrap();
let size = window.inner_size();
let width = size.width.max(1);
let height = size.height.max(1);
#[repr(align(4))]
struct ShaderCode<const N: usize>([u8; N]);
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Pipeline Layout"),
bind_group_layouts: &[],
push_constant_ranges: &[],
});
let vert_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Vertex Shader"),
source: unsafe {
static shader_code: &[u8] =
&ShaderCode(*include_bytes!(concat!(env!("OUT_DIR"), "/vert.spv"))).0;
// assert!(bytes.len() % 4 == 0);
let shader = shader_code.align_to::<u32>().1;
wgpu::ShaderSource::SpirV(std::borrow::Cow::Borrowed(shader))
},
});
let frag_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Fragment Shader"),
source: unsafe {
static shader_code: &[u8] =
&ShaderCode(*include_bytes!(concat!(env!("OUT_DIR"), "/frag.spv"))).0;
// assert!(bytes.len() % 4 == 0);
let shader = shader_code.align_to::<u32>().1;
wgpu::ShaderSource::SpirV(std::borrow::Cow::Borrowed(shader))
},
});
let var_name = [Some(wgpu::ColorTargetState {
format: surface.get_capabilities(&adapter).formats[0],
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})];
let pipeline_descriptor = wgpu::RenderPipelineDescriptor {
label: Some("Main pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &vert_shader,
entry_point: Some("main"),
buffers: &[Vertex::desc()],
compilation_options: Default::default(),
},
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: Some(wgpu::Face::Back),
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: None,
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
fragment: Some(FragmentState {
module: &frag_shader,
entry_point: Some("main"),
compilation_options: PipelineCompilationOptions::default(),
targets: &var_name,
}),
multiview: None,
cache: None,
};
// todo!();
let surface_caps = surface.get_capabilities(&adapter);
let present_mode = if surface_caps
.present_modes
.contains(&wgpu::PresentMode::Mailbox)
{
wgpu::PresentMode::Mailbox
} else {
wgpu::PresentMode::Fifo
};
let surface_config = wgpu::SurfaceConfiguration {
width,
height,
format: surface_caps.formats[0],
present_mode,
alpha_mode: wgpu::CompositeAlphaMode::Auto,
view_formats: vec![],
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
desired_maximum_frame_latency: 3,
};
surface.configure(&device, &surface_config);
let render_pipeline = device.create_render_pipeline(&pipeline_descriptor);
WgpuRenderer {
surface,
surface_config,
render_pipeline,
device,
queue,
}
}
}
static VERTICES: LazyLock<[Vertex; 3]> = std::sync::LazyLock::new(|| {
[
Vertex {
position: glm::vec3(-0.6, -0.5, 0.0),
color: glm::vec3(1.0, 0.0, 0.0),
},
Vertex {
position: glm::vec3(0.6, -0.5, 0.0),
color: glm::vec3(0.0, 1.0, 0.0),
},
Vertex {
position: glm::vec3(0.0, 0.5, 0.0),
color: glm::vec3(0.0, 0.0, 1.0),
},
]
});
static INDICIES: [u32; 3] = [0, 1, 2];
impl<'window> ApplicationHandler for App<'window> {
fn window_event(
&mut self,
event_loop: &winit::event_loop::ActiveEventLoop,
window_id: WindowId,
event: winit::event::WindowEvent,
) {
match event {
winit::event::WindowEvent::RedrawRequested => {
let window_ctx = self.windows.get(&window_id).unwrap();
let surface_texture = window_ctx.renderer.surface.get_current_texture().unwrap();
let view = surface_texture.texture.create_view(&Default::default());
let mut encoder = window_ctx.renderer.device.create_command_encoder(
&wgpu::wgt::CommandEncoderDescriptor {
label: Some("Render encoder"),
},
);
{
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Render"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
rpass.set_pipeline(&window_ctx.renderer.render_pipeline);
let vertex_buffer = window_ctx.renderer.device.create_buffer_init(
&wgpu::util::BufferInitDescriptor {
label: Some("Vertex buffer"),
contents: unsafe { VERTICES.align_to::<u8>().1 },
usage: BufferUsages::VERTEX,
},
);
let index_buffer = window_ctx.renderer.device.create_buffer_init(
&wgpu::util::BufferInitDescriptor {
label: Some("Index buffer"),
contents: unsafe { INDICIES.align_to::<u8>().1 },
usage: BufferUsages::INDEX,
},
);
rpass.set_vertex_buffer(0, vertex_buffer.slice(..));
rpass.set_index_buffer(index_buffer.slice(..), IndexFormat::Uint32);
rpass.draw_indexed(0..INDICIES.len() as u32, 0, 0..1);
}
window_ctx.renderer.queue.submit(Some(encoder.finish()));
surface_texture.present()
}
winit::event::WindowEvent::CloseRequested => {
event_loop.exit();
}
winit::event::WindowEvent::Resized(size) => {
if let Some(window_ctx) = self.windows.get(&window_id) {
if size.width == 0 || size.height == 0 {
return;
}
let mut new_config = window_ctx.renderer.surface_config.clone();
new_config.width = size.width;
new_config.height = size.height;
window_ctx
.renderer
.surface
.configure(&window_ctx.renderer.device, &new_config)
}
}
_ => (),
}
}
fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
let attr = WindowAttributes::default()
.with_title("Zenyx")
.with_min_inner_size(winit::dpi::LogicalSize::new(1, 1));
if self.windows.is_empty() {
let window = event_loop.create_window(attr).unwrap();
let window = Arc::new(window);
let renderer = self.state.create_renderer(window.clone());
let window_ctx = WindowContext {
renderer: smol::block_on(renderer),
window: window.clone(),
};
let window_id = window.id();
self.windows.insert(window_id, window_ctx);
}
}
}
fn main() -> Result<(), terminator::Terminator> {
let config = LoggerConfig::default() let config = LoggerConfig::default()
.colored_stdout(true) .colored_stdout(true)
.log_to_stdout(true) .log_to_stdout(true)
@ -9,6 +329,10 @@ fn main() {
.log_to_file(true) .log_to_file(true)
.log_path("zenyx.log"); .log_path("zenyx.log");
let _logger = zlog::Logger::new(config); let _logger = zlog::Logger::new(config);
let event_loop = EventLoop::new()?;
event_loop.set_control_flow(winit::event_loop::ControlFlow::Poll);
info!("This workss") let mut app = App::new();
event_loop.run_app(&mut app)?;
Ok(())
} }

View file

@ -0,0 +1,6 @@
[package]
name = "renderer"
version = "0.1.0"
edition = "2024"
[dependencies]

View file

@ -0,0 +1 @@

View file

@ -1,6 +1,7 @@
use crate::LogLevel;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use crate::LogLevel;
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct LoggerConfig { pub struct LoggerConfig {
pub(crate) log_level: LogLevel, pub(crate) log_level: LogLevel,
@ -17,26 +18,32 @@ impl LoggerConfig {
self.log_level = level; self.log_level = level;
self self
} }
pub fn log_to_file(mut self, f: bool) -> Self { pub fn log_to_file(mut self, f: bool) -> Self {
self.log_to_file = f; self.log_to_file = f;
self self
} }
pub fn colored_stdout(mut self, c: bool) -> Self { pub fn colored_stdout(mut self, c: bool) -> Self {
self.stdout_color = c; self.stdout_color = c;
self self
} }
pub fn log_to_stdout(mut self, s: bool) -> Self { pub fn log_to_stdout(mut self, s: bool) -> Self {
self.log_to_stdout = s; self.log_to_stdout = s;
self self
} }
pub fn log_path<P: AsRef<Path>>(mut self, p: P) -> Self { pub fn log_path<P: AsRef<Path>>(mut self, p: P) -> Self {
self.log_file_path = p.as_ref().to_path_buf(); self.log_file_path = p.as_ref().to_path_buf();
self self
} }
pub fn stdout_include_time(mut self, i: bool) -> Self { pub fn stdout_include_time(mut self, i: bool) -> Self {
self.stdout_include_time = i; self.stdout_include_time = i;
self self
} }
pub fn file_include_time(mut self, i: bool) -> Self { pub fn file_include_time(mut self, i: bool) -> Self {
self.file_include_time = i; self.file_include_time = i;
self self

View file

@ -3,8 +3,6 @@ pub mod query;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
use config::LoggerConfig;
use query::LogQuery;
use std::{ use std::{
fmt, fmt,
fs::OpenOptions, fs::OpenOptions,
@ -17,6 +15,9 @@ use std::{
thread, thread,
time::SystemTime, time::SystemTime,
}; };
use config::LoggerConfig;
use query::LogQuery;
use tracing::{Event, Level, Subscriber}; use tracing::{Event, Level, Subscriber};
use tracing_subscriber::{ use tracing_subscriber::{
layer::{Context, Layer, SubscriberExt}, layer::{Context, Layer, SubscriberExt},
@ -161,6 +162,7 @@ impl fmt::Display for LogLevel {
impl FromStr for LogLevel { impl FromStr for LogLevel {
type Err = (); type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
match s { match s {
"trace" => Ok(Self::Trace), "trace" => Ok(Self::Trace),

View file

@ -1,10 +1,12 @@
use super::*;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use tracing::Level; use tracing::Level;
use super::*;
#[test] #[test]
fn test_logger_sequential_consistency() { fn test_logger_sequential_consistency() {
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use tracing::{debug, error, info, trace, warn}; use tracing::{debug, error, info, trace, warn};
let config = LoggerConfig::default() let config = LoggerConfig::default()