perf(ui): optimized object rendering

- Throttle opacity and compositing fill rendering to 100ms
- Reduce compositing rect rendering to minimum
This commit is contained in:
psychedelicious
2024-10-08 16:21:55 +10:00
parent 00be03b5b9
commit 7f2fdae870
3 changed files with 56 additions and 28 deletions

View File

@@ -39,6 +39,9 @@ export class CanvasEntityAdapterInpaintMask extends CanvasEntityAdapterBase<
return;
}
// If prevState is undefined, this is the first render. Some logic is only needed on the first render, or required
// on first render.
if (!prevState || this.state.isEnabled !== prevState.isEnabled) {
this.syncIsEnabled();
}
@@ -55,21 +58,16 @@ export class CanvasEntityAdapterInpaintMask extends CanvasEntityAdapterBase<
this.syncOpacity();
}
if (!prevState || this.state.fill !== prevState.fill) {
this.syncCompositingRectFill();
// On first render, we must force the update
this.renderer.updateCompositingRectFill(!prevState);
}
if (!prevState) {
this.syncCompositingRectSize();
// On first render, we must force the updates
this.renderer.updateCompositingRectSize(true);
this.renderer.updateCompositingRectPosition(true);
}
};
syncCompositingRectSize = () => {
this.renderer.updateCompositingRectSize();
};
syncCompositingRectFill = () => {
this.renderer.updateCompositingRectFill();
};
getHashableState = (): SerializableObject => {
const keysToOmit: (keyof CanvasInpaintMaskState)[] = ['fill', 'name', 'opacity'];
return omit(this.state, keysToOmit);

View File

@@ -39,6 +39,9 @@ export class CanvasEntityAdapterRegionalGuidance extends CanvasEntityAdapterBase
return;
}
// If prevState is undefined, this is the first render. Some logic is only needed on the first render, or required
// on first render.
if (!prevState || this.state.isEnabled !== prevState.isEnabled) {
this.syncIsEnabled();
}
@@ -55,21 +58,16 @@ export class CanvasEntityAdapterRegionalGuidance extends CanvasEntityAdapterBase
this.syncOpacity();
}
if (!prevState || this.state.fill !== prevState.fill) {
this.syncCompositingRectFill();
// On first render, we must force the update
this.renderer.updateCompositingRectFill(!prevState);
}
if (!prevState) {
this.syncCompositingRectSize();
// On first render, we must force the updates
this.renderer.updateCompositingRectSize(true);
this.renderer.updateCompositingRectPosition(true);
}
};
syncCompositingRectSize = () => {
this.renderer.updateCompositingRectSize();
};
syncCompositingRectFill = () => {
this.renderer.updateCompositingRectFill();
};
getHashableState = (): SerializableObject => {
const keysToOmit: (keyof CanvasRegionalGuidanceState)[] = ['fill', 'name', 'opacity'];
return omit(this.state, keysToOmit);

View File

@@ -25,6 +25,7 @@ import type { Rect } from 'features/controlLayers/store/types';
import { imageDTOToImageObject } from 'features/controlLayers/store/util';
import Konva from 'konva';
import type { GroupConfig } from 'konva/lib/Group';
import { throttle } from 'lodash-es';
import type { Logger } from 'roarr';
import { getImageDTOSafe, uploadImage } from 'services/api/endpoints/images';
import type { ImageDTO } from 'services/api/types';
@@ -131,13 +132,22 @@ export class CanvasEntityObjectRenderer extends CanvasModuleBase {
// The compositing rect must cover the whole stage at all times. When the stage is scaled, moved or resized, we
// need to update the compositing rect to match the stage.
this.subscriptions.add(
this.manager.stage.$stageAttrs.listen(() => {
this.manager.stage.$stageAttrs.listen((stageAttrs, oldStageAttrs) => {
if (!this.konva.compositing) {
return;
}
if (
this.konva.compositing &&
(this.parent.type === 'inpaint_mask_adapter' || this.parent.type === 'regional_guidance_adapter')
stageAttrs.width !== oldStageAttrs.width ||
stageAttrs.height !== oldStageAttrs.height ||
stageAttrs.scale !== oldStageAttrs.scale
) {
this.updateCompositingRectSize();
}
if (stageAttrs.x !== oldStageAttrs.x || stageAttrs.y !== oldStageAttrs.y) {
this.updateCompositingRectPosition();
}
})
);
}
@@ -197,7 +207,11 @@ export class CanvasEntityObjectRenderer extends CanvasModuleBase {
}
};
updateCompositingRectFill = () => {
updateCompositingRectFill = throttle((force?: boolean) => {
if (!force && !this.hasObjects()) {
return;
}
this.log.trace('Updating compositing rect fill');
assert(this.konva.compositing, 'Missing compositing rect');
@@ -216,9 +230,13 @@ export class CanvasEntityObjectRenderer extends CanvasModuleBase {
});
setFillPatternImage(this.konva.compositing.rect, fill.style, fill.color);
}
};
}, 100);
updateCompositingRectSize = (force?: boolean) => {
if (!force && !this.hasObjects()) {
return;
}
updateCompositingRectSize = () => {
this.log.trace('Updating compositing rect size');
assert(this.konva.compositing, 'Missing compositing rect');
@@ -232,7 +250,21 @@ export class CanvasEntityObjectRenderer extends CanvasModuleBase {
});
};
updateOpacity = () => {
updateCompositingRectPosition = (force?: boolean) => {
if (!force && !this.hasObjects()) {
return;
}
this.log.trace('Updating compositing rect position');
assert(this.konva.compositing, 'Missing compositing rect');
this.konva.compositing.rect.setAttrs({
...this.manager.stage.getScaledStageRect(),
});
};
updateOpacity = throttle(() => {
this.log.trace('Updating opacity');
const opacity = this.parent.state.opacity;
@@ -243,7 +275,7 @@ export class CanvasEntityObjectRenderer extends CanvasModuleBase {
this.konva.objectGroup.opacity(opacity);
}
this.parent.bufferRenderer.konva.group.opacity(opacity);
};
}, 100);
/**
* Renders the given object. If the object renderer does not exist, it will be created and its Konva group added to the