Skip to content

Commit b367bcb

Browse files
authored
Merge pull request #117 from ford442/performance-optimization-packing-loop-11334017873056937667
⚡ Optimize WebGPU data packing by removing redundant loop
2 parents 1649a73 + 6f99862 commit b367bcb

12 files changed

Lines changed: 1065 additions & 568 deletions

dist/bezel-square.png

-3.19 MB
Loading

dist/bezel.orig.png

-4.62 MB
Loading

dist/bezel.png

-9.49 MB
Loading

dist/shaders/patternv0.42.wgsl

Lines changed: 352 additions & 58 deletions
Large diffs are not rendered by default.

dist/shaders/patternv0.45.wgsl

Lines changed: 95 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -160,32 +160,13 @@ fn sdTriangle(p: vec2<f32>, r: f32) -> f32 {
160160
return -length(p2) * sign(p2.y);
161161
}
162162

163-
fn toUpperAscii(code: u32) -> u32 {
164-
return select(code, code - 32u, (code >= 97u) & (code <= 122u));
165-
}
166-
163+
// Correct pitch class from raw OpenMPT note value (1–120).
164+
// OpenMPT: note 1 = C-0, note 2 = C#0 … note 12 = B-0, note 13 = C-1, …
165+
// (note - 1) % 12 → 0=C, 1=C#, 2=D, 3=D#, 4=E, 5=F, 6=F#, 7=G, 8=G#, 9=A, 10=A#, 11=B
167166
fn pitchClassFromPacked(packed: u32) -> f32 {
168-
let c0 = toUpperAscii((packed >> 24) & 255u);
169-
var semitone: i32 = 0;
170-
var valid = true;
171-
switch (c0) {
172-
case 65u: { semitone = 9; }
173-
case 66u: { semitone = 11; }
174-
case 67u: { semitone = 0; }
175-
case 68u: { semitone = 2; }
176-
case 69u: { semitone = 4; }
177-
case 70u: { semitone = 5; }
178-
case 71u: { semitone = 7; }
179-
default: { valid = false; }
180-
}
181-
if (!valid) { return 0.0; }
182-
let c1 = toUpperAscii((packed >> 16) & 255u);
183-
if ((c1 == 35u) || (c1 == 43u)) {
184-
semitone = (semitone + 1) % 12;
185-
} else if (c1 == 66u) {
186-
semitone = (semitone + 11) % 12;
187-
}
188-
return f32(semitone) / 12.0;
167+
let note = (packed >> 24) & 255u;
168+
if (note == 0u || note > 120u) { return 0.0; }
169+
return f32((note - 1u) % 12u) / 12.0;
189170
}
190171

191172
@fragment
@@ -233,71 +214,114 @@ fn fs(in: VertexOut) -> @location(0) vec4<f32> {
233214
return vec4<f32>(col * dimFactor, 1.0);
234215
}
235216

