diff --git a/Cargo.lock b/Cargo.lock index 1b6f09b..4f7ac10 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1431,6 +1431,8 @@ dependencies = [ name = "sursface_examples" version = "0.1.0" dependencies = [ + "env_logger", + "log", "sursface", ] diff --git a/examples/Cargo.toml b/examples/Cargo.toml index a7c8615..b86369f 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -13,5 +13,11 @@ crate-type = ["cdylib"] name = "hello_window" path = "src/hello_window/main.rs" +[[bin]] +name = "hello_triangle" +path = "src/hello_triangle/main.rs" + [dependencies] -sursface = { path = "../sursface" } \ No newline at end of file +env_logger = "0.11.3" +log = "0.4.21" +sursface = { path = "../sursface" } diff --git a/examples/src/hello_triangle/lib.rs b/examples/src/hello_triangle/lib.rs new file mode 100644 index 0000000..57d8b48 --- /dev/null +++ b/examples/src/hello_triangle/lib.rs @@ -0,0 +1,10 @@ +use sursface::start; + +#[cfg(target_arch = "wasm32")] +use sursface::wasm_bindgen; + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen::prelude::wasm_bindgen] +pub fn start_browser(canvas: sursface::wgpu::web_sys::HtmlCanvasElement) { + start::create_window_browser(canvas); +} \ No newline at end of file diff --git a/examples/src/hello_triangle/main.rs b/examples/src/hello_triangle/main.rs new file mode 100644 index 0000000..ae8cf73 --- /dev/null +++ b/examples/src/hello_triangle/main.rs @@ -0,0 +1,203 @@ +use std::any::Any; +use std::borrow::Cow; +use std::sync::{Arc, Mutex}; +use sursface::app::{App, State}; +use sursface::wgpu::{self, Color, CommandEncoder, RenderPipeline, ShaderModule, SurfaceTexture, TextureFormat, TextureView}; + +// Define the State trait + + +// Implement State for TriangleState +struct TriangleState { + render_pipeline: RenderPipeline, + shader: ShaderModule, + pipeline_layout: wgpu::PipelineLayout, + format: TextureFormat, + view: Option, +} + +impl State for TriangleState {} + +impl TriangleState { + fn new( + render_pipeline: RenderPipeline, + shader: ShaderModule, + pipeline_layout: wgpu::PipelineLayout, + format: TextureFormat, + ) -> Self { + TriangleState { + render_pipeline, + shader, + pipeline_layout, + format, + view: None, + } + } + + fn set_view(&mut self, view: TextureView) { + self.view = Some(view); + } +} + +fn init(app: &mut App) -> Box { + let display = app.display.as_ref().unwrap(); + let device = &display.device; + + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))), + }); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[], + push_constant_ranges: &[], + }); + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: None, + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[Some(display.config.format.into())], + compilation_options: Default::default(), + }), + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + }); + + Box::new(TriangleState::new( + render_pipeline, + shader, + pipeline_layout, + display.config.format, + )) +} + +fn render(app: &mut App, mut state: &mut Box) { + let color = Color { + r: 100.0 / 255.0, + g: 149.0 / 255.0, + b: 237.0 / 255.0, + a: 255.0 / 255.0, + }; + + let output = { + let (output, mut encoder, view) = { + let display = app.display.as_ref().unwrap(); + + // Clear the screen and get the output, encoder, and view + let (output, mut encoder) = clear_screen(app, color).unwrap(); + let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default()); + + (output, encoder, view) + }; + + { + let state = unsafe { core::mem::transmute::<&mut Box, &mut Box>(state) }; + state.set_view(view); + + let mut rpass = create_render_pass(state, &mut encoder, state.view.as_ref().unwrap(), color); + draw_triangle(&mut rpass, &state.render_pipeline); + } + + // Submit the queue outside of mutable borrow of state + { + let display = app.display.as_ref().unwrap(); + display.queue.submit(std::iter::once(encoder.finish())); + } + + output + }; + + // Now perform the present operation after mutable borrows are done + present(output); +} + +fn clear_screen( + app: &mut App, + color: Color, +) -> Result<(SurfaceTexture, CommandEncoder), wgpu::SurfaceError> { + let display = app.display.as_ref().unwrap(); + let output = display.surface.get_current_texture()?; + let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default()); + + let mut encoder = display.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Clear Screen Encoder"), + }); + + // Clear the screen with the specified color + { + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Clear Screen Render Pass"), + timestamp_writes: Default::default(), + occlusion_query_set: Default::default(), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(color), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + }); + // No further commands needed for clearing + } + + Ok((output, encoder)) +} + +fn create_render_pass<'a>( + state: &'a TriangleState, + encoder: &'a mut CommandEncoder, + view: &'a TextureView, + color: Color, +) -> wgpu::RenderPass<'a> { + let rpass_descriptor = wgpu::RenderPassDescriptor { + label: Some("Render Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(color), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: Default::default(), + occlusion_query_set: Default::default(), + }; + + encoder.begin_render_pass(&rpass_descriptor) +} + +pub fn draw_triangle<'a>( + rpass: &mut wgpu::RenderPass<'a>, + pipeline: &'a RenderPipeline, +) { + rpass.set_pipeline(pipeline); + rpass.draw(0..3, 0..1); +} + +fn present(output: SurfaceTexture) { + output.present(); +} + +#[cfg(not(target_arch = "wasm32"))] +fn main() { + use sursface::winit::dpi::PhysicalSize; + sursface::start::create_window_desktop(PhysicalSize::new(1280, 720), init, render); +} + +#[cfg(target_arch = "wasm32")] +fn main() {} diff --git a/examples/src/hello_triangle/shader.wgsl b/examples/src/hello_triangle/shader.wgsl new file mode 100644 index 0000000..859ffa4 --- /dev/null +++ b/examples/src/hello_triangle/shader.wgsl @@ -0,0 +1,11 @@ +@vertex +fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4 { + let x = f32(i32(in_vertex_index) - 1); + let y = f32(i32(in_vertex_index & 1u) * 2 - 1); + return vec4(x, y, 0.0, 1.0); +} + +@fragment +fn fs_main() -> @location(0) vec4 { + return vec4(1.0, 0.0, 0.0, 1.0); +} \ No newline at end of file diff --git a/examples/src/hello_window/main.rs b/examples/src/hello_window/main.rs index 76b1c05..b00b39c 100644 --- a/examples/src/hello_window/main.rs +++ b/examples/src/hello_window/main.rs @@ -1,16 +1,40 @@ -use sursface::{app::App, wgpu}; +use sursface::{app::{App, State}, wgpu}; +use std::any::Any; #[cfg(not(target_arch = "wasm32"))] fn main() { use sursface::winit::dpi::PhysicalSize; - sursface::start::create_window_desktop(PhysicalSize::new(1280, 720), render); + env_logger::init(); + log::info!("Starting application"); + sursface::start::create_window_desktop(PhysicalSize::new(1280, 720), init, render); } -fn render<'a>(app: &mut App<'a>) { - let _ = clear_screen(app, sursface::wgpu::Color { r: 100f64 / 255f64, g: 149f64 / 255f64, b: 237f64 / 255f64, a: 255f64 / 255f64 }); +#[cfg(target_arch = "wasm32")] +fn main() {} + +#[derive(Clone)] +struct EmptyState {} +impl State for EmptyState {} + +fn init<'a>(app: &mut App<'a>) -> Box { + log::info!("Initializing state"); + Box::new(EmptyState {}) +} + +fn render<'a>(app: &mut App<'a>, _state: &mut Box) { + log::error!("hhhhom"); + let output = clear_screen(app, wgpu::Color { + r: 100.0 / 255.0, + g: 149.0 / 255.0, + b: 237.0 / 255.0, + a: 1.0, + }).unwrap(); + let _ = present(app, output); } -fn clear_screen<'a>(app: &mut App<'a>, color: sursface::wgpu::Color) -> Result<(), wgpu::SurfaceError> { + +fn clear_screen<'a>(app: &mut App<'a>, color: sursface::wgpu::Color) -> Result { + log::info!("gaspar"); let display = app.display.as_ref().unwrap(); let output = display.surface.get_current_texture()?; let view = output @@ -39,9 +63,13 @@ fn clear_screen<'a>(app: &mut App<'a>, color: sursface::wgpu::Color) -> Result<( } display.queue.submit(std::iter::once(encoder.finish())); - output.present(); - Ok(()) + Ok(output) +} + +fn present<'a>(app: &mut App<'a>, output: sursface::wgpu::SurfaceTexture) -> Result<(), wgpu::SurfaceError> { + output.present(); + Ok(()) } #[cfg(target_arch = "wasm32")] diff --git a/sursface/src/app.rs b/sursface/src/app.rs index 9d8a2b1..933a584 100644 --- a/sursface/src/app.rs +++ b/sursface/src/app.rs @@ -1,31 +1,69 @@ -use std::panic; use std::sync::{Arc, Mutex}; use winit::application::ApplicationHandler; use winit::dpi::PhysicalSize; use winit::event::{ElementState, KeyEvent, WindowEvent}; -use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; +use winit::event_loop::ActiveEventLoop; use winit::keyboard::{KeyCode, PhysicalKey}; use winit::window::WindowId; +use std::any::Any; #[cfg(target_arch = "wasm32")] extern crate console_error_panic_hook; use super::display::Display; +pub trait State: Any + Send + Sync {} + #[derive(Default)] pub struct App<'a> { pub display: Option>, pub initial_size: PhysicalSize, #[cfg(target_arch = "wasm32")] pub canvas: Option, - pub render: Option) + 'a>>, + pub state: Option>>>, + pub init: Option) -> Box>>, + pub render: Option, &mut Box)>>, } +fn init_logger() { + #[cfg(target_arch = "wasm32")] + { + // We keep wgpu at Error level, as it's very noisy. + let base_level = log::LevelFilter::Info; + let wgpu_level = log::LevelFilter::Error; + + // On web, we use fern, as console_log doesn't have filtering on a per-module level. + fern::Dispatch::new() + .level(base_level) + .level_for("wgpu_core", wgpu_level) + .level_for("wgpu_hal", wgpu_level) + .level_for("naga", wgpu_level) + .chain(fern::Output::call(console_log::log)) + .apply() + .unwrap(); + std::panic::set_hook(Box::new(console_error_panic_hook::hook)); + } + #[cfg(not(target_arch = "wasm32"))] + { + let base_level = log::LevelFilter::Info; + let wgpu_level = log::LevelFilter::Error; + + // parse_default_env will read the RUST_LOG environment variable and apply it on top + // of these default filters. + env_logger::builder() + .filter_level(base_level) + .filter_module("wgpu_core", wgpu_level) + .filter_module("wgpu_hal", wgpu_level) + .filter_module("naga", wgpu_level) + .parse_default_env() + .init(); + } +} + impl<'a> App<'a> { pub fn from_window_size(size: PhysicalSize) -> Self { - log::info!("setting size"); - + log::info!("Setting window size"); App { initial_size: size, ..Default::default() @@ -34,8 +72,7 @@ impl<'a> App<'a> { #[cfg(target_arch = "wasm32")] pub fn from_canvas(canvas: wgpu::web_sys::HtmlCanvasElement) -> Self { - log::info!("setting canvas"); - + log::info!("Setting canvas size"); App { initial_size: PhysicalSize::new(canvas.width(), canvas.height()), canvas: Some(canvas), @@ -43,9 +80,16 @@ impl<'a> App<'a> { } } + pub fn set_init_function(&mut self, init_func: F) + where + F: Fn(&mut App<'a>) -> Box + 'static, + { + self.init = Some(Box::new(init_func)); + } + pub fn set_render_function(&mut self, render_func: F) where - F: Fn(&mut App<'a>) + 'a, + F: Fn(&mut App<'a>, &mut Box) + 'static, { self.render = Some(Box::new(render_func)); } @@ -56,7 +100,7 @@ impl<'a> ApplicationHandler for App<'a> { #[cfg(not(target_arch = "wasm32"))] { self.display = Some(Display::from_window_size(event_loop, self.initial_size)); - }; + } #[cfg(target_arch = "wasm32")] { if let Some(canvas) = self.canvas.clone() { @@ -64,7 +108,14 @@ impl<'a> ApplicationHandler for App<'a> { } else { log::error!("Canvas is not set"); } - }; + } + + log::error!("buzicomant"); + if let Some(init) = self.init.take() { + self.state = Some(Arc::new(Mutex::new(init(self)))); + self.init = Some(init); + } + log::error!("resumed"); } fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) { @@ -82,32 +133,33 @@ impl<'a> ApplicationHandler for App<'a> { event_loop.exit(); } WindowEvent::Resized(physical_size) => { - log::info!("physical_size: {physical_size:?}"); - self.display.as_mut().unwrap().resize(physical_size); - } - WindowEvent::RedrawRequested => { + log::info!("Window resized: {:?}", physical_size); if let Some(display) = self.display.as_mut() { - display.window.request_redraw(); - - match display.render() { - Ok(_) => {} - Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => { - display.resize(display.size) - } - Err(wgpu::SurfaceError::OutOfMemory) => { - log::error!("OutOfMemory"); - event_loop.exit(); - } - Err(wgpu::SurfaceError::Timeout) => { - log::warn!("Surface timeout") - } - } + display.resize(physical_size); } + if let Some(init) = self.init.take() { + self.state = Some(Arc::new(Mutex::new(init(self)))); + self.init = Some(init); + } + } + WindowEvent::RedrawRequested => { + log::error!("Redraw requested"); + log::error!("Before rendering"); + if let Some(render) = self.render.take() { - render(self); - self.render = Some(render); + // Lock the state, clone it, and then release the lock + log::error!("Locking state"); + let state_arc = Arc::clone(self.state.as_ref().unwrap()); + let mut state_lock = state_arc.lock().unwrap(); + log::error!("State locked"); + + // Pass a reference to the render function + log::error!("render"); + render(self, &mut *state_lock); } + + log::error!("After rendering"); } _ => (), } diff --git a/sursface/src/start.rs b/sursface/src/start.rs index e8db655..0b2da83 100644 --- a/sursface/src/start.rs +++ b/sursface/src/start.rs @@ -16,26 +16,42 @@ use pollster; extern crate console_error_panic_hook; use super::display::Display; - +use crate::app::State; #[cfg(not(target_arch = "wasm32"))] -pub fn create_window_desktop<'a>(window_size: PhysicalSize, render: fn(&mut App<'a>)) { +pub fn create_window_desktop<'a>( + window_size: PhysicalSize, + init: fn(&mut App) -> Box, + render: fn(&mut App, &mut Box) +) { use crate::app::App; + log::error!("stating"); + let event_loop = EventLoop::new().unwrap(); + event_loop.set_control_flow(ControlFlow::Poll); let mut app = App::from_window_size(window_size); + log::error!("megbuilt"); + app.set_init_function(init); app.set_render_function(render); + log::error!("ayayyayay"); event_loop.run_app(&mut app).unwrap(); } #[cfg(target_arch = "wasm32")] -pub fn create_window_browser<'a>(canvas: HtmlCanvasElement, render: fn(&mut App<'a>)) { +pub fn create_window_browser<'a>( + canvas: HtmlCanvasElement, + init: fn(&mut App) -> Box, + render: fn(&mut App, Box) +) { use crate::app::App; let event_loop = EventLoop::new().unwrap(); + event_loop.set_control_flow(ControlFlow::Poll); let mut app = App::from_canvas(canvas); + app.set_init_function(init); app.set_render_function(render); - event_loop.run_app(app).unwrap(); + event_loop.run_app(&mut app).unwrap(); } \ No newline at end of file