JUCE 8.0.13 released

This post is taken from the JUCE email newsletter.

To get these delivered to your email inbox, sign up here: Newsletter - JUCE

8.0.13 is out and there’s a lot in it!

There are over 240 commits since the last release.

As always, the JUCE team’s main job has been keeping things working smoothly on top of changes to operating systems, compilers, plug-in SDKs, DAWs, and dependencies.

That said, there’s also a ton of interesting little features and bug fixes, so let’s dive into some detail.

Please also note the deprecations in this release. Over 80% of respondents to the 2025 JUCE survey said they were happy to see well-documented breaking changes — we’ve taken you at your word! Please view the section at the bottom and checkout BREAKING_CHANGES.md for more.

GUI

The unusually big compilation unit in the juce_gui_basics module has been split into 5 pieces, reducing the overall compile-time. (commit)

Apple’s new Icon Composer is now supported in MacOS and iOS builds. Add an .icon bundle to your Projucer or CMake project and JUCE will produce the correct asset catalogue automatically; legacy .icns icons still work as before. (commit, forum, commit)

Component painting performance

A new diagnostics framework lets you inspect and time the paint routine of any component (commit, commit)

There’s also a fantastic new ComponentDiagnosticsDemo in the DemoRunner which illustrates how you could use the diagnostics to speed up your drawing code. (commit)


Melatonin Inspector is already taking advantage of the diagnostics, giving JUCE 8.0.13 users real-time performance stats without any additional setup.
533.8xauto
Component::paintComponentAndChildren has been substantially refactored for performance and to tighten the opaque-region checks resulting in fewer redundant redraws and unnecessary clip operations. (commit)

The stack size of Component and ListenerList has been reduced. (commit, commit)

The old macOS image tiling fallback has been removed, which means image tiling is faster and less artifact-y. (commit)

Windows

The Windows minimum deployment target is now Windows 10 version 1607. This will impact people still targetting the initial launch version of Windows 10, which should be a very small number. (commit, forum)

Repaint performance is improved. The D2D renderer is now driven by WM_PAINT, allowing the system to better optimise drawing when there are multiple heavy plugin editors all onscreen at the same time. (commit, commit, forum)

JUCE no longer dynamically loads functions that have shipped with Windows 10 for years. (commit and many follow-ups)

Mirrored vertical/horizontal flip transforms now draw correctly on the D2D renderer. (commit, forum)

Several Windows resize handling issues are fixed. (commit, forum, commit, commit, forum).

iOS

iOS input handling improvements, including improved handling of different pointer types (touch, mouse, pencil). Apple pencil hover states work properly. (commit, forum, commit, commit)

Switching between text input targets now reloads the input view instead of restarting it, resulting in smoother transitions. (commit)

There’s a new warning when JUCE_IN_APP_PURCHASES_USE_SANDBOX_ENVIRONMENT is defined, so you don’t ship sandbox-mode IAP to the store by accident. (commit)

Linux

JUCE now uses gio to implement moveToTrash, complying with the FreeDesktop.org Trash specification (commit)

WebView ResourceProvider throughput performance increased. (commit)

Lots of WebView fixes (commit, commit, commit)

PopupMenu scaling fixed in plugins where the host (e.g. Ardour) overrides the system scale. (commit, forum)

Dependencies

Xcode 26.4.1 support (commit, commit)

The ASIO SDK is now bundled in the JUCE source. Steinberg relicensed the ASIO SDK under GPLv3. Both the DemoRunner and AudioPluginHost have ASIO enabled by default. (commit)

The ARA SDK was updated to 2.3.0. New helpers AudioProcessor::getARAExtensions() / AudioProcessorEditor::getARAExtensions() are now the preferred way to access ARA extensions — deriving from the extension types is being phased out. (commit, commit, commit, commit)

Breaking Change Highlights

Please see BREAKING_CHANGES.md for more detail. Here are some highlights:

Typeface::getStringWidth(), Font::getStringWidth() and their friends that have been deprecated since JUCE 8 are now removed in this release. Replace with GlyphArrangement::getStringWidth() and TextLayout::getStringWidth() which takes font-fallback and shaping into account. (commit)

Windows hi-dpi behaviour in plugins is now consistent with behaviour in Standalones. Peers (heavyweight components) will adopt the scale factor of the enclosing display by default, with new API to override. Previously, plugins would always report a scale factor of 1. (commit, forum, commit)

The AlertWindow::show return value is now consistent between native and non-native windows. Please look at your code to make sure it’s handling the updated return values. (commit, forum)

If you use ARA, it must be updated to version 2.3.0. The ARA::ChannelArrangement type is replaced by ARA::ChannelFormat. A new type member ARAConfigurationType has been added to ARADemoPluginDocumentControllerSpecialisation.

The ExtensionsVisitor type and functions on VSTPluginFormatHeadless have been removed. Use the new member functions of AudioPluginInstance. (commit, commit, commit)

20 Likes

