Skip to content

Commit c588b24

Browse files
committed
Include all missing fixes: HDR tonemapping, shader optimizations, GPU FFT bug, and UI enhancements
1 parent e8d06a1 commit c588b24

23 files changed

Lines changed: 266 additions & 661 deletions

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/engine.rs

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ pub struct VideoParams {
127127
pub color_space: u32,
128128
pub color_range: u32,
129129
pub bit_depth: u32,
130-
pub _pad: u32,
130+
pub color_trc: u32,
131131
pub viewport_width: f32,
132132
pub viewport_height: f32,
133133
pub video_width: f32,
@@ -145,6 +145,7 @@ pub struct VideoState {
145145
pub color_space: u32,
146146
pub color_range: u32,
147147
pub bit_depth: u32,
148+
pub color_trc: u32,
148149
}
149150

150151
pub struct VulkanEngine<'a> {
@@ -440,6 +441,17 @@ impl<'a> VulkanEngine<'a> {
440441
immediate_size: 0,
441442
});
442443

444+
// Shared shader headers — included at compile time, resolved via simple string replacement.
445+
// This is the single source of truth for AudioUniforms layout and glyph font.
446+
const SHADER_COMMON: &str = include_str!("shaders/_common.wgsl");
447+
const SHADER_GLYPH_FONT: &str = include_str!("shaders/_glyph_font.wgsl");
448+
449+
let resolve_shader_includes = |source: &str| -> String {
450+
source
451+
.replace("// INCLUDE: common", SHADER_COMMON)
452+
.replace("// INCLUDE: glyph_font", SHADER_GLYPH_FONT)
453+
};
454+
443455
let get_shader_source = |id: u32| -> &'static str {
444456
match id {
445457
0 => include_str!("shaders/vis_spectrum.wgsl"),
@@ -457,14 +469,14 @@ impl<'a> VulkanEngine<'a> {
457469
}
458470
};
459471

460-
let shader_sources: Vec<&'static str> = crate::state::VISUALIZERS.iter().map(|v| get_shader_source(v.id)).collect();
472+
let shader_sources: Vec<String> = crate::state::VISUALIZERS.iter().map(|v| resolve_shader_includes(get_shader_source(v.id))).collect();
461473

462474
let mut render_pipelines = Vec::new();
463475

464476
let scope_fallback = device.push_error_scope(wgpu::ErrorFilter::Validation);
465477
let fallback_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
466478
label: Some("Fallback Shader"),
467-
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(shader_sources[0])),
479+
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(&shader_sources[0])),
468480
});
469481

470482
let fallback_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
@@ -511,7 +523,7 @@ impl<'a> VulkanEngine<'a> {
511523

512524
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
513525
label: Some(&format!("Shader {}", i)),
514-
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(source)),
526+
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(source.as_str())),
515527
});
516528

517529
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
@@ -657,7 +669,11 @@ impl<'a> VulkanEngine<'a> {
657669
cache: None,
658670
});
659671

660-
let hud_shader = device.create_shader_module(wgpu::include_wgsl!("shaders/hud.wgsl"));
672+
let hud_source = resolve_shader_includes(include_str!("shaders/hud.wgsl"));
673+
let hud_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
674+
label: Some("HUD Shader"),
675+
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(&hud_source)),
676+
});
661677
let hud_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
662678
label: Some("HUD Pipeline"),
663679
layout: Some(&render_pipeline_layout),
@@ -740,7 +756,11 @@ impl<'a> VulkanEngine<'a> {
740756
wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(&history_view) },
741757
],
742758
});
743-
let heatmap_compute_shader = device.create_shader_module(wgpu::include_wgsl!("shaders/heatmap_compute.wgsl"));
759+
let heatmap_source = resolve_shader_includes(include_str!("shaders/heatmap_compute.wgsl"));
760+
let heatmap_compute_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
761+
label: Some("Heatmap Compute Shader"),
762+
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(&heatmap_source)),
763+
});
744764
let heatmap_compute_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
745765
label: Some("heatmap_compute_layout"), bind_group_layouts: &[Some(&heatmap_compute_layout)], immediate_size: 0,
746766
});
@@ -826,7 +846,11 @@ impl<'a> VulkanEngine<'a> {
826846
],
827847
});
828848

