Skip to content

Commit

Permalink
Experiments with key glow (#111)
Browse files Browse the repository at this point in the history
  • Loading branch information
PolyMeilex authored Nov 15, 2024
1 parent 8b709b1 commit c57ec6a
Show file tree
Hide file tree
Showing 8 changed files with 250 additions and 2 deletions.
36 changes: 36 additions & 0 deletions neothesia-core/src/render/glow/instance_data.rs
Original file line number Diff line number Diff line change
@@ -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::<GlowInstance>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Instance,
attributes,
}
}
}
105 changes: 105 additions & 0 deletions neothesia-core/src/render/glow/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
mod instance_data;
pub use instance_data::GlowInstance;

use wgpu_jumpstart::{wgpu, Gpu, Instances, Shape, TransformUniform, Uniform};

pub struct GlowPipeline {
render_pipeline: wgpu::RenderPipeline,
quad: Shape,
instances: Instances<GlowInstance>,
}

impl<'a> GlowPipeline {
pub fn new(gpu: &Gpu, transform_uniform: &Uniform<TransformUniform>) -> 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 = gpu
.device
.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
layout: Some(&render_pipeline_layout),
fragment: Some(wgpu_jumpstart::default_fragment(&shader, &[Some(target)])),
..wgpu_jumpstart::default_render_pipeline(wgpu_jumpstart::default_vertex(
&shader,
&[Shape::layout(), GlowInstance::layout(&ri_attrs)],
))
});

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<TransformUniform>,
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<GlowInstance> {
&mut self.instances.data
}

pub fn prepare(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) {
self.instances.update(device, queue);
}

pub fn update_instance_buffer(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
instances: Vec<GlowInstance>,
) {
self.instances.data = instances;
self.instances.update(device, queue);
}

pub fn with_instances_mut<F: FnOnce(&mut Vec<GlowInstance>)>(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
cb: F,
) {
cb(&mut self.instances.data);
self.instances.update(device, queue);
}
}
46 changes: 46 additions & 0 deletions neothesia-core/src/render/glow/shader.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
struct ViewUniform {
transform: mat4x4<f32>,
size: vec2<f32>,
}

@group(0) @binding(0)
var<uniform> view_uniform: ViewUniform;

struct Vertex {
@location(0) position: vec2<f32>,
}

struct QuadInstance {
@location(1) q_position: vec2<f32>,
@location(2) size: vec2<f32>,
@location(3) color: vec4<f32>,
}

struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) uv: vec2<f32>,
@location(1) quad_color: vec4<f32>,
}

@vertex
fn vs_main(vertex: Vertex, quad: QuadInstance) -> VertexOutput {
var i_transform: mat4x4<f32> = mat4x4<f32>(
vec4<f32>(quad.size.x, 0.0, 0.0, 0.0),
vec4<f32>(0.0, quad.size.y, 0.0, 0.0),
vec4<f32>(0.0, 0.0, 1.0, 0.0),
vec4<f32>(quad.q_position, 0.0, 1.0)
);

var out: VertexOutput;
out.position = view_uniform.transform * i_transform * vec4<f32>(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<f32> {
let m = distance(in.uv, vec2(0.5, 0.5)) * 2.0;
return mix(in.quad_color, vec4<f32>(0.0), m);

}
4 changes: 4 additions & 0 deletions neothesia-core/src/render/keyboard/key_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ impl KeyState {
}
}

pub fn pressed_by_file(&self) -> Option<&Color> {
self.pressed_by_file.as_ref()
}

pub fn set_pressed_by_user(&mut self, is: bool) {
self.pressed_by_user = is;
}
Expand Down
4 changes: 4 additions & 0 deletions neothesia-core/src/render/keyboard/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
2 changes: 2 additions & 0 deletions neothesia-core/src/render/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
mod background_animation;
mod guidelines;
mod glow;
mod keyboard;
mod quad;
mod text;
mod waterfall;

pub use background_animation::BgPipeline;
pub use guidelines::GuidelineRenderer;
pub use glow::{GlowInstance, GlowPipeline};
pub use keyboard::{KeyState as KeyboardKeyState, KeyboardRenderer};
pub use quad::{QuadInstance, QuadPipeline};
pub use text::TextRenderer;
Expand Down
6 changes: 5 additions & 1 deletion neothesia/src/scene/playing_scene/keyboard.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use midi_file::midly::MidiMessage;
use neothesia_core::{
piano_layout,
render::{QuadPipeline, TextRenderer},
render::{KeyboardKeyState, QuadPipeline, TextRenderer},
utils::Point,
};
use piano_layout::KeyboardRange;
Expand Down Expand Up @@ -42,6 +42,10 @@ impl Keyboard {
}
}

