I was building a small rack-style DAW for Linux in Rust. It hosts VST3 plugins, and one of them refused to draw.
Not a crash, and no error in the log. The editor window opened at the right size and painted nothing.
One drew its editor. One didn't.
Surge XT opened its editor and rendered correctly. sfizz opened a window and painted nothing. Same host application, same machine, same session, one after the other.
A blank window is a worse symptom than a crash. A crash gives you a stack. A window that opens at the correct dimensions and then does nothing tells you the plugin was found, loaded, instantiated and asked for its editor, and that something after all of that quietly didn't happen.
The difference was which toolkit drew the window
Two plugins, one host, opposite outcomes. That's a differential, and it's worth more than any amount of reading the code, because it narrows the search before you start.
Surge XT builds its interface on JUCE. sfizz builds its interface on VSTGUI. Everything else about how the host loaded them was identical.
- Surge XT, JUCE-based: editor drew correctly
- sfizz, VSTGUI-based: window opened, stayed black
VST3 on Linux expects the host to provide a run-loop
On Linux, a VST3 plugin editor doesn't own an event loop. It asks the host for one. The host is expected to expose a run-loop service so the plugin can register file descriptors and timers and be called back when it needs to paint or respond.
VSTGUI depends on that service. JUCE doesn't - it manages its own message handling and never asks.
That asymmetry is why the bug was invisible from one side. A host that never implemented the service works with JUCE-based plugins and fails silently with VSTGUI ones, and nothing in the logs says so.
The fault sat in the library in between
The dependency direction is the part worth getting right, because it's the part most people get backwards when this is described.
The host library sits underneath the host application. My application depended on it. The plugin doesn't - it never sees it. The run-loop is a service the host owes the plugin, and the library that was supposed to provide it on my behalf had never implemented it.
So the plugin was correct, my code was correct, and the thing between them was missing a contract neither of them could see.
Patched, then verified against reality rather than against itself
I implemented the run-loop service in a vendored copy of the library, so the host could hand plugins something that actually worked.
Then I verified it by capturing real audio off the system's PipeWire stream and listening to it.
That distinction matters more than the patch does. A unit test written by the person who wrote the fix tests that person's assumptions. It agrees with you by construction. Capturing what actually came out of the machine doesn't. It's the same habit that decides which accuracy figure gets published on the other thing I built.
Upstreamed, and one thing still open
A vendored patch fixes one tree. It doesn't fix the problem.
So I forked the library, ported the patch onto the maintainer's current release, ran it against their full CI matrix, and opened a pull request.
PR #10, "Implement Linux IRunLoop so VSTGUI-based plugin editors work", merged on 27 July 2026. It has shipped in released versions since v0.8.0, so anyone building a VST3 host on that library already has it.
The patch is public: PR #10 on the rust-vst3-host repository.
One thing is still open, and it comes from the same mechanism. Audio can stutter while a VSTGUI-heavy editor is on screen, because the audio thread and the editor-servicing pump contend for a lock. It's diagnosed precisely, the workaround is closing the editor while you listen, and the proper fix, splitting the audio-critical and GUI-servicing locks, is scoped but not built. Still in development.
- Forked the upstream repository
- Ported the patch to their current release, not my vendored version
- Verified against their full CI matrix
- PR #10, merged 27 July 2026
The same move, one layer down
Around 1999 I hit the ceiling of what Dreamweaver would let me do and went down a layer, to HTML, CSS and JavaScript. This is that again, further down.
It's also what a hard performance job looks like. When a site won't get faster and every obvious cause has been ruled out, the answer is usually one layer below where anyone has looked, and it's rarely the thing being blamed.
Pages worth acting on
Related page
Technical profile
AI systems, automation and full-stack engineering work, set out properly.
View pageRelated page
WordPress performance optimisation
The same habit applied to a site that wouldn't get faster.
View pageRelated page
Portfolio and case studies
Shipped work, with what got measured before and after.
View pageRelated page
New website builds
Where the build-then-check approach goes on client work.
View pageWhat to read next
Custom-coded vs WordPress: a real-world speed comparison
A rare like-for-like test. The same local business, the same services, one website replaced by another, both measured with Google PageSpeed Insights on the same day.
Read articleTwo numbers came out of the same run. I published the worse one
Precision was about 90%. Exact agreement was 27%. Both describe the same evaluation, and only one of them tells you whether the thing is any good.
Read articleQuestions local business owners usually ask next
Contract and subcontract development
Root-cause debugging, audio and systems work, and performance jobs on the web. If you've got something that needs someone to go a layer down, start here.
