perf: implement spatial culling for magnified overlays

Reduces layout thrashing by filtering nodes based on their graph position before accessing their DOM widgets. This significantly improves performance on large graphs when using the magnifying glass.

- Calculates visible graph area based on cursor position and zoom.
- Checks intersection of node bounding box with visible area.
- Skips widget processing for off-screen nodes.
This commit is contained in:
google-labs-jules[bot]
2026-01-15 04:58:23 +00:00
parent 319474549c
commit 8dc8f2449e
2 changed files with 37 additions and 0 deletions
+4
View File
@@ -1,3 +1,7 @@
## 2024-05-23 - Throttling High-Frequency Events
**Learning:** High-frequency events like `mousemove` can cause performance issues if they trigger expensive DOM updates or calculations on every firing.
**Action:** Use `requestAnimationFrame` to throttle these updates to the screen refresh rate. Ensure that the event data (like coordinates) is captured synchronously in the event handler so that the rAF callback has access to the latest state.
## 2024-05-24 - Spatial Culling for LiteGraph Nodes
**Learning:** LiteGraph nodes expose `pos` and `size` properties in graph coordinates. Iterating over all nodes and accessing their DOM widgets (e.g., via `getBoundingClientRect`) causes massive layout thrashing and performance degradation when the graph is large.
**Action:** Always pre-calculate the visible graph area (converting screen/mouse coordinates to graph coordinates) and filter nodes using simple AABB overlap checks before accessing any DOM elements. This reduces O(N) DOM reads to O(K) where K is the number of visible nodes.
+33
View File
@@ -380,7 +380,40 @@ export class MagnifyGlass {
const nodes: ComfyNode[] = graph._nodes;
if (!nodes) return;
// Calculate source region in graph coordinates for culling
// This mirrors calculateSourceRegion() but stops at graph coords
const rect = this.litegraphCanvas.getBoundingClientRect();
const dpr = rect.width > 0 ? this.litegraphCanvas.width / rect.width : 1;
const cursorCssX = this.state.x / dpr;
const cursorCssY = this.state.y / dpr;
const cursorGraphX = (cursorCssX - this.state.canvasOffsetX) / this.state.canvasScale;
const cursorGraphY = (cursorCssY - this.state.canvasOffsetY) / this.state.canvasScale;
const targetGraphCenterX = cursorGraphX + this.config.offsetX;
const targetGraphCenterY = cursorGraphY + this.config.offsetY;
const sourceGraphWidth = (this.config.glassSize / this.config.zoomFactor) / this.state.canvasScale;
const sourceGraphHeight = (this.config.glassSize / this.config.zoomFactor) / this.state.canvasScale;
const sourceGraphRect = {
x: targetGraphCenterX - (sourceGraphWidth / 2),
y: targetGraphCenterY - (sourceGraphHeight / 2),
width: sourceGraphWidth,
height: sourceGraphHeight
};
for (const node of nodes) {
// Optimization: Cull nodes that are not in the source region
if (node.pos && node.size) {
const nodeRect = {
x: node.pos[0],
y: node.pos[1],
width: node.size[0],
height: node.size[1]
};
if (!rectsOverlap(sourceGraphRect, nodeRect)) {
continue;
}
}
const widgets = (node as any).widgets;
if (!widgets) continue;