829-
let ferrofluidsim_compute_shader = device.create_shader_module(wgpu::include_wgsl!("shaders/ferrofluidsim_compute.wgsl"));
849+
let ferrofluidsim_source = resolve_shader_includes(include_str!("shaders/ferrofluidsim_compute.wgsl"));
850+
let ferrofluidsim_compute_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
851+
label: Some("Ferrofluid Sim Compute Shader"),
852+
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(&ferrofluidsim_source)),
853+
});
830854
let ferrofluidsim_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
831855
label: Some("ferrofluidsim_layout"), bind_group_layouts: &[Some(&ferrofluidsim_compute_layout)], immediate_size: 0,
832856
});
@@ -1281,11 +1305,13 @@ impl<'a> VulkanEngine<'a> {
12811305
color_space: frame.color_space,
12821306
color_range: frame.color_range,
12831307
bit_depth: frame.bit_depth as u32,
1308+
color_trc: frame.color_trc,
12841309
});
12851310
} else if let Some(vs) = &mut self.video_state {
12861311
vs.color_space = frame.color_space;
12871312
vs.color_range = frame.color_range;
12881313
vs.bit_depth = frame.bit_depth as u32;
1314+
vs.color_trc = frame.color_trc;
12891315
}
12901316

12911317
if let Some(vs) = &self.video_state {
@@ -1312,7 +1338,7 @@ impl<'a> VulkanEngine<'a> {
13121338
color_space: frame.color_space,
13131339
color_range: frame.color_range,
13141340
bit_depth: frame.bit_depth as u32,
1315-
_pad: 0,
1341+
color_trc: frame.color_trc,
13161342
viewport_width: 1920.0,
13171343
viewport_height: 1080.0,
13181344
video_width: frame.width as f32,
@@ -2808,7 +2834,7 @@ impl<'a> VulkanEngine<'a> {
28082834
color_space: vs.color_space,
28092835
color_range: vs.color_range,
28102836
bit_depth: vs.bit_depth,
2811-
_pad: 0,
2837+
color_trc: vs.color_trc,
28122838
viewport_width: v_vp_w,
28132839
viewport_height: v_vp_h,
28142840
video_width: vs.width as f32,

src/main.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -506,8 +506,8 @@ async fn run_gui(app_state: Arc<Mutex<AppState>>, mut active_stream: Option<audi
506506
state.song_title = path.clone();
507507
active_stream = Some(stream);
508508
} else {
509-
app_state.lock().unwrap().file_loaded = false;
510509
let mut state = app_state.lock().unwrap();
510+
state.file_loaded = false;
511511
state.artist = "Load Failed".to_string();
512512
}
513513
}
@@ -1135,14 +1135,14 @@ where std::io::Error: From<<B as Backend>::Error>
11351135
let target = (state.current_seconds + 5.0).min(state.duration_seconds);
11361136
state.seek_request = Some(target);
11371137
state.spectrum_history.clear();
1138-
for _ in 0..120 { state.spectrum_history.push_back(vec![0.0; 128]); }
1138+
for _ in 0..120 { state.spectrum_history.push_back(vec![0.0; 1024]); }
11391139
}
11401140
KeyCode::Left => {
11411141
let mut state = app_state.lock().unwrap();
11421142
let target = (state.current_seconds - 5.0).max(0.0);
11431143
state.seek_request = Some(target);
11441144
state.spectrum_history.clear();
1145-
for _ in 0..120 { state.spectrum_history.push_back(vec![0.0; 128]); }
1145+
for _ in 0..120 { state.spectrum_history.push_back(vec![0.0; 1024]); }
11461146
}
11471147
_ => {}
11481148
}

src/shaders/_common.wgsl

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// =====================================================
2+
// RustTracker Shared Shader Header — AudioUniforms + Fullscreen Quad Vertex
3+
// This is the single source of truth for the AudioUniforms struct layout.
4+
// Any field changes MUST be made here — all shaders include this file.
5+
// =====================================================
6+
7+
struct VertexOutput {
8+
@builtin(position) clip_position: vec4<f32>,
9+
@location(0) uv: vec2<f32>,
10+
};
11+
12+
@vertex
13+
fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> VertexOutput {
14+
var out: VertexOutput;
15+
let u = f32((in_vertex_index << 1u) & 2u);
16+
let v = f32(in_vertex_index & 2u);
17+
out.clip_position = vec4<f32>(u * 2.0 - 1.0, -(v * 2.0 - 1.0), 0.0, 1.0);
18+
out.uv = vec2<f32>(u, v);
19+
return out;
20+
}
21+
22+
struct AudioUniforms {
23+
spectrum: array<vec4<f32>, 256>,
24+
fire_heat: array<vec4<f32>, 256>,
25+
channels: array<vec4<f32>, 8>,
26+
channel_peaks: array<vec4<f32>, 8>,
27+
spatial_channels: array<vec4<f32>, 4>,
28+
display_order: array<vec4<u32>, 4>,
29+
num_channels: u32,
30+
mode: u32,
31+
time: f32,
32+
duration: f32,
33+
smooth_time: f32,
34+
heatmap_row: u32,
35+
fft_channels: u32,
36+
num_spatial_channels: u32,
37+
ui_meters_rect: vec4<f32>,
38+
ui_heatmap_rect: vec4<f32>,
39+
ui_fire_rect: vec4<f32>,
40+
waveform_resolution: u32,
41+
waveform_history_size: u32,
42+
_pad0: u32,
43+
_pad1: u32,
44+
};

