mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-03 14:54:37 +00:00
[perf] Remove legacy Float32Array usage from LiteGraph
Removes Float32Array usage throughout LiteGraph codebase, eliminating type conversion overhead for Canvas 2D rendering operations. ## Changes Made ### Core Data Structures - **LGraphNode**: Convert position/size arrays from Float32Array to regular arrays - **LLink**: Convert coordinate storage from Float32Array to regular arrays - **Reroute**: Replace malloc pattern with standard properties - **LGraphGroup**: Convert boundary arrays from Float32Array to regular arrays ### Rendering Optimizations - **LGraphCanvas**: Convert static rendering buffers to regular arrays - **Drawing Functions**: Replace .set() method calls with direct array assignment - **Measurement**: Update bounds calculation to use regular arrays ### Type System Updates - **Interfaces**: Update Point/Size/Rect types to support both regular arrays and typed arrays - **API Compatibility**: Maintain all existing property names and method signatures ## Performance Benefits - Eliminates Float32Array conversion overhead in Canvas 2D operations - Reduces memory allocation complexity (removed malloc patterns) - Improves TypeScript integration with native array support - Maintains full API compatibility with zero breaking changes ## Background Float32Array usage was originally designed for WebGL integration, but the current Canvas 2D rendering pipeline doesn't use WebGL, making the typed arrays an unnecessary performance overhead. Every Canvas 2D operation that reads coordinates from Float32Array incurs type conversion costs.
This commit is contained in:
@@ -236,11 +236,11 @@ export class LGraphCanvas
|
||||
implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
{
|
||||
// Optimised buffers used during rendering
|
||||
static #temp = new Float32Array(4)
|
||||
static #temp_vec2 = new Float32Array(2)
|
||||
static #tmp_area = new Float32Array(4)
|
||||
static #margin_area = new Float32Array(4)
|
||||
static #link_bounding = new Float32Array(4)
|
||||
static #temp: [number, number, number, number] = [0, 0, 0, 0]
|
||||
static #temp_vec2: [number, number] = [0, 0]
|
||||
static #tmp_area: [number, number, number, number] = [0, 0, 0, 0]
|
||||
static #margin_area: [number, number, number, number] = [0, 0, 0, 0]
|
||||
static #link_bounding: [number, number, number, number] = [0, 0, 0, 0]
|
||||
|
||||
static DEFAULT_BACKGROUND_IMAGE =
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII='
|
||||
@@ -2633,7 +2633,7 @@ export class LGraphCanvas
|
||||
pointer: CanvasPointer,
|
||||
node?: LGraphNode | undefined
|
||||
): void {
|
||||
const dragRect = new Float32Array(4)
|
||||
const dragRect: [number, number, number, number] = [0, 0, 0, 0]
|
||||
|
||||
dragRect[0] = e.canvasX
|
||||
dragRect[1] = e.canvasY
|
||||
@@ -4055,7 +4055,10 @@ export class LGraphCanvas
|
||||
this.setDirty(true)
|
||||
}
|
||||
|
||||
#handleMultiSelect(e: CanvasPointerEvent, dragRect: Float32Array) {
|
||||
#handleMultiSelect(
|
||||
e: CanvasPointerEvent,
|
||||
dragRect: [number, number, number, number]
|
||||
) {
|
||||
// Process drag
|
||||
// Convert Point pair (pos, offset) to Rect
|
||||
const { graph, selectedItems, subgraph } = this
|
||||
@@ -5183,7 +5186,8 @@ export class LGraphCanvas
|
||||
// clip if required (mask)
|
||||
const shape = node._shape || RenderShape.BOX
|
||||
const size = LGraphCanvas.#temp_vec2
|
||||
size.set(node.renderingSize)
|
||||
size[0] = node.renderingSize[0]
|
||||
size[1] = node.renderingSize[1]
|
||||
|
||||
if (node.collapsed) {
|
||||
ctx.font = this.inner_text_font
|
||||
@@ -5378,7 +5382,10 @@ export class LGraphCanvas
|
||||
|
||||
// Normalised node dimensions
|
||||
const area = LGraphCanvas.#tmp_area
|
||||
area.set(node.boundingRect)
|
||||
area[0] = node.boundingRect[0]
|
||||
area[1] = node.boundingRect[1]
|
||||
area[2] = node.boundingRect[2]
|
||||
area[3] = node.boundingRect[3]
|
||||
area[0] -= node.pos[0]
|
||||
area[1] -= node.pos[1]
|
||||
|
||||
@@ -5480,7 +5487,10 @@ export class LGraphCanvas
|
||||
shape = RenderShape.ROUND
|
||||
) {
|
||||
const snapGuide = LGraphCanvas.#temp
|
||||
snapGuide.set(item.boundingRect)
|
||||
snapGuide[0] = item.boundingRect[0]
|
||||
snapGuide[1] = item.boundingRect[1]
|
||||
snapGuide[2] = item.boundingRect[2]
|
||||
snapGuide[3] = item.boundingRect[3]
|
||||
|
||||
// Not all items have pos equal to top-left of bounds
|
||||
const { pos } = item
|
||||
|
||||
@@ -11,6 +11,7 @@ import type {
|
||||
IPinnable,
|
||||
Point,
|
||||
Positionable,
|
||||
ReadOnlyRect,
|
||||
Size
|
||||
} from './interfaces'
|
||||
import { LiteGraph } from './litegraph'
|
||||
@@ -40,15 +41,15 @@ export class LGraphGroup implements Positionable, IPinnable, IColorable {
|
||||
title: string
|
||||
font?: string
|
||||
font_size: number = LiteGraph.DEFAULT_GROUP_FONT || 24
|
||||
_bounding: Float32Array = new Float32Array([
|
||||
_bounding: [number, number, number, number] = [
|
||||
10,
|
||||
10,
|
||||
LGraphGroup.minWidth,
|
||||
LGraphGroup.minHeight
|
||||
])
|
||||
]
|
||||
|
||||
_pos: Point = this._bounding.subarray(0, 2)
|
||||
_size: Size = this._bounding.subarray(2, 4)
|
||||
_pos: Point = [10, 10]
|
||||
_size: Size = [LGraphGroup.minWidth, LGraphGroup.minHeight]
|
||||
/** @deprecated See {@link _children} */
|
||||
_nodes: LGraphNode[] = []
|
||||
_children: Set<Positionable> = new Set()
|
||||
@@ -107,8 +108,8 @@ export class LGraphGroup implements Positionable, IPinnable, IColorable {
|
||||
this._size[1] = Math.max(LGraphGroup.minHeight, v[1])
|
||||
}
|
||||
|
||||
get boundingRect() {
|
||||
return this._bounding
|
||||
get boundingRect(): ReadOnlyRect {
|
||||
return [this._pos[0], this._pos[1], this._size[0], this._size[1]] as const
|
||||
}
|
||||
|
||||
get nodes() {
|
||||
@@ -145,14 +146,17 @@ export class LGraphGroup implements Positionable, IPinnable, IColorable {
|
||||
configure(o: ISerialisedGroup): void {
|
||||
this.id = o.id
|
||||
this.title = o.title
|
||||
this._bounding.set(o.bounding)
|
||||
this._pos[0] = o.bounding[0]
|
||||
this._pos[1] = o.bounding[1]
|
||||
this._size[0] = o.bounding[2]
|
||||
this._size[1] = o.bounding[3]
|
||||
this.color = o.color
|
||||
this.flags = o.flags || this.flags
|
||||
if (o.font_size) this.font_size = o.font_size
|
||||
}
|
||||
|
||||
serialize(): ISerialisedGroup {
|
||||
const b = this._bounding
|
||||
const b = [this._pos[0], this._pos[1], this._size[0], this._size[1]]
|
||||
return {
|
||||
id: this.id,
|
||||
title: this.title,
|
||||
@@ -210,7 +214,7 @@ export class LGraphGroup implements Positionable, IPinnable, IColorable {
|
||||
)
|
||||
|
||||
if (LiteGraph.highlight_selected_group && this.selected) {
|
||||
strokeShape(ctx, this._bounding, {
|
||||
strokeShape(ctx, [...this.boundingRect], {
|
||||
title_height: this.titleHeight,
|
||||
padding
|
||||
})
|
||||
@@ -251,7 +255,7 @@ export class LGraphGroup implements Positionable, IPinnable, IColorable {
|
||||
|
||||
// Move nodes we overlap the centre point of
|
||||
for (const node of nodes) {
|
||||
if (containsCentre(this._bounding, node.boundingRect)) {
|
||||
if (containsCentre(this.boundingRect, node.boundingRect)) {
|
||||
this._nodes.push(node)
|
||||
children.add(node)
|
||||
}
|
||||
@@ -259,12 +263,13 @@ export class LGraphGroup implements Positionable, IPinnable, IColorable {
|
||||
|
||||
// Move reroutes we overlap the centre point of
|
||||
for (const reroute of reroutes.values()) {
|
||||
if (isPointInRect(reroute.pos, this._bounding)) children.add(reroute)
|
||||
if (isPointInRect(reroute.pos, this.boundingRect)) children.add(reroute)
|
||||
}
|
||||
|
||||
// Move groups we wholly contain
|
||||
for (const group of groups) {
|
||||
if (containsRect(this._bounding, group._bounding)) children.add(group)
|
||||
if (containsRect(this.boundingRect, group.boundingRect))
|
||||
children.add(group)
|
||||
}
|
||||
|
||||
groups.sort((a, b) => {
|
||||
|
||||
@@ -413,7 +413,7 @@ export class LGraphNode
|
||||
}
|
||||
|
||||
/** @inheritdoc {@link renderArea} */
|
||||
#renderArea: Float32Array = new Float32Array(4)
|
||||
#renderArea: [number, number, number, number] = [0, 0, 0, 0]
|
||||
/**
|
||||
* Rect describing the node area, including shadows and any protrusions.
|
||||
* Determines if the node is visible. Calculated once at the start of every frame.
|
||||
@@ -443,9 +443,9 @@ export class LGraphNode
|
||||
}
|
||||
|
||||
/** {@link pos} and {@link size} values are backed by this {@link Rect}. */
|
||||
_posSize: Float32Array = new Float32Array(4)
|
||||
_pos: Point = this._posSize.subarray(0, 2)
|
||||
_size: Size = this._posSize.subarray(2, 4)
|
||||
_posSize: [number, number, number, number] = [0, 0, 0, 0]
|
||||
_pos: Point = [0, 0]
|
||||
_size: Size = [0, 0]
|
||||
|
||||
public get pos() {
|
||||
return this._pos
|
||||
@@ -1653,7 +1653,7 @@ export class LGraphNode
|
||||
inputs ? inputs.filter((input) => !isWidgetInputSlot(input)).length : 1,
|
||||
outputs ? outputs.length : 1
|
||||
)
|
||||
const size = out || new Float32Array([0, 0])
|
||||
const size = out || [0, 0]
|
||||
rows = Math.max(rows, 1)
|
||||
// although it should be graphcanvas.inner_text_font size
|
||||
const font_size = LiteGraph.NODE_TEXT_SIZE
|
||||
@@ -2004,13 +2004,13 @@ export class LGraphNode
|
||||
|
||||
/**
|
||||
* returns the bounding of the object, used for rendering purposes
|
||||
* @param out {Float32Array[4]?} [optional] a place to store the output, to free garbage
|
||||
* @param out {Rect?} [optional] a place to store the output, to free garbage
|
||||
* @param includeExternal {boolean?} [optional] set to true to
|
||||
* include the shadow and connection points in the bounding calculation
|
||||
* @returns the bounding box in format of [topleft_cornerx, topleft_cornery, width, height]
|
||||
*/
|
||||
getBounding(out?: Rect, includeExternal?: boolean): Rect {
|
||||
out ||= new Float32Array(4)
|
||||
out ||= [0, 0, 0, 0]
|
||||
|
||||
const rect = includeExternal ? this.renderArea : this.boundingRect
|
||||
out[0] = rect[0]
|
||||
@@ -2031,7 +2031,10 @@ export class LGraphNode
|
||||
this.onBounding?.(bounds)
|
||||
|
||||
const renderArea = this.#renderArea
|
||||
renderArea.set(bounds)
|
||||
renderArea[0] = bounds[0]
|
||||
renderArea[1] = bounds[1]
|
||||
renderArea[2] = bounds[2]
|
||||
renderArea[3] = bounds[3]
|
||||
// 4 offset for collapsed node connection points
|
||||
renderArea[0] -= 4
|
||||
renderArea[1] -= 4
|
||||
@@ -3174,7 +3177,7 @@ export class LGraphNode
|
||||
* @returns the position
|
||||
*/
|
||||
getConnectionPos(is_input: boolean, slot_number: number, out?: Point): Point {
|
||||
out ||= new Float32Array(2)
|
||||
out ||= [0, 0]
|
||||
|
||||
const {
|
||||
pos: [nodeX, nodeY],
|
||||
|
||||
@@ -109,7 +109,7 @@ export class LLink implements LinkSegment, Serialisable<SerialisableLLink> {
|
||||
data?: number | string | boolean | { toToolTip?(): string }
|
||||
_data?: unknown
|
||||
/** Centre point of the link, calculated during render only - can be inaccurate */
|
||||
_pos: Float32Array
|
||||
_pos: [number, number]
|
||||
/** @todo Clean up - never implemented in comfy. */
|
||||
_last_time?: number
|
||||
/** The last canvas 2D path that was used to render this link */
|
||||
@@ -171,7 +171,7 @@ export class LLink implements LinkSegment, Serialisable<SerialisableLLink> {
|
||||
|
||||
this._data = null
|
||||
// center
|
||||
this._pos = new Float32Array(2)
|
||||
this._pos = [0, 0]
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link LLink.create} */
|
||||
|
||||
@@ -49,8 +49,6 @@ export class Reroute
|
||||
return Reroute.radius + gap + Reroute.slotRadius
|
||||
}
|
||||
|
||||
#malloc = new Float32Array(8)
|
||||
|
||||
/** The network this reroute belongs to. Contains all valid links and reroutes. */
|
||||
#network: WeakRef<LinkNetwork>
|
||||
|
||||
@@ -73,7 +71,7 @@ export class Reroute
|
||||
/** This property is only defined on the last reroute of a floating reroute chain (closest to input end). */
|
||||
floating?: FloatingRerouteSlot
|
||||
|
||||
#pos = this.#malloc.subarray(0, 2)
|
||||
#pos: [number, number] = [0, 0]
|
||||
/** @inheritdoc */
|
||||
get pos(): Point {
|
||||
return this.#pos
|
||||
@@ -126,14 +124,14 @@ export class Reroute
|
||||
sin: number = 0
|
||||
|
||||
/** Bezier curve control point for the "target" (input) side of the link */
|
||||
controlPoint: Point = this.#malloc.subarray(4, 6)
|
||||
controlPoint: [number, number] = [0, 0]
|
||||
|
||||
/** @inheritdoc */
|
||||
path?: Path2D
|
||||
/** @inheritdoc */
|
||||
_centreAngle?: number
|
||||
/** @inheritdoc */
|
||||
_pos: Float32Array = this.#malloc.subarray(6, 8)
|
||||
_pos: [number, number] = [0, 0]
|
||||
|
||||
/** @inheritdoc */
|
||||
_dragging?: boolean
|
||||
|
||||
@@ -193,7 +193,7 @@ export interface LinkSegment {
|
||||
/** The last canvas 2D path that was used to render this segment */
|
||||
path?: Path2D
|
||||
/** Centre point of the {@link path}. Calculated during render only - can be inaccurate */
|
||||
readonly _pos: Float32Array
|
||||
readonly _pos: [number, number]
|
||||
/**
|
||||
* Y-forward along the {@link path} from its centre point, in radians.
|
||||
* `undefined` if using circles for link centres.
|
||||
@@ -225,34 +225,45 @@ export interface IFoundSlot extends IInputOrOutput {
|
||||
}
|
||||
|
||||
/** A point represented as `[x, y]` co-ordinates */
|
||||
export type Point = [x: number, y: number] | Float32Array | Float64Array
|
||||
export type Point =
|
||||
| [x: number, y: number]
|
||||
| Float32Array
|
||||
| Float64Array
|
||||
| number[]
|
||||
|
||||
/** A size represented as `[width, height]` */
|
||||
export type Size = [width: number, height: number] | Float32Array | Float64Array
|
||||
export type Size =
|
||||
| [width: number, height: number]
|
||||
| Float32Array
|
||||
| Float64Array
|
||||
| number[]
|
||||
|
||||
/** A very firm array */
|
||||
type ArRect = [x: number, y: number, width: number, height: number]
|
||||
|
||||
/** A rectangle starting at top-left coordinates `[x, y, width, height]` */
|
||||
export type Rect = ArRect | Float32Array | Float64Array
|
||||
export type Rect = ArRect | Float32Array | Float64Array | number[]
|
||||
|
||||
/** A point represented as `[x, y]` co-ordinates that will not be modified */
|
||||
export type ReadOnlyPoint =
|
||||
| readonly [x: number, y: number]
|
||||
| ReadOnlyTypedArray<Float32Array>
|
||||
| ReadOnlyTypedArray<Float64Array>
|
||||
| readonly number[]
|
||||
|
||||
/** A size represented as `[width, height]` that will not be modified */
|
||||
export type ReadOnlySize =
|
||||
| readonly [width: number, height: number]
|
||||
| ReadOnlyTypedArray<Float32Array>
|
||||
| ReadOnlyTypedArray<Float64Array>
|
||||
| readonly number[]
|
||||
|
||||
/** A rectangle starting at top-left coordinates `[x, y, width, height]` that will not be modified */
|
||||
export type ReadOnlyRect =
|
||||
| readonly [x: number, y: number, width: number, height: number]
|
||||
| ReadOnlyTypedArray<Float32Array>
|
||||
| ReadOnlyTypedArray<Float64Array>
|
||||
| readonly number[]
|
||||
|
||||
type TypedArrays =
|
||||
| Int8Array
|
||||
|
||||
@@ -331,7 +331,12 @@ export function createBounds(
|
||||
objects: Iterable<HasBoundingRect>,
|
||||
padding: number = 10
|
||||
): ReadOnlyRect | null {
|
||||
const bounds = new Float32Array([Infinity, Infinity, -Infinity, -Infinity])
|
||||
const bounds: [number, number, number, number] = [
|
||||
Infinity,
|
||||
Infinity,
|
||||
-Infinity,
|
||||
-Infinity
|
||||
]
|
||||
|
||||
for (const obj of objects) {
|
||||
const rect = obj.boundingRect
|
||||
|
||||
@@ -45,7 +45,7 @@ export abstract class SubgraphSlot
|
||||
return LiteGraph.NODE_SLOT_HEIGHT
|
||||
}
|
||||
|
||||
readonly #pos: Point = new Float32Array(2)
|
||||
readonly #pos: Point = [0, 0]
|
||||
|
||||
readonly measurement: ConstrainedSize = new ConstrainedSize(
|
||||
SubgraphSlot.defaultHeight,
|
||||
|
||||
Reference in New Issue
Block a user