fix: cap preview resolution and extract limits from node definition

- Clamp render resolution to 1024px max dimension for browser perf
- Extract maxInputs/maxFloatUniforms/maxIntUniforms from node autogrow config
- Lazily create renderer with node-specific config
- Pass dynamic limits to uniform value loops

Amp-Thread-ID: https://ampcode.com/threads/T-019ca2a6-bf8d-75fc-b497-d0338562b57f
This commit is contained in:
bymyself
2026-02-27 21:20:59 -08:00
parent 4a3ef3b42b
commit 3d4ff94678

View File

@@ -7,11 +7,53 @@ import type { UUID } from '@/lib/litegraph/src/utils/uuid'
import { useNodeOutputStore } from '@/stores/imagePreviewStore'
import { useWidgetValueStore } from '@/stores/widgetValueStore'
import type { GLSLRendererConfig } from '@/renderer/glsl/useGLSLRenderer'
import { useGLSLRenderer } from '@/renderer/glsl/useGLSLRenderer'
const GLSL_NODE_TYPE = 'GLSLShader'
const DEBOUNCE_MS = 50
const DEFAULT_SIZE = 512
const MAX_PREVIEW_DIMENSION = 1024
interface AutogrowGroup {
max: number
min: number
prefix?: string
}
function getAutogrowLimits(node: LGraphNode): GLSLRendererConfig {
const defaults: GLSLRendererConfig = {
maxInputs: 5,
maxFloatUniforms: 5,
maxIntUniforms: 5
}
if (!('comfyDynamic' in node)) return defaults
const dynamic = node.comfyDynamic
if (
typeof dynamic !== 'object' ||
dynamic === null ||
!('autogrow' in dynamic)
)
return defaults
const groups = dynamic.autogrow as Record<string, AutogrowGroup> | undefined
if (!groups) return defaults
return {
maxInputs: groups['images']?.max ?? defaults.maxInputs,
maxFloatUniforms: groups['floats']?.max ?? defaults.maxFloatUniforms,
maxIntUniforms: groups['ints']?.max ?? defaults.maxIntUniforms
}
}
function clampResolution(w: number, h: number): [number, number] {
const maxDim = Math.max(w, h)
if (maxDim <= MAX_PREVIEW_DIMENSION) return [w, h]
const scale = MAX_PREVIEW_DIMENSION / maxDim
return [Math.round(w * scale), Math.round(h * scale)]
}
export function useGLSLPreview(
nodeMaybe: MaybeRefOrGetter<LGraphNode | null | undefined>
@@ -20,7 +62,7 @@ export function useGLSLPreview(
const widgetValueStore = useWidgetValueStore()
const nodeOutputStore = useNodeOutputStore()
const renderer = useGLSLRenderer()
let renderer: ReturnType<typeof useGLSLRenderer> | null = null
let rendererReady = false
let currentBlobUrl: string | null = null
@@ -49,13 +91,19 @@ export function useGLSLPreview(
| undefined
})
const rendererConfig = computed(() => {
const node = nodeRef.value
if (!node) return { maxInputs: 5, maxFloatUniforms: 5, maxIntUniforms: 5 }
return getAutogrowLimits(node)
})
const floatValues = computed(() => {
const gId = graphId.value
const nId = nodeId.value
if (!gId || nId == null) return []
const values: number[] = []
for (let i = 0; i < 5; i++) {
for (let i = 0; i < rendererConfig.value.maxFloatUniforms; i++) {
const widget = widgetValueStore.getWidget(gId, nId, `floats.u_float${i}`)
if (widget === undefined) break
values.push(Number(widget.value) || 0)
@@ -69,7 +117,7 @@ export function useGLSLPreview(
if (!gId || nId == null) return []
const values: number[] = []
for (let i = 0; i < 5; i++) {
for (let i = 0; i < rendererConfig.value.maxIntUniforms; i++) {
const widget = widgetValueStore.getWidget(gId, nId, `ints.u_int${i}`)
if (widget === undefined) break
values.push(Number(widget.value) || 0)
@@ -86,7 +134,7 @@ export function useGLSLPreview(
function loadInputImages(): void {
const node = nodeRef.value
if (!node?.inputs) return
if (!node?.inputs || !renderer) return
let imageSlotIndex = 0
for (let slot = 0; slot < node.inputs.length; slot++) {
@@ -116,10 +164,10 @@ export function useGLSLPreview(
if (!upstreamNode?.imgs?.length) continue
const img = upstreamNode.imgs[0]
return [
return clampResolution(
img.naturalWidth || DEFAULT_SIZE,
img.naturalHeight || DEFAULT_SIZE
]
)
}
const gId = graphId.value
@@ -136,30 +184,39 @@ export function useGLSLPreview(
'size_mode.height'
)
if (widthWidget && heightWidget) {
return [
return clampResolution(
Number(widthWidget.value) || DEFAULT_SIZE,
Number(heightWidget.value) || DEFAULT_SIZE
]
)
}
}
return [DEFAULT_SIZE, DEFAULT_SIZE]
}
function ensureRenderer(): ReturnType<typeof useGLSLRenderer> {
if (!renderer) {
renderer = useGLSLRenderer(rendererConfig.value)
}
return renderer
}
async function renderPreview(): Promise<void> {
const source = shaderSource.value
if (!source || !isActive.value) return
const r = ensureRenderer()
if (!rendererReady) {
const [w, h] = getResolution()
if (!renderer.init(w, h)) {
if (!r.init(w, h)) {
lastError.value = 'WebGL2 not available'
return
}
rendererReady = true
}
const result = renderer.compileFragment(source)
const result = r.compileFragment(source)
if (!result.success) {
lastError.value = result.log
return
@@ -167,20 +224,20 @@ export function useGLSLPreview(
lastError.value = null
const [w, h] = getResolution()
renderer.setResolution(w, h)
r.setResolution(w, h)
loadInputImages()
for (let i = 0; i < floatValues.value.length; i++) {
renderer.setFloatUniform(i, floatValues.value[i])
r.setFloatUniform(i, floatValues.value[i])
}
for (let i = 0; i < intValues.value.length; i++) {
renderer.setIntUniform(i, intValues.value[i])
r.setIntUniform(i, intValues.value[i])
}
renderer.render()
r.render()
const blob = await renderer.toBlob()
const blob = await r.toBlob()
revokeBlobUrl()
currentBlobUrl = URL.createObjectURL(blob)
@@ -209,7 +266,7 @@ export function useGLSLPreview(
function dispose(): void {
debouncedRender.cancel()
revokeBlobUrl()
renderer.dispose()
renderer?.dispose()
}
onScopeDispose(dispose)