src/shaders/_glyph_font.wgsl

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// =====================================================
2+
// RustTracker Shared 3x5 Bitmap Font
3+
// Used by visualizers that render channel labels on the GPU.
4+
// =====================================================
5+
6+
fn glyph_bitmap(ch: u32) -> u32 {
7+
// 3x5 pixel font. 15 bits per glyph, MSB = top-left.
8+
// Index: 0-9=digits, 10=L, 11=R, 12=C, 13=S, 14=F, 15=E, 16=T, 17=M, 18=W
9+
switch ch {
10+
case 0u { return 31599u; } // 0
11+
case 1u { return 11415u; } // 1
12+
case 2u { return 29671u; } // 2
13+
case 3u { return 29647u; } // 3
14+
case 4u { return 23497u; } // 4
15+
case 5u { return 31183u; } // 5
16+
case 6u { return 31215u; } // 6
17+
case 7u { return 29330u; } // 7
18+
case 8u { return 31727u; } // 8
19+
case 9u { return 31695u; } // 9
20+
case 10u { return 18727u; } // L: #.. #.. #.. #.. ###
21+
case 11u { return 31733u; } // R: ### #.# ### ##. #.#
22+
case 12u { return 31015u; } // C: ### #.. #.. #.. ###
23+
case 13u { return 31183u; } // S (same as 5)
24+
case 14u { return 31204u; } // F: ### #.. ### #.. #..
25+
case 15u { return 31207u; } // E: ### #.. ### #.. ###
26+
case 16u { return 29842u; } // T: ### .#. .#. .#. .#.
27+
case 17u { return 24429u; } // M: #.# ### #.# #.# #.#
28+
case 18u { return 23421u; } // W: #.# #.# #.# ### #.#
29+
default { return 0u; } // space
30+
}
31+
}
32+
33+
fn draw_label_char(ch: u32, frag: vec2<f32>, origin: vec2<f32>, px: f32) -> f32 {
34+
let local = frag - origin;
35+
if local.x < 0.0 || local.x >= px * 3.0 || local.y < 0.0 || local.y >= px * 5.0 { return 0.0; }
36+
let col = u32(floor(local.x / px));
37+
let row = u32(floor(local.y / px));
38+
let bit = (4u - row) * 3u + (2u - col);
39+
return f32((glyph_bitmap(ch) >> bit) & 1u);
40+
}

src/shaders/ferrofluidsim_compute.wgsl

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,4 @@
1-
struct AudioUniforms {
2-
spectrum: array<vec4<f32>, 256>,
3-
fire_heat: array<vec4<f32>, 256>,
4-
channels: array<vec4<f32>, 8>,
5-
channel_peaks: array<vec4<f32>, 8>,
6-
spatial_channels: array<vec4<f32>, 4>,
7-
display_order: array<vec4<u32>, 4>,
8-
num_channels: u32,
9-
mode: u32,
10-
time: f32,
11-
duration: f32,
12-
smooth_time: f32,
13-
heatmap_row: u32,
14-
fft_channels: u32,
15-
num_spatial_channels: u32,
16-
ui_meters_rect: vec4<f32>,
17-
ui_heatmap_rect: vec4<f32>,
18-
ui_fire_rect: vec4<f32>,
19-
waveform_resolution: u32,
20-
waveform_history_size: u32,
21-
_pad0: u32,
22-
_pad1: u32,
23-
};
1+
// INCLUDE: common
242

253
@group(0) @binding(0)
264
var<uniform> audio: AudioUniforms;

src/shaders/gpu_fft.wgsl

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,22 +34,25 @@ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
3434

3535
let k = 2.0 * PI * freq / params.sample_rate;
3636

