Skip to content

AUv2: request_resize reenters the plugin, ignores the calling thread, and always reports success #573

Description

@defiantnerd

Found while reviewing #572. That one was a VST3 problem — an inverted tresult and a cached rect handed to the host while a reentrant getSize was writing to it. AUv2 does not have that shape, but the same corner is wrong here in a different way, and AUv2 is the only one of the four wrappers left with it.

The call chain

plugin -> clap_host_gui.request_resize(w, h)
  WrapAsAUV2::gui_request_resize          src/detail/auv2/auv2_base_classes.h:678
  auv2shared_mm_request_resize            src/detail/auv2/auv2_shared.mm:10
  [nsv setFrame:NSMakeRect(0, 0, w, h)]   src/detail/auv2/auv2_shared.mm:15
  -setFrame: override                     src/detail/auv2/wrappedview.asinclude.mm:194
  gui->set_size(plugin, w, h)             src/detail/auv2/wrappedview.asinclude.mm:204

_window holds the CLAP_WRAPPER_COCOA_CLASS_NSVIEW instance, and that class overrides -setFrame:, so the message lands on the override and set_size() goes straight back into the plugin — from inside the plugin's own request_resize() call, with the numbers it just passed in.

1. The plugin is reentered synchronously

Every other wrapper deliberately avoids this:

  • VST3 calls IPlugFrame::resizeView and lets the host come back through onSize later. That is the sequence Fix VST3 request_resize #572 just aligned with.
  • AUv3 hands the request to the main queue (src/detail/auv3/auv3_audiounit.mm:577) and returns.
  • The standalone forwards to onRequestResize and returns false when there is nothing wired up.

A plugin asks for 800x600 and gets set_size(800, 600) called on it before request_resize has returned. Editors that recompute their layout in set_size and ask again for the size they settle on will bounce between the two.

src/detail/auv2/wrappedview.asinclude.mm:29 already declares the flag for this:

bool setSizeByZoom;  // use this flag to see if resize comes from here or from external

It is never read or written anywhere in the tree. Same for underlyingUISize and lastScale on the two lines above it.

2. Nothing marshals to the main thread

request_resize is [thread-safe] in clap/ext/gui.h, so a plugin may call it from a worker. AUv2 then calls -setFrame: off that thread, which AppKit does not allow, and set_size() off it as well, which CLAP marks [main-thread].

Since #536 the -setFrame: override also takes the AU main-thread mutex, and the SDK holds that across its whole non-realtime dispatch, so an off-thread request_resize can park a plugin worker behind host initialization.

VST3 checks the thread id and defers through an atomic serviced in onIdle (src/wrapasvst3.cpp:1665). AUv3 dispatches. AUv2 does neither.

3. The answer to the plugin is always true

src/detail/auv2/auv2_shared.mm:17 returns true unconditionally and gui_request_resize passes it through, so the plugin never finds out the host did nothing with the request.

That also contradicts gui_can_resize() four lines above it (src/detail/auv2/auv2_base_classes.h:674), which returns false. The wrapper tells the plugin the host cannot resize, then tells it the resize worked.

4. setFrame: does not ask adjust_size

src/detail/auv2/wrappedview.asinclude.mm:204 pushes the host's size into set_size as-is. VST3 runs it through adjust_size in both onSize and checkSizeConstraint and uses what comes back, and #565 did the same for AUv3.

Suggested shape

  • Marshal the request to the main thread. Either dispatch_async onto the main queue the way AUv3 does, or park it and service it from the idle tick the way VST3 does — AUv2 already has onIdle().
  • Use setSizeByZoom for what it was declared for: set it around the wrapper-initiated -setFrame: so the override skips the set_size echo, and clear it after. A resize the host initiates still has to reach the plugin.
  • Run the size through adjust_size in -setFrame: before calling set_size, and honour the result.
  • Decide what the return value should say. AUv2 Cocoa gives no synchronous answer from the host, so either keep returning true — clap/ext/gui.h allows that for a request that is handled asynchronously, provided the host calls set_size to revert if it cannot satisfy it — or make gui_can_resize() honest first. Right now the two disagree with each other.

Note for whoever picks this up

ui_connection::_window is declared clap_window_t * but carries the NSView. That is the contract on AUv2, not an oversight — the cast in at wrappedview.asinclude.mm:99 and back out at auv2_shared.mm:14 is deliberate and the compiler cannot check it. Leave it alone.

Building and checking

macOS only, AUv2 is not built anywhere else.

cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=Debug \
      -DCLAP_WRAPPER_DOWNLOAD_DEPENDENCIES=TRUE \
      -DCLAP_WRAPPER_BUILD_AUV2=TRUE \
      -DCLAP_WRAPPER_OUTPUT_NAME=testplug
cmake --build build --config Debug

and the clap-first component through auval, which is what CI runs:

cmake -S . -B build -DCLAP_WRAPPER_BUILD_TESTS=TRUE
cmake --build build --config Debug --target clap-first-distortion_all
mkdir -p ~/Library/Audio/Plug-Ins/Components/
cp -R "build/clap-first-distortion_assets/ClapFirst Bad Distortion.component" ~/Library/Audio/Plug-Ins/Components/
killall -9 AudioComponentRegistrar || true
auval -strict -v aufx BdDt FrAD

The wrapper targets build with -Wall -Wextra -Wpedantic -Werror, and commits have to be clang-formatted against .clang-format.

auval never opens the editor, so it will not exercise any of this. It is there to show nothing else broke. Confirming the fix itself needs a real host — Logic or Live with a plugin that resizes itself.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    MacEverything related to macOSauv2Everything related to AudioUnit V2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions