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:
@@ -13380,7 +13380,7 @@ const ExportProgressModal = ({ open, exportProgress, isExporting, onClose }) =>
|
|||||||
const boxRef = React.useRef(null);
|
const boxRef = React.useRef(null);
|
||||||
const deadRef = React.useRef(false);
|
const deadRef = React.useRef(false);
|
||||||
const screenRectRef = React.useRef(null);
|
const screenRectRef = React.useRef(null);
|
||||||
const [dead, setDead] = React.useState(false);
|
const [, setDead] = React.useState(false);
|
||||||
const [frameLoaded, setFrameLoaded] = React.useState(false);
|
const [frameLoaded, setFrameLoaded] = React.useState(false);
|
||||||
const post = (payload) => { try { fetch(url + '/input', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }).catch(() => {}); } catch (e) {} };
|
const post = (payload) => { try { fetch(url + '/input', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }).catch(() => {}); } catch (e) {} };
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@@ -13475,13 +13475,7 @@ const ExportProgressModal = ({ open, exportProgress, isExporting, onClose }) =>
|
|||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<div className="relative w-full h-full flex flex-col">
|
<div className="relative w-full h-full flex flex-col">
|
||||||
<div className="flex items-center justify-between px-3 py-1.5 bg-slate-900/80 border border-slate-800 rounded-t-lg shrink-0">
|
<div ref={boxRef} tabIndex={0} className="flex-1 relative bg-slate-950 border border-slate-800 rounded-lg overflow-hidden outline-none"
|
||||||
<span className="text-[10px] text-violet-300 font-mono">VST GUI — nhúng (frame-capture · ~10fps)</span>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{dead && <span className="text-[9px] text-red-400 font-mono">bridge mất kết nối</span>}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div ref={boxRef} tabIndex={0} className="flex-1 relative bg-slate-950 border border-slate-800 rounded-b-lg overflow-hidden outline-none"
|
|
||||||
onMouseDown={handle} onMouseUp={handle} onMouseMove={handle}
|
onMouseDown={handle} onMouseUp={handle} onMouseMove={handle}
|
||||||
onMouseLeave={() => post({ type: 'mouseup', x: -1, y: -1, button: 0 })}
|
onMouseLeave={() => post({ type: 'mouseup', x: -1, y: -1, button: 0 })}
|
||||||
onWheel={e => { const p = pt(e); if (p) post({ type: 'wheel', x: p.x, y: p.y, deltaY: e.deltaY, rect: screenRectRef.current || undefined }); }}
|
onWheel={e => { const p = pt(e); if (p) post({ type: 'wheel', x: p.x, y: p.y, deltaY: e.deltaY, rect: screenRectRef.current || undefined }); }}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -51,7 +51,7 @@
|
|||||||
<script src="/static/js/services/promptTemplateManager.js?v=202608290945"></script>
|
<script src="/static/js/services/promptTemplateManager.js?v=202608290945"></script>
|
||||||
<script src="/static/js/services/undoRedoEngine.js?v=202608290945"></script>
|
<script src="/static/js/services/undoRedoEngine.js?v=202608290945"></script>
|
||||||
<script src="/static/js/services/chordTheory.js?v=202608290945"></script>
|
<script src="/static/js/services/chordTheory.js?v=202608290945"></script>
|
||||||
<script src="/static/js/app.precompiled.js?v=202609021718" defer></script>
|
<script src="/static/js/app.precompiled.js?v=202609021810" defer></script>
|
||||||
<link rel="stylesheet" href="/static/css/styles.css?v=202608290945">
|
<link rel="stylesheet" href="/static/css/styles.css?v=202608290945">
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
|
|||||||
@@ -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
|
// Tell RenderFxJob's SHM audio feeder which realtime session is live (the
|
||||||
// frontend pushes the session name; "" = session stopped/restarted).
|
// frontend pushes the session name; "" = session stopped/restarted).
|
||||||
extern void fxGuiSetShm(const std::string& name);
|
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 {
|
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) ------------------------
|
// --- tiny JSON helpers (same API as RenderFxJob.cpp) ------------------------
|
||||||
const json_object_element_s* member(const json_object_s* o, const char* key) {
|
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)
|
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
|
// 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) {
|
bool captureHwndToJpeg(HWND hwnd, std::vector<unsigned char>& out, int quality, float* outAvg = 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;
|
||||||
@@ -204,13 +212,20 @@ bool captureHwndToJpeg(HWND hwnd, std::vector<unsigned char>& out, int quality)
|
|||||||
ReleaseDC(hwnd, win);
|
ReleaseDC(hwnd, win);
|
||||||
if (!px) return false;
|
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);
|
std::vector<unsigned char> rgb((size_t)w * h * 3);
|
||||||
|
double lumaSum = 0;
|
||||||
for (int i = 0; i < w * h; ++i) {
|
for (int i = 0; i < w * h; ++i) {
|
||||||
rgb[(size_t)i * 3 + 0] = px[(size_t)i * 4 + 2];
|
const unsigned char b = px[(size_t)i * 4 + 0];
|
||||||
rgb[(size_t)i * 3 + 1] = px[(size_t)i * 4 + 1];
|
const unsigned char g = px[(size_t)i * 4 + 1];
|
||||||
rgb[(size_t)i * 3 + 2] = px[(size_t)i * 4 + 0];
|
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_compress_struct cinfo = {};
|
||||||
jpeg_error_mgr jerr;
|
jpeg_error_mgr jerr;
|
||||||
@@ -485,11 +500,26 @@ void runHttpServer(HWND hwnd, std::atomic<bool>& stop) {
|
|||||||
if (g_dance && g_parkUntil &&
|
if (g_dance && g_parkUntil &&
|
||||||
(int32_t)(GetTickCount() - g_parkUntil) >= 0)
|
(int32_t)(GetTickCount() - g_parkUntil) >= 0)
|
||||||
danceEnd(hwnd);
|
danceEnd(hwnd);
|
||||||
std::vector<unsigned char> jpg;
|
if (!g_fxGuiEditorReady.load()) {
|
||||||
if (captureHwndToJpeg(hwnd, jpg, 70))
|
// VST still loading / editor attaching (pump thread): refuse
|
||||||
sendResponse(c, 200, "image/jpeg", jpg.data(), jpg.size());
|
// rather than stream the empty host window — the webview used
|
||||||
else
|
// to show that blank frame as a white flash on open.
|
||||||
sendResponse(c, 500, "application/json", "{\"error\":\"capture failed\"}", 25);
|
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") {
|
} else if (req.path == "/input") {
|
||||||
json_parse_result_s pres = {};
|
json_parse_result_s pres = {};
|
||||||
json_value_s* root = json_parse_ex(req.body.data(), req.body.size(),
|
json_value_s* root = json_parse_ex(req.body.data(), req.body.size(),
|
||||||
|
|||||||
@@ -62,6 +62,12 @@
|
|||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
// Editor-ready flag (fx-gui server mode). Set true once the VST editor
|
||||||
|
// attached to the host window (fxGuiLoadAttach, pump thread). FxGuiServer.cpp
|
||||||
|
// refuses to serve /frame while false: before attach the host window is empty
|
||||||
|
// and capturing it would stream a blank frame -> white flash in the webview.
|
||||||
|
std::atomic<bool> g_fxGuiEditorReady{false};
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// --- sheredom/json.h helpers (same as RenderJob.cpp) ------------------------
|
// --- sheredom/json.h helpers (same as RenderJob.cpp) ------------------------
|
||||||
@@ -1819,11 +1825,16 @@ static int fxGuiLoadAttach(Vst3Fx* fx, const std::string& path, const std::strin
|
|||||||
std::cerr << "[FxGui] preset apply FAILED (default kept): " << path << std::endl;
|
std::cerr << "[FxGui] preset apply FAILED (default kept): " << path << std::endl;
|
||||||
else if (!presetB64.empty())
|
else if (!presetB64.empty())
|
||||||
std::cerr << "[FxGui] preset applied: " << path << std::endl;
|
std::cerr << "[FxGui] preset applied: " << path << std::endl;
|
||||||
|
g_fxGuiEditorReady.store(false);
|
||||||
if (!fxGuiAttachSafe(fx, hwnd)) {
|
if (!fxGuiAttachSafe(fx, hwnd)) {
|
||||||
std::cerr << "[FxGui] editor attach failed for " << path << std::endl;
|
std::cerr << "[FxGui] editor attach failed for " << path << std::endl;
|
||||||
DestroyWindow(hwnd);
|
DestroyWindow(hwnd);
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
// Attach OK: allow /frame. First captures may still be blank (plugin paints
|
||||||
|
// on its first WM_PAINT) — FxGuiServer's luma check rejects those and
|
||||||
|
// serves the last good frame instead.
|
||||||
|
g_fxGuiEditorReady.store(true);
|
||||||
std::cout << "[FxGui] editor open: " << (name.empty() ? path : name) << std::endl;
|
std::cout << "[FxGui] editor open: " << (name.empty() ? path : name) << std::endl;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user