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.

This commit is contained in:
2026-09-02 21:47:16 +07:00
parent 244dc8ee9e
commit f958348a20
2 changed files with 109 additions and 6 deletions
+95 -4
View File
@@ -28,6 +28,7 @@
#include <atomic>
#include <cstring>
#include <cstdlib>
#include <cstdint>
#include <functional>
#include <iostream>
@@ -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<unsigned char>& out, int quality, float* outAvg = nullptr) {
bool captureHwndToJpeg(HWND hwnd, std::vector<unsigned char>& 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<unsigned char>& 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<bool>& 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<unsigned char> 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<bool>& 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<bool>& 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);
+14 -2
View File
@@ -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);
}