wip walkthrough 2 bridge: jobEvent_/MsgWaitForMultipleObjects pump, SetThreadPriority + PowerThrottling, isBypassed silence quanh assign/unload, closeGUI view->removed(), reloadForGUI reuse instance, bo mute khi GUI mo, CoInitializeEx MTA — CHƯA verified: driver test 1/3 PASS (AV c0000005 Nexus!GetPluginFactory luc attachView ch5, nghi MTA/bo mute), exe deploy giu ban 0cec3d3 (28104a47)
This commit is contained in:
@@ -51,6 +51,7 @@ public:
|
||||
// with the channel worker pumping messages (see bridge main.cpp).
|
||||
virtual bool reloadForGUI() { return true; }
|
||||
virtual bool attachView(void* /*parentWindowHandle*/) { return false; }
|
||||
virtual void resizeView(int /*w*/, int /*h*/) {}
|
||||
// True while the editor view is attached (GUI window open). The bridge uses
|
||||
// it to dedupe repeated OPEN_GUI (frontend retry spam) — re-attaching a
|
||||
// view that is already attached corrupts plugins (Nexus createView→null).
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
|
||||
// FluidSynth (.sf2 / .sf3)
|
||||
class FluidSynthInstrument : public INativeInstrument {
|
||||
@@ -119,6 +120,9 @@ public:
|
||||
|
||||
size_t count() const { std::lock_guard<std::mutex> lock(mu_); return channels_.size(); }
|
||||
|
||||
void setBypassed(bool on) { isBypassed_.store(on); }
|
||||
bool isBypassed() const { return isBypassed_.load(); }
|
||||
|
||||
private:
|
||||
static std::unique_ptr<INativeInstrument> create_instrument(InstrumentType type);
|
||||
|
||||
@@ -134,6 +138,7 @@ private:
|
||||
// compare inside -- Windows plugin paths are case-insensitive.
|
||||
bool channelQuiet(uint32_t ch) const;
|
||||
std::function<bool(const std::string&)> editorOpenFn_;
|
||||
std::atomic<bool> isBypassed_{false};
|
||||
};
|
||||
|
||||
#endif // NATIVE_INSTRUMENT_ENGINE_H
|
||||
|
||||
@@ -27,6 +27,7 @@ public:
|
||||
bool openGUI(void* parentWindowHandle) override;
|
||||
bool reloadForGUI() override;
|
||||
bool attachView(void* parentWindowHandle) override;
|
||||
void resizeView(int w, int h) override;
|
||||
bool hasAttachedView() const override;
|
||||
void closeGUI() override;
|
||||
bool reload() override;
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// 1. SOUNDFONT ENGINE (.SF2 / .SF3) VIA FLUIDSYNTH C API
|
||||
@@ -194,7 +196,17 @@ bool InstrumentEngineManager::assign(uint32_t channel, InstrumentType type,
|
||||
// flows to it again.
|
||||
reloadingCh_[channel] = false;
|
||||
}
|
||||
oldInst.reset();
|
||||
if (oldInst) {
|
||||
isBypassed_.store(true);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
try {
|
||||
oldInst.reset();
|
||||
} catch (...) {
|
||||
isBypassed_.store(false);
|
||||
throw;
|
||||
}
|
||||
isBypassed_.store(false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -211,7 +223,17 @@ void InstrumentEngineManager::unload(uint32_t channel) {
|
||||
paths_.erase(channel);
|
||||
reloadingCh_[channel] = false;
|
||||
}
|
||||
oldInst.reset();
|
||||
if (oldInst) {
|
||||
isBypassed_.store(true);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
try {
|
||||
oldInst.reset();
|
||||
} catch (...) {
|
||||
isBypassed_.store(false);
|
||||
throw;
|
||||
}
|
||||
isBypassed_.store(false);
|
||||
}
|
||||
}
|
||||
|
||||
std::string InstrumentEngineManager::pathOf(uint32_t channel) {
|
||||
@@ -313,6 +335,11 @@ void InstrumentEngineManager::allNotesOff() {
|
||||
}
|
||||
|
||||
void InstrumentEngineManager::renderAll(float* outputL, float* outputR, uint32_t numSamples) {
|
||||
if (isBypassed_.load()) {
|
||||
std::memset(outputL, 0, numSamples * sizeof(float));
|
||||
std::memset(outputR, 0, numSamples * sizeof(float));
|
||||
return;
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
std::memset(outputL, 0, numSamples * sizeof(float));
|
||||
std::memset(outputR, 0, numSamples * sizeof(float));
|
||||
|
||||
@@ -97,7 +97,13 @@ constexpr ParamID kHostMidiProgramChange = 0x3000;
|
||||
// Minimal IComponentHandler so the plugin can inform the host of param edits.
|
||||
class HostComponentHandler : public IComponentHandler {
|
||||
public:
|
||||
tresult queryInterface(const char*, void** v) override {
|
||||
tresult queryInterface(const char* iid, void** v) override {
|
||||
if (std::memcmp(iid, Steinberg::Vst::IComponentHandler::iid, 16) == 0 ||
|
||||
std::memcmp(iid, Steinberg::FUnknown::iid, 16) == 0) {
|
||||
*v = static_cast<Steinberg::Vst::IComponentHandler*>(this);
|
||||
addRef();
|
||||
return kResultOk;
|
||||
}
|
||||
*v = nullptr;
|
||||
return kNoInterface;
|
||||
}
|
||||
@@ -112,7 +118,13 @@ public:
|
||||
// Minimal IPlugFrame so plugins can resize their editor view.
|
||||
class HostPlugFrame : public Steinberg::IPlugFrame {
|
||||
public:
|
||||
tresult queryInterface(const char*, void** v) override {
|
||||
tresult queryInterface(const char* iid, void** v) override {
|
||||
if (std::memcmp(iid, Steinberg::IPlugFrame::iid, 16) == 0 ||
|
||||
std::memcmp(iid, Steinberg::FUnknown::iid, 16) == 0) {
|
||||
*v = static_cast<Steinberg::IPlugFrame*>(this);
|
||||
addRef();
|
||||
return kResultOk;
|
||||
}
|
||||
*v = nullptr;
|
||||
return kNoInterface;
|
||||
}
|
||||
@@ -531,18 +543,6 @@ bool Vst3Instrument::reloadForGUI() {
|
||||
#else
|
||||
auto* s = static_cast<Vst3HostState*>(state_);
|
||||
if (!s || !s->controller) return false;
|
||||
if (s->view && guiAttached_) return true;
|
||||
if (hasAttachedOnce_ && !guiAttached_) {
|
||||
// Reopen GUI sau khi dong: mot so plugin (Nexus...) hang o
|
||||
// view->attached() LAN 2 tren cung component instance. Tao lai inst
|
||||
// moi tren CUNG worker thread (COM STA con song) roi attach lai.
|
||||
// PHAN NAY PHAI chay tren worker thread — apartment cua channel song
|
||||
// o day; chay tren thread tam thi apartment moi chet ngay sau do
|
||||
// (reopen lan sau -> treo nhu bridge_like).
|
||||
if (!reload()) return false;
|
||||
s = static_cast<Vst3HostState*>(state_);
|
||||
if (!s || !s->controller) return false;
|
||||
}
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
@@ -598,6 +598,16 @@ bool Vst3Instrument::attachView(void* parentWindowHandle) {
|
||||
#endif
|
||||
}
|
||||
|
||||
void Vst3Instrument::resizeView(int w, int h) {
|
||||
#ifdef HAVE_VST3SDK
|
||||
auto* s = static_cast<Vst3HostState*>(state_);
|
||||
if (s && s->view) {
|
||||
Steinberg::ViewRect rect(0, 0, w, h);
|
||||
s->view->onSize(&rect);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Vst3Instrument::hasAttachedView() const {
|
||||
return state_ != nullptr && guiAttached_;
|
||||
}
|
||||
@@ -632,16 +642,16 @@ void Vst3Instrument::closeGUI() {
|
||||
return;
|
||||
#else
|
||||
auto* s = static_cast<Vst3HostState*>(state_);
|
||||
// Do NOT call s->view->removed(): Nexus's removed() enters an internal
|
||||
// modal wait for a message only this thread's message pump can dispatch
|
||||
// — but this thread is inside removed(), so it deadlocks forever
|
||||
// (observed: closeGUI job never returns; wdisp flood from the editor).
|
||||
// Nulling the view and letting the next terminate() (reload / destructor)
|
||||
// tear the editor windows down on this same thread is the deterministic
|
||||
// path out. Callers (post_close_gui / load job) additionally destroy the
|
||||
// editor's child windows right here on this thread so the plugin stops
|
||||
// pumping; the leaked editor object is inert once its windows are gone.
|
||||
if (s) s->view = nullptr;
|
||||
if (s && s->view) {
|
||||
if (guiAttached_) {
|
||||
try {
|
||||
s->view->removed();
|
||||
} catch (...) {
|
||||
std::cerr << "[Vst3Instrument] Exception inside view->removed()" << std::endl;
|
||||
}
|
||||
}
|
||||
s->view = nullptr;
|
||||
}
|
||||
guiAttached_ = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
+150
-126
@@ -190,6 +190,33 @@ static LRESULT CALLBACK VstWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARA
|
||||
}
|
||||
}
|
||||
if (ch != UINT32_MAX) post_close_gui(ch, hwnd);
|
||||
} else if (uMsg == WM_ERASEBKGND) {
|
||||
return 1; // Do not paint background to prevent white flashes
|
||||
} else if (uMsg == WM_PAINT) {
|
||||
PAINTSTRUCT ps;
|
||||
HDC hdc = BeginPaint(hwnd, &ps);
|
||||
EndPaint(hwnd, &ps);
|
||||
return 0;
|
||||
} else if (uMsg == WM_SIZE) {
|
||||
int w = LOWORD(lParam);
|
||||
int h = HIWORD(lParam);
|
||||
HWND child = FindWindowExA(hwnd, nullptr, nullptr, nullptr);
|
||||
if (child) {
|
||||
MoveWindow(child, 0, 0, w, h, TRUE);
|
||||
}
|
||||
uint32_t ch = UINT32_MAX;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_guiMutex);
|
||||
auto it = g_hwndToCh.find(hwnd);
|
||||
if (it != g_hwndToCh.end()) ch = it->second;
|
||||
}
|
||||
if (ch != UINT32_MAX && g_engine) {
|
||||
auto* inst = g_engine->get(ch);
|
||||
if (inst) {
|
||||
inst->resizeView(w, h);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return DefWindowProcA(hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
@@ -209,18 +236,17 @@ static std::map<uint32_t, std::pair<void*, std::string>> g_pendingGui;
|
||||
// B9: native Win32 window for the VST editor (replaces the WebView2 surface —
|
||||
// the HTML window was drawn ON TOP of the plugin GUI). MUST be created on the
|
||||
// ChannelWorker thread so the worker's idle message pump services its messages.
|
||||
static std::once_flag registerWindowOnce;
|
||||
static void* create_native_vst_window(const char* title) {
|
||||
#ifdef _WIN32
|
||||
static const char* kWndClass = "SonicForge_Native_VST3_Class";
|
||||
static bool registered = false;
|
||||
if (!registered) {
|
||||
std::call_once(registerWindowOnce, []() {
|
||||
WNDCLASSA wc = {};
|
||||
wc.lpfnWndProc = VstWindowProc;
|
||||
wc.hInstance = GetModuleHandleA(nullptr);
|
||||
wc.lpszClassName = kWndClass;
|
||||
RegisterClassA(&wc);
|
||||
registered = true;
|
||||
}
|
||||
});
|
||||
HWND hwnd = CreateWindowExA(0, kWndClass, title ? title : "VST",
|
||||
WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
|
||||
CW_USEDEFAULT, CW_USEDEFAULT, 800, 600,
|
||||
@@ -242,6 +268,9 @@ static void* create_native_vst_window(const char* title) {
|
||||
class ChannelWorker {
|
||||
public:
|
||||
ChannelWorker() {
|
||||
#ifdef _WIN32
|
||||
jobEvent_ = CreateEvent(nullptr, FALSE, FALSE, nullptr);
|
||||
#endif
|
||||
th_ = std::thread([this] {
|
||||
#ifdef _WIN32
|
||||
{
|
||||
@@ -256,6 +285,9 @@ public:
|
||||
// Global hooks fail from an exe module — install per thread, and
|
||||
// this thread creates the plugin editor windows.
|
||||
SetWindowsHookExW(WH_CBT, ImeCbtHookProc, GetModuleHandleW(nullptr), GetCurrentThreadId());
|
||||
|
||||
// Elevate VST GUI thread priority to prevent throttling
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);
|
||||
#endif
|
||||
std::unique_lock<std::mutex> lk(mu_);
|
||||
for (;;) {
|
||||
@@ -268,18 +300,14 @@ public:
|
||||
lk.lock();
|
||||
continue;
|
||||
}
|
||||
cv_.wait_for(lk, std::chrono::milliseconds(5));
|
||||
// Pump THIS thread's message queue while idle — VST editor
|
||||
// windows are created on this thread, their messages must be
|
||||
// dispatched here or the editor freezes after attach.
|
||||
lk.unlock();
|
||||
MSG msg;
|
||||
// Cap the idle pump: a busy editor (Nexus posts timer/paint
|
||||
// messages continuously) otherwise starves this worker's job
|
||||
// queue forever — post_close_gui never runs, Option B's 5s
|
||||
// close wait times out, two editors stay alive (deadlock).
|
||||
for (int pumped = 0; pumped < 16; ++pumped) {
|
||||
bool hadMessages = false;
|
||||
// Cap the idle pump per iteration to avoid starving the job queue,
|
||||
// but pump up to 128 messages for high GUI responsiveness.
|
||||
for (int pumped = 0; pumped < 128; ++pumped) {
|
||||
if (!PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) break;
|
||||
hadMessages = true;
|
||||
// CRASH FIX (0xc000041d): JUCE editor child windows of a
|
||||
// plugin DLL are owned by the FIRST worker that ran the
|
||||
// DLL's global JUCE MessageManager - not by the channel
|
||||
@@ -307,7 +335,7 @@ public:
|
||||
if (std::strncmp(cls, "JUCE_", 5) == 0) {
|
||||
DWORD ot = GetWindowThreadProcessId(msg.hwnd, nullptr);
|
||||
if (ot) {
|
||||
std::lock_guard<std::mutex> lk(g_juceTidsMutex);
|
||||
std::lock_guard<std::mutex> lock(g_juceTidsMutex);
|
||||
g_juceOwnerTids.insert(ot);
|
||||
}
|
||||
}
|
||||
@@ -328,6 +356,19 @@ public:
|
||||
}
|
||||
}
|
||||
lk.lock();
|
||||
if (!hadMessages && jobs_.empty()) {
|
||||
#ifdef _WIN32
|
||||
if (jobEvent_) {
|
||||
lk.unlock();
|
||||
MsgWaitForMultipleObjects(1, &jobEvent_, FALSE, 10, QS_ALLINPUT);
|
||||
lk.lock();
|
||||
} else {
|
||||
cv_.wait_for(lk, std::chrono::milliseconds(2));
|
||||
}
|
||||
#else
|
||||
cv_.wait_for(lk, std::chrono::milliseconds(2));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#ifdef _WIN32
|
||||
{
|
||||
@@ -343,14 +384,30 @@ public:
|
||||
std::lock_guard<std::mutex> lk(mu_);
|
||||
stop_ = true;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
if (jobEvent_) {
|
||||
SetEvent(jobEvent_);
|
||||
}
|
||||
#endif
|
||||
cv_.notify_all();
|
||||
if (th_.joinable()) th_.join();
|
||||
#ifdef _WIN32
|
||||
if (jobEvent_) {
|
||||
CloseHandle(jobEvent_);
|
||||
jobEvent_ = nullptr;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
void post(std::function<void()> job) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(mu_);
|
||||
jobs_.push_back(std::move(job));
|
||||
}
|
||||
#ifdef _WIN32
|
||||
if (jobEvent_) {
|
||||
SetEvent(jobEvent_);
|
||||
}
|
||||
#endif
|
||||
cv_.notify_all();
|
||||
}
|
||||
// Run job on THIS worker and wait (5s cap) until it finished. Used to
|
||||
@@ -372,6 +429,11 @@ public:
|
||||
doneCv.notify_all();
|
||||
});
|
||||
}
|
||||
#ifdef _WIN32
|
||||
if (jobEvent_) {
|
||||
SetEvent(jobEvent_);
|
||||
}
|
||||
#endif
|
||||
cv_.notify_all();
|
||||
std::unique_lock<std::mutex> dk(doneMx);
|
||||
return doneCv.wait_for(dk, std::chrono::seconds(5), [&] { return done; });
|
||||
@@ -383,6 +445,9 @@ private:
|
||||
std::condition_variable cv_;
|
||||
std::deque<std::function<void()>> jobs_;
|
||||
bool stop_ = false;
|
||||
#ifdef _WIN32
|
||||
HANDLE jobEvent_ = nullptr;
|
||||
#endif
|
||||
};
|
||||
|
||||
// --- same-plugin-DLL silence helpers ----------------------------------------
|
||||
@@ -573,46 +638,18 @@ static void post_close_gui(uint32_t ch, HWND hwnd) {
|
||||
// Silence first: the audio loop must not process() the instance
|
||||
// while we tear its editor down on this thread.
|
||||
g_engine->setReloading(ch, true);
|
||||
// Close-in-flight: until the fresh instance is loaded, no other
|
||||
// job may unmute this channel (its plugin is being torn down).
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_attachMutex);
|
||||
g_closeInFlight[ch] = true;
|
||||
}
|
||||
if (auto* i = g_engine->get(ch)) {
|
||||
// closeGUI() nulls the view WITHOUT view->removed() (Nexus
|
||||
// removed() deadlocks this worker - modal wait for a message
|
||||
// only its own pump can dispatch, but it is inside removed()).
|
||||
try {
|
||||
i->closeGUI();
|
||||
} catch (...) {
|
||||
std::cerr << "[NativeBridge] closeGUI EXCEPTION ch=" << ch
|
||||
<< " — plugin threw" << std::endl;
|
||||
}
|
||||
// CRASH FIX: the editor was dropped WITHOUT view->removed() —
|
||||
// the instance's editor state is dangling, so the audio loop
|
||||
// must NOT process() it. Rebuild a fresh instance NOW on this
|
||||
// worker (its COM STA apartment is alive); reload() ==
|
||||
// terminate + loadPlugin (createInstance). Same-plugin
|
||||
// channels are silenced so no second thread enters this DLL
|
||||
// during createInstance (2 threads in one DLL -> Nexus exits
|
||||
// silently, observed in probes). Serialized with other loads
|
||||
// by g_loadMutex.
|
||||
std::vector<uint32_t> sp = same_plugin_channels(ch);
|
||||
for (uint32_t y : sp) {
|
||||
g_engine->setReloading(y, true);
|
||||
}
|
||||
#ifdef _WIN32
|
||||
// CRASH FIX (0xc000041d): destroy the editor's child windows
|
||||
// on the thread that OWNS them (the plugin's JUCE
|
||||
// MessageManager thread - a single worker), never
|
||||
// cross-thread. Wait for each destroy to finish (owner worker
|
||||
// serializes it with its pump) BEFORE reload() - otherwise
|
||||
// reload's createInstance enters the DLL while the owner
|
||||
// thread is still inside the plugin wndproc (2 threads in one
|
||||
// DLL -> Nexus crash). Kept OUTSIDE g_loadMutex: the wait
|
||||
// must not block other loads (the owner worker may itself be
|
||||
// waiting on that lock).
|
||||
disable_ime_contexts(hwnd);
|
||||
if (hwnd && IsWindow(hwnd)) {
|
||||
while (HWND c = FindWindowExA(hwnd, nullptr, nullptr, nullptr)) {
|
||||
@@ -620,51 +657,13 @@ static void post_close_gui(uint32_t ch, HWND hwnd) {
|
||||
}
|
||||
}
|
||||
#endif
|
||||
{
|
||||
// reload() = terminate + loadPlugin (createInstance) —
|
||||
// serialized with all other plugin-DLL entry points.
|
||||
// Nexus may throw a C++ exception here too; catch it,
|
||||
// treat as failed reload, and let the restore below
|
||||
// unmute the channel (instance may be broken; next
|
||||
// load/assign rebuilds it).
|
||||
std::lock_guard<std::mutex> lg(g_loadMutex);
|
||||
try {
|
||||
i->reload();
|
||||
} catch (...) {
|
||||
std::cerr << "[NativeBridge] closeGUI reload EXCEPTION ch=" << ch
|
||||
<< " — plugin threw (createInstance)" << std::endl;
|
||||
}
|
||||
}
|
||||
// Editor detached + fresh instance loaded: drop editor-open
|
||||
// state (channelQuiet falls back to reloading flags only).
|
||||
clear_editor_open(ch);
|
||||
// createInstance is done: unmuting is safe again. But if an
|
||||
// attach for the SAME plugin path is still running (its
|
||||
// createView must not race process() on any same-path
|
||||
// channel), leave the silence in place — the attach job's
|
||||
// restore unmutes everything once attachView finished.
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_attachMutex);
|
||||
g_closeInFlight[ch] = false;
|
||||
}
|
||||
bool pathBusy = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_attachMutex);
|
||||
std::string p = lower_plugin_path(ch);
|
||||
pathBusy = !p.empty() &&
|
||||
std::find(g_attachPaths.begin(), g_attachPaths.end(), p) != g_attachPaths.end();
|
||||
}
|
||||
if (!pathBusy) {
|
||||
for (uint32_t y : sp) unmute_if_not_closing(y, "closeGUI");
|
||||
g_engine->setReloading(ch, false);
|
||||
}
|
||||
} else {
|
||||
// Instrument already gone (unloaded) — nothing to rebuild.
|
||||
g_engine->setReloading(ch, false);
|
||||
clear_editor_open(ch);
|
||||
}
|
||||
clear_editor_open(ch);
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_attachMutex);
|
||||
g_closeInFlight[ch] = false;
|
||||
}
|
||||
g_engine->setReloading(ch, false);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -672,6 +671,24 @@ static void post_close_gui(uint32_t ch, HWND hwnd) {
|
||||
int main(int argc, char* argv[]) {
|
||||
std::cout << "[NativeBridge] Starting DAW Host Bridge Engine..." << std::endl;
|
||||
#ifdef _WIN32
|
||||
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
|
||||
// Disable Power Throttling (Efficiency Mode) for this background process
|
||||
#ifndef PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION
|
||||
#define PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION 0x00000004
|
||||
#endif
|
||||
#ifndef PROCESS_POWER_THROTTLING_EXECUTION_SPEED
|
||||
#define PROCESS_POWER_THROTTLING_EXECUTION_SPEED 0x00000001
|
||||
#endif
|
||||
PROCESS_POWER_THROTTLING_STATE powerState = {};
|
||||
powerState.Version = PROCESS_POWER_THROTTLING_CURRENT_VERSION;
|
||||
powerState.ControlMask = PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION | PROCESS_POWER_THROTTLING_EXECUTION_SPEED;
|
||||
powerState.StateMask = 0; // Turn off throttling
|
||||
SetProcessInformation(GetCurrentProcess(), ProcessPowerThrottling, &powerState, sizeof(powerState));
|
||||
|
||||
// Elevate main audio processing thread to time-critical priority
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
|
||||
|
||||
// DPI: declare per-monitor v2 FIRST (before any window is created).
|
||||
// Without it, on scaled displays (125/150%) the plugin's own renderer
|
||||
// paints at 100% into an OS-scaled window -> blank/white panel with dark
|
||||
@@ -751,10 +768,8 @@ int main(int argc, char* argv[]) {
|
||||
// Editor-open predicate: channelQuiet() checks whether ANY editor is
|
||||
// attached for the channel's plugin DLL path (refcounted in
|
||||
// g_editorPathCount, updated by the open/close GUI jobs).
|
||||
instruments.setEditorOpenPredicate([](const std::string& lp) {
|
||||
std::lock_guard<std::mutex> lk(g_editorMutex);
|
||||
auto it = g_editorPathCount.find(lp);
|
||||
return it != g_editorPathCount.end() && it->second > 0;
|
||||
instruments.setEditorOpenPredicate([](const std::string& /*lp*/) {
|
||||
return false;
|
||||
});
|
||||
// Per-channel persistent workers: loadPlugin + openGUI run on the SAME
|
||||
// thread whose COM STA apartment stays alive for the channel's lifetime
|
||||
@@ -868,44 +883,9 @@ int main(int argc, char* argv[]) {
|
||||
if (!workers[guiCh]) {
|
||||
workers[guiCh] = std::make_unique<ChannelWorker>();
|
||||
}
|
||||
void* hwnd = (void*)arg1;
|
||||
#ifdef _WIN32
|
||||
// Window PHAI thuoc MAIN thread (audio loop pump nay dispatch
|
||||
// messages cua no moi vong lap). Tao/cap nhat window ngay tai day.
|
||||
HWND nativeHwnd = nullptr;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_guiMutex);
|
||||
auto it = g_guiWindows.find(guiCh);
|
||||
if (it != g_guiWindows.end()) nativeHwnd = (HWND)it->second;
|
||||
}
|
||||
if (hwnd == 0) {
|
||||
if (nativeHwnd && IsWindow(nativeHwnd)) {
|
||||
hwnd = (void*)nativeHwnd;
|
||||
SetWindowTextA(nativeHwnd, pluginId.c_str());
|
||||
ShowWindow(nativeHwnd, SW_SHOW);
|
||||
SetForegroundWindow(nativeHwnd);
|
||||
// Reuse: cap nhat USERDATA (channel+1) - inst CU da bi thay
|
||||
// the boi assign() -> WM_DESTROY sau nay lookup inst MOI.
|
||||
SetWindowLongPtrA(nativeHwnd, GWLP_USERDATA, (LONG_PTR)(guiCh + 1));
|
||||
} else {
|
||||
nativeHwnd = (HWND)create_native_vst_window(pluginId.c_str());
|
||||
if (!nativeHwnd) {
|
||||
std::cerr << "[NativeBridge] GUI create window FAILED plugin=" << pluginId << std::endl;
|
||||
return;
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_guiMutex);
|
||||
g_guiWindows[guiCh] = nativeHwnd; // keep window alive
|
||||
g_hwndToCh[nativeHwnd] = guiCh; // WM_DESTROY cleanup
|
||||
}
|
||||
SetWindowLongPtrA(nativeHwnd, GWLP_USERDATA, (LONG_PTR)(guiCh + 1));
|
||||
hwnd = (void*)nativeHwnd;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
workers[guiCh]->post([&instruments, guiCh, hwnd, arg2 = pluginId]() {
|
||||
workers[guiCh]->post([&instruments, guiCh, arg1, arg2 = pluginId]() {
|
||||
if (!instruments.get(guiCh)) {
|
||||
std::cerr << "[NativeBridge] GUI attach FAILED hwnd=" << hwnd
|
||||
std::cerr << "[NativeBridge] GUI attach FAILED hwnd=" << (void*)arg1
|
||||
<< " plugin=" << arg2 << " ch=" << guiCh << " (no instrument loaded)" << std::endl;
|
||||
return;
|
||||
}
|
||||
@@ -914,6 +894,44 @@ int main(int argc, char* argv[]) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
void* hwnd = (void*)arg1;
|
||||
#ifdef _WIN32
|
||||
HWND nativeHwnd = nullptr;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_guiMutex);
|
||||
auto it = g_guiWindows.find(guiCh);
|
||||
if (it != g_guiWindows.end()) nativeHwnd = (HWND)it->second;
|
||||
}
|
||||
if (hwnd == 0) {
|
||||
if (nativeHwnd && IsWindow(nativeHwnd)) {
|
||||
hwnd = (void*)nativeHwnd;
|
||||
SetWindowTextA(nativeHwnd, arg2.c_str());
|
||||
ShowWindow(nativeHwnd, SW_SHOW);
|
||||
SetForegroundWindow(nativeHwnd);
|
||||
// Reuse: cap nhat USERDATA (channel+1) - inst CU da bi thay
|
||||
// the boi assign() -> WM_DESTROY sau nay lookup inst MOI.
|
||||
SetWindowLongPtrA(nativeHwnd, GWLP_USERDATA, (LONG_PTR)(guiCh + 1));
|
||||
} else {
|
||||
nativeHwnd = (HWND)create_native_vst_window(arg2.c_str());
|
||||
if (!nativeHwnd) {
|
||||
std::cerr << "[NativeBridge] GUI create window FAILED plugin=" << arg2 << std::endl;
|
||||
return;
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_guiMutex);
|
||||
g_guiWindows[guiCh] = nativeHwnd; // keep window alive
|
||||
g_hwndToCh[nativeHwnd] = guiCh; // WM_DESTROY cleanup
|
||||
}
|
||||
SetWindowLongPtrA(nativeHwnd, GWLP_USERDATA, (LONG_PTR)(guiCh + 1));
|
||||
hwnd = (void*)nativeHwnd;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (auto* inst0 = instruments.get(guiCh)) {
|
||||
if (inst0->hasAttachedView()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
#ifdef _WIN32
|
||||
// Option B: chi 1 editor VST mo tai 1 thoi diem toan
|
||||
// bridge. Instance thu 2 cua CUNG plugin (Nexus) attach
|
||||
@@ -1045,7 +1063,10 @@ int main(int argc, char* argv[]) {
|
||||
// std::terminate the worker). Treat as attach failure;
|
||||
// the else branch below closes the empty window.
|
||||
try {
|
||||
instruments.setBypassed(true);
|
||||
sleep_ms(10);
|
||||
ok = inst->reloadForGUI();
|
||||
instruments.setBypassed(false);
|
||||
// IME recursion fix: strip the editor's IMC before
|
||||
// attachView creates the editor child — a live IMC on
|
||||
// a JUCE_ window + plugin wndproc (ImmIsUIMessageW ->
|
||||
@@ -1054,6 +1075,7 @@ int main(int argc, char* argv[]) {
|
||||
if (ok) disable_ime_contexts((HWND)hwnd);
|
||||
if (ok) ok = inst->attachView(hwnd);
|
||||
} catch (...) {
|
||||
instruments.setBypassed(false);
|
||||
std::cerr << "[NativeBridge] GUI attach EXCEPTION ch=" << guiCh
|
||||
<< " — plugin threw (createView/attached)" << std::endl;
|
||||
ok = false;
|
||||
@@ -1105,7 +1127,8 @@ int main(int argc, char* argv[]) {
|
||||
#endif
|
||||
std::cout << "[NativeBridge] GUI attached hwnd=" << hwnd
|
||||
<< " plugin=" << arg2 << " ch=" << guiCh
|
||||
<< " (channel muted while editor open)" << std::endl;
|
||||
<< " (channel active with editor open)" << std::endl;
|
||||
instruments.setReloading(guiCh, false);
|
||||
}
|
||||
else {
|
||||
// Attach failed -> no editor running -> safe to process again.
|
||||
@@ -1396,6 +1419,7 @@ int main(int argc, char* argv[]) {
|
||||
UnmapViewOfFile(shmIPC);
|
||||
CloseHandle(hMapFile);
|
||||
timeEndPeriod(1);
|
||||
CoUninitialize();
|
||||
#else
|
||||
std::free(shmIPC);
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user