From 308857c4a7d3854ff5d4b6ed9ad56244164c91b7 Mon Sep 17 00:00:00 2001 From: locphamtran Date: Tue, 18 Aug 2026 09:56:53 +0700 Subject: [PATCH] fix: VST GUI embed chup frame trang (Ozone 11 GPU) - luon chup ca BitBlt va PrintWindow PW_RENDERFULLCONTENT, chon frame chi tiet hon; fix loi JPEG chi ghi hang dau tien --- native_bridge/src/FxGuiServer.cpp | 53 +++++++++++++++++++------------ 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/native_bridge/src/FxGuiServer.cpp b/native_bridge/src/FxGuiServer.cpp index 9366556..6e914be 100644 --- a/native_bridge/src/FxGuiServer.cpp +++ b/native_bridge/src/FxGuiServer.cpp @@ -128,9 +128,11 @@ void sendResponse(SOCKET c, int code, const char* contentType, } // --- frame capture ----------------------------------------------------------- -// BitBlt the window client rect; if the result is all-black (plugin uses a -// layered/DirectX surface GDI cannot see), fall back to PrintWindow with -// PW_RENDERFULLCONTENT. Returns JPEG bytes in `out`. +// Capture the window client rect twice — BitBlt (GDI view) and PrintWindow +// with PW_RENDERFULLCONTENT (DWM composition, includes DirectX/GPU surfaces +// that GDI cannot see) — and keep the frame with more visual detail. Ozone 11 +// and other GPU-rendered plugins paint WHITE into the GDI DC, so the old +// all-black fallback never fired and a white frame was served. Returns JPEG. bool captureHwndToJpeg(HWND hwnd, std::vector& out, int quality) { RECT rc; if (!GetClientRect(hwnd, &rc)) return false; @@ -143,8 +145,8 @@ bool captureHwndToJpeg(HWND hwnd, std::vector& out, int quality) HBITMAP bmp = CreateCompatibleBitmap(win, w, h); HGDIOBJ oldBmp = SelectObject(mem, bmp); - std::vector px((size_t)w * h * 4); - auto grab = [&]() -> bool { + std::vector pxB((size_t)w * h * 4), pxP((size_t)w * h * 4); + auto grabInto = [&](unsigned char* p) -> bool { BITMAPINFO bi = {}; bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bi.bmiHeader.biWidth = w; @@ -152,29 +154,37 @@ bool captureHwndToJpeg(HWND hwnd, std::vector& out, int quality) bi.bmiHeader.biPlanes = 1; bi.bmiHeader.biBitCount = 32; bi.bmiHeader.biCompression = BI_RGB; - return GetDIBits(mem, bmp, 0, h, px.data(), &bi, DIB_RGB_COLORS) == h; + return GetDIBits(mem, bmp, 0, h, p, &bi, DIB_RGB_COLORS) == h; }; - auto isAllBlack = [&]() -> bool { - // Sample a coarse grid of pixels; all zero => invisible to GDI. - const int step = 8; + auto detailScore = [&](const unsigned char* p) -> long long { + // Count sampled pixels that are neither near-black nor near-white. + // GPU-rendered plugins (Ozone 11, ...) are invisible to GDI: BitBlt + // yields a white/black-strip frame with ~2 distinct values, while the + // real UI has many mid-grey levels. Pick the frame with more detail. + long long d = 0; + const int step = 4; for (int y = 0; y < h; y += step) for (int x = 0; x < w; x += step) { const size_t i = ((size_t)y * w + x) * 4; - if (px[i] || px[i + 1] || px[i + 2]) return false; + const unsigned lum = ((unsigned)p[i] * 77u + (unsigned)p[i + 1] * 150u + (unsigned)p[i + 2] * 29u) >> 8; + if (lum >= 28 && lum <= 232) ++d; // exclude near-black/white } - return true; + return d; }; - bool ok = BitBlt(mem, 0, 0, w, h, win, 0, 0, SRCCOPY | CAPTUREBLT) != 0 && grab(); - if (ok && isAllBlack()) { - ok = PrintWindow(hwnd, mem, PW_RENDERFULLCONTENT) != 0 && grab(); - } - + bool okB = BitBlt(mem, 0, 0, w, h, win, 0, 0, SRCCOPY | CAPTUREBLT) != 0 && grabInto(pxB.data()); + // PW_RENDERFULLCONTENT asks DWM to compose the window (includes child + // windows + DirectX surfaces) — the only way to see GPU-rendered editors. + bool okP = PrintWindow(hwnd, mem, PW_RENDERFULLCONTENT) != 0 && grabInto(pxP.data()); + unsigned char* px = nullptr; + if (okB && okP) px = detailScore(pxP.data()) > detailScore(pxB.data()) ? pxP.data() : pxB.data(); + else if (okP) px = pxP.data(); + else if (okB) px = pxB.data(); SelectObject(mem, oldBmp); DeleteObject(bmp); DeleteDC(mem); ReleaseDC(hwnd, win); - if (!ok) return false; + if (!px) return false; // BGRA (GDI) -> RGB, then JPEG. std::vector rgb((size_t)w * h * 3); @@ -198,9 +208,12 @@ bool captureHwndToJpeg(HWND hwnd, std::vector& out, int quality) jpeg_set_defaults(&cinfo); jpeg_set_quality(&cinfo, quality, TRUE); jpeg_start_compress(&cinfo, TRUE); - JSAMPROW row = rgb.data(); - while (cinfo.next_scanline < cinfo.image_height) - jpeg_write_scanlines(&cinfo, &row, 1); + JSAMPROW rows[1]; + rows[0] = rgb.data(); + while (cinfo.next_scanline < cinfo.image_height) { + jpeg_write_scanlines(&cinfo, rows, 1); + rows[0] += (size_t)w * 3; + } jpeg_finish_compress(&cinfo); if (memBuf && memSize > 0) out.assign(memBuf, memBuf + memSize); std::free(memBuf);