Would it be possible to get a global componentPainted()callback? Or at least one for each child? It seems with the current API I need to iterate over every component in my app and add a listener. Or am I misunderstanding the api? I’m trying to remove my hacks from juce and update my ComponentProfiler that looks like this to use the new api.

The most parent componentPainted() callback will include the time it took to paint itself and all it’s children in the totalPaintDuration. However, if you want to know how much time is spent painting each child individually, you will need to add yourself as a listener of each child.

A nice addition would be to limit the number of busses to 1 by default in the projucer generated plugin code.

i agree with RolandMR it’s a bit inconvenient, something more advanced and ergonomic can be designed for juce in 2026.

Thanks for the feedback. The initial API was designed to be relatively low level, aimed predominately at those building tools around it while trying to keep default overheads to a minimum. It shouldn’t be too difficult to build upon the API that’s there. There is an example in the new Demo in the DemoRunner. Based on feedback we may extend it within the framework itself, we’re open to suggestions. Would it be that you want to just listen to one component and get callbacks for that component and all it’s children? Or would you expect a single callback with all the data? Or something else entirely?

Either one global callback or you could listen to a root object. It doesn’t really matter either way. If it was global you could use isParentOf to ignore components you don’t care about. Or you could iterate over the top level windows and list to each. Both are a lot easier that the current API of adding a listener to every Component, and then overriding componentChildrenChanged and trying to figure out which child was added (of any) and then adding the listeners.

I think there’s a bug (or a breaking change) in the JUCE 8.0.13 Projucer. It now always sets Code Signing Inject Base Entitlements to ‘Yes’ for the Xcode / macOS exporter in the Signing section, which is OK for debug but it’s also set for Release builds, which makes it impossible to get anything notarised. I believe this wasn’t the case in previous JUCE versions?

Can override this with a custom Xcode flag:
CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO

I’ve taken a quick look, it seems to me that both the older version of JUCE and the latest version leave the CODE_SIGN_INJECT_BASE_ENTITLEMENTS flag alone and this defaults to YES. However, one key difference is that the older version of the Projucer wouldn’t use the same signing command before copying a plugin. Are you maybe notarising the one copied to the plugin directory rather than the copy in your build directory? I think the correct solution is probably to set CODE_SIGN_INJECT_BASE_ENTITLEMENTS to NO.

I’m using the binaries from the build folder; I’ll experiment with the custom Xcode flag to disable the base entitlements - thanks for the quick response!

If i were a JUCE customer, this is what i would have expected instead, we are in 2026 after all :rofl:

1. Measurement Model: Pull vs. Push

JUCE fires componentPainted() synchronously inside the paint path, the callback executes while internalPaint() is still on the stack. The diagnostic struct is built inline and handed to every registered ComponentListener. This means:

  • Listener code runs during paint, contributing to the very time being measured.
  • Any listener that allocates (e.g., to log or update a table) introduces paint jitter.
  • ComponentListener is a general observer; paint diagnostics piggyback on it with no isolation.

YUP fires nothing during the paint path. PaintProfileScope records to a pre-allocated ring buffer slot in its destructor, then returns. A UI tool calls createSnapshot() from the render thread at its own cadence. The measurement and the consumer are decoupled.

2. History vs. Single-Sample

JUCE delivers one ComponentPaintDiagnostics per paint call. There is no built-in history. Callers that want trends must maintain their own rolling storage.

YUP builds history into PaintProfileStats: a pre-allocated ring buffer (sampleCapacity = 300 by default, five seconds at 60 Hz). summarize() computes min, max, mean, p50, p95, p99 from the buffer on demand. createHistogram() produces a bucket distribution. No listener code needed; the data is always there.

3. Time Decomposition

JUCE exposes four timers:

  • paintDuration: paint() only
  • paintOverChildrenDuration: paintOverChildren() only
  • applyEffectDuration: ImageEffectFilter::applyEffect()
  • totalPaintDuration: full cycle

There is no explicit child-subtree time and no framework overhead term.

YUP plan exposes PaintProfileTimeKind with four orthogonal buckets:

  • self: paint() + paintOverChildren() for this component
  • children: sum of child internalPaint() totals via thread-local stack
  • framework: total - self - children (clipped to zero), traversal, clip setup, opacity, opaque-child skip logic
  • total: full internalPaint() duration

The children and framework decomposition is what tells you whether slowness is in your code, your children’s code, or the framework’s traversal overhead. JUCE has no equivalent.

4. Global Frame Totals

JUCE has no frame-level measurement. Each component’s timing is independent.

YUP adds PaintProfiler::beginFrame() / endFrame() hooks in the native renderer. The profiler maintains a separate PaintProfileStats for global frame totals. Snapshot::globalFrameHistogram shows end-to-end frame time distribution without double-counting parent and child work. This is the histogram a developer watches to see if a frame budget is being met.

5. Zero-Cost Disabled Builds

JUCE always compiles diagnostic support. The ComponentPaintDiagnostics struct and the internal diagnostics& parameters are present in every build.