236-
// Frosted Cap Shape
217+
// Frosted Cap Shape — use for alpha masking, not early discard
237218
let dBox = sdRoundedBox(uv - 0.5, vec2<f32>(0.42), 0.1);
238219
let isCap = dBox < 0.0;
239-
240-
if (!isCap) {
241-
discard;
242-
}
243-
220+
let capEdge = smoothstep(0.0, 0.08, -dBox);
221+
244222
var capColor = vec3<f32>(0.15, 0.16, 0.18); // Inactive plastic
245223
var glow = 0.0;
246-
247-
let noteChar = (in.packedA >> 24) & 255u;
248-
let hasNote = (noteChar >= 65u && noteChar <= 122u); // Simple check
249-
250-
let playheadStep = uniforms.playheadRow - floor(uniforms.playheadRow / 64.0) * 64.0;
251-
let rowDistRaw = abs(f32(in.row % 64u) - playheadStep);
252-
let rowDist = min(rowDistRaw, 64.0 - rowDistRaw);
224+
let p = uv - 0.5;
225+
226+
// Unpack all fields (OpenMPT numeric encoding)
227+
let note = (in.packedA >> 24) & 255u;
228+
let volCmd = (in.packedA >> 8) & 255u;
229+
let effCmd = (in.packedB >> 8) & 255u;
230+
231+
let hasNote = (note > 0u) && (note <= 120u); // regular note
232+
let isNoteOff = (note == 121u); // --- note-off
233+
let isNoteCut = (note == 122u) || (note == 123u); // === note-cut/fade
234+
let hasVol = (volCmd > 0u);
235+
let hasEffect = (effCmd > 0u);
236+
237+
let playheadStep = uniforms.playheadRow - floor(uniforms.playheadRow / 64.0) * 64.0;
238+
let rowDistRaw = abs(f32(in.row % 64u) - playheadStep);
239+
let rowDist = min(rowDistRaw, 64.0 - rowDistRaw);
253240
let playheadActivation = 1.0 - smoothstep(0.0, 1.5, rowDist);
241+
let onPlayhead = rowDist < 1.5;
242+
243+
// Channel live state
244+
var chTrigger = false;
245+
if (in.channel < arrayLength(&channels)) {
246+
let ch = channels[in.channel];
247+
chTrigger = (ch.trigger > 0u) && onPlayhead;
248+
}
249+
254250
if (hasNote) {
255251
let pitchHue = pitchClassFromPacked(in.packedA);
256-
let baseCol = neonPalette(pitchHue);
257-
capColor = mix(capColor, baseCol, 0.4);
258-
259-
// Highlight if active row
252+
let baseCol = neonPalette(pitchHue);
253+
capColor = mix(capColor, baseCol * 0.55, 0.5);
254+
260255
if (playheadActivation > 0.0) {
261-
glow = playheadActivation;
262-
capColor = mix(capColor, vec3<f32>(1.0), 0.5);
256+
glow = playheadActivation;
257+
capColor = mix(capColor, baseCol, playheadActivation * 0.6);
263258
}
264-
265-
// Trigger flash
266-
let ch = channels[in.channel];
267-
if (ch.trigger > 0u && playheadActivation > 0.5) {
268-
glow += 1.0;
269-
capColor += vec3<f32>(0.5);
259+
if (chTrigger) {
260+
glow += 1.0;
261+
capColor = mix(capColor, baseCol * 1.4 + 0.3, 0.5);
270262
}
263+
264+
// ── Blue note-on indicator (top-left micro-LED) ──────────────
265+
let ledNoteOn = vec2<f32>(-0.30, -0.30);
266+
let dLedOn = length(p - ledNoteOn) - 0.07;
267+
if (dLedOn < 0.0) {
268+
let ledCol = select(
269+
vec3<f32>(0.05, 0.10, 0.22),
270+
vec3<f32>(0.25, 0.55, 1.00),
271+
chTrigger || (playheadActivation > 0.5)
272+
);
273+
return vec4<f32>(ledCol * dimFactor, 1.0);
274+
}
275+
} else if (isNoteOff) {
276+
capColor = mix(capColor, vec3<f32>(0.45, 0.05, 0.05), 0.55);
277+
} else if (isNoteCut) {
278+
capColor = mix(capColor, vec3<f32>(0.60, 0.20, 0.02), 0.45);
271279
}
272-
273-
// Playhead Highlight Line
280+
281+
// ── Amber volume indicator (top-right micro-LED) ─────────────────
282+
let ledVol = vec2<f32>(0.30, -0.30);
283+
let dLedVol = length(p - ledVol) - 0.07;
284+
if (dLedVol < 0.0) {
285+
let ledCol = select(
286+
vec3<f32>(0.18, 0.12, 0.02),
287+
vec3<f32>(1.00, 0.65, 0.10),
288+
hasVol
289+
);
290+
let ledGlow = select(0.0, bloom * 0.8, hasVol && onPlayhead);
291+
return vec4<f32>((ledCol + ledGlow) * dimFactor, 1.0);
292+
}
293+
294+
// ── Effect indicator (bottom-centre micro-LED) ────────────────────
295+
let ledEff = vec2<f32>(0.0, 0.30);
296+
let dLedEff = length(p - ledEff) - 0.06;
297+
if (dLedEff < 0.0) {
298+
let ledCol = select(
299+
vec3<f32>(0.08, 0.12, 0.08),
300+
vec3<f32>(0.30, 0.95, 0.40),
301+
hasEffect
302+
);
303+
return vec4<f32>(ledCol * dimFactor, 1.0);
304+
}
305+
306+
// Playhead highlight
274307
if (playheadActivation > 0.0) {
275-
capColor += vec3<f32>(0.1, 0.1, 0.15) * playheadActivation;
276-
if (isPlaying && playheadActivation > 0.5) {
277-
glow += 0.2;
278-
}
308+
capColor += vec3<f32>(0.08, 0.08, 0.12) * playheadActivation;
309+
if (isPlaying && playheadActivation > 0.5) { glow += 0.2; }
279310
}
280-
281-
// Frosted Effect
282-
let edge = smoothstep(0.0, 0.1, -dBox);
283-
let light = vec3<f32>(0.5, -0.8, 1.0);
284-
let n = normalize(vec3<f32>((uv.x - 0.5), (uv.y - 0.5), 0.5));
285-
let diff = max(0.0, dot(n, normalize(light)));
286-
311+
312+
// Frosted surface shading
313+
let edge = smoothstep(0.0, 0.1, -dBox);
314+
let nrm = normalize(vec3<f32>(p.x, p.y, 0.5));
315+
let light = normalize(vec3<f32>(0.5, -0.8, 1.0));
316+
let diff = max(0.0, dot(nrm, light));
287317
capColor *= (0.5 + 0.5 * diff);
288318
capColor += vec3<f32>(glow * 0.5);
289-
290-
// Bloom boost
291-
if (glow > 0.0) {
292-
capColor *= (1.0 + bloom);
293-
}
319+
if (glow > 0.0) { capColor *= (1.0 + bloom); }
294320

295321
// Kick reactive glow
296-
let p = in.uv - 0.5;
297322
let kickPulse = uniforms.kickTrigger * exp(-length(p) * 3.0) * 0.3;
298323
capColor += vec3<f32>(0.9, 0.2, 0.4) * kickPulse * uniforms.bloomIntensity;
299-
// Dithering for night mode
300-
let noise = fract(sin(dot(in.uv * uniforms.timeSec, vec2<f32>(12.9898, 78.233))) * 43758.5453);
324+
let noise = fract(sin(dot(uv * uniforms.timeSec, vec2<f32>(12.9898, 78.233))) * 43758.5453);
301325
capColor += (noise - 0.5) * 0.01;
302326

303327
return vec4<f32>(capColor * dimFactor, edge);

0 commit comments

Comments
 (0)