Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 0 additions & 9 deletions packages/design-system/src/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -1315,15 +1315,6 @@ audio.comfy-audio.empty-audio-widget {
font-size 0.1s ease;
}

/* Performance optimization during canvas interaction */
.transform-pane--interacting .lg-node * {
transition: none !important;
}

.transform-pane--interacting .lg-node {
will-change: transform;
}

/* ===================== Mask Editor Styles ===================== */
/* To be migrated to Tailwind later */
#maskEditor_brush {
Expand Down
3 changes: 0 additions & 3 deletions src/components/graph/GraphCanvas.vue
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@
<TransformPane
v-if="shouldRenderVueNodes && comfyApp.canvas && comfyAppReady"
:canvas="comfyApp.canvas"
@transform-update="handleTransformUpdate"
@wheel.capture="canvasInteractions.forwardEventToCanvas"
>
<!-- Vue nodes rendered based on graph nodes -->
Expand Down Expand Up @@ -118,7 +117,6 @@ import TopbarBadges from '@/components/topbar/TopbarBadges.vue'
import WorkflowTabs from '@/components/topbar/WorkflowTabs.vue'
import { useChainCallback } from '@/composables/functional/useChainCallback'
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
import { useViewportCulling } from '@/composables/graph/useViewportCulling'
import { useVueNodeLifecycle } from '@/composables/graph/useVueNodeLifecycle'
import { useNodeBadge } from '@/composables/node/useNodeBadge'
import { useCanvasDrop } from '@/composables/useCanvasDrop'
Expand Down Expand Up @@ -202,7 +200,6 @@ const { shouldRenderVueNodes } = useVueFeatureFlags()

// Vue node system
const vueNodeLifecycle = useVueNodeLifecycle()
const { handleTransformUpdate } = useViewportCulling()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Viewport Culling was one of if not the most needle moving performance improvement to large workflows (removing nodes from the layout tree via display: none in order to shrink the layer bounds) from the performance dev tool flame graph traces I ran.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I compared this branch with main (that has viewport culling) and in 300+ node large workflows when zoomed in helps the responsiveness by a considerable amount. What tests did you use to determine it was unnecessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested with a workflow with 896 nodes and it was a lot smoother to both zoom and pan.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could definitely vary across OS/Browser/Browser settings.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tested this with several people on different systems, there's near universally better subjective performance when panning and zooming without the culling.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting - I still see a 10-15fps gain when zoomed in close on large workflows, but no real difference when zoomed out. Happy to cut it / simplify if it's not a big win either way.


const handleVueNodeLifecycleReset = async () => {
if (shouldRenderVueNodes.value) {
Expand Down
103 changes: 0 additions & 103 deletions src/composables/graph/useViewportCulling.ts

This file was deleted.

87 changes: 1 addition & 86 deletions src/renderer/core/layout/__tests__/TransformPane.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,89 +115,6 @@ describe('TransformPane', () => {
const transformState = useTransformState()
expect(transformState.syncWithCanvas).toHaveBeenCalledWith(mockCanvas)
})

it('should emit transform update timing', async () => {
const mockCanvas = createMockCanvas()
const wrapper = mount(TransformPane, {
props: {
canvas: mockCanvas
}
})

await nextTick()

// Allow RAF to execute
vi.advanceTimersToNextFrame()

expect(wrapper.emitted('transformUpdate')).toBeTruthy()
})
})

describe('canvas event listeners', () => {
it('should add event listeners to canvas on mount', async () => {
const mockCanvas = createMockCanvas()
mount(TransformPane, {
props: {
canvas: mockCanvas
}
})

await nextTick()

expect(mockCanvas.canvas.addEventListener).toHaveBeenCalledWith(
'wheel',
expect.any(Function),
expect.any(Object)
)
expect(mockCanvas.canvas.addEventListener).not.toHaveBeenCalledWith(
'pointerdown',
expect.any(Function),
expect.any(Object)
)
expect(mockCanvas.canvas.addEventListener).not.toHaveBeenCalledWith(
'pointerup',
expect.any(Function),
expect.any(Object)
)
expect(mockCanvas.canvas.addEventListener).not.toHaveBeenCalledWith(
'pointercancel',
expect.any(Function),
expect.any(Object)
)
})

it('should remove event listeners on unmount', async () => {
const mockCanvas = createMockCanvas()
const wrapper = mount(TransformPane, {
props: {
canvas: mockCanvas
}
})

await nextTick()
wrapper.unmount()

expect(mockCanvas.canvas.removeEventListener).toHaveBeenCalledWith(
'wheel',
expect.any(Function),
expect.any(Object)
)
expect(mockCanvas.canvas.removeEventListener).not.toHaveBeenCalledWith(
'pointerdown',
expect.any(Function),
expect.any(Object)
)
expect(mockCanvas.canvas.removeEventListener).not.toHaveBeenCalledWith(
'pointerup',
expect.any(Function),
expect.any(Object)
)
expect(mockCanvas.canvas.removeEventListener).not.toHaveBeenCalledWith(
'pointercancel',
expect.any(Function),
expect.any(Object)
)
})
})

describe('interaction state management', () => {
Expand All @@ -214,9 +131,7 @@ describe('TransformPane', () => {
const transformPane = wrapper.find('[data-testid="transform-pane"]')

// Initially should not have interacting class
expect(transformPane.classes()).not.toContain(
'transform-pane--interacting'
)
expect(transformPane.classes()).not.toContain('will-change-transform')
})

it('should handle pointer events for node delegation', async () => {
Expand Down
26 changes: 1 addition & 25 deletions src/renderer/core/layout/transform/TransformPane.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
<template>
<div
data-testid="transform-pane"
:class="
cn(
'absolute inset-0 w-full h-full pointer-events-none',
isInteracting ? 'transform-pane--interacting' : 'will-change-auto'
)
"
class="absolute inset-0 w-full h-full pointer-events-none"
:style="transformStyle"
>
<!-- Vue nodes will be rendered here -->
Expand All @@ -16,12 +11,9 @@

<script setup lang="ts">
import { useRafFn } from '@vueuse/core'
import { computed } from 'vue'

import type { LGraphCanvas } from '@/lib/litegraph/src/litegraph'
import { useTransformSettling } from '@/renderer/core/layout/transform/useTransformSettling'
import { useTransformState } from '@/renderer/core/layout/transform/useTransformState'
import { cn } from '@/utils/tailwindUtil'

interface TransformPaneProps {
canvas?: LGraphCanvas
Expand All @@ -31,29 +23,13 @@ const props = defineProps<TransformPaneProps>()

const { transformStyle, syncWithCanvas } = useTransformState()

const canvasElement = computed(() => props.canvas?.canvas)
const { isTransforming: isInteracting } = useTransformSettling(canvasElement, {
settleDelay: 512
})

const emit = defineEmits<{
transformUpdate: []
}>()

useRafFn(
() => {
if (!props.canvas) {
return
}
syncWithCanvas(props.canvas)
emit('transformUpdate')
},
{ immediate: true }
)
</script>

<style scoped>
.transform-pane--interacting {
will-change: transform;
}
</style>
84 changes: 0 additions & 84 deletions src/renderer/core/layout/transform/useTransformSettling.ts

This file was deleted.

Loading