YUP uses #if YUP_ENABLE_COMPONENT_PAINT_PROFILING. When the flag is off:

  • No PaintProfileStats member on Component
  • No ring buffer allocation
  • No PaintProfileScope object
  • One unconditional branch removed from internalPaint()

Disabled builds pay zero cost in binary size and zero branches in the hot path.

6. Session Lifecycle

JUCE has no session concept. Diagnostics run for every listener that is registered, with no subtree scoping.

YUP introduces PaintProfiler::ScopedSession:

  • startSession(root, options) recursively enables a subtree and returns an RAII handle.
  • setPaused() suspends recording without deregistering.
  • reset() clears only the session’s components.

This makes temporary profiling in a demo or diagnostic tool safe and scoped without manual cleanup.

7. Allocation Budget

In JUCE componentPainted() listeners receive a freshly constructed ComponentPaintDiagnostics by value on each call. Listeners that store history must allocate on every callback or plan their allocations wisely.

In YUP the ring buffers are allocated once at session start. recordSample() overwrites one slot, no allocation. copySamples() and createHistogram() allocate, but they are tool-path (dashboard redraw), not paint-path.

8. Thread Safety Contract

In JUCE the callback fires on the render thread; listeners are responsible for their own locking if they touch shared state.

YUP makes the contract explicit: recordSample() must not lock. The registryLock guards only registration and snapshot assembly, never the sample recording path. Snapshot creation is documented as render-thread work so no cross-thread locking is needed in practice.

Results:

Happy coding !

Thanks @kunitoki it’s useful to have some real world examples. This all looks great and I largely agree with the suggestions, but the idea was to get this out as a first step to allow developers to build tools (like what you’ve done in YUP) on top of what JUCE offers without needing to make changes to the JUCE codebase.

We specifically avoided having it behind a preprocessor macro so that tools like Melatonin Inspector can measure these things without needing developers to make a special build. There could even be a feature bundled in release builds to get results from customers if needs be. Again maybe this is something other developers might expand on and share via the marketplace.

We did perform measurements to check what impact this has when there are no listeners in place or even if there is a lot of (10,000) listeners that are not implementing the callbacks to ensure any impact from the implementation is an absolute minimum, even in a debug build.

Correct me if I’m wrong but I think everything (or very close to everything) suggested is possible to build on top of the component diagnostics where-as previously it would have required changes to the JUCE codebase.

If there is strong demand for another abstraction layer on top of what is currently offered I’m sure we’ll take that into consideration for future versions.

Thanks again for the feedback.

The preprocessor macro can still be enabled if you need it in release (and want it there), with the ability to just not turn on capturing.

I wonder if people will ship to customers with Melatonin inspector in (and enabling that will require a special build anyway), potentially creating issues if for some reason a Melatonin release will by mistake start allocating inside the listener callback even when disabled.

There is no frame-level measurement in that, every component measurement is independent and there is no correlation to frame deadlines, making it impossible to attribute UI work to an actual presented frame budget or detect deadline misses in a meaningful way, which is rather important on high FPS realtime rendering apps. And you can’t do that in just Component listeners without ingraining the infrastructure into your renderer. Just my 2 cents.

I could be wrong, but I think there are already cases of this.

I’ve seen cases of special key combinations, an option in a menu, or it could be a setting stored in a file on disk.

If the inspector isn’t created / attached to anything then it shouldn’t be receiving callbacks so I think it’s relatively safe in that regard.

I appreciate the feedback and I see your point, I’ll give this some thought. You could approximate total UI paint cost in a single frame by listening to the top-level component but that is only part of the story. Ultimately this wasn’t the focus of the work and I think that would be a separate chunk of work.

The work carried out here mostly came off the back of the component paint refactoring, the demo that was added to demonstrate that work (which in-itself came off the back of a talk I did in Japan last year and later a talk I did for ADC), and this FR: Callback or other mechanism for exposing Component debugging/timing

Maybe all that’s needed for now is an easy way to add some sort of global component listener?

1 Like

:raised_hand: Sine Machine ships the inspector in release. Surge and a handful of other commercial projects do as well.

(I’ve been thinking about hiding/removing it for UX purposes, 0 of my customers care and it’s probably overwhelming to accidentally open).

Right now it’s just 1 std::array ring buffer cleared and reused when you switch components. We have a couple maintainers who would hopefully catch any perf ugliness accidentally committed.

FWIW, I kinda prefer having high quality “ingredients” to make my custom tooling vs. if JUCE guessed at what I wanted and made a performance tool themselves.

+1 to this ingredient. I’d love to bake in a version of @RolandMR’s cool “all components” histogram in the inspector!

2 Likes

Fair enough! Keep up with the tooling!

1 Like

My approach doesn’t implement a performance tool, that one is written in the final app, you can present the data the way you want.

You too!

Small bug introduced in 8.0.13:

When switching from software to Direct2D renderer the components are not repainted with the new renderer until something else triggers a repaint.

eg:

peer->setCurrentRenderingEngine(1);
peer->repaint({ 0, 0, 1, 1 }); // necessary in 8.0.13