diff --git a/Cargo.lock b/Cargo.lock index 25f73db..e1ed616 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2590,10 +2590,12 @@ name = "shady" version = "0.1.0" dependencies = [ "bytemuck", + "pollster", "shady-audio", "thiserror 2.0.8", "tracing", "wgpu", + "winit", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 2c02eee..5d39118 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,9 @@ wgpu = { version = "22.0", default-features = false, features = [ tracing = "0.1" thiserror = "2" +pollster = "0.4.0" +winit = "0.30" + cpal = "0.15" realfft = "3.4" splines = "4" diff --git a/README.md b/README.md index c73bf23..ac47b16 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,14 @@ For example on my system it looks like this (after starting [pavucontrol]): Here are some other sources/similar projects if you're interested: -- https://github.com/phip1611/spectrum-analyzer -- https://github.com/BrunoWallner/crav -- https://www.youtube.com/watch?v=Xdbk1Pr5WXU&list=PLpM-Dvs8t0Vak1rrE2NJn8XYEJ5M7-BqT +- Other music visualizers: + - https://github.com/phip1611/spectrum-analyzer + - https://github.com/BrunoWallner/crav + - https://github.com/karlstav/cava +- Tscoding implementing [musializer] https://www.youtube.com/watch?v=Xdbk1Pr5WXU&list=PLpM-Dvs8t0Vak1rrE2NJn8XYEJ5M7-BqT +- WGPU tutorials: + - https://sotrh.github.io/learn-wgpu/ + - https://webgpufundamentals.org/ [shadertoy]: https://www.shadertoy.com/ [pavucontrol]: https://github.com/pulseaudio/pavucontrol @@ -33,3 +38,4 @@ Here are some other sources/similar projects if you're interested: [shady-cli]: https://github.com/TornaxO7/shady/tree/main/shady-cli [glsl]: https://www.khronos.org/opengl/wiki/Core_Language_(GLSL) [wgsl]: https://www.w3.org/TR/WGSL/ +[musializer]: https://github.com/tsoding/musializer diff --git a/shady-lib/Cargo.toml b/shady-lib/Cargo.toml index 4c963b3..c07adfa 100644 --- a/shady-lib/Cargo.toml +++ b/shady-lib/Cargo.toml @@ -15,6 +15,10 @@ wgpu.workspace = true tracing.workspace = true thiserror.workspace = true +[dev-dependencies] +winit.workspace = true +pollster.workspace = true + [features] default = ["time", "resolution", "audio", "mouse", "frame"] diff --git a/shady-lib/README.md b/shady-lib/README.md new file mode 100644 index 0000000..cd9bf72 --- /dev/null +++ b/shady-lib/README.md @@ -0,0 +1,22 @@ +# `shady-lib` + +The main library which takes care of the uniform/storage buffers, vertices and templates. + +The idea is that other applications who wish to include [shadertoy]-like shaders into their +application to use this library which takes care most of the data to be able to run those shaders. + +# State + +It's useable, however I'm a bit unsure about the architecture because I don't really know what +a good API looks like for a graphics-programmer. It may sound stupid, but the reason why I didn't +publish this yet, is: + +1. It's very likely that breaking changes are going to appear ... +2. ... hence there aren't any docs yet. + +However, I hope that this will change after some time. + +Nevertheless, there's a "simple" example (I tried to create a very small example) in the `examples` directory if you want +to include it to your app. All relevant places, where you have to "interact" with shady are annoted with the `// SHADY` comments. + +[shadertoy]: https://www.youtube.com/watch?v=Xdbk1Pr5WXU&list=PLpM-Dvs8t0Vak1rrE2NJn8XYEJ5M7-BqT diff --git a/shady-lib/examples/mini-simple.rs b/shady-lib/examples/mini-simple.rs new file mode 100644 index 0000000..f441774 --- /dev/null +++ b/shady-lib/examples/mini-simple.rs @@ -0,0 +1,199 @@ +/// Every relevant part is marked with the prefix `SHADY` so you can just search in this code with `SHADY`. +use std::sync::Arc; + +use pollster::FutureExt; +use shady::{Shady, ShadyDescriptor, Wgsl}; +use wgpu::{ + Backends, Device, Instance, Queue, Surface, SurfaceConfiguration, TextureViewDescriptor, +}; +use winit::{ + application::ApplicationHandler, + dpi::PhysicalSize, + event::WindowEvent, + event_loop::{ActiveEventLoop, EventLoop}, + window::{Window, WindowAttributes, WindowId}, +}; + +const SHADY_BIND_GROUP_INDEX: u32 = 0; +const SHADY_VERTEX_BUFFER_INDEX: u32 = 0; + +struct State<'a> { + surface: Surface<'a>, + device: Device, + queue: Queue, + config: SurfaceConfiguration, + window: Arc, + // SHADY + shady: Shady, +} + +impl<'a> State<'a> { + fn new(window: Window) -> Self { + let window = Arc::new(window); + + let instance = Instance::new(wgpu::InstanceDescriptor { + backends: Backends::PRIMARY, + ..Default::default() + }); + + let surface = instance.create_surface(window.clone()).unwrap(); + + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + compatible_surface: Some(&surface), + ..Default::default() + }) + .block_on() + .unwrap(); + + let (device, queue) = adapter + .request_device(&wgpu::DeviceDescriptor::default(), None) + .block_on() + .unwrap(); + + let (config, shady) = { + let surface_caps = surface.get_capabilities(&adapter); + let surface_format = surface_caps + .formats + .iter() + .find(|f| f.is_srgb()) + .copied() + .unwrap_or(surface_caps.formats[0]); + + let size = window.clone().inner_size(); + + let config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: surface_format, + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::AutoVsync, + alpha_mode: surface_caps.alpha_modes[0], + view_formats: vec![], + desired_maximum_frame_latency: 2, + }; + + // SHADY + let fragment_shader = { + let mut fragment_code = String::new(); + shady::get_template( + shady::TemplateLang::Wgsl { + bind_group_index: 0, + }, + &mut fragment_code, + ) + .unwrap(); + fragment_code + }; + + // SHADY + let shady = Shady::new(&ShadyDescriptor { + device: &device, + fragment_shader: &fragment_shader, + texture_format: surface_format, + bind_group_index: SHADY_BIND_GROUP_INDEX, + vertex_buffer_index: SHADY_VERTEX_BUFFER_INDEX, + }) + .unwrap(); + + (config, shady) + }; + + Self { + surface, + device, + queue, + config, + window, + shady, + } + } + + pub fn prepare_next_frame(&mut self) { + // SHADY + self.shady.prepare_next_frame(&mut self.queue); + + self.surface.configure(&self.device, &self.config); + } + + pub fn render(&mut self) { + let output = self.surface.get_current_texture().unwrap(); + let view = output + .texture + .create_view(&TextureViewDescriptor::default()); + + let mut encoder = self + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Render encoder"), + }); + + self.shady.add_render_pass(&mut encoder, &view); + + self.queue.submit(std::iter::once(encoder.finish())); + output.present(); + } + + pub fn window(&self) -> Arc { + self.window.clone() + } + + pub fn resize(&mut self, new_size: PhysicalSize) { + // SHADY + self.shady + .update_resolution(new_size.width, new_size.height); + } +} + +struct App<'a> { + state: Option>, +} + +impl<'a> App<'a> { + pub fn new() -> Self { + Self { state: None } + } +} + +impl<'a> ApplicationHandler<()> for App<'a> { + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + let window = event_loop + .create_window(WindowAttributes::default()) + .unwrap(); + + self.state = Some(State::new(window)); + } + + fn window_event( + &mut self, + event_loop: &ActiveEventLoop, + _window_id: WindowId, + event: WindowEvent, + ) { + let Some(state) = &mut self.state else { return }; + let window = state.window(); + + match event { + WindowEvent::CloseRequested => event_loop.exit(), + WindowEvent::RedrawRequested => { + window.request_redraw(); + state.prepare_next_frame(); + state.render(); + } + WindowEvent::Resized(new_size) => state.resize(new_size), + WindowEvent::KeyboardInput { event, .. } + if event.logical_key.to_text() == Some("q") => + { + event_loop.exit(); + } + _ => (), + } + } +} + +fn main() { + let event_loop = EventLoop::new().unwrap(); + let mut app = App::new(); + + event_loop.run_app(&mut app).unwrap(); +} diff --git a/shady-toy/Cargo.toml b/shady-toy/Cargo.toml index 6ff13ae..640d49b 100644 --- a/shady-toy/Cargo.toml +++ b/shady-toy/Cargo.toml @@ -10,15 +10,15 @@ license = "GPL-3.0-or-later" anyhow = "1" ariadne = "0.5" tracing-subscriber = { version = "0.3", features = ["env-filter"] } -pollster = "0.4.0" notify = "7" shady = { path = "../shady-lib" } -winit = "0.30" +winit.workspace = true wgpu.workspace = true tracing.workspace = true thiserror.workspace = true clap.workspace = true +pollster.workspace = true [dev-dependencies] image = "0.25"