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 <map>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -72,6 +73,19 @@ public:
|
|||||||
|
|
||||||
INativeInstrument* get(uint32_t channel);
|
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
|
// Remove and destroy the instrument on `channel` (its destructor may call
|
||||||
// VST terminate — MUST run on the channel worker thread, caller's duty).
|
// 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)
|
// Used by Option B: two live instances of the same plugin DLL (Nexus)
|
||||||
@@ -87,6 +101,16 @@ public:
|
|||||||
// teardown never races the audio thread.
|
// teardown never races the audio thread.
|
||||||
void setReloading(uint32_t channel, bool on);
|
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.
|
// Flush every sounding note on every assigned channel.
|
||||||
void allNotesOff();
|
void allNotesOff();
|
||||||
|
|
||||||
@@ -103,6 +127,13 @@ private:
|
|||||||
std::map<uint32_t, std::string> paths_; // last assigned path per channel
|
std::map<uint32_t, std::string> paths_; // last assigned path per channel
|
||||||
// Per-instrument scratch so engines that overwrite (not mix) stay additive.
|
// Per-instrument scratch so engines that overwrite (not mix) stay additive.
|
||||||
std::vector<float> scratchL_, scratchR_;
|
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
|
#endif // NATIVE_INSTRUMENT_ENGINE_H
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
#define FS_SYNTH (static_cast<fluid_synth_t*>(synth))
|
#define FS_SYNTH (static_cast<fluid_synth_t*>(synth))
|
||||||
#define FS_SETTINGS (static_cast<fluid_settings_t*>(settings))
|
#define FS_SETTINGS (static_cast<fluid_settings_t*>(settings))
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
@@ -187,6 +189,10 @@ bool InstrumentEngineManager::assign(uint32_t channel, InstrumentType type,
|
|||||||
if (it != channels_.end()) oldInst = std::move(it->second);
|
if (it != channels_.end()) oldInst = std::move(it->second);
|
||||||
channels_[channel] = std::move(inst);
|
channels_[channel] = std::move(inst);
|
||||||
paths_[channel] = path;
|
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();
|
oldInst.reset();
|
||||||
return true;
|
return true;
|
||||||
@@ -203,6 +209,7 @@ void InstrumentEngineManager::unload(uint32_t channel) {
|
|||||||
if (it != channels_.end()) oldInst = std::move(it->second);
|
if (it != channels_.end()) oldInst = std::move(it->second);
|
||||||
channels_.erase(channel);
|
channels_.erase(channel);
|
||||||
paths_.erase(channel);
|
paths_.erase(channel);
|
||||||
|
reloadingCh_[channel] = false;
|
||||||
}
|
}
|
||||||
oldInst.reset();
|
oldInst.reset();
|
||||||
}
|
}
|
||||||
@@ -221,13 +228,86 @@ INativeInstrument* InstrumentEngineManager::get(uint32_t channel) {
|
|||||||
|
|
||||||
void InstrumentEngineManager::setReloading(uint32_t channel, bool on) {
|
void InstrumentEngineManager::setReloading(uint32_t channel, bool on) {
|
||||||
std::lock_guard<std::mutex> lock(mu_);
|
std::lock_guard<std::mutex> lock(mu_);
|
||||||
|
if (channel < 16) reloadingCh_[channel] = on;
|
||||||
auto it = channels_.find(channel);
|
auto it = channels_.find(channel);
|
||||||
if (it != channels_.end()) it->second->setReloading(on);
|
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() {
|
void InstrumentEngineManager::allNotesOff() {
|
||||||
std::lock_guard<std::mutex> lock(mu_);
|
std::lock_guard<std::mutex> lock(mu_);
|
||||||
for (auto& [ch, inst] : channels_) {
|
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);
|
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);
|
scratchR_.resize(numSamples);
|
||||||
}
|
}
|
||||||
for (auto& [ch, inst] : channels_) {
|
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(scratchL_.data(), 0, numSamples * sizeof(float));
|
||||||
std::memset(scratchR_.data(), 0, numSamples * sizeof(float));
|
std::memset(scratchR_.data(), 0, numSamples * sizeof(float));
|
||||||
inst->processAudioBlock(scratchL_.data(), scratchR_.data(), numSamples);
|
inst->processAudioBlock(scratchL_.data(), scratchR_.data(), numSamples);
|
||||||
|
|||||||
@@ -555,10 +555,12 @@ bool Vst3Instrument::attachView(void* parentWindowHandle) {
|
|||||||
auto* s = static_cast<Vst3HostState*>(state_);
|
auto* s = static_cast<Vst3HostState*>(state_);
|
||||||
if (!s || !s->controller || !parentWindowHandle) return false;
|
if (!s || !s->controller || !parentWindowHandle) return false;
|
||||||
IPlugView* rawView = nullptr;
|
IPlugView* rawView = nullptr;
|
||||||
|
s->controller->queryInterface(IPlugView::iid, (void**)&rawView);
|
||||||
FUnknownPtr<IPlugView> view(rawView);
|
FUnknownPtr<IPlugView> view(rawView);
|
||||||
if (!view) {
|
if (!view) {
|
||||||
// Mot so plugin khong expose IPlugView tren edit controller; thu component.
|
// Mot so plugin khong expose IPlugView tren edit controller; thu component.
|
||||||
IPlugView* rawViewC = nullptr;
|
IPlugView* rawViewC = nullptr;
|
||||||
|
s->component->queryInterface(IPlugView::iid, (void**)&rawViewC);
|
||||||
view = FUnknownPtr<IPlugView>(rawViewC);
|
view = FUnknownPtr<IPlugView>(rawViewC);
|
||||||
}
|
}
|
||||||
if (!view) {
|
if (!view) {
|
||||||
@@ -566,7 +568,9 @@ bool Vst3Instrument::attachView(void* parentWindowHandle) {
|
|||||||
// JUCE-based plugins (Scaler2) expose the editor only this way.
|
// JUCE-based plugins (Scaler2) expose the editor only this way.
|
||||||
view = owned(s->controller->createView(Steinberg::Vst::ViewType::kEditor));
|
view = owned(s->controller->createView(Steinberg::Vst::ViewType::kEditor));
|
||||||
}
|
}
|
||||||
|
if (!view) return false;
|
||||||
view->setFrame(&s->plugFrame);
|
view->setFrame(&s->plugFrame);
|
||||||
|
view->isPlatformTypeSupported(kPlatformTypeHWND);
|
||||||
tresult ta = view->attached(parentWindowHandle, kPlatformTypeHWND);
|
tresult ta = view->attached(parentWindowHandle, kPlatformTypeHWND);
|
||||||
if (ta != kResultOk) return false;
|
if (ta != kResultOk) return false;
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@@ -578,6 +582,10 @@ bool Vst3Instrument::attachView(void* parentWindowHandle) {
|
|||||||
RECT r = { 0, 0, w, h };
|
RECT r = { 0, 0, w, h };
|
||||||
AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, FALSE);
|
AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, FALSE);
|
||||||
SetWindowPos(hwnd, nullptr, 0, 0, r.right - r.left, r.bottom - r.top, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
|
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
|
#endif
|
||||||
s->view = view;
|
s->view = view;
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ static void* create_native_vst_window(const char* title) {
|
|||||||
registered = true;
|
registered = true;
|
||||||
}
|
}
|
||||||
HWND hwnd = CreateWindowExA(0, kWndClass, title ? title : "VST",
|
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,
|
CW_USEDEFAULT, CW_USEDEFAULT, 800, 600,
|
||||||
nullptr, nullptr, GetModuleHandleA(nullptr), nullptr);
|
nullptr, nullptr, GetModuleHandleA(nullptr), nullptr);
|
||||||
return (void*)hwnd;
|
return (void*)hwnd;
|
||||||
@@ -672,6 +672,12 @@ static void post_close_gui(uint32_t ch, HWND hwnd) {
|
|||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
std::cout << "[NativeBridge] Starting DAW Host Bridge Engine..." << std::endl;
|
std::cout << "[NativeBridge] Starting DAW Host Bridge Engine..." << std::endl;
|
||||||
#ifdef _WIN32
|
#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).
|
// Default IMC = none on the main thread (IME recursion fix).
|
||||||
ImmAssociateContextEx(nullptr, nullptr, IACE_DEFAULT);
|
ImmAssociateContextEx(nullptr, nullptr, IACE_DEFAULT);
|
||||||
// Catch JUCE_* window creation and strip its IMC at birth. Global hooks
|
// Catch JUCE_* window creation and strip its IMC at birth. Global hooks
|
||||||
|
|||||||
Reference in New Issue
Block a user