From a8b86634be3534cfcff987c94e3c145c8116d3f5 Mon Sep 17 00:00:00 2001 From: PolyMeilex Date: Sat, 16 Dec 2023 19:24:52 +0100 Subject: [PATCH] Experiment with glow --- .../src/render/glow/instance_data.rs | 36 +++++++ neothesia-core/src/render/glow/mod.rs | 96 +++++++++++++++++++ neothesia-core/src/render/glow/shader.wgsl | 46 +++++++++ .../src/render/keyboard/key_state.rs | 2 +- neothesia-core/src/render/keyboard/mod.rs | 4 + neothesia-core/src/render/mod.rs | 2 + neothesia/src/scene/playing_scene/keyboard.rs | 13 ++- neothesia/src/scene/playing_scene/mod.rs | 48 +++++++++- 8 files changed, 243 insertions(+), 4 deletions(-) create mode 100644 neothesia-core/src/render/glow/instance_data.rs create mode 100644 neothesia-core/src/render/glow/mod.rs create mode 100644 neothesia-core/src/render/glow/shader.wgsl diff --git a/neothesia-core/src/render/glow/instance_data.rs b/neothesia-core/src/render/glow/instance_data.rs new file mode 100644 index 00000000..374d4be4 --- /dev/null +++ b/neothesia-core/src/render/glow/instance_data.rs @@ -0,0 +1,36 @@ +use wgpu::vertex_attr_array; +use wgpu_jumpstart::wgpu; + +use bytemuck::{Pod, Zeroable}; + +#[repr(C)] +#[derive(Debug, Copy, Clone, Pod, Zeroable, PartialEq)] +pub struct GlowInstance { + pub position: [f32; 2], + pub size: [f32; 2], + pub color: [f32; 4], +} + +impl Default for GlowInstance { + fn default() -> Self { + Self { + position: [0.0, 0.0], + size: [0.0, 0.0], + color: [0.0, 0.0, 0.0, 1.0], + } + } +} + +impl GlowInstance { + pub fn attributes() -> [wgpu::VertexAttribute; 4] { + vertex_attr_array!(1 => Float32x2, 2 => Float32x2, 3 => Float32x4, 4 => Float32x4) + } + + pub fn layout(attributes: &[wgpu::VertexAttribute]) -> wgpu::VertexBufferLayout { + wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Instance, + attributes, + } + } +} diff --git a/neothesia-core/src/render/glow/mod.rs b/neothesia-core/src/render/glow/mod.rs new file mode 100644 index 00000000..61dba93e --- /dev/null +++ b/neothesia-core/src/render/glow/mod.rs @@ -0,0 +1,96 @@ +mod instance_data; +pub use instance_data::GlowInstance; + +use wgpu_jumpstart::{ + wgpu, Gpu, Instances, RenderPipelineBuilder, Shape, TransformUniform, Uniform, +}; + +pub struct GlowPipeline { + render_pipeline: wgpu::RenderPipeline, + quad: Shape, + instances: Instances, +} + +impl<'a> GlowPipeline { + pub fn new(gpu: &Gpu, transform_uniform: &Uniform) -> Self { + let shader = gpu + .device + .create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("RectanglePipeline::shader"), + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!( + "./shader.wgsl" + ))), + }); + + let render_pipeline_layout = + gpu.device + .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[&transform_uniform.bind_group_layout], + push_constant_ranges: &[], + }); + + let ri_attrs = GlowInstance::attributes(); + + let target = wgpu_jumpstart::default_color_target_state(gpu.texture_format); + + let render_pipeline = + RenderPipelineBuilder::new(&render_pipeline_layout, "vs_main", &shader) + .fragment("fs_main", &shader, &[Some(target)]) + .vertex_buffers(&[Shape::layout(), GlowInstance::layout(&ri_attrs)]) + .build(&gpu.device); + + let quad = Shape::new_quad(&gpu.device); + let instances = Instances::new(&gpu.device, 100_000); + + Self { + render_pipeline, + + quad, + + instances, + } + } + + pub fn render( + &'a self, + transform_uniform: &'a Uniform, + render_pass: &mut wgpu::RenderPass<'a>, + ) { + render_pass.set_pipeline(&self.render_pipeline); + render_pass.set_bind_group(0, &transform_uniform.bind_group, &[]); + + render_pass.set_vertex_buffer(0, self.quad.vertex_buffer.slice(..)); + render_pass.set_vertex_buffer(1, self.instances.buffer.slice(..)); + + render_pass.set_index_buffer(self.quad.index_buffer.slice(..), wgpu::IndexFormat::Uint16); + + render_pass.draw_indexed(0..self.quad.indices_len, 0, 0..self.instances.len()); + } + + pub fn clear(&mut self) { + self.instances.data.clear(); + } + + pub fn instances(&mut self) -> &mut Vec { + &mut self.instances.data + } + + pub fn prepare(&self, queue: &wgpu::Queue) { + self.instances.update(queue); + } + + pub fn update_instance_buffer(&mut self, queue: &wgpu::Queue, instances: Vec) { + self.instances.data = instances; + self.instances.update(queue); + } + + pub fn with_instances_mut)>( + &mut self, + queue: &wgpu::Queue, + cb: F, + ) { + cb(&mut self.instances.data); + self.instances.update(queue); + } +} diff --git a/neothesia-core/src/render/glow/shader.wgsl b/neothesia-core/src/render/glow/shader.wgsl new file mode 100644 index 00000000..c644f330 --- /dev/null +++ b/neothesia-core/src/render/glow/shader.wgsl @@ -0,0 +1,46 @@ +struct ViewUniform { + transform: mat4x4, + size: vec2, +} + +@group(0) @binding(0) +var view_uniform: ViewUniform; + +struct Vertex { + @location(0) position: vec2, +} + +struct QuadInstance { + @location(1) q_position: vec2, + @location(2) size: vec2, + @location(3) color: vec4, +} + +struct VertexOutput { + @builtin(position) position: vec4, + @location(0) uv: vec2, + @location(1) quad_color: vec4, +} + +@vertex +fn vs_main(vertex: Vertex, quad: QuadInstance) -> VertexOutput { + var i_transform: mat4x4 = mat4x4( + vec4(quad.size.x, 0.0, 0.0, 0.0), + vec4(0.0, quad.size.y, 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(quad.q_position, 0.0, 1.0) + ); + + var out: VertexOutput; + out.position = view_uniform.transform * i_transform * vec4(vertex.position, 0.0, 1.0); + out.uv = vertex.position; + out.quad_color = quad.color; + return out; +} + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + let m = distance(in.uv, vec2(0.5, 0.5)) * 2.0; + return mix(in.quad_color, vec4(0.0), m); + +} diff --git a/neothesia-core/src/render/keyboard/key_state.rs b/neothesia-core/src/render/keyboard/key_state.rs index 300d68f1..0b6f8002 100644 --- a/neothesia-core/src/render/keyboard/key_state.rs +++ b/neothesia-core/src/render/keyboard/key_state.rs @@ -9,7 +9,7 @@ use wgpu_jumpstart::Color; pub struct KeyState { is_sharp: bool, - pressed_by_file: Option, + pub pressed_by_file: Option, pressed_by_user: bool, } diff --git a/neothesia-core/src/render/keyboard/mod.rs b/neothesia-core/src/render/keyboard/mod.rs index e336db11..dc8b1f16 100644 --- a/neothesia-core/src/render/keyboard/mod.rs +++ b/neothesia-core/src/render/keyboard/mod.rs @@ -53,6 +53,10 @@ impl KeyboardRenderer { &self.layout.range } + pub fn key_states(&self) -> &[KeyState] { + &self.key_states + } + pub fn key_states_mut(&mut self) -> &mut [KeyState] { &mut self.key_states } diff --git a/neothesia-core/src/render/mod.rs b/neothesia-core/src/render/mod.rs index 9f38b210..35df6d8a 100644 --- a/neothesia-core/src/render/mod.rs +++ b/neothesia-core/src/render/mod.rs @@ -1,10 +1,12 @@ mod background_animation; +mod glow; mod keyboard; mod quad; mod text; mod waterfall; pub use background_animation::BgPipeline; +pub use glow::{GlowInstance, GlowPipeline}; pub use keyboard::{KeyState as KeyboardKeyState, KeyboardRenderer}; pub use quad::{QuadInstance, QuadPipeline}; pub use text::TextRenderer; diff --git a/neothesia/src/scene/playing_scene/keyboard.rs b/neothesia/src/scene/playing_scene/keyboard.rs index d984572f..9634f399 100644 --- a/neothesia/src/scene/playing_scene/keyboard.rs +++ b/neothesia/src/scene/playing_scene/keyboard.rs @@ -1,5 +1,8 @@ use midi_file::midly::MidiMessage; -use neothesia_core::render::{QuadPipeline, TextRenderer}; +use neothesia_core::{ + render::{KeyboardKeyState, QuadPipeline, TextRenderer}, + utils::Point, +}; use piano_math::KeyboardRange; use crate::{config::Config, render::KeyboardRenderer, song::SongConfig, target::Target}; @@ -34,6 +37,14 @@ impl Keyboard { } } + pub fn pos(&self) -> &Point { + self.renderer.pos() + } + + pub fn key_states(&self) -> &[KeyboardKeyState] { + self.renderer.key_states() + } + pub fn layout(&self) -> &piano_math::KeyboardLayout { self.renderer.layout() } diff --git a/neothesia/src/scene/playing_scene/mod.rs b/neothesia/src/scene/playing_scene/mod.rs index e6b3cbf5..2aaa15ef 100644 --- a/neothesia/src/scene/playing_scene/mod.rs +++ b/neothesia/src/scene/playing_scene/mod.rs @@ -1,5 +1,5 @@ use midi_file::midly::MidiMessage; -use neothesia_core::render::{QuadInstance, QuadPipeline}; +use neothesia_core::render::{GlowInstance, GlowPipeline, QuadInstance, QuadPipeline}; use std::time::Duration; use wgpu_jumpstart::{Color, TransformUniform, Uniform}; use winit::event::{KeyboardInput, WindowEvent}; @@ -22,6 +22,10 @@ use rewind_controller::RewindController; mod toast_manager; use toast_manager::ToastManager; +struct GlowState { + time: f32, +} + pub struct PlayingScene { keyboard: Keyboard, notes: WaterfallRenderer, @@ -29,6 +33,8 @@ pub struct PlayingScene { player: MidiPlayer, rewind_controler: RewindController, quad_pipeline: QuadPipeline, + glow_pipeline: GlowPipeline, + glow_states: Vec, toast_manager: ToastManager, } @@ -58,6 +64,13 @@ impl PlayingScene { let player = MidiPlayer::new(target, song, keyboard_layout.range.clone()); notes.update(&target.gpu.queue, player.time_without_lead_in()); + let glow_states: Vec = keyboard + .layout() + .range + .iter() + .map(|_| GlowState { time: 0.0 }) + .collect(); + Self { keyboard, @@ -65,6 +78,8 @@ impl PlayingScene { player, rewind_controler: RewindController::new(), quad_pipeline: QuadPipeline::new(&target.gpu, &target.transform), + glow_pipeline: GlowPipeline::new(&target.gpu, &target.transform), + glow_states, toast_manager: ToastManager::default(), } } @@ -78,6 +93,32 @@ impl PlayingScene { ..Default::default() }); } + + fn update_glow(&mut self) { + self.glow_pipeline.clear(); + + let key_states = self.keyboard.key_states(); + for key in self.keyboard.layout().keys.iter() { + let glow_state = &mut self.glow_states[key.id()]; + let glow_w = 200.0 + glow_state.time.sin() * 50.0; + let glow_h = 200.0 + glow_state.time.sin() * 50.0; + + let y = self.keyboard.pos().y; + if let Some(color) = &key_states[key.id()].pressed_by_file { + glow_state.time += 0.1; + let mut color = color.into_linear_rgba(); + color[0] += 0.1; + color[1] += 0.1; + color[2] += 0.1; + color[3] = 0.3; + self.glow_pipeline.instances().push(GlowInstance { + position: [key.x() - glow_w / 2.0 + key.width() / 2.0, y - glow_w / 2.0], + size: [glow_w, glow_h], + color, + }); + } + } + } } impl Scene for PlayingScene { @@ -111,8 +152,10 @@ impl Scene for PlayingScene { .update(&mut self.quad_pipeline, &mut target.text_renderer); self.update_progresbar(&target.window_state); + self.update_glow(); self.quad_pipeline.prepare(&target.gpu.queue); + self.glow_pipeline.prepare(&target.gpu.queue); } fn render<'pass>( @@ -121,7 +164,8 @@ impl Scene for PlayingScene { rpass: &mut wgpu::RenderPass<'pass>, ) { self.notes.render(transform, rpass); - self.quad_pipeline.render(transform, rpass) + self.quad_pipeline.render(transform, rpass); + self.glow_pipeline.render(transform, rpass); } fn window_event(&mut self, target: &mut Target, event: &WindowEvent) {