37-
// Direct log-spaced DFT
38-
let num_samples = params.num_samples;
39-
for (var n = 0u; n < num_samples; n = n + 1u) {
40-
let sample = raw_audio[ch_idx * num_samples + n];
37+
// Adaptive sample count: lower bins need full temporal resolution for
38+
// precise frequency discrimination; upper bins can use fewer samples
39+
// since treble frequencies resolve in shorter windows.
40+
let bin_scale = max(1u, (bin_idx + 64u) / 128u);
41+
let adaptive_samples = max(512u, params.num_samples / bin_scale);
42+
for (var n = 0u; n < adaptive_samples; n = n + 1u) {
43+
let sample = raw_audio[ch_idx * params.num_samples + n];
4144

42-
// Apply Hann window
43-
let window = 0.5 * (1.0 - cos(2.0 * PI * f32(n) / f32(num_samples - 1u)));
45+
// Apply Hann window (scaled to adaptive length)
46+
let window = 0.5 * (1.0 - cos(2.0 * PI * f32(n) / f32(adaptive_samples - 1u)));
4447
let w_sample = sample * window;
4548

4649
let phase = k * f32(n);
4750
re += w_sample * cos(phase);
4851
im -= w_sample * sin(phase);
4952
}
5053

51-
let norm_re = re / sqrt(f32(num_samples));
52-
let norm_im = im / sqrt(f32(num_samples));
54+
let norm_re = re / sqrt(f32(adaptive_samples));
55+
let norm_im = im / sqrt(f32(adaptive_samples));
5356

5457
// Write out normalized complex values
5558
spectrum[ch_idx * NUM_BINS + bin_idx] = vec2<f32>(norm_re, norm_im);

src/shaders/heatmap_compute.wgsl

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,4 @@
1-
struct AudioUniforms {
2-
spectrum: array<vec4<f32>, 256>,
3-
fire_heat: array<vec4<f32>, 256>,
4-
channels: array<vec4<f32>, 8>,
5-
channel_peaks: array<vec4<f32>, 8>,
6-
spatial_channels: array<vec4<f32>, 4>,
7-
display_order: array<vec4<u32>, 4>,
8-
num_channels: u32,
9-
mode: u32,
10-
time: f32,
11-
duration: f32,
12-
smooth_time: f32,
13-
heatmap_row: u32,
14-
fft_channels: u32,
15-
num_spatial_channels: u32,
16-
ui_meters_rect: vec4<f32>,
17-
ui_heatmap_rect: vec4<f32>,
18-
ui_fire_rect: vec4<f32>,
19-
};
1+
// INCLUDE: common
202

213
@group(0) @binding(0) var<uniform> uniforms: AudioUniforms;
224
@group(0) @binding(1) var heatmap_tex: texture_storage_2d<r32float, write>;

src/shaders/hud.wgsl

Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,4 @@
1-
struct VertexOutput {
2-
@builtin(position) clip_position: vec4<f32>,
3-
@location(0) uv: vec2<f32>,
4-
};
5-
6-
@vertex
7-
fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> VertexOutput {
8-
var out: VertexOutput;
9-
let u = f32((in_vertex_index << 1u) & 2u);
10-
let v = f32(in_vertex_index & 2u);
11-
out.clip_position = vec4<f32>(u * 2.0 - 1.0, -(v * 2.0 - 1.0), 0.0, 1.0);
12-
out.uv = vec2<f32>(u, v);
13-
return out;
14-
}
15-
16-
struct AudioUniforms {
17-
spectrum: array<vec4<f32>, 256>,
18-
fire_heat: array<vec4<f32>, 256>,
19-
channels: array<vec4<f32>, 8>,
20-
channel_peaks: array<vec4<f32>, 8>,
21-
spatial_channels: array<vec4<f32>, 4>,
22-
display_order: array<vec4<u32>, 4>,
23-
num_channels: u32,
24-
mode: u32,
25-
time: f32,
26-
duration: f32,
27-
smooth_time: f32,
28-
heatmap_row: u32,
29-
fft_channels: u32,
30-
num_spatial_channels: u32,
31-
ui_meters_rect: vec4<f32>,
32-
ui_heatmap_rect: vec4<f32>,
33-
ui_fire_rect: vec4<f32>,
34-
};
1+
// INCLUDE: common
352

363
@group(0) @binding(0) var<uniform> uniforms: AudioUniforms;
374
@group(0) @binding(2) var heatmap_tex: texture_2d<f32>;

0 commit comments

Comments
 (0)