V45.42: xoa toolbar label 'VST GUI - nhung (frame-capture)' trong VstGuiEmbed (GUI hien thi thang o row, khong con header bar) + fix flash trang khi load GUI embed o tang C++: RenderFxJob.cpp them global g_fxGuiEditorReady (set sau khi editor attach thanh cong tren pump thread), FxGuiServer.cpp /frame tu choi 503 khi chua ready, tinh mean-luma trong captureHwndToJpeg de loai frame trang/den (avg <0.02 hoac >0.98), cache g_lastGood de serve frame tot cuoi thay vi frame blank khi window dang repaint. Rebuild daw_vst_bridge+fx_vst_bridge+engine+installer NSIS/MSI v=202609021810.
This commit is contained in:
@@ -49,9 +49,17 @@ extern void fxGuiRequestCapture(void* hwnd, std::string& outB64, int timeoutMs);
|
||||
// Tell RenderFxJob's SHM audio feeder which realtime session is live (the
|
||||
// frontend pushes the session name; "" = session stopped/restarted).
|
||||
extern void fxGuiSetShm(const std::string& name);
|
||||
// Set by RenderFxJob once the VST editor attached to the host window; /frame
|
||||
// refuses (503) until then so the webview never shows the empty host window.
|
||||
extern std::atomic<bool> g_fxGuiEditorReady;
|
||||
|
||||
namespace {
|
||||
|
||||
// Last good captured JPEG: /frame falls back to it while the editor window is
|
||||
// blank/repainting (avoids flashing the panel white/black). Only touched by
|
||||
// the single HTTP worker thread — no lock needed.
|
||||
std::vector<unsigned char> g_lastGood;
|
||||
|
||||
// --- tiny JSON helpers (same API as RenderFxJob.cpp) ------------------------
|
||||
const json_object_element_s* member(const json_object_s* o, const char* key) {
|
||||
for (const json_object_element_s* e = o ? o->start : nullptr; e; e = e->next)
|
||||
@@ -163,7 +171,7 @@ 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) {
|
||||
bool captureHwndToJpeg(HWND hwnd, std::vector<unsigned char>& out, int quality, float* outAvg = nullptr) {
|
||||
RECT rc;
|
||||
if (!GetClientRect(hwnd, &rc)) return false;
|
||||
const int w = rc.right - rc.left;
|
||||
@@ -204,13 +212,20 @@ bool captureHwndToJpeg(HWND hwnd, std::vector<unsigned char>& out, int quality)
|
||||
ReleaseDC(hwnd, win);
|
||||
if (!px) return false;
|
||||
|
||||
// BGRA (GDI) -> RGB, then JPEG.
|
||||
// BGRA (GDI) -> RGB, then JPEG. Accumulate mean luma (0..1) so the
|
||||
// caller can reject blank frames (host window not painted yet).
|
||||
std::vector<unsigned char> rgb((size_t)w * h * 3);
|
||||
double lumaSum = 0;
|
||||
for (int i = 0; i < w * h; ++i) {
|
||||
rgb[(size_t)i * 3 + 0] = px[(size_t)i * 4 + 2];
|
||||
rgb[(size_t)i * 3 + 1] = px[(size_t)i * 4 + 1];
|
||||
rgb[(size_t)i * 3 + 2] = px[(size_t)i * 4 + 0];
|
||||
const unsigned char b = px[(size_t)i * 4 + 0];
|
||||
const unsigned char g = px[(size_t)i * 4 + 1];
|
||||
const unsigned char r = px[(size_t)i * 4 + 2];
|
||||
rgb[(size_t)i * 3 + 0] = r;
|
||||
rgb[(size_t)i * 3 + 1] = g;
|
||||
rgb[(size_t)i * 3 + 2] = b;
|
||||
lumaSum += (double)r + g + b;
|
||||
}
|
||||
if (outAvg) *outAvg = (float)(lumaSum / ((double)w * h * 3 * 255.0));
|
||||
|
||||
jpeg_compress_struct cinfo = {};
|
||||
jpeg_error_mgr jerr;
|
||||
@@ -485,11 +500,26 @@ void runHttpServer(HWND hwnd, std::atomic<bool>& stop) {
|
||||
if (g_dance && g_parkUntil &&
|
||||
(int32_t)(GetTickCount() - g_parkUntil) >= 0)
|
||||
danceEnd(hwnd);
|
||||
std::vector<unsigned char> jpg;
|
||||
if (captureHwndToJpeg(hwnd, jpg, 70))
|
||||
sendResponse(c, 200, "image/jpeg", jpg.data(), jpg.size());
|
||||
else
|
||||
sendResponse(c, 500, "application/json", "{\"error\":\"capture failed\"}", 25);
|
||||
if (!g_fxGuiEditorReady.load()) {
|
||||
// VST still loading / editor attaching (pump thread): refuse
|
||||
// rather than stream the empty host window — the webview used
|
||||
// to show that blank frame as a white flash on open.
|
||||
sendResponse(c, 503, "application/json", "{\"error\":\"loading\"}", 19);
|
||||
} else {
|
||||
std::vector<unsigned char> jpg;
|
||||
float avg = -1.0f;
|
||||
if (captureHwndToJpeg(hwnd, jpg, 70, &avg) &&
|
||||
avg >= 0.02f && avg <= 0.98f) {
|
||||
g_lastGood = jpg; // single HTTP worker thread — no lock
|
||||
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.
|
||||
sendResponse(c, 200, "image/jpeg", g_lastGood.data(), g_lastGood.size());
|
||||
} else {
|
||||
sendResponse(c, 503, "application/json", "{\"error\":\"loading\"}", 19);
|
||||
}
|
||||
}
|
||||
} else if (req.path == "/input") {
|
||||
json_parse_result_s pres = {};
|
||||
json_value_s* root = json_parse_ex(req.body.data(), req.body.size(),
|
||||
|
||||
Reference in New Issue
Block a user