perf: tidy frame bg render pass

This commit is contained in:
Ryan Di
2026-03-18 19:36:41 +11:00
parent a7281de157
commit 2b3871856e
+54 -42
View File
@@ -26,6 +26,7 @@ import { getElementAbsoluteCoords } from "@excalidraw/element";
import type { import type {
ElementsMap, ElementsMap,
ExcalidrawFrameElement,
ExcalidrawFrameLikeElement, ExcalidrawFrameLikeElement,
NonDeletedExcalidrawElement, NonDeletedExcalidrawElement,
} from "@excalidraw/element/types"; } from "@excalidraw/element/types";
@@ -278,39 +279,15 @@ const _renderStaticScene = ({
} }
const groupsToBeAddedToFrame = new Set<string>(); const groupsToBeAddedToFrame = new Set<string>();
const renderedFrameBackgrounds = new Set<string>(); // iframe-like elements are rendered in a separate top-layer pass.
const nonIframeVisibleElements = visibleElements.filter(
const maybeRenderFrameBackground = ( (el) => !isIframeLikeElement(el),
element: NonDeletedExcalidrawElement | ExcalidrawFrameLikeElement, );
) => { // Frame background to render right before a given element id.
if ( const frameBackgroundByElementId = new Map<
!appState.frameRendering.enabled || NonDeletedExcalidrawElement["id"],
(!appState.frameRendering.outline && !renderConfig.exportingFrame) ExcalidrawFrameElement
) { >();
return;
}
const frame =
renderConfig.exportingFrame ||
(isFrameLikeElement(element)
? element
: getContainingFrame(element, elementsMap));
if (!isFrameElement(frame)) {
return;
}
if (!frame || renderedFrameBackgrounds.has(frame.id)) {
return;
}
renderFrameBackground(frame, context, appState, {
roundCorners:
!renderConfig.exportingFrame ||
frame.id !== renderConfig.exportingFrame.id,
});
renderedFrameBackgrounds.add(frame.id);
};
visibleElements.forEach((element) => { visibleElements.forEach((element) => {
if ( if (
@@ -332,19 +309,54 @@ const _renderStaticScene = ({
const inFrameGroupsMap = new Map<string, boolean>(); const inFrameGroupsMap = new Map<string, boolean>();
if (renderConfig.exportingFrame) { if (
maybeRenderFrameBackground(renderConfig.exportingFrame); appState.frameRendering.enabled &&
(appState.frameRendering.outline || renderConfig.exportingFrame)
) {
// Precompute where each frame background should be emitted to avoid
// re-resolving containing frames during the paint loop.
const renderedFrameBackgrounds = new Set<string>();
if (
renderConfig.exportingFrame &&
isFrameElement(renderConfig.exportingFrame)
) {
renderFrameBackground(renderConfig.exportingFrame, context, appState, {
roundCorners: false,
});
renderedFrameBackgrounds.add(renderConfig.exportingFrame.id);
}
const maybeQueueFrameBackground = (
element: NonDeletedExcalidrawElement | ExcalidrawFrameLikeElement,
) => {
const frame = isFrameLikeElement(element)
? element
: getContainingFrame(element, elementsMap);
if (!isFrameElement(frame) || renderedFrameBackgrounds.has(frame.id)) {
return;
}
frameBackgroundByElementId.set(element.id, frame);
renderedFrameBackgrounds.add(frame.id);
};
nonIframeVisibleElements.forEach((element) => {
maybeQueueFrameBackground(element);
});
} }
// Paint visible elements // Paint visible elements
visibleElements nonIframeVisibleElements.forEach((element) => {
.filter((el) => !isIframeLikeElement(el))
.forEach((element) => {
try { try {
// TODO: optimize (currently we call this func for each element because const frameBackground = frameBackgroundByElementId.get(element.id);
// children come before their frames and we neeed to render the frame if (frameBackground) {
// background at the bottom) renderFrameBackground(frameBackground, context, appState, {
maybeRenderFrameBackground(element); roundCorners:
!renderConfig.exportingFrame ||
frameBackground.id !== renderConfig.exportingFrame.id,
});
}
const frameId = element.frameId || appState.frameToHighlight?.id; const frameId = element.frameId || appState.frameToHighlight?.id;
if ( if (