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(); + } + }); }; /**