From 148bdc736c25b6db63a920a955e4598607bc6b35 Mon Sep 17 00:00:00 2001 From: Kyle Havlovitz Date: Tue, 14 Oct 2025 15:25:44 -0700 Subject: [PATCH] Fix an issue with multiple quick-queued generations after moving bbox MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After moving the canvas bbox we still handed out the previous regional-guidance mask because only two parts of the system knew anything had changed. The adapter’s cache key doesn’t include the bbox, so the next few graph builds reused the stale mask from before the move; if the user queued several runs back‑to‑back, every background enqueue except the last skipped rerasterizing altogether because another raster job was still in flight. The fix makes the canvas manager invalidate each region adapter’s cached mask whenever the bbox (or a related setting) changes, and—if a reraster is already running—queues up and waits instead of bailing. Now the first run after a bbox edit forces a new mask, and rapid-fire enqueues just wait their turn, so every queued generation gets the correct regional prompt. --- .../CanvasEntity/CanvasEntityAdapterBase.ts | 4 ++++ .../CanvasEntityObjectRenderer.ts | 19 ++++++++++++++++- .../controlLayers/konva/CanvasManager.ts | 6 ++++++ .../konva/CanvasStateApiModule.ts | 21 +++++++++++++++++++ 4 files changed, 49 insertions(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntity/CanvasEntityAdapterBase.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntity/CanvasEntityAdapterBase.ts index 2b45f61b291..472551cddb7 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntity/CanvasEntityAdapterBase.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntity/CanvasEntityAdapterBase.ts @@ -546,6 +546,10 @@ export abstract class CanvasEntityAdapterBase { + this.renderer.invalidateRasterCache(); + }; + /** * Synchronizes the entity's locked state with the canvas. */ diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntity/CanvasEntityObjectRenderer.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntity/CanvasEntityObjectRenderer.ts index f2d7140d68a..df9d92d3f4e 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntity/CanvasEntityObjectRenderer.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntity/CanvasEntityObjectRenderer.ts @@ -66,6 +66,11 @@ export class CanvasEntityObjectRenderer extends CanvasModuleBase { */ renderers = new SyncableMap(); + /** + * Tracks the cache keys used when rasterizing this entity so they can be invalidated on demand. + */ + rasterCacheKeys = new Set(); + /** * A object containing singleton Konva nodes. */ @@ -477,7 +482,7 @@ export class CanvasEntityObjectRenderer extends CanvasModuleBase { }): Promise => { const rasterizingAdapter = this.manager.stateApi.$rasterizingAdapter.get(); if (rasterizingAdapter) { - assert(false, `Already rasterizing an entity: ${rasterizingAdapter.id}`); + await this.manager.stateApi.waitForRasterizationToFinish(); } const { rect, replaceObjects, attrs, bg, ignoreCache } = { @@ -492,6 +497,7 @@ export class CanvasEntityObjectRenderer extends CanvasModuleBase { const cachedImageName = this.manager.cache.imageNameCache.get(hash); if (cachedImageName && !ignoreCache) { + this.rasterCacheKeys.add(hash); imageDTO = await getImageDTOSafe(cachedImageName); if (imageDTO) { this.log.trace({ rect, cachedImageName, imageDTO }, 'Using cached rasterized image'); @@ -525,6 +531,7 @@ export class CanvasEntityObjectRenderer extends CanvasModuleBase { replaceObjects, }); this.manager.cache.imageNameCache.set(hash, imageDTO.image_name); + this.rasterCacheKeys.add(hash); return imageDTO; } catch (error) { this.log.error({ rasterizeArgs, error: serializeError(error as Error) }, 'Failed to rasterize entity'); @@ -534,6 +541,16 @@ export class CanvasEntityObjectRenderer extends CanvasModuleBase { } }; + invalidateRasterCache = () => { + if (this.rasterCacheKeys.size === 0) { + return; + } + for (const key of this.rasterCacheKeys) { + this.manager.cache.imageNameCache.delete(key); + } + this.rasterCacheKeys.clear(); + }; + cloneObjectGroup = (arg: { attrs?: GroupConfig } = {}): Konva.Group => { const { attrs } = arg; const clone = this.konva.objectGroup.clone(); diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts index 38248136625..737be876724 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts @@ -199,6 +199,12 @@ export class CanvasManager extends CanvasModuleBase { ]; }; + invalidateRegionalGuidanceRasterCache = () => { + for (const adapter of this.adapters.regionMasks.values()) { + adapter.invalidateRasterCache(); + } + }; + createAdapter = (entityIdentifier: CanvasEntityIdentifier): CanvasEntityAdapter => { if (isRasterLayerEntityIdentifier(entityIdentifier)) { const adapter = new CanvasEntityAdapterRasterLayer(entityIdentifier, this); diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApiModule.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApiModule.ts index 57027aaa8f9..5970cf1eb88 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApiModule.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApiModule.ts @@ -213,6 +213,27 @@ export class CanvasStateApiModule extends CanvasModuleBase { */ setGenerationBbox = (rect: Rect) => { this.store.dispatch(bboxChangedFromCanvas(rect)); + this.manager.invalidateRegionalGuidanceRasterCache(); + }; + + waitForRasterizationToFinish = async () => { + if (!this.$rasterizingAdapter.get()) { + return; + } + + await new Promise((resolve) => { + const unsubscribe = this.$rasterizingAdapter.listen((adapter) => { + if (!adapter) { + unsubscribe(); + resolve(); + } + }); + + if (!this.$rasterizingAdapter.get()) { + unsubscribe(); + resolve(); + } + }); }; /**