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:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user