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 <atomic>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <cstdlib>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@@ -171,7 +172,9 @@ void sendOptions(SOCKET c) {
|
|||||||
// frame, while the GDI BitBlt view is a stale first paint for GPU-rendered
|
// 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.
|
// editors (JUCE/FAST Compressor) and never updates. BitBlt stays as fallback.
|
||||||
// Returns JPEG.
|
// 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;
|
RECT rc;
|
||||||
if (!GetClientRect(hwnd, &rc)) return false;
|
if (!GetClientRect(hwnd, &rc)) return false;
|
||||||
const int w = rc.right - rc.left;
|
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;
|
unsigned char* px = nullptr;
|
||||||
if (okP) px = pxP.data();
|
if (okP) px = pxP.data();
|
||||||
else if (okB) px = pxB.data();
|
else if (okB) px = pxB.data();
|
||||||
|
if (outOkB) *outOkB = okB;
|
||||||
|
if (outOkP) *outOkP = okP;
|
||||||
SelectObject(mem, oldBmp);
|
SelectObject(mem, oldBmp);
|
||||||
DeleteObject(bmp);
|
DeleteObject(bmp);
|
||||||
DeleteDC(mem);
|
DeleteDC(mem);
|
||||||
@@ -273,6 +278,17 @@ POINT g_savedCursor = {0, 0}; // cursor to restore after the dance
|
|||||||
RECT g_embedRect = {0, 0, 0, 0};
|
RECT g_embedRect = {0, 0, 0, 0};
|
||||||
bool g_anchored = false; // window anchored over panel (stays until /close)
|
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
|
// Lift the window above the foreground app without making it TOPMOST (the
|
||||||
// foreground-lock would otherwise swallow real clicks). Attaching to 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.
|
// to show that blank frame as a white flash on open.
|
||||||
sendResponse(c, 503, "application/json", "{\"error\":\"loading\"}", 19);
|
sendResponse(c, 503, "application/json", "{\"error\":\"loading\"}", 19);
|
||||||
} else {
|
} 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;
|
std::vector<unsigned char> jpg;
|
||||||
float avg = -1.0f;
|
float avg = -1.0f;
|
||||||
if (captureHwndToJpeg(hwnd, jpg, 70, &avg) &&
|
bool capOkB = false, capOkP = false;
|
||||||
avg >= 0.02f && avg <= 0.98f) {
|
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
|
g_lastGood = jpg; // single HTTP worker thread — no lock
|
||||||
|
served = "frame";
|
||||||
sendResponse(c, 200, "image/jpeg", jpg.data(), jpg.size());
|
sendResponse(c, 200, "image/jpeg", jpg.data(), jpg.size());
|
||||||
} else if (!g_lastGood.empty()) {
|
} else if (!g_lastGood.empty()) {
|
||||||
// Blank/transient frame (window still repainting) — keep
|
// Blank/transient frame (window still repainting) — keep
|
||||||
// the last good frame so the panel never flashes blank.
|
// the last good frame so the panel never flashes blank.
|
||||||
|
served = "cached";
|
||||||
sendResponse(c, 200, "image/jpeg", g_lastGood.data(), g_lastGood.size());
|
sendResponse(c, 200, "image/jpeg", g_lastGood.data(), g_lastGood.size());
|
||||||
} else {
|
} else {
|
||||||
|
served = "503";
|
||||||
sendResponse(c, 503, "application/json", "{\"error\":\"loading\"}", 19);
|
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") {
|
} else if (req.path == "/input") {
|
||||||
json_parse_result_s pres = {};
|
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,
|
SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0,
|
||||||
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
||||||
ShowWindow(hwnd, SW_HIDE);
|
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);
|
sendResponse(c, 200, "application/json", "{\"ok\":true}", 11);
|
||||||
} else if (req.path == "/show") {
|
} else if (req.path == "/show") {
|
||||||
// Optional body {rect:{x,y,w,h}} (screen px) — anchored placement
|
// 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)
|
if (cur.right - cur.left != cw || cur.bottom - cur.top != ch)
|
||||||
fxGuiRequestResize(hwnd, cw, 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,
|
SetWindowPos(hwnd, HWND_NOTOPMOST, -20000, -20000, 0, 0,
|
||||||
SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW);
|
SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW);
|
||||||
|
}
|
||||||
// Dance/anchor state is owned by the input relay — /show must not
|
// Dance/anchor state is owned by the input relay — /show must not
|
||||||
// yank the window off-screen (real input is always on).
|
// yank the window off-screen (real input is always on).
|
||||||
sendResponse(c, 200, "application/json", "{\"ok\":true}", 11);
|
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
|
fxGuiShmSetName(shm); // EARLY — frontend may /shm push before load finishes
|
||||||
|
|
||||||
int loadRc = 0;
|
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, [&]() {
|
const int srv = fxGuiServerLoop(hwnd, [&]() {
|
||||||
// Some plugin GL editors (e.g. iZotope Ozone 11 on Intel UHD 630) crash
|
// 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
|
// 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);
|
loadRc = fxGuiLoadAttach(&fx, path, name, hwnd, presetB64);
|
||||||
if (loadRc == 0) {
|
if (loadRc == 0) {
|
||||||
fxGuiStartFeederThread(&fx, hwnd); // after load+attach only
|
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,
|
SetWindowPos(hwnd, nullptr, -20000, -20000, 0, 0,
|
||||||
SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
|
SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user