fix GUI VSTi trắng/không load: DPI per-monitor v2 + WS_CLIPCHILDREN + view->onSize() sau attached; restore API manager header/cpp (2596372 xoá nhầm) và deploy exe mới
This commit is contained in:
Binary file not shown.
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -72,6 +73,19 @@ public:
|
||||
|
||||
INativeInstrument* get(uint32_t channel);
|
||||
|
||||
// Real-time MIDI dispatch (audio loop thread). Each call holds mu_ for the
|
||||
// WHOLE call — lookup + reloading check + instrument call under one lock —
|
||||
// so a worker thread's assign()/unload() can never swap the map and destroy
|
||||
// the old instance while dispatch is inside a method on it (use-after-free
|
||||
// when loading a new VSTi while others play). Events for unassigned or
|
||||
// reloading channels are dropped (a mid-reload instance must not be touched
|
||||
// while its worker rebuilds state_ in place).
|
||||
void noteOn(uint32_t channel, uint32_t pitch, float velocity);
|
||||
void noteOff(uint32_t channel, uint32_t pitch);
|
||||
void controlChange(uint32_t channel, uint32_t cc, uint32_t value);
|
||||
void programChange(uint32_t channel, uint32_t program);
|
||||
void pitchBend(uint32_t channel, uint32_t bend14);
|
||||
|
||||
// Remove and destroy the instrument on `channel` (its destructor may call
|
||||
// VST terminate — MUST run on the channel worker thread, caller's duty).
|
||||
// Used by Option B: two live instances of the same plugin DLL (Nexus)
|
||||
@@ -87,6 +101,16 @@ public:
|
||||
// teardown never races the audio thread.
|
||||
void setReloading(uint32_t channel, bool on);
|
||||
|
||||
// True while a channel is being rebuilt (instance teardown/reload on its
|
||||
// worker thread): real-time code drops MIDI for it. Channel-level flag —
|
||||
// survives the instance swap (the per-instance flag dies with the object).
|
||||
bool isReloading(uint32_t channel) const;
|
||||
// Editor-open predicate: while any VST editor is attached, channels whose
|
||||
// plugin DLL path has an open editor count as quiet (same-plugin channels
|
||||
// too -- the worker pumps the editor's window proc inside the DLL while the
|
||||
// audio loop process()es other instances of the same DLL).
|
||||
void setEditorOpenPredicate(std::function<bool(const std::string& lowerPath)> fn);
|
||||
|
||||
// Flush every sounding note on every assigned channel.
|
||||
void allNotesOff();
|
||||
|
||||
@@ -103,6 +127,13 @@ private:
|
||||
std::map<uint32_t, std::string> paths_; // last assigned path per channel
|
||||
// Per-instrument scratch so engines that overwrite (not mix) stay additive.
|
||||
std::vector<float> scratchL_, scratchR_;
|
||||
// Channel-level reload flag (see isReloading) — indexed by MIDI channel.
|
||||
bool reloadingCh_[16] = {};
|
||||
// True when the channel's plugin DLL has an attached editor (or its reload
|
||||
// is in progress): real-time code drops MIDI and skips rendering. Lowercased
|
||||
// compare inside -- Windows plugin paths are case-insensitive.
|
||||
bool channelQuiet(uint32_t ch) const;
|
||||
std::function<bool(const std::string&)> editorOpenFn_;
|
||||
};
|
||||
|
||||
#endif // NATIVE_INSTRUMENT_ENGINE_H
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#define FS_SYNTH (static_cast<fluid_synth_t*>(synth))
|
||||
#define FS_SETTINGS (static_cast<fluid_settings_t*>(settings))
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
@@ -187,6 +189,10 @@ bool InstrumentEngineManager::assign(uint32_t channel, InstrumentType type,
|
||||
if (it != channels_.end()) oldInst = std::move(it->second);
|
||||
channels_[channel] = std::move(inst);
|
||||
paths_[channel] = path;
|
||||
// Fresh instance is by construction not reloading — clear the channel
|
||||
// flag so real-time MIDI dispatch (which drops reloading channels)
|
||||
// flows to it again.
|
||||
reloadingCh_[channel] = false;
|
||||
}
|
||||
oldInst.reset();
|
||||
return true;
|
||||
@@ -203,6 +209,7 @@ void InstrumentEngineManager::unload(uint32_t channel) {
|
||||
if (it != channels_.end()) oldInst = std::move(it->second);
|
||||
channels_.erase(channel);
|
||||
paths_.erase(channel);
|
||||
reloadingCh_[channel] = false;
|
||||
}
|
||||
oldInst.reset();
|
||||
}
|
||||
@@ -221,13 +228,86 @@ INativeInstrument* InstrumentEngineManager::get(uint32_t channel) {
|
||||
|
||||
void InstrumentEngineManager::setReloading(uint32_t channel, bool on) {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
if (channel < 16) reloadingCh_[channel] = on;
|
||||
auto it = channels_.find(channel);
|
||||
if (it != channels_.end()) it->second->setReloading(on);
|
||||
}
|
||||
|
||||
bool InstrumentEngineManager::isReloading(uint32_t channel) const {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
return channel < 16 && reloadingCh_[channel];
|
||||
}
|
||||
|
||||
void InstrumentEngineManager::setEditorOpenPredicate(std::function<bool(const std::string&)> fn) {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
editorOpenFn_ = std::move(fn);
|
||||
}
|
||||
|
||||
// Quiet = reloading OR an editor attached for the channel's plugin DLL path.
|
||||
// Called with mu_ held by the real-time dispatch (the predicate locks
|
||||
// g_editorMutex -- lock order mu_ -> g_editorMutex; g_editorMutex is never
|
||||
// held while taking mu_).
|
||||
bool InstrumentEngineManager::channelQuiet(uint32_t ch) const {
|
||||
if (ch >= 16 || reloadingCh_[ch]) return true;
|
||||
if (!editorOpenFn_) return false;
|
||||
auto it = paths_.find(ch);
|
||||
if (it == paths_.end() || it->second.empty()) return false;
|
||||
std::string lp = it->second;
|
||||
std::transform(lp.begin(), lp.end(), lp.begin(),
|
||||
[](unsigned char c) { return (char)::tolower(c); });
|
||||
return editorOpenFn_(lp);
|
||||
}
|
||||
|
||||
// Real-time MIDI dispatch: hold mu_ for the WHOLE call so assign()/unload()
|
||||
// (map swap + old-instance destruction outside the lock) and reload()/reloadForGUI()
|
||||
// (state_ deleted in place on the worker) can never destroy/free the instance
|
||||
// while dispatch is inside a method on it — use-after-free when loading a new
|
||||
// VSTi while other channels keep playing. Reloading channels are skipped: their
|
||||
// instance is mid-teardown and must not be touched.
|
||||
void InstrumentEngineManager::noteOn(uint32_t channel, uint32_t pitch, float velocity) {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
if (channelQuiet(channel)) return;
|
||||
auto it = channels_.find(channel);
|
||||
if (it == channels_.end()) return;
|
||||
it->second->noteOn(channel, pitch, velocity, 0);
|
||||
}
|
||||
|
||||
void InstrumentEngineManager::noteOff(uint32_t channel, uint32_t pitch) {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
if (channelQuiet(channel)) return;
|
||||
auto it = channels_.find(channel);
|
||||
if (it == channels_.end()) return;
|
||||
it->second->noteOff(channel, pitch, 0);
|
||||
}
|
||||
|
||||
void InstrumentEngineManager::controlChange(uint32_t channel, uint32_t cc, uint32_t value) {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
if (channelQuiet(channel)) return;
|
||||
auto it = channels_.find(channel);
|
||||
if (it == channels_.end()) return;
|
||||
it->second->controlChange(channel, cc, value);
|
||||
}
|
||||
|
||||
void InstrumentEngineManager::programChange(uint32_t channel, uint32_t program) {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
if (channelQuiet(channel)) return;
|
||||
auto it = channels_.find(channel);
|
||||
if (it == channels_.end()) return;
|
||||
it->second->programChange(channel, program);
|
||||
}
|
||||
|
||||
void InstrumentEngineManager::pitchBend(uint32_t channel, uint32_t bend14) {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
if (channelQuiet(channel)) return;
|
||||
auto it = channels_.find(channel);
|
||||
if (it == channels_.end()) return;
|
||||
it->second->pitchBend(channel, bend14);
|
||||
}
|
||||
|
||||
void InstrumentEngineManager::allNotesOff() {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
for (auto& [ch, inst] : channels_) {
|
||||
if (channelQuiet(ch)) continue; // mid-rebuild / editor open: do not touch the instance
|
||||
for (uint32_t n = 0; n < 128; ++n) inst->noteOff(ch, n, 0);
|
||||
}
|
||||
}
|
||||
@@ -242,6 +322,7 @@ void InstrumentEngineManager::renderAll(float* outputL, float* outputR, uint32_t
|
||||
scratchR_.resize(numSamples);
|
||||
}
|
||||
for (auto& [ch, inst] : channels_) {
|
||||
if (channelQuiet(ch)) continue; // editor open on this plugin DLL: do not process
|
||||
std::memset(scratchL_.data(), 0, numSamples * sizeof(float));
|
||||
std::memset(scratchR_.data(), 0, numSamples * sizeof(float));
|
||||
inst->processAudioBlock(scratchL_.data(), scratchR_.data(), numSamples);
|
||||
|
||||
@@ -555,10 +555,12 @@ bool Vst3Instrument::attachView(void* parentWindowHandle) {
|
||||
auto* s = static_cast<Vst3HostState*>(state_);
|
||||
if (!s || !s->controller || !parentWindowHandle) return false;
|
||||
IPlugView* rawView = nullptr;
|
||||
s->controller->queryInterface(IPlugView::iid, (void**)&rawView);
|
||||
FUnknownPtr<IPlugView> view(rawView);
|
||||
if (!view) {
|
||||
// Mot so plugin khong expose IPlugView tren edit controller; thu component.
|
||||
IPlugView* rawViewC = nullptr;
|
||||
s->component->queryInterface(IPlugView::iid, (void**)&rawViewC);
|
||||
view = FUnknownPtr<IPlugView>(rawViewC);
|
||||
}
|
||||
if (!view) {
|
||||
@@ -566,7 +568,9 @@ bool Vst3Instrument::attachView(void* parentWindowHandle) {
|
||||
// JUCE-based plugins (Scaler2) expose the editor only this way.
|
||||
view = owned(s->controller->createView(Steinberg::Vst::ViewType::kEditor));
|
||||
}
|
||||
if (!view) return false;
|
||||
view->setFrame(&s->plugFrame);
|
||||
view->isPlatformTypeSupported(kPlatformTypeHWND);
|
||||
tresult ta = view->attached(parentWindowHandle, kPlatformTypeHWND);
|
||||
if (ta != kResultOk) return false;
|
||||
#ifdef _WIN32
|
||||
@@ -578,6 +582,10 @@ bool Vst3Instrument::attachView(void* parentWindowHandle) {
|
||||
RECT r = { 0, 0, w, h };
|
||||
AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, FALSE);
|
||||
SetWindowPos(hwnd, nullptr, 0, 0, r.right - r.left, r.bottom - r.top, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
// Tell the plugin the client size right after attach — some editors
|
||||
// (Skia/OpenGL-based) skip their first paint until onSize arrives,
|
||||
// leaving a blank/white surface.
|
||||
view->onSize(&rect);
|
||||
}
|
||||
#endif
|
||||
s->view = view;
|
||||
|
||||
@@ -222,7 +222,7 @@ static void* create_native_vst_window(const char* title) {
|
||||
registered = true;
|
||||
}
|
||||
HWND hwnd = CreateWindowExA(0, kWndClass, title ? title : "VST",
|
||||
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
|
||||
WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
|
||||
CW_USEDEFAULT, CW_USEDEFAULT, 800, 600,
|
||||
nullptr, nullptr, GetModuleHandleA(nullptr), nullptr);
|
||||
return (void*)hwnd;
|
||||
@@ -672,6 +672,12 @@ 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
|
||||
// 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
|
||||
// strips (reported "GUI trắng, không load"). Runtime call works even
|
||||
// without an exe manifest. Ignore failure (already aware).
|
||||
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
||||
// Default IMC = none on the main thread (IME recursion fix).
|
||||
ImmAssociateContextEx(nullptr, nullptr, IACE_DEFAULT);
|
||||
// Catch JUCE_* window creation and strip its IMC at birth. Global hooks
|
||||
|
||||
Reference in New Issue
Block a user