287 lines
8.6 KiB
JavaScript
287 lines
8.6 KiB
JavaScript
const { app } = window.comfyAPI.app;
|
|
|
|
const _savedRoundRadius = typeof LiteGraph !== "undefined" ? LiteGraph.ROUND_RADIUS : 8;
|
|
|
|
function getSetting(id, fallback) {
|
|
return app.ui.settings.getSettingValue(id) ?? fallback;
|
|
}
|
|
|
|
function toggleSetting(id) {
|
|
const cur = app.ui.settings.getSettingValue(id);
|
|
app.ui.settings.setSettingValue(id, !cur);
|
|
}
|
|
|
|
app.registerExtension({
|
|
name: "KJNodes.Performance",
|
|
|
|
commands: [
|
|
{
|
|
id: "KJNodes.perf.toggleSingleCanvasPan",
|
|
label: "Single-canvas mode during pan",
|
|
menubarLabel: "Single-canvas mode during pan",
|
|
function: () => toggleSetting("KJNodes.perf.singleCanvasPan"),
|
|
active: () => getSetting("KJNodes.perf.singleCanvasPan", false),
|
|
},
|
|
{
|
|
id: "KJNodes.perf.toggleDisableShadows",
|
|
label: "Disable node shadows",
|
|
menubarLabel: "Disable node shadows",
|
|
function: () => toggleSetting("KJNodes.perf.disableShadows"),
|
|
active: () => getSetting("KJNodes.perf.disableShadows", false),
|
|
},
|
|
{
|
|
id: "KJNodes.perf.toggleDisableConnectionBorders",
|
|
label: "Disable connection borders",
|
|
menubarLabel: "Disable connection borders",
|
|
function: () => toggleSetting("KJNodes.perf.disableConnectionBorders"),
|
|
active: () => getSetting("KJNodes.perf.disableConnectionBorders", false),
|
|
},
|
|
{
|
|
id: "KJNodes.perf.toggleDisableRoundedCorners",
|
|
label: "Disable rounded corners",
|
|
menubarLabel: "Disable rounded corners",
|
|
function: () => toggleSetting("KJNodes.perf.disableRoundedCorners"),
|
|
active: () => getSetting("KJNodes.perf.disableRoundedCorners", false),
|
|
},
|
|
{
|
|
id: "KJNodes.perf.toggleThrottleRenderInfo",
|
|
label: "Throttle info overlay",
|
|
menubarLabel: "Throttle info overlay",
|
|
function: () => toggleSetting("KJNodes.perf.throttleRenderInfo"),
|
|
active: () => getSetting("KJNodes.perf.throttleRenderInfo", false),
|
|
},
|
|
],
|
|
|
|
menuCommands: [
|
|
{
|
|
path: ["KJNodes", "Performance"],
|
|
commands: [
|
|
"KJNodes.perf.toggleSingleCanvasPan",
|
|
"KJNodes.perf.toggleDisableShadows",
|
|
"KJNodes.perf.toggleDisableConnectionBorders",
|
|
"KJNodes.perf.toggleDisableRoundedCorners",
|
|
"KJNodes.perf.toggleThrottleRenderInfo",
|
|
],
|
|
},
|
|
],
|
|
|
|
settings: [
|
|
{
|
|
id: "KJNodes.perf.singleCanvasPan",
|
|
name: "Single-canvas mode during pan",
|
|
category: ["KJNodes", "Performance", "Single-canvas mode during pan"],
|
|
tooltip: "Eliminates the expensive bgcanvas-to-canvas copy during panning. Significant improvement without hardware acceleration.",
|
|
type: "boolean",
|
|
defaultValue: false,
|
|
onChange: (value) => {
|
|
const canvas = app.canvas;
|
|
if (!canvas) return;
|
|
if (value) {
|
|
installSingleCanvasPan(canvas);
|
|
} else {
|
|
uninstallSingleCanvasPan(canvas);
|
|
}
|
|
},
|
|
},
|
|
{
|
|
id: "KJNodes.perf.disableShadows",
|
|
name: "Disable node shadows",
|
|
category: ["KJNodes", "Performance", "Disable node shadows"],
|
|
tooltip: "Disable shadow rendering on nodes for better performance without hardware acceleration.",
|
|
type: "boolean",
|
|
defaultValue: false,
|
|
onChange: (value) => {
|
|
if (app.canvas) {
|
|
app.canvas.render_shadows = !value;
|
|
app.canvas.setDirty(true, true);
|
|
}
|
|
},
|
|
},
|
|
{
|
|
id: "KJNodes.perf.disableConnectionBorders",
|
|
name: "Disable connection borders",
|
|
category: ["KJNodes", "Performance", "Disable connection borders"],
|
|
tooltip: "Disable the outer border stroke on connection lines. Each link draws two strokes instead of one when enabled.",
|
|
type: "boolean",
|
|
defaultValue: false,
|
|
onChange: (value) => {
|
|
if (app.canvas) {
|
|
app.canvas.render_connections_border = !value;
|
|
app.canvas.setDirty(true, true);
|
|
}
|
|
},
|
|
},
|
|
{
|
|
id: "KJNodes.perf.disableRoundedCorners",
|
|
name: "Disable rounded corners",
|
|
category: ["KJNodes", "Performance", "Disable rounded corners"],
|
|
tooltip: "Use square corners on nodes instead of rounded. Avoids roundRect calls in software rendering.",
|
|
type: "boolean",
|
|
defaultValue: false,
|
|
onChange: (value) => {
|
|
if (typeof LiteGraph !== "undefined") {
|
|
LiteGraph.ROUND_RADIUS = value ? 0 : _savedRoundRadius;
|
|
app.canvas?.setDirty(true, true);
|
|
}
|
|
},
|
|
},
|
|
{
|
|
id: "KJNodes.perf.throttleRenderInfo",
|
|
name: "Throttle info overlay",
|
|
category: ["KJNodes", "Performance", "Throttle info overlay"],
|
|
tooltip: "Cache the FPS/info text overlay and only re-render it a few times per second. Saves ~20ms per frame in software rendering.",
|
|
type: "boolean",
|
|
defaultValue: false,
|
|
onChange: (value) => {
|
|
const canvas = app.canvas;
|
|
if (!canvas) return;
|
|
if (value) {
|
|
installThrottleRenderInfo(canvas);
|
|
} else {
|
|
uninstallThrottleRenderInfo(canvas);
|
|
}
|
|
},
|
|
},
|
|
],
|
|
|
|
setup() {
|
|
const canvas = app.canvas;
|
|
if (!canvas) return;
|
|
|
|
if (getSetting("KJNodes.perf.singleCanvasPan", false)) {
|
|
installSingleCanvasPan(canvas);
|
|
}
|
|
if (getSetting("KJNodes.perf.disableShadows", false)) {
|
|
canvas.render_shadows = false;
|
|
}
|
|
if (getSetting("KJNodes.perf.disableConnectionBorders", false)) {
|
|
canvas.render_connections_border = false;
|
|
}
|
|
if (getSetting("KJNodes.perf.disableRoundedCorners", false)) {
|
|
LiteGraph.ROUND_RADIUS = 0;
|
|
}
|
|
if (getSetting("KJNodes.perf.throttleRenderInfo", false)) {
|
|
installThrottleRenderInfo(canvas);
|
|
}
|
|
},
|
|
});
|
|
|
|
// ── Single-canvas mode during pan ───────────────────────────────────────────
|
|
|
|
let _origDraw = null;
|
|
let _panInstalledOn = null;
|
|
|
|
function installSingleCanvasPan(canvas) {
|
|
if (_origDraw && _panInstalledOn === canvas) return;
|
|
if (_origDraw && _panInstalledOn !== canvas) {
|
|
// Canvas was replaced — discard stale reference
|
|
_origDraw = null;
|
|
}
|
|
_panInstalledOn = canvas;
|
|
_origDraw = canvas.draw;
|
|
let panning = false;
|
|
let savedBgCanvas = null;
|
|
let savedBgCtx = null;
|
|
|
|
canvas.draw = function (force_canvas, force_bgcanvas) {
|
|
if (this.dragging_canvas) {
|
|
if (!panning) {
|
|
// Flush any pending bg redraw before aliasing to avoid a stale-frame flash
|
|
if (this.dirty_bgcanvas) {
|
|
_origDraw.call(this, false, true);
|
|
}
|
|
savedBgCanvas = this.bgcanvas;
|
|
savedBgCtx = this.bgctx;
|
|
panning = true;
|
|
this.bgcanvas = this.canvas;
|
|
this.bgctx = this.ctx;
|
|
}
|
|
} else if (panning) {
|
|
panning = false;
|
|
this.bgcanvas = savedBgCanvas;
|
|
this.bgctx = savedBgCtx;
|
|
this.dirty_bgcanvas = true;
|
|
this.dirty_canvas = true;
|
|
}
|
|
_origDraw.call(this, force_canvas, force_bgcanvas);
|
|
};
|
|
}
|
|
|
|
function uninstallSingleCanvasPan(canvas) {
|
|
if (!_origDraw) return;
|
|
if (_panInstalledOn === canvas) {
|
|
canvas.draw = _origDraw;
|
|
}
|
|
_origDraw = null;
|
|
_panInstalledOn = null;
|
|
canvas.dirty_bgcanvas = true;
|
|
canvas.dirty_canvas = true;
|
|
}
|
|
|
|
// ── Throttle renderInfo ─────────────────────────────────────────────────────
|
|
|
|
let _origRenderInfo = null;
|
|
let _infoCanvas = null;
|
|
let _infoInstalledOn = null;
|
|
|
|
function installThrottleRenderInfo(canvas) {
|
|
if (_origRenderInfo && _infoInstalledOn === canvas) return;
|
|
if (_origRenderInfo && _infoInstalledOn !== canvas) {
|
|
_origRenderInfo = null;
|
|
_infoCanvas = null;
|
|
}
|
|
_infoInstalledOn = canvas;
|
|
_origRenderInfo = canvas.renderInfo;
|
|
_infoCanvas = document.createElement("canvas");
|
|
let infoCtx = _infoCanvas.getContext("2d");
|
|
let lastInfoTime = 0;
|
|
let cachedDpr = 0;
|
|
let _infoDrawX = 10;
|
|
let _infoDrawY = 0;
|
|
|
|
canvas.renderInfo = function (ctx, x, y) {
|
|
const dpr = window.devicePixelRatio || 1;
|
|
|
|
// Resize offscreen canvas if DPR changed
|
|
if (dpr !== cachedDpr) {
|
|
cachedDpr = dpr;
|
|
_infoCanvas.width = Math.ceil(200 * dpr);
|
|
_infoCanvas.height = Math.ceil(100 * dpr);
|
|
infoCtx = _infoCanvas.getContext("2d");
|
|
infoCtx.scale(dpr, dpr);
|
|
lastInfoTime = 0;
|
|
}
|
|
|
|
const now = performance.now();
|
|
if (now - lastInfoTime > 250) {
|
|
lastInfoTime = now;
|
|
infoCtx.clearRect(0, 0, 200, 100);
|
|
// Render with the same x/y the original would use, but offset
|
|
// into offscreen canvas coordinates
|
|
const origX = x || 10;
|
|
const lineHeight = 13;
|
|
const lineCount = (this.graph ? 5 : 1) + (this.info_text ? 1 : 0);
|
|
const origY = y || (this.canvas.height / dpr - (lineCount + 1) * lineHeight);
|
|
// Store the computed position for blitting
|
|
_infoDrawX = origX;
|
|
_infoDrawY = origY;
|
|
// Draw into offscreen at (1, 1), text flows from there
|
|
_origRenderInfo.call(this, infoCtx, 1, 1);
|
|
}
|
|
|
|
// Blit cached text at the original position
|
|
ctx.drawImage(_infoCanvas, _infoDrawX - 1, _infoDrawY - 1, 200, 100);
|
|
};
|
|
}
|
|
|
|
function uninstallThrottleRenderInfo(canvas) {
|
|
if (!_origRenderInfo) return;
|
|
if (_infoInstalledOn === canvas) {
|
|
canvas.renderInfo = _origRenderInfo;
|
|
}
|
|
_origRenderInfo = null;
|
|
_infoCanvas = null;
|
|
_infoInstalledOn = null;
|
|
canvas.setDirty(true, true);
|
|
}
|