From dbf6ca457238ce658c3580df9a23d75aaf0509eb Mon Sep 17 00:00:00 2001 From: nanoqsh Date: Wed, 10 Jan 2024 00:36:50 +0600 Subject: [PATCH] Event loop example --- Cargo.toml | 9 +++++- dunge/Cargo.toml | 2 +- dunge/src/context.rs | 8 ++--- dunge/src/el.rs | 47 ++++++++++++++++------------- dunge/src/format.rs | 31 +++++++++++++++++++ dunge/src/layer.rs | 2 +- dunge/src/lib.rs | 3 +- dunge/src/state.rs | 34 ++++++++++----------- dunge/src/texture.rs | 38 ++++++------------------ dunge/src/update.rs | 8 ++--- dunge/src/window.rs | 44 ++++++++++++++------------- dunge/tests/triangle_group.rs | 3 +- dunge/tests/triangle_index.rs | 3 +- dunge/tests/triangle_vertex.rs | 7 +++-- dunge_shader/Cargo.toml | 1 + dunge_shader/src/eval.rs | 2 +- examples/triangle/Cargo.toml | 12 ++++++++ examples/triangle/src/main.rs | 54 ++++++++++++++++++++++++++++++++++ 18 files changed, 202 insertions(+), 106 deletions(-) create mode 100644 dunge/src/format.rs create mode 100644 examples/triangle/Cargo.toml create mode 100644 examples/triangle/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 31a9a0e..7eae211 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,17 @@ [workspace] -members = ["dunge", "dunge_macros", "dunge_shader", "helpers"] resolver = "2" +members = [ + "dunge", + "dunge_macros", + "dunge_shader", + "examples/triangle", + "helpers", +] [workspace.dependencies] bytemuck = "1.13" glam = "0.25" +log = "0.4" [workspace.lints.clippy] use-self = "deny" diff --git a/dunge/Cargo.toml b/dunge/Cargo.toml index a1e378b..fb6b559 100644 --- a/dunge/Cargo.toml +++ b/dunge/Cargo.toml @@ -16,7 +16,7 @@ dunge_shader = { version = "0.2.3", path = "../dunge_shader" } bytemuck = { workspace = true } glam = { workspace = true } instant = { version = "0.1", optional = true } -log = "0.4" +log = { workspace = true } wgpu = { version = "0.18", default-features = false, features = ["naga"] } [dependencies.winit] diff --git a/dunge/src/context.rs b/dunge/src/context.rs index 926fa8e..248397d 100644 --- a/dunge/src/context.rs +++ b/dunge/src/context.rs @@ -2,14 +2,14 @@ use { crate::{ bind::{self, Binder, GroupHandler, UniqueBinding, Update, Visit}, draw::Draw, + format::Format, layer::Layer, mesh::{self, Mesh}, shader::Shader, sl::IntoModule, - state::{Render, RenderView, State}, + state::{Render, State}, texture::{ - self, CopyBuffer, CopyBufferView, DrawTexture, Filter, Format, Make, MapResult, Mapped, - Sampler, + self, CopyBuffer, CopyBufferView, DrawTexture, Filter, Make, MapResult, Mapped, Sampler, }, Vertex, }, @@ -80,7 +80,7 @@ impl Context { T: DrawTexture, D: Draw, { - let view = RenderView::from_texture(texture.draw_texture()); + let view = texture.draw_texture().render_view(); self.0.draw(render, view, draw); } diff --git a/dunge/src/el.rs b/dunge/src/el.rs index 263b753..c1a1704 100644 --- a/dunge/src/el.rs +++ b/dunge/src/el.rs @@ -1,21 +1,24 @@ use { crate::{ context::Context, - state::{Render, RenderView}, + state::Render, time::{Fps, Time}, update::Update, window::View, }, - std::{error, fmt, time::Duration}, + std::{cell::Cell, error, fmt, time::Duration}, wgpu::SurfaceError, winit::{ error::EventLoopError, event, event_loop::{self, EventLoop}, - keyboard::{KeyCode, PhysicalKey, SmolStr}, + keyboard, }, }; +pub type KeyCode = keyboard::KeyCode; +pub type SmolStr = keyboard::SmolStr; + pub(crate) struct Loop(EventLoop<()>); impl Loop { @@ -72,14 +75,15 @@ where use { event::{ElementState, KeyEvent, StartCause, WindowEvent}, event_loop::ControlFlow, + keyboard::PhysicalKey, winit::dpi::PhysicalSize, }; const WAIT_TIME: Duration = Duration::from_millis(100); let mut ctrl = Control { - close: false, - min_delta_time: Duration::from_secs_f32(1. / 60.), + close: Cell::default(), + min_delta_time: Cell::new(Duration::from_secs_f32(1. / 60.)), fps: 0, pressed_keys: vec![], released_keys: vec![], @@ -94,12 +98,6 @@ where Event::NewEvents(cause) => match cause { StartCause::ResumeTimeReached { .. } => { log::debug!("resume time reached"); - if ctrl.close { - log::debug!("close"); - target.exit(); - return; - } - view.request_redraw(); } StartCause::WaitCancelled { @@ -173,8 +171,9 @@ where } let delta_time = time.delta(); - if delta_time < ctrl.min_delta_time { - let wait = ctrl.min_delta_time - delta_time; + let min_delta_time = ctrl.min_delta_time.get(); + if delta_time < min_delta_time { + let wait = min_delta_time - delta_time; target.set_control_flow(ControlFlow::wait_duration(wait)); return; } @@ -184,11 +183,17 @@ where ctrl.fps = fps; } - update.update(&mut ctrl); + update.update(&ctrl); + if ctrl.close.get() { + log::debug!("close"); + target.exit(); + return; + } + ctrl.clear_keys(); match view.output() { Ok(output) => { - let view = RenderView::from_output(&output); + let view = output.render_view(); cx.state().draw(&mut render, view, &update); output.present(); } @@ -224,20 +229,20 @@ where } pub struct Control { - close: bool, - min_delta_time: Duration, + close: Cell, + min_delta_time: Cell, fps: u32, pressed_keys: Vec, released_keys: Vec, } impl Control { - pub fn close(&mut self) { - self.close = true; + pub fn close(&self) { + self.close.set(true); } - pub fn set_min_delta_time(&mut self, min_delta_time: Duration) { - self.min_delta_time = min_delta_time; + pub fn set_min_delta_time(&self, min_delta_time: Duration) { + self.min_delta_time.set(min_delta_time); } pub fn fps(&self) -> u32 { diff --git a/dunge/src/format.rs b/dunge/src/format.rs new file mode 100644 index 0000000..803a910 --- /dev/null +++ b/dunge/src/format.rs @@ -0,0 +1,31 @@ +use wgpu::TextureFormat; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Format { + RgbAlpha, + BgrAlpha, +} + +impl Format { + pub(crate) const fn bytes(self) -> u32 { + match self { + Self::RgbAlpha => 4, + Self::BgrAlpha => 4, + } + } + + pub(crate) const fn wgpu(self) -> TextureFormat { + match self { + Self::RgbAlpha => TextureFormat::Rgba8UnormSrgb, + Self::BgrAlpha => TextureFormat::Bgra8UnormSrgb, + } + } + + pub(crate) const fn from_wgpu(format: TextureFormat) -> Option { + match format { + TextureFormat::Rgba8UnormSrgb => Some(Self::RgbAlpha), + TextureFormat::Bgra8UnormSrgb => Some(Self::BgrAlpha), + _ => None, + } + } +} diff --git a/dunge/src/layer.rs b/dunge/src/layer.rs index 6ea9819..825c336 100644 --- a/dunge/src/layer.rs +++ b/dunge/src/layer.rs @@ -1,5 +1,5 @@ use { - crate::{bind::Binding, mesh::Mesh, shader::Shader, state::State, texture::Format}, + crate::{bind::Binding, format::Format, mesh::Mesh, shader::Shader, state::State}, std::{iter, marker::PhantomData}, wgpu::{RenderPass, RenderPipeline}, }; diff --git a/dunge/src/lib.rs b/dunge/src/lib.rs index 5bc4124..b77297c 100644 --- a/dunge/src/lib.rs +++ b/dunge/src/lib.rs @@ -2,6 +2,7 @@ pub mod bind; pub mod color; pub mod context; pub mod draw; +pub mod format; pub mod group; mod init; pub mod layer; @@ -21,7 +22,7 @@ pub mod update; pub mod window; pub use { - crate::init::context, + crate::{init::context, state::Frame}, dunge_macros::{Group, Vertex}, dunge_shader::{group::Group, sl, types, vertex::Vertex}, glam, diff --git a/dunge/src/state.rs b/dunge/src/state.rs index 1652f9d..caad927 100644 --- a/dunge/src/state.rs +++ b/dunge/src/state.rs @@ -3,15 +3,16 @@ use { color::Rgba, context::Error, draw::Draw, + format::Format, layer::{Layer, SetLayer}, - texture::{CopyBuffer, CopyTexture, DrawTexture, Format, Texture}, + texture::{CopyBuffer, CopyTexture, DrawTexture}, }, std::sync::atomic::{self, AtomicUsize}, wgpu::{Color, CommandEncoder, Device, Instance, LoadOp, Queue, TextureView}, }; #[cfg(feature = "winit")] -use {crate::window::Output, wgpu::Adapter}; +use wgpu::Adapter; pub(crate) struct State { #[cfg(feature = "winit")] @@ -116,6 +117,12 @@ impl Options { } } +impl From for Options { + fn from(v: Rgba) -> Self { + Self::default().with_clear(v) + } +} + pub struct Frame<'v, 'e> { view: RenderView<'v>, device: &'e Device, @@ -128,11 +135,14 @@ impl Frame<'_, '_> { where T: DrawTexture, { - let view = RenderView::from_texture(texture.draw_texture()); + let view = texture.draw_texture().render_view(); self.encoders.make(self.device, view) } - pub fn layer<'p, V>(&'p mut self, layer: &'p Layer, opts: Options) -> SetLayer<'p, V> { + pub fn layer<'p, V, O>(&'p mut self, layer: &'p Layer, opts: O) -> SetLayer<'p, V> + where + O: Into, + { use wgpu::*; assert!( @@ -140,6 +150,7 @@ impl Frame<'_, '_> { "layer format doesn't match frame format", ); + let opts = opts.into(); let attachment = RenderPassColorAttachment { view: self.view.txview, resolve_target: None, @@ -206,18 +217,7 @@ pub(crate) struct RenderView<'v> { } impl<'v> RenderView<'v> { - pub fn from_texture(texture: &'v Texture) -> Self { - Self { - txview: texture.view(), - format: texture.format(), - } - } - - #[cfg(feature = "winit")] - pub fn from_output(output: &'v Output) -> Self { - Self { - txview: output.view(), - format: output.format(), - } + pub fn new(txview: &'v TextureView, format: Format) -> Self { + Self { txview, format } } } diff --git a/dunge/src/texture.rs b/dunge/src/texture.rs index 17c1b50..e89846a 100644 --- a/dunge/src/texture.rs +++ b/dunge/src/texture.rs @@ -1,9 +1,12 @@ use { - crate::state::State, + crate::{ + format::Format, + state::{RenderView, State}, + }, std::{error, fmt, future::IntoFuture, mem}, wgpu::{ Buffer, BufferAsyncError, BufferSlice, BufferView, CommandEncoder, FilterMode, - TextureFormat, TextureUsages, TextureView, WasmNotSend, + TextureUsages, TextureView, WasmNotSend, }, }; @@ -94,33 +97,6 @@ impl fmt::Display for ZeroSized { impl error::Error for ZeroSized {} -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Format { - RgbAlpha, -} - -impl Format { - const fn bytes(self) -> u32 { - match self { - Self::RgbAlpha => 4, - } - } - - pub(crate) const fn wgpu(self) -> TextureFormat { - match self { - Self::RgbAlpha => TextureFormat::Rgba8UnormSrgb, - } - } - - #[allow(dead_code)] - const fn from_wgpu(format: TextureFormat) -> Option { - match format { - TextureFormat::Rgba8UnormSrgb => Some(Self::RgbAlpha), - _ => None, - } - } -} - pub struct Texture { inner: wgpu::Texture, view: TextureView, @@ -191,6 +167,10 @@ impl Texture { pub(crate) fn view(&self) -> &TextureView { &self.view } + + pub(crate) fn render_view(&self) -> RenderView { + RenderView::new(&self.view, self.format()) + } } pub(crate) fn make(state: &State, data: M) -> M::Out diff --git a/dunge/src/update.rs b/dunge/src/update.rs index dedf302..4b237fb 100644 --- a/dunge/src/update.rs +++ b/dunge/src/update.rs @@ -1,12 +1,12 @@ use crate::{draw::Draw, el::Control, state::Frame}; pub trait Update: Draw { - fn update(&mut self, ctrl: &mut Control); + fn update(&mut self, ctrl: &Control); } pub fn from_fn(update: U, draw: D) -> impl Update where - U: FnMut(&mut Control), + U: FnMut(&Control), D: Fn(Frame), { struct Func(U, D); @@ -22,10 +22,10 @@ where impl Update for Func where - U: FnMut(&mut Control), + U: FnMut(&Control), D: Fn(Frame), { - fn update(&mut self, ctrl: &mut Control) { + fn update(&mut self, ctrl: &Control) { (self.0)(ctrl); } } diff --git a/dunge/src/window.rs b/dunge/src/window.rs index 6924875..be157a9 100644 --- a/dunge/src/window.rs +++ b/dunge/src/window.rs @@ -2,8 +2,8 @@ use { crate::{ context::{self, Context}, el::Loop, - state::State, - texture::Format, + format::Format, + state::{RenderView, State}, update::Update, }, std::{error, fmt}, @@ -23,7 +23,6 @@ use crate::el::LoopError; pub struct WindowBuilder { title: String, size: Option<(u32, u32)>, - show_cursor: bool, } impl WindowBuilder { @@ -31,7 +30,6 @@ impl WindowBuilder { Self { title: String::default(), size: Some((600, 600)), - show_cursor: true, } } @@ -53,11 +51,6 @@ impl WindowBuilder { self } - pub fn with_show_cursor(mut self, show_cursor: bool) -> Self { - self.show_cursor = show_cursor; - self - } - pub(crate) fn build(self, cx: Context, instance: &Instance) -> Result { use winit::{dpi::PhysicalSize, window::Fullscreen}; @@ -88,6 +81,10 @@ impl Window { self.cx.clone() } + pub fn format(&self) -> Format { + self.view.format() + } + #[cfg(not(target_arch = "wasm32"))] pub fn run(self, update: U) -> Result<(), LoopError> where @@ -116,11 +113,11 @@ pub(crate) struct View { } impl View { - const FORMAT: Format = Format::RgbAlpha; - fn new(state: &State, instance: &Instance, inner: window::Window) -> Result { use wgpu::*; + const SUPPORTED_FORMATS: [Format; 2] = [Format::RgbAlpha, Format::BgrAlpha]; + // # Safety // // The surface needs to live as long as the window that created it. @@ -128,10 +125,15 @@ impl View { let surface = unsafe { instance.create_surface(&inner)? }; let conf = { let caps = surface.get_capabilities(state.adapter()); - let format = Self::FORMAT.wgpu(); - if !caps.formats.contains(&format) { + let format = SUPPORTED_FORMATS.into_iter().find_map(|format| { + let format = format.wgpu(); + caps.formats.contains(&format).then_some(format) + }); + + let Some(format) = format else { + log::error!("surface formats: {formats:?}", formats = &caps.formats); return Err(ErrorKind::UnsupportedSurface.into()); - } + }; let size = inner.inner_size(); SurfaceConfiguration { @@ -153,6 +155,10 @@ impl View { }) } + fn format(&self) -> Format { + Format::from_wgpu(self.conf.format).expect("supported format") + } + pub fn id(&self) -> WindowId { self.inner.id() } @@ -172,7 +178,7 @@ impl View { Ok(Output { view, - format: Self::FORMAT, + format: self.format(), surface: output, }) } @@ -194,12 +200,8 @@ pub(crate) struct Output { } impl Output { - pub fn view(&self) -> &TextureView { - &self.view - } - - pub fn format(&self) -> Format { - self.format + pub fn render_view(&self) -> RenderView { + RenderView::new(&self.view, self.format) } pub fn present(self) { diff --git a/dunge/tests/triangle_group.rs b/dunge/tests/triangle_group.rs index b9808ff..2efa822 100644 --- a/dunge/tests/triangle_group.rs +++ b/dunge/tests/triangle_group.rs @@ -4,11 +4,12 @@ use { dunge::{ color::Rgba, draw, + format::Format, group::BoundTexture, mesh, sl::{self, Groups, Input, Out}, state::{Options, Render}, - texture::{self, Filter, Format, Sampler}, + texture::{self, Filter, Sampler}, Group, Vertex, }, glam::Vec2, diff --git a/dunge/tests/triangle_index.rs b/dunge/tests/triangle_index.rs index ea7835e..7ffc606 100644 --- a/dunge/tests/triangle_index.rs +++ b/dunge/tests/triangle_index.rs @@ -4,9 +4,10 @@ use { dunge::{ color::Rgba, draw, + format::Format, sl::{self, Index, Out}, state::{Options, Render}, - texture::{self, Format}, + texture, }, glam::Vec4, helpers::Image, diff --git a/dunge/tests/triangle_vertex.rs b/dunge/tests/triangle_vertex.rs index d1369b6..654bc99 100644 --- a/dunge/tests/triangle_vertex.rs +++ b/dunge/tests/triangle_vertex.rs @@ -3,11 +3,12 @@ use { dunge::{ color::Rgba, - draw, mesh, + draw, + format::Format, + mesh, sl::{self, Input, Out}, state::{Options, Render}, - texture::{self, Format}, - Vertex, + texture, Vertex, }, glam::{Vec2, Vec3}, helpers::Image, diff --git a/dunge_shader/Cargo.toml b/dunge_shader/Cargo.toml index 64a27d5..2fea5df 100644 --- a/dunge_shader/Cargo.toml +++ b/dunge_shader/Cargo.toml @@ -9,6 +9,7 @@ repository = "https://github.com/nanoqsh/dunge" [dependencies] glam = { workspace = true } +log = { workspace = true } naga = "0.14" [lints] diff --git a/dunge_shader/src/eval.rs b/dunge_shader/src/eval.rs index bc59701..21114cc 100644 --- a/dunge_shader/src/eval.rs +++ b/dunge_shader/src/eval.rs @@ -76,7 +76,7 @@ where let mut validator = Validator::new(ValidationFlags::all(), Capabilities::empty()); if let Err(err) = validator.validate(&nm) { - eprintln!("{nm:#?}"); + log::error!("{nm:#?}"); panic!("shader error: {err}\n{val:#?}", val = err.as_inner()); } } diff --git a/examples/triangle/Cargo.toml b/examples/triangle/Cargo.toml new file mode 100644 index 0000000..0fc3e4c --- /dev/null +++ b/examples/triangle/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "triangle" +version = "0.1.0" +edition = "2021" + +[dependencies] +dunge = { path = "../../dunge", features = ["winit"] } +env_logger = "0.10" +helpers = { path = "../../helpers" } + +[lints] +workspace = true diff --git a/examples/triangle/src/main.rs b/examples/triangle/src/main.rs new file mode 100644 index 0000000..b66aa8f --- /dev/null +++ b/examples/triangle/src/main.rs @@ -0,0 +1,54 @@ +type Error = Box; + +fn main() { + env_logger::init(); + if let Err(err) = run() { + eprintln!("error: {err}"); + } +} + +fn run() -> Result<(), Error> { + use { + dunge::{ + color::Rgba, + el::KeyCode, + glam::Vec4, + sl::{self, Index, Out}, + update, Control, Frame, + }, + std::f32::consts, + }; + + const COLOR: Vec4 = Vec4::new(1., 0., 0., 1.); + const THIRD: f32 = consts::TAU / 3.; + const R_OFFSET: f32 = -consts::TAU / 4.; + const Y_OFFSET: f32 = 0.25; + + let triangle = |Index(index): Index| { + let [x, y] = sl::thunk(sl::f32(index) * THIRD + R_OFFSET); + Out { + place: sl::vec4(sl::cos(x), sl::sin(y) + Y_OFFSET, 0., 1.), + color: COLOR, + } + }; + + let window = helpers::block_on(dunge::window().with_title("Triangle").make())?; + let cx = window.context(); + let shader = cx.make_shader(triangle); + let layer = cx.make_layer(window.format(), &shader); + let update = |ctrl: &Control| { + for key in ctrl.pressed_keys() { + if key.code == KeyCode::Escape { + ctrl.close(); + } + } + }; + + let draw = |mut frame: Frame| { + let clear = Rgba::from_standard([0., 0., 0., 1.]); + frame.layer(&layer, clear).bind_empty().draw_triangles(1); + }; + + window.run(update::from_fn(update, draw))?; + Ok(()) +}