Skip to content

Commit e8c63dc

Browse files
committed
feat(examples): preserve aspect ratio and center UI panels
- Update fullscreen shader to preserve texture aspect ratio (letterbox/pillarbox) - Center the audio monitor panel in the viewport - Make audio panel larger (400x120 waveform, 32x160 meters) - Center the help overlay at the bottom
1 parent 2c52eee commit e8c63dc

1 file changed

Lines changed: 67 additions & 36 deletions

File tree

examples/metal_overlay/main.rs

Lines changed: 67 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,9 @@ struct TexturedVertex {
119119
// Uniforms buffer
120120
struct Uniforms {
121121
float2 viewport_size;
122+
float2 texture_size;
122123
float time;
123-
float padding;
124+
float padding[3];
124125
};
125126
126127
struct VertexOut {
@@ -153,15 +154,35 @@ fragment float4 fragment_colored(VertexOut in [[stage_in]]) {
153154
return in.color;
154155
}
155156
156-
// Fullscreen quad vertex shader
157-
vertex TexturedVertexOut vertex_fullscreen(uint vid [[vertex_id]]) {
157+
// Fullscreen quad vertex shader with aspect ratio preservation
158+
vertex TexturedVertexOut vertex_fullscreen(
159+
uint vid [[vertex_id]],
160+
constant Uniforms& uniforms [[buffer(0)]]
161+
) {
158162
TexturedVertexOut out;
159-
// Generate fullscreen triangle strip from vertex id
163+
164+
// Calculate aspect ratios
165+
float viewportAspect = uniforms.viewport_size.x / uniforms.viewport_size.y;
166+
float textureAspect = uniforms.texture_size.x / uniforms.texture_size.y;
167+
168+
// Scale to fit while preserving aspect ratio (letterbox/pillarbox)
169+
float scaleX = 1.0;
170+
float scaleY = 1.0;
171+
172+
if (textureAspect > viewportAspect) {
173+
// Texture is wider - pillarbox (black bars top/bottom)
174+
scaleY = viewportAspect / textureAspect;
175+
} else {
176+
// Texture is taller - letterbox (black bars left/right)
177+
scaleX = textureAspect / viewportAspect;
178+
}
179+
180+
// Generate quad vertices centered in viewport
160181
float2 positions[4] = {
161-
float2(-1.0, -1.0),
162-
float2( 1.0, -1.0),
163-
float2(-1.0, 1.0),
164-
float2( 1.0, 1.0)
182+
float2(-scaleX, -scaleY),
183+
float2( scaleX, -scaleY),
184+
float2(-scaleX, scaleY),
185+
float2( scaleX, scaleY)
165186
};
166187
float2 texcoords[4] = {
167188
float2(0.0, 1.0),
@@ -226,13 +247,14 @@ impl TexturedVertex {
226247
}
227248
}
228249

229-
/// Uniforms buffer (16 bytes, 16-byte aligned for GPU)
250+
/// Uniforms buffer (32 bytes, 16-byte aligned for GPU)
230251
#[repr(C)]
231252
#[derive(Clone, Copy, Debug)]
232253
struct Uniforms {
233254
viewport_size: [f32; 2], // 8 bytes
255+
texture_size: [f32; 2], // 8 bytes - for aspect ratio calculation
234256
time: f32, // 4 bytes
235-
_padding: f32, // 4 bytes (ensures 16-byte alignment)
257+
_padding: [f32; 3], // 12 bytes (ensures 32-byte alignment)
236258
}
237259

238260
// ============================================================================
@@ -992,28 +1014,32 @@ fn main() {
9921014
};
9931015
vertex_builder.text(&font, &status, 8.0, 8.0, 2.0, [0.2, 1.0, 0.3, 1.0]);
9941016

995-
// Waveform panel
1017+
// Waveform panel - centered and larger
9961018
if overlay.show_waveform {
997-
let wave_x = 16.0;
998-
let wave_y = (height - 180.0).floor();
999-
let wave_w = 280.0;
1000-
let wave_h = 80.0;
1001-
let meter_w = 24.0;
1002-
let meter_h = 120.0;
1003-
let panel_w = wave_w + meter_w * 2.0 + 48.0;
1004-
let panel_h = 160.0;
1019+
let wave_w = 400.0;
1020+
let wave_h = 120.0;
1021+
let meter_w = 32.0;
1022+
let meter_h = 160.0;
1023+
let panel_w = wave_w + meter_w * 2.0 + 64.0;
1024+
let panel_h = 220.0;
1025+
1026+
// Center the panel
1027+
let panel_x = (width - panel_w) / 2.0;
1028+
let panel_y = (height - panel_h) / 2.0;
1029+
let wave_x = panel_x + 16.0;
1030+
let wave_y = panel_y + 40.0;
10051031

10061032
// Panel background
10071033
vertex_builder.rect(
1008-
wave_x - 8.0,
1009-
wave_y - 24.0,
1034+
panel_x,
1035+
panel_y,
10101036
panel_w,
10111037
panel_h,
1012-
[0.12, 0.12, 0.15, 0.9],
1038+
[0.12, 0.12, 0.15, 0.95],
10131039
);
10141040
vertex_builder.rect_outline(
1015-
wave_x - 8.0,
1016-
wave_y - 24.0,
1041+
panel_x,
1042+
panel_y,
10171043
panel_w,
10181044
panel_h,
10191045
2.0,
@@ -1023,10 +1049,10 @@ fn main() {
10231049
// Title
10241050
vertex_builder.text(
10251051
&font,
1026-
"AUDIO",
1027-
wave_x,
1028-
wave_y - 20.0,
1029-
2.0,
1052+
"AUDIO MONITOR",
1053+
panel_x + 16.0,
1054+
panel_y + 12.0,
1055+
2.5,
10301056
[1.0, 1.0, 1.0, 1.0],
10311057
);
10321058

@@ -1043,16 +1069,16 @@ fn main() {
10431069

10441070
// Horizontal VU meter for system audio
10451071
let audio_level = capture_state.audio_waveform.lock().unwrap().rms(2048);
1046-
vertex_builder.vu_meter(audio_level, wave_x, wave_y + wave_h + 8.0, wave_w, 12.0);
1072+
vertex_builder.vu_meter(audio_level, wave_x, wave_y + wave_h + 12.0, wave_w, 16.0);
10471073

10481074
// Vertical meters on the right side
1049-
let meters_x = wave_x + wave_w + 16.0;
1075+
let meters_x = wave_x + wave_w + 24.0;
10501076

10511077
// System audio vertical meter
10521078
vertex_builder.vu_meter_vertical(
10531079
audio_level,
10541080
meters_x,
1055-
wave_y - 8.0,
1081+
wave_y,
10561082
meter_w,
10571083
meter_h,
10581084
"SYS",
@@ -1063,18 +1089,21 @@ fn main() {
10631089
let mic_level = capture_state.mic_waveform.lock().unwrap().rms(2048);
10641090
vertex_builder.vu_meter_vertical(
10651091
mic_level,
1066-
meters_x + meter_w + 8.0,
1067-
wave_y - 8.0,
1092+
meters_x + meter_w + 12.0,
1093+
wave_y,
10681094
meter_w,
10691095
meter_h,
10701096
"MIC",
10711097
&font,
10721098
);
10731099
}
10741100

1075-
// Help overlay
1101+
// Help overlay - center it
10761102
if overlay.show_help {
1077-
vertex_builder.help_overlay(&font, width - 230.0, 40.0, capturing.load(Ordering::Relaxed));
1103+
let help_w = 260.0;
1104+
let help_x = (width - help_w) / 2.0;
1105+
let help_y = height - 140.0;
1106+
vertex_builder.help_overlay(&font, help_x, help_y, capturing.load(Ordering::Relaxed));
10781107
}
10791108

10801109
// Build GPU buffer
@@ -1087,8 +1116,9 @@ fn main() {
10871116
// Uniforms - pass capture texture dimensions for aspect ratio
10881117
let uniforms = Uniforms {
10891118
viewport_size: [width, height],
1119+
texture_size: [tex_width, tex_height],
10901120
time,
1091-
_padding: 0.0,
1121+
_padding: [0.0; 3],
10921122
};
10931123
let uniforms_buffer = device.new_buffer_with_data(
10941124
(&uniforms as *const Uniforms).cast(),
@@ -1115,6 +1145,7 @@ fn main() {
11151145
// First pass: Draw captured frame as background (if available)
11161146
if let Some(ref texture) = capture_texture {
11171147
encoder.set_render_pipeline_state(&fullscreen_pipeline);
1148+
encoder.set_vertex_buffer(0, Some(&uniforms_buffer), 0);
11181149
encoder.set_fragment_texture(0, Some(texture));
11191150
encoder.draw_primitives(MTLPrimitiveType::TriangleStrip, 0, 4);
11201151
}

0 commit comments

Comments
 (0)