From f958348a20fe3b68afd59ffe4ac47c168302e812 Mon Sep 17 00:00:00 2001 From: locphamtran Date: Wed, 2 Sep 2026 21:47:16 +0700 Subject: [PATCH] V45.44: fix Ozone 11 GUI den tren Intel UHD 630 - env SF_FXGUI_EMBED_ONSCREEN giu window tren man hinh (HWND_BOTTOM tai rect panel, duoi DAW) de DWM redirect GPU surface thay vi park -20000 (PrintWindow chi capture duoc window dang tren monitor); EMBED_ONSCREEN tuong duong ATTACH_ONSCREEN (khong regress crash attach); /hide reset anchored/dance de reopen re-place dung; them SF_FXGUI_DEBUG=1 log [FxGuiDbg] moi 2s (served/capOk/okB/okP/avg/vis/rect/anchored) lam ground truth cho lan test toi; captureHwndToJpeg expose okB/okP. Rebuild daw_vst_bridge+fx_vst_bridge+installer NSIS. --- native_bridge/src/FxGuiServer.cpp | 99 +++++++++++++++++++++++++++++-- native_bridge/src/RenderFxJob.cpp | 16 ++++- 2 files changed, 109 insertions(+), 6 deletions(-) diff --git a/native_bridge/src/FxGuiServer.cpp b/native_bridge/src/FxGuiServer.cpp index 461009a..71ad62f 100644 --- a/native_bridge/src/FxGuiServer.cpp +++ b/native_bridge/src/FxGuiServer.cpp @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -171,7 +172,9 @@ void sendOptions(SOCKET c) { // frame, while the GDI BitBlt view is a stale first paint for GPU-rendered // editors (JUCE/FAST Compressor) and never updates. BitBlt stays as fallback. // Returns JPEG. -bool captureHwndToJpeg(HWND hwnd, std::vector& out, int quality, float* outAvg = nullptr) { +bool captureHwndToJpeg(HWND hwnd, std::vector& out, int quality, + float* outAvg = nullptr, bool* outOkB = nullptr, + bool* outOkP = nullptr) { RECT rc; if (!GetClientRect(hwnd, &rc)) return false; const int w = rc.right - rc.left; @@ -206,6 +209,8 @@ bool captureHwndToJpeg(HWND hwnd, std::vector& out, int quality, unsigned char* px = nullptr; if (okP) px = pxP.data(); else if (okB) px = pxB.data(); + if (outOkB) *outOkB = okB; + if (outOkP) *outOkP = okP; SelectObject(mem, oldBmp); DeleteObject(bmp); DeleteDC(mem); @@ -273,6 +278,17 @@ POINT g_savedCursor = {0, 0}; // cursor to restore after the dance RECT g_embedRect = {0, 0, 0, 0}; bool g_anchored = false; // window anchored over panel (stays until /close) +// SF_FXGUI_EMBED_ONSCREEN=1: park the host window ON a monitor at the embed +// panel rect (HWND_BOTTOM, under the DAW -> user never sees it) instead of +// off-screen at -20000. DWM does not redirect windows parked fully off the +// desktop on some drivers (Intel UHD 630 + iZotope GL editor) -> PrintWindow +// captures only black. On a monitor (even occluded by the DAW above) DWM +// keeps a redirection surface -> /frame gets real pixels. Real-input dance +// still lifts the window to HWND_TOP for actual clicks (g_anchored guard). +static const bool g_embedOnscreen = std::getenv("SF_FXGUI_EMBED_ONSCREEN") != nullptr; +// SF_FXGUI_DEBUG=1: rate-limited /frame capture ground truth ([FxGuiDbg] lines). +static const bool g_fxGuiDbg = std::getenv("SF_FXGUI_DEBUG") != nullptr; + // Lift the window above the foreground app without making it TOPMOST (the // foreground-lock would otherwise swallow real clicks). Attaching to the @@ -506,19 +522,68 @@ void runHttpServer(HWND hwnd, std::atomic& stop) { // to show that blank frame as a white flash on open. sendResponse(c, 503, "application/json", "{\"error\":\"loading\"}", 19); } else { + // EMBED_ONSCREEN: keep the host window ON a monitor at the + // embed panel rect (HWND_BOTTOM, under the DAW) so DWM + // redirects the editor's GPU surface and capture sees real + // pixels — the one-shot rect /show may have run pre-attach + // (deduped), so reconcile here on every poll until it fits. + if (g_embedOnscreen && !g_anchored && !g_dance && + g_embedRect.right > g_embedRect.left && + g_embedRect.bottom > g_embedRect.top) { + const int cw = g_embedRect.right - g_embedRect.left; + const int ch = g_embedRect.bottom - g_embedRect.top; + RECT cur = {}; + GetWindowRect(hwnd, &cur); + if (cur.left != g_embedRect.left || cur.top != g_embedRect.top || + cur.right - cur.left != cw || cur.bottom - cur.top != ch) + SetWindowPos(hwnd, HWND_BOTTOM, g_embedRect.left, + g_embedRect.top, cw, ch, + SWP_NOACTIVATE | SWP_SHOWWINDOW); + } std::vector jpg; float avg = -1.0f; - if (captureHwndToJpeg(hwnd, jpg, 70, &avg) && - avg >= 0.02f && avg <= 0.98f) { + bool capOkB = false, capOkP = false; + const bool capOk = captureHwndToJpeg(hwnd, jpg, 70, &avg, &capOkB, &capOkP); + const char* served = nullptr; + if (capOk && avg >= 0.02f && avg <= 0.98f) { g_lastGood = jpg; // single HTTP worker thread — no lock + served = "frame"; sendResponse(c, 200, "image/jpeg", jpg.data(), jpg.size()); } else if (!g_lastGood.empty()) { // Blank/transient frame (window still repainting) — keep // the last good frame so the panel never flashes blank. + served = "cached"; sendResponse(c, 200, "image/jpeg", g_lastGood.data(), g_lastGood.size()); } else { + served = "503"; sendResponse(c, 503, "application/json", "{\"error\":\"loading\"}", 19); } + // SF_FXGUI_DEBUG=1: rate-limited capture ground truth. + if (g_fxGuiDbg) { + static DWORD64 s_lastDbg = 0; // HTTP worker thread — no lock + const DWORD64 nowMs = GetTickCount64(); + if (nowMs - s_lastDbg >= 2000) { + s_lastDbg = nowMs; + RECT cr = {}, wr = {}; + GetClientRect(hwnd, &cr); + GetWindowRect(hwnd, &wr); + std::cout << "[FxGuiDbg] served=" << served + << " capOk=" << (capOk ? 1 : 0) + << " okB=" << (capOkB ? 1 : 0) + << " okP=" << (capOkP ? 1 : 0) + << " avg=" << avg + << " vis=" << (IsWindowVisible(hwnd) ? 1 : 0) + << " client=" << (cr.right - cr.left) << "x" + << (cr.bottom - cr.top) + << " win=(" << wr.left << "," << wr.top << " " + << (wr.right - wr.left) << "x" + << (wr.bottom - wr.top) << ")" + << " lastGood=" << g_lastGood.size() + << " anchored=" << (g_anchored ? 1 : 0) + << " dance=" << (g_dance ? 1 : 0) + << std::endl; + } + } } } else if (req.path == "/input") { json_parse_result_s pres = {}; @@ -538,6 +603,12 @@ void runHttpServer(HWND hwnd, std::atomic& stop) { SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); ShowWindow(hwnd, SW_HIDE); + // Reopen re-anchors from scratch (/show + rect) — clear the + // real-input state or the reopened panel would never re-place + // the window at the panel rect (stuck anchored/offscreen). + g_anchored = false; + g_dance = false; + g_parkUntil = 0; sendResponse(c, 200, "application/json", "{\"ok\":true}", 11); } else if (req.path == "/show") { // Optional body {rect:{x,y,w,h}} (screen px) — anchored placement @@ -581,9 +652,29 @@ void runHttpServer(HWND hwnd, std::atomic& stop) { if (cur.right - cur.left != cw || cur.bottom - cur.top != ch) fxGuiRequestResize(hwnd, cw, ch); } - if (!IsWindowVisible(hwnd)) + // EMBED_ONSCREEN: park the window ON the monitor at the embed + // panel rect, HWND_BOTTOM (below the DAW -> user never sees it) + // so DWM redirects the editor's GPU surface and /frame can + // capture it. Skip while the editor is still attaching (the + // pump thread is busy; SetWindowPos from here would block this + // worker until load/attach finishes) and while anchored/dancing + // (real-input keeps the window on top over the panel for clicks). + if (g_embedOnscreen && rectOk && !g_anchored && !g_dance && + g_fxGuiEditorReady.load()) { + const int cw = g_embedRect.right - g_embedRect.left; + const int ch = g_embedRect.bottom - g_embedRect.top; + RECT cur = {}; + GetWindowRect(hwnd, &cur); + if (!IsWindowVisible(hwnd) || cur.left != g_embedRect.left || + cur.top != g_embedRect.top || cur.right - cur.left != cw || + cur.bottom - cur.top != ch) + SetWindowPos(hwnd, HWND_BOTTOM, g_embedRect.left, + g_embedRect.top, cw, ch, + SWP_NOACTIVATE | SWP_SHOWWINDOW); + } else if (!IsWindowVisible(hwnd)) { SetWindowPos(hwnd, HWND_NOTOPMOST, -20000, -20000, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW); + } // Dance/anchor state is owned by the input relay — /show must not // yank the window off-screen (real input is always on). sendResponse(c, 200, "application/json", "{\"ok\":true}", 11); diff --git a/native_bridge/src/RenderFxJob.cpp b/native_bridge/src/RenderFxJob.cpp index 9679d4c..060071e 100644 --- a/native_bridge/src/RenderFxJob.cpp +++ b/native_bridge/src/RenderFxJob.cpp @@ -1906,7 +1906,16 @@ int run_fx_gui_server(const std::string& jobPath) { fxGuiShmSetName(shm); // EARLY — frontend may /shm push before load finishes int loadRc = 0; - const bool attachOnscreen = std::getenv("SF_FXGUI_ATTACH_ONSCREEN") != nullptr; + // EMBED_ONSCREEN implies ATTACH_ONSCREEN: the Intel UHD 630 attach + // crash fix (attach on a real monitor) and the black-capture fix (stay + // on a monitor afterwards, parked under the DAW at the panel rect) go + // together — setting only EMBED_ONSCREEN must not regress the crash. + const bool embedOnscreen = std::getenv("SF_FXGUI_EMBED_ONSCREEN") != nullptr; + const bool attachOnscreen = embedOnscreen || std::getenv("SF_FXGUI_ATTACH_ONSCREEN") != nullptr; + std::cout << "[FxGui] env SF_FXGUI_ATTACH_ONSCREEN=" << (attachOnscreen ? 1 : 0) + << " SF_FXGUI_EMBED_ONSCREEN=" << (embedOnscreen ? 1 : 0) + << " SF_FXGUI_DEBUG=" << (std::getenv("SF_FXGUI_DEBUG") ? 1 : 0) + << std::endl; const int srv = fxGuiServerLoop(hwnd, [&]() { // Some plugin GL editors (e.g. iZotope Ozone 11 on Intel UHD 630) crash // when their editor window is parked offscreen during attach. Env toggle @@ -1919,7 +1928,10 @@ int run_fx_gui_server(const std::string& jobPath) { loadRc = fxGuiLoadAttach(&fx, path, name, hwnd, presetB64); if (loadRc == 0) { fxGuiStartFeederThread(&fx, hwnd); // after load+attach only - if (attachOnscreen) + // EMBED_ONSCREEN: leave the window where attach put it (on a + // monitor); the /show + /frame reconcile park it at the panel + // rect (HWND_BOTTOM) once the frontend supplies the rect. + if (attachOnscreen && !embedOnscreen) SetWindowPos(hwnd, nullptr, -20000, -20000, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); }