Skip to content

Commit 4f75314

Browse files
committed
Release v0.9.10: Aspect ratio scaling fixes
1 parent bd3d0ad commit 4f75314

13 files changed

Lines changed: 132 additions & 71 deletions

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rusttracker"
3-
version = "0.9.9"
3+
version = "0.9.10"
44
edition = "2024"
55
default-run = "rusttracker"
66
description = "High-Performance Vulkan Tracker Module Visualizer"

src/engine.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ pub struct AudioUniforms {
9595
pub frame_count: u32,
9696
pub step_fraction: f32,
9797
pub steps_to_fill: u32,
98-
pub padding: [u32; 3],
98+
pub aspect_ratio: f32,
99+
pub padding: [u32; 2],
99100
}
100101

101102

@@ -2189,7 +2190,8 @@ impl VulkanEngine {
21892190
frame_count: self.frame_count,
21902191
step_fraction,
21912192
steps_to_fill: steps,
2192-
padding: [0; 3],
2193+
aspect_ratio: self.size.width as f32 / self.size.height as f32,
2194+
padding: [0; 2],
21932195
};
21942196

21952197
uniforms.spectrum.copy_from_slice(&state.spectrum_data);
@@ -4313,8 +4315,13 @@ impl VulkanEngine {
43134315

43144316
// --- 3D Engine Camera Math ---
43154317
let aspect = vp_w / vp_h.max(1.0);
4318+
4319+
// Update aspect_ratio uniform dynamically based on the current viewport
4320+
self.queue.write_buffer(&self.uniform_buffer, 8676, bytemuck::cast_slice(&[aspect as f32]));
43164321

4317-
let proj = glam::Mat4::perspective_rh_gl(std::f32::consts::PI / 3.0, aspect, 0.1, 1000.0);
4322+
let fov_x = std::f32::consts::PI / 2.0;
4323+
let fov_y = 2.0 * ((fov_x / 2.0).tan() / aspect).atan();
4324+
let proj = glam::Mat4::perspective_rh_gl(fov_y, aspect, 0.1, 1000.0);
43184325
let view = if state.visualizer_mode == 19 && state.biolum_top_down {
43194326
glam::Mat4::look_at_rh(
43204327
glam::Vec3::new(0.0, 14.0, 0.0),

src/main.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -382,12 +382,14 @@ async fn run_gui(app_state: Arc<Mutex<AppState>>, mut active_stream: Option<audi
382382
state.show_hud = !state.show_hud;
383383
},
384384
WinitKeyCode::KeyF => {
385-
is_fullscreen = !is_fullscreen;
385+
let currently_fullscreen = window.fullscreen().is_some();
386+
is_fullscreen = !currently_fullscreen;
386387
if is_fullscreen {
387388
window.set_fullscreen(Some(winit::window::Fullscreen::Borderless(None)));
388389
keep_awake = keepawake::Builder::default().display(true).idle(true).create().ok();
389390
} else {
390391
window.set_fullscreen(None);
392+
let _ = window.request_inner_size(winit::dpi::LogicalSize::new(1024.0, 768.0));
391393
keep_awake = None;
392394
}
393395
},
@@ -1236,12 +1238,14 @@ async fn run_gui(app_state: Arc<Mutex<AppState>>, mut active_stream: Option<audi
12361238
continue;
12371239
}
12381240
gilrs::Button::Start => {
1239-
is_fullscreen = !is_fullscreen;
1241+
let currently_fullscreen = window.fullscreen().is_some();
1242+
is_fullscreen = !currently_fullscreen;
12401243
if is_fullscreen {
12411244
window.set_fullscreen(Some(winit::window::Fullscreen::Borderless(None)));
12421245
keep_awake = keepawake::Builder::default().display(true).idle(true).create().ok();
12431246
} else {
12441247
window.set_fullscreen(None);
1248+
let _ = window.request_inner_size(winit::dpi::LogicalSize::new(1024.0, 768.0));
12451249
keep_awake = None;
12461250
}
12471251
continue;

src/shaders/_common.wgsl

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,64 @@ struct AudioUniforms {
4242
frame_count: u32,
4343
step_fraction: f32,
4444
steps_to_fill: u32,
45-
pad1: u32,
45+
aspect_ratio: f32,
4646
pad2: u32,
4747
pad3: u32,
4848
};
49+
50+
fn hash21_crt(p: vec2<f32>) -> f32 {
51+
var p3 = fract(vec3<f32>(p.xyx) * 0.1031);
52+
p3 = p3 + dot(p3, p3.yzx + 33.33);
53+
return fract((p3.x + p3.y) * p3.z);
54+
}
55+
56+
fn crt_distort_uv(uv: vec2<f32>, distortion: f32) -> vec2<f32> {
57+
let crt_uv = uv * 2.0 - 1.0;
58+
let r2 = dot(crt_uv, crt_uv);
59+
return (crt_uv * (1.0 + r2 * distortion)) * 0.5 + 0.5;
60+
}
61+
62+
struct CRTSettings {
63+
scanline_intensity: f32,
64+
vignette_scale: f32,
65+
vignette_softness: f32,
66+
noise_intensity: f32,
67+
flicker_intensity: f32,
68+
phosphor_tint: vec3<f32>,
69+
}
70+
71+
fn get_default_crt() -> CRTSettings {
72+
var settings: CRTSettings;
73+
settings.scanline_intensity = 0.15;
74+
settings.vignette_scale = 1.4;
75+
settings.vignette_softness = 0.85;
76+
settings.noise_intensity = 0.025;
77+
settings.flicker_intensity = 0.03;
78+
settings.phosphor_tint = vec3<f32>(1.0, 1.0, 1.0);
79+
return settings;
80+
}
81+
82+
fn apply_crt_effects(color: vec3<f32>, uv: vec2<f32>, clip_pos: vec2<f32>, time: f32, settings: CRTSettings) -> vec3<f32> {
83+
var final_color = color;
84+
85+
// CRT scanlines
86+
let scanline = 1.0 - settings.scanline_intensity + settings.scanline_intensity * cos(clip_pos.y * 3.14159);
87+
final_color *= scanline;
88+
89+
// Vignette
90+
let crt_uv = uv * 2.0 - 1.0;
91+
let r = length(crt_uv);
92+
let bezel = smoothstep(settings.vignette_scale, settings.vignette_softness, r);
93+
final_color *= bezel;
94+
95+
// Flicker
96+
let flicker = 1.0 - settings.flicker_intensity + settings.flicker_intensity * sin(time * 377.0);
97+
final_color *= flicker;
98+
99+
// Analog noise
100+
let noise_val = hash21_crt(clip_pos + fract(time) * 137.0);
101+
let static_noise = noise_val * settings.noise_intensity * bezel;
102+
final_color = final_color + static_noise;
103+
104+
return final_color * settings.phosphor_tint;
105+
}

src/shaders/spectrum.wgsl

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -173,17 +173,14 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
173173
}
174174
else {
175175
// --- MODE 2: AMBER CRT ---
176-
// Apply radial distortion
177-
let crt_uv = in.uv * 2.0 - 1.0;
178-
let r2 = dot(crt_uv, crt_uv);
179-
let distorted_uv = crt_uv * (1.0 + r2 * 0.15); // Curve
180-
let final_uv = distorted_uv * 0.5 + 0.5;
176+
// Apply radial distortion (Disabled to preserve grid alignment with UI)
177+
let final_uv = in.uv;
181178

182179
if final_uv.x < 0.0 || final_uv.x > 1.0 || final_uv.y < 0.0 || final_uv.y > 1.0 {
183180
return vec4<f32>(0.0, 0.0, 0.0, 1.0);
184181
}
185182

186-
let aspect = dpdx(in.uv.x) / dpdy(in.uv.y);
183+
let aspect = audio.aspect_ratio;
187184
var final_color = vec3<f32>(0.0);
188185
let amber = vec3<f32>(1.0, 0.65, 0.1);
189186

@@ -230,11 +227,14 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
230227
let mask_intensity = 0.7 + 0.3 * sin(mask_x * 3.14159) * sin(mask_y * 3.14159);
231228
final_color = final_color * mask_intensity;
232229

233-
let vignette = smoothstep(1.5, 0.2, length(distorted_uv));
234-
let scanlines = sin(final_uv.y * 400.0 * aspect) * 0.15 + 0.85;
235-
let flicker = sin(audio.time * 60.0) * 0.03 + 0.97;
230+
var crt_settings = get_default_crt();
231+
crt_settings.scanline_intensity = 0.15;
232+
crt_settings.vignette_scale = 1.5;
233+
crt_settings.vignette_softness = 0.2;
234+
crt_settings.flicker_intensity = 0.03;
235+
crt_settings.noise_intensity = 0.0;
236236

237-
final_color = final_color * vignette * scanlines * flicker;
237+
final_color = apply_crt_effects(final_color, in.uv, in.clip_position.xy, audio.time, crt_settings);
238238

239239
return vec4<f32>(final_color, 1.0);
240240
}

src/shaders/vis_3doscilloscope.wgsl

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
5252
let dy = dpdy(in.uv.y);
5353
let aspect = dy / max(dx, 0.00001);
5454

55-
// Scale screen coords
55+
// Scale screen coords to maintain horizontal FOV
5656
let p = vec2<f32>(distorted_uv.x * aspect, distorted_uv.y) * 0.9;
5757

5858
// Subtly rotate camera around center
@@ -75,7 +75,10 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
7575

7676
let num_lines = min(max(1u, audio.waveform_history_size), 144u);
7777
let res = f32(max(audio.waveform_resolution, 128u));
78-
let inv_res_minus_1 = 19.2 / (res - 1.0);
78+
let safe_aspect = max(aspect, 0.001);
79+
let bound = 9.6 * safe_aspect;
80+
let inner_bound = max(bound - 3.0, 0.0);
81+
let inv_res_minus_1 = (bound * 2.0) / (res - 1.0);
7982

8083
if rd.y > 0.0 {
8184
let t_min = max(0.0, (ro.z - 1.5) / max(0.2 - rd.z, 0.00001));
@@ -96,13 +99,13 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
9699

97100
let hit_x = ro.x + rd.x * t;
98101

99-
let edge_fade = smoothstep(9.6, 6.6, abs(hit_x));
102+
let edge_fade = smoothstep(bound, inner_bound, abs(hit_x));
100103
if edge_fade <= 0.00001 {
101104
continue;
102105
}
103106

104107
// Map X from -9.6 to 9.6 to dynamic resolution
105-
let float_idx = (hit_x + 9.6) / 19.2 * (res - 1.0);
108+
let float_idx = (hit_x + bound) / (bound * 2.0) * (res - 1.0);
106109
let idx = i32(round(float_idx));
107110

108111
// Search radius proportional to distance to avoid aliasing/dropout on distant lines
@@ -116,17 +119,17 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
116119
let hist_offset = clamp(hist_idx, 0u, max(1u, audio.waveform_history_size) - 1u) * 2048u;
117120

118121
var j_u = u32(start_idx);
119-
var x_prev = -9.6 + f32(j_u) * inv_res_minus_1;
120-
var mask_prev = smoothstep(9.6, 6.6, abs(x_prev));
122+
var x_prev = -bound + f32(j_u) * inv_res_minus_1;
123+
var mask_prev = smoothstep(bound, inner_bound, abs(x_prev));
121124
var v_prev = waveform_history[hist_offset + j_u];
122125
var p_prev = v_prev * mask_prev * 1.2;
123126
var p3_prev = vec3<f32>(x_prev, y_line, p_prev);
124127
var proj_prev = project_3d(p3_prev, ro, u, v_cam, w);
125128

126129
for (var j = start_idx; j <= end_idx; j = j + 1) {
127130
j_u = u32(j + 1);
128-
let x_curr = -9.6 + f32(j_u) * inv_res_minus_1;
129-
let mask_curr = smoothstep(9.6, 6.6, abs(x_curr));
131+
let x_curr = -bound + f32(j_u) * inv_res_minus_1;
132+
let mask_curr = smoothstep(bound, inner_bound, abs(x_curr));
130133
let v_curr = waveform_history[hist_offset + j_u];
131134
let p_curr = v_curr * mask_curr * 1.2;
132135
let p3_curr = vec3<f32>(x_curr, y_line, p_curr);
@@ -157,7 +160,7 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
157160
let age_fade = mix(0.05, 1.0, f32(i) / f32(num_lines - 1u));
158161

159162
// Sample waveform height at hit point for color grading
160-
let hit_wave_idx = u32(clamp((hit_x + 9.6) / 19.2 * (res - 1.0), 0.0, res - 1.0));
163+
let hit_wave_idx = u32(clamp((hit_x + bound) / (bound * 2.0) * (res - 1.0), 0.0, res - 1.0));
161164
let wave_height = clamp(abs(waveform_history[hist_offset + hit_wave_idx]) * 2.0, 0.0, 1.0);
162165
let line_amber = mix(amber_lo, amber_hi, wave_height);
163166

src/shaders/vis_3doscilloscope_freq.wgsl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
5353
let dy = dpdy(in.uv.y);
5454
let aspect = dy / max(dx, 0.00001);
5555

56-
// Scale screen coords
56+
// Scale screen coords to maintain horizontal FOV
5757
let p = vec2<f32>(distorted_uv.x * aspect, distorted_uv.y) * 0.9;
5858

5959
// Subtly rotate camera around center

src/shaders/vis_3drain.wgsl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
153153
let dy = dpdy(in.uv.y);
154154
let aspect = dy / max(dx, 0.00001);
155155
let safe_aspect = select(1.0, aspect, abs(aspect) > 0.0001);
156-
let p = vec2<f32>(uv.x * safe_aspect, -uv.y); // p.y is +1 at top, -1 at bottom
156+
let p = vec2<f32>(uv.x, -uv.y / safe_aspect); // p.y is +1 at top, -1 at bottom
157157

158158
let bass = max(audio.spectrum[0].x, audio.spectrum[1].x);
159159
let mid = (audio.spectrum[20].x + audio.spectrum[30].x) * 0.5;

src/shaders/vis_flame.wgsl

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,7 @@ fn demoscene_palette(h: f32) -> vec3<f32> {
3131
@fragment
3232
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
3333
// --- CRT barrel distortion ---
34-
let crt_uv = in.uv * 2.0 - 1.0;
35-
let r2 = dot(crt_uv, crt_uv);
36-
// Reduced from 0.08 to 0.03 for a more subtle bend
37-
let distorted = crt_uv * (1.0 + r2 * 0.03);
38-
let uv = distorted * 0.5 + 0.5;
34+
let uv = crt_distort_uv(in.uv, 0.03);
3935

4036
// Outside the CRT tube → pure black
4137
if uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0 {
@@ -81,22 +77,19 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
8177
}
8278
color *= phosphor;
8379

84-
// --- Vignette (darken edges of CRT) ---
85-
let vignette = smoothstep(1.5, 0.85, length(crt_uv));
86-
color *= vignette;
87-
88-
// --- Analog noise / static ---
89-
let noise = hash12(in.clip_position.xy + fract(audio.smooth_time) * 137.0);
90-
color += vec3<f32>(noise * 0.025 * vignette);
91-
92-
// --- CRT flicker (subtle 60Hz-ish wobble) ---
93-
let flicker = 0.97 + 0.03 * sin(audio.smooth_time * 377.0);
94-
color *= flicker;
95-
9680
// --- Bezel edge glow (faint amber reflection near corners) ---
81+
let crt_uv = uv * 2.0 - 1.0;
9782
let bezel_dist = max(abs(crt_uv.x) - 0.85, 0.0) + max(abs(crt_uv.y) - 0.85, 0.0);
9883
let bezel_glow = exp(-bezel_dist * 30.0) * 0.03;
9984
color += vec3<f32>(1.0, 0.6, 0.2) * bezel_glow * clamp(heat * 3.0, 0.0, 1.0);
10085

86+
var crt_settings = get_default_crt();
87+
crt_settings.scanline_intensity = 0.0; // Use vis_flame's custom scanlines instead
88+
crt_settings.vignette_scale = 1.5;
89+
crt_settings.vignette_softness = 0.85;
90+
crt_settings.noise_intensity = 0.025;
91+
crt_settings.flicker_intensity = 0.03;
92+
color = apply_crt_effects(color, in.uv, in.clip_position.xy, audio.smooth_time, crt_settings);
93+
10194
return vec4<f32>(color, 1.0);
10295
}

src/shaders/vis_oscilloscope.wgsl

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -80,14 +80,12 @@ fn get_wave_dist(hist_idx: u32, uv: vec2<f32>, aspect: f32) -> f32 {
8080
@fragment
8181
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
8282
// CRT barrel distortion
83-
let crt_uv = in.uv * 2.0 - 1.0;
84-
let r2 = dot(crt_uv, crt_uv);
85-
let distorted_uv = crt_uv * (1.0 + r2 * 0.05);
86-
let final_uv = distorted_uv * 0.5 + 0.5;
83+
let final_uv = crt_distort_uv(in.uv, 0.05);
8784

88-
let aspect = dpdx(in.uv.x) / dpdy(in.uv.y);
85+
let aspect = audio.aspect_ratio;
8986
var final_color = vec3<f32>(0.0);
9087

88+
let crt_uv = in.uv * 2.0 - 1.0;
9189
let r = length(crt_uv);
9290
let edge_blur = smoothstep(0.2, 1.5, r);
9391

@@ -96,11 +94,20 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
9694

9795
var wave_intensity = 0.0;
9896

97+
let dy = dpdy(in.uv.y);
98+
let base_thickness = max(dy * 0.6, 0.0005);
99+
let blur_thickness = max(dy * 2.0, 0.0015);
100+
99101
// Performance: skip every other history frame when count exceeds 72,
100102
// compensating with doubled contribution to maintain total brightness.
101103
let hist_count = min(audio.waveform_history_size, 144u);
102104
let step = select(1u, 2u, hist_count > 72u);
103105
let step_scale = f32(step);
106+
107+
// Lock thickness to physical screen pixels so it doesn't vanish on short windows
108+
let thickness = base_thickness + edge_blur * blur_thickness;
109+
// Tighter bloom (reduced spread and intensity)
110+
let bloom_spread = max(dy * 0.2, 0.0002);
104111

105112
for (var i = 0u; i < hist_count; i = i + step) {
106113
let true_dist = get_wave_dist(i, final_uv, aspect);
@@ -111,12 +118,8 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
111118
// 0.05 gives a long 1-second smooth trail.
112119
let age = exp(-frames_old * 0.06);
113120

114-
// Much thinner beam for crisp, high-resolution lines instead of thick noodles
115-
let thickness = 0.0005 + edge_blur * 0.0015;
116121
let core = smoothstep(thickness, 0.0, true_dist) * 0.6;
117-
118-
// Tighter bloom (reduced spread and intensity)
119-
let bloom = 0.00005 / (true_dist * true_dist + 0.0002) * 0.02;
122+
let bloom = 0.00005 / (true_dist * true_dist + bloom_spread) * 0.02;
120123

121124
// Tighter halation (faster falloff)
122125
let halation = exp(-true_dist * 120.0) * 0.01;
@@ -130,24 +133,18 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
130133
let mapped = wave_intensity * amber;
131134
var tonemapped = (mapped * (2.51 * mapped + 0.03)) / (mapped * (2.43 * mapped + 0.59) + 0.14);
132135

133-
final_color = tonemapped;
134-
135-
// CRT scanlines
136-
let scanline = 0.85 + 0.15 * cos(in.clip_position.y * 3.14159);
137-
final_color *= scanline;
138-
136+
var crt_settings = get_default_crt();
137+
crt_settings.phosphor_tint = amber; // Not used as tint for everything, just to configure if needed. Actually we'll keep final_color.
138+
139139
// Smooth CRT bezel fade (radial, no rectangular edges)
140140
let bezel = smoothstep(1.4, 0.9, r);
141-
142-
// Analog noise / static (like vis_flame)
143-
let noise_val = hash21(in.clip_position.xy + fract(audio.smooth_time) * 137.0);
144-
let static_noise = noise_val * 0.035 * bezel;
145141

146-
// Add extra faint glow from the waveform onto the noise
142+
// Analog noise glow
143+
let noise_val = hash21_crt(in.clip_position.xy + fract(audio.smooth_time) * 137.0);
147144
let noise_glow = amber * noise_val * 0.015 * bezel * clamp(wave_intensity * 0.8, 0.0, 1.0);
148145

149-
final_color = final_color * bezel;
150-
final_color = final_color + static_noise + noise_glow;
146+
final_color = apply_crt_effects(tonemapped, in.uv, in.clip_position.xy, audio.smooth_time, crt_settings);
147+
final_color = final_color + noise_glow;
151148

152149
// Output Linear RGB. WGPU Srgb surface will apply the sRGB gamma curve automatically.
153150
return vec4<f32>(final_color, 1.0);

0 commit comments

Comments
 (0)