pub fn key_states(&self) -> &[KeyboardKeyState] {
self.renderer.key_states()
}

pub fn layout(&self) -> &piano_layout::KeyboardLayout {
self.renderer.layout()
}
Expand Down
49 changes: 48 additions & 1 deletion neothesia/src/scene/playing_scene/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use midi_file::midly::MidiMessage;
use neothesia_core::render::{GuidelineRenderer, QuadPipeline};
use neothesia_core::render::{GlowInstance, GlowPipeline, GuidelineRenderer, QuadPipeline};
use std::time::Duration;
use wgpu_jumpstart::{TransformUniform, Uniform};
use winit::{
Expand Down Expand Up @@ -33,6 +33,10 @@ const EVENT_IGNORED: bool = false;
const LAYER_BG: usize = 0;
const LAYER_FG: usize = 1;

struct GlowState {
time: f32,
}

pub struct PlayingScene {
keyboard: Keyboard,
waterfall: WaterfallRenderer,
Expand All @@ -41,6 +45,8 @@ pub struct PlayingScene {
player: MidiPlayer,
rewind_controller: RewindController,
quad_pipeline: QuadPipeline,
glow_pipeline: GlowPipeline,
glow_states: Vec<GlowState>,
toast_manager: ToastManager,

top_bar: TopBar,
Expand Down Expand Up @@ -88,6 +94,13 @@ impl PlayingScene {
quad_pipeline.init_layer(&ctx.gpu, 50); // BG
quad_pipeline.init_layer(&ctx.gpu, 150); // FG

let glow_states: Vec<GlowState> = keyboard
.layout()
.range
.iter()
.map(|_| GlowState { time: 0.0 })
.collect();

Self {
keyboard,
guidelines,
Expand All @@ -96,11 +109,41 @@ impl PlayingScene {
player,
rewind_controller: RewindController::new(),
quad_pipeline,
glow_pipeline: GlowPipeline::new(&ctx.gpu, &ctx.transform),
glow_states,
toast_manager: ToastManager::default(),
top_bar: TopBar::new(),
}
}

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 = 150.0 + glow_state.time.sin() * 10.0;
let glow_h = 150.0 + glow_state.time.sin() * 10.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();
let v = 0.2 * glow_state.time.cos().abs();
let v = v.min(1.0);
color[0] += v;
color[1] += v;
color[2] += v;
color[3] = 0.2;
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,
});
}
}
}

#[profiling::function]
fn update_midi_player(&mut self, ctx: &Context, delta: Duration) -> f32 {
if self.top_bar.looper.is_active()
Expand Down Expand Up @@ -156,7 +199,10 @@ impl Scene for PlayingScene {

TopBar::update(self, ctx);

self.update_glow();

self.quad_pipeline.prepare(&ctx.gpu.device, &ctx.gpu.queue);
self.glow_pipeline.prepare(&ctx.gpu.device, &ctx.gpu.queue);

if self.player.is_finished() && !self.player.is_paused() {
ctx.proxy
Expand All @@ -174,6 +220,7 @@ impl Scene for PlayingScene {
self.quad_pipeline.render(LAYER_BG, transform, rpass);
self.waterfall.render(transform, rpass);
self.quad_pipeline.render(LAYER_FG, transform, rpass);
self.glow_pipeline.render(transform, rpass);
}

fn window_event(&mut self, ctx: &mut Context, event: &WindowEvent) {
Expand Down

0 comments on commit c57ec6a

Please sign in to comment.