nyx.wnd: open-source, cross-platform glue windowing library

Overview

A problem I've run into lately is that I want to test graphics-oriented plugins with different rendering backends on both Linux and Windows (sorry Apple, I'll get to you one day).

Doing that inside a real plugin environment gets cumbersome fast. Starting a new VST3 project means setting up the controller, implementing the OS-specific UI plumbing, integrating a rendering backend, building the plugin, and finally loading it into a VST3 host just to test the thing I actually care about. At that point, the window hierarchy and event loop I'm trying to debug are running inside a host that is largely a black box.

That's especially painful when the thing I'm testing is the windowing and event-loop behavior.

There are already excellent libraries in this space, such as Pugl and JUCE, but they solve a larger problem than the one I'm interested in solving. I don't want another application or UI framework. I don't want to abstract away SFML, SDL2, SDL3, OpenGL, Vulkan, or whatever renderer I decide to experiment with next. I want to stay as close to the bones of the plugin API and native platform as possible.

What I really want is much smaller: give me a native parent window and I'll create a child, attach it, manage its lifetime, and give you an event loop that executes the work on the thread where it belongs.

Then get out of the way.

So, over the last few months (between making music and having a full-time job), I've been building two small libraries to do exactly that: nyx.wnd and nyx.host.

nyx.wnd?

https://github.com/nhreimer/nyx.wnd

Plugin APIs have an interesting relationship with user interfaces. Rather than giving the plugin control over a top-level application window, the host generally hands it a native parent window and effectively says:

"Here's your parent. Good luck. See ya later."

From there, the plugin is responsible for creating or attaching its UI underneath that window while still living inside somebody else's application, window hierarchy, and event loop.

nyx.wnd is the glue layer I wanted for that boundary.

So what does it do exactly?

Given a native parent window, it:

  1. creates and attaches a child window and manages the platform-specific lifecycle around it.
  2. provides an event-loop scheduler so that operations with UI-thread affinity (window creation and destruction, rendering callbacks, queued work, and other native operations) execute on the thread that actually owns the window.

What it deliberately does not do is choose how that window gets rendered.

The child window can be handed to SFML, SDL2, SDL3, OpenGL, Vulkan, a custom renderer, or anything else capable of rendering into a native window. nyx.wnd owns the awkward boundary between the host and the child window; the rendering library remains the rendering library and you work with it as you normally would.

That separation is important. The goal isn't to build another cross-platform GUI framework. The goal is to make this particular problem boring:

Parent window -> child window -> event loop -> your renderer.

nyx.wnd.architecture.png

As you can see in the diagram, there is a difference in the way the event loop works between X11 and Win32. nyx.wnd attempts to hide threading details by using callbacks that operate on the correct thread to the client, but it's an important distinction to be aware of when using the library.

nyx.host

https://github.com/nhreimer/nyx.host

Okay, but how do you test something whose entire purpose is to live inside somebody else's host?

And that is where nyx.host comes in.

I could keep building VST3 plugins and loading them into a DAW every time I wanted to test a change, but that puts me right back where I started. Instead, nyx.host provides the other half of the equation: a lightweight, standalone environment that behaves enough like a plugin host to exercise nyx.wnd without requiring a full plugin or DAW. More importantly, I own the entire environment, with access to the console, debugger, host event loop, and everything happening around the embedded window.

nyx.host owns the top-level application window and its native event loop. It creates the parent window that would normally be supplied by the plugin host, then hands control to a user-provided controller. From there, the controller can create a nyx.wnd child and attach whatever rendering backend is being tested.

The result is a small, cross-platform test environment where I can run the same embedded-window architecture with SFML, SDL2, SDL3, OpenGL, or another renderer without changing the host itself. Each renderer has its own assumptions about window ownership, events, focus, and lifecycle, so being able to see and control the host, glue layer, and user-defined UI independently makes those differences much easier to isolate.

Like nyx.wnd, the intent is to keep the abstraction narrow. nyx.host isn't a DAW, and it isn't trying to become a complete VST3 host. It provides just enough of the environment around an embedded plugin UI to make that environment predictable, inspectable, and easy to test.

Together, the boundary becomes pretty simple:

  • nyx.host owns the parent.
  • nyx.wnd owns the child.
  • Your code owns everything interesting.
nyx.host                    nyx.wnd
────────                    ───────
Owns application            Owns embedded child
Owns parent                 Owns child lifecycle
Owns host event loop   →    Schedules child/UI work
Creates controller          Exposes native window
                                 │
                                 ▼
                           Your UI / Renderer
                                 │
                    ┌────────────┼────────────┐
                    ▼            ▼            ▼
                  SFML          SDL        OpenGL ...