From 21cfd1b0f74134ae1a407f10f6dd75417d281059 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Tue, 2 Jun 2026 22:07:43 +0800 Subject: [PATCH] perf(grid): cache canvas render state --- apps/desktop/src/components/grid/DataGrid.vue | 6 + .../desktop/src/lib/canvasDataGridRenderer.ts | 137 ++++++++++++------ 2 files changed, 100 insertions(+), 43 deletions(-) diff --git a/apps/desktop/src/components/grid/DataGrid.vue b/apps/desktop/src/components/grid/DataGrid.vue index 4b5ff2303b..47b9e0ebb5 100644 --- a/apps/desktop/src/components/grid/DataGrid.vue +++ b/apps/desktop/src/components/grid/DataGrid.vue @@ -2977,6 +2977,9 @@ const canvasScrollTop = ref(0); const canvasHoverCell = ref<{ rowIndex: number; visibleColIdx: number } | null>(null); const useCanvasGridRows = computed(() => dataGridRenderMode.value === "canvas"); const canvasContentHeight = computed(() => Math.max(1, displayItems.value.length * CANVAS_DATA_GRID_ROW_HEIGHT)); +const canvasRenderStyleKey = computed( + () => `${settingsStore.editorSettings.theme}:${settingsStore.editorSettings.uiScale}:${isDark.value}`, +); let canvasResizeObserver: ResizeObserver | null = null; let canvasDrawFrame = 0; @@ -3299,8 +3302,10 @@ function drawCanvasGrid() { width: Math.max(1, canvasViewportWidth.value || scroller.clientWidth), height: Math.max(1, canvasViewportHeight.value || scroller.clientHeight), isDark: isDark.value, + styleKey: canvasRenderStyleKey.value, rows: displayItems.value, renderedColumnWidths: renderedColumnWidths.value, + renderedColumnOffsets: renderedColumnOffsets.value, visibleColumnIndexes: visibleColumnIndexes.value, rowNumberWidth: DATA_GRID_ROW_NUM_WIDTH, hoverCell: canvasHoverCell.value, @@ -3331,6 +3336,7 @@ watch( searchMatchSet, currentSearchMatch, isDark, + canvasRenderStyleKey, isScrolling, hoveredDetailCell, detailCell, diff --git a/apps/desktop/src/lib/canvasDataGridRenderer.ts b/apps/desktop/src/lib/canvasDataGridRenderer.ts index d88ad24daf..cafcad5fd6 100644 --- a/apps/desktop/src/lib/canvasDataGridRenderer.ts +++ b/apps/desktop/src/lib/canvasDataGridRenderer.ts @@ -1,6 +1,10 @@ import type { CellValue } from "@/lib/cellValue"; import type { RowStatus } from "@/lib/gridRowStatus"; -import { DATA_GRID_DARK_SEARCH_COLORS, resolveDataGridPaintTheme } from "@/lib/dataGridPaintTheme"; +import { + DATA_GRID_DARK_SEARCH_COLORS, + resolveDataGridPaintTheme, + type DataGridPaintTheme, +} from "@/lib/dataGridPaintTheme"; export const CANVAS_DATA_GRID_ROW_HEIGHT = 26; @@ -40,8 +44,10 @@ export interface DrawCanvasDataGridOptions { width: number; height: number; isDark: boolean; + styleKey?: string; rows: CanvasDataGridRow[]; renderedColumnWidths: number[]; + renderedColumnOffsets?: number[]; visibleColumnIndexes: number[]; rowNumberWidth: number; hoverCell: CanvasHoverCell | null; @@ -62,6 +68,20 @@ type NumericCanvasContext = CanvasRenderingContext2D & { fontVariantNumeric?: string; }; +interface CanvasRenderState { + cacheKey: string; + normalFont: string; + tabularFont: string; + semiboldFont: string; + italicFont: string; + theme: DataGridPaintTheme; + searchFill: string; + currentSearchFill: string; + currentSearchBorder: string; +} + +const canvasRenderStateCache = new WeakMap(); + function setCanvasNumericVariant(ctx: CanvasRenderingContext2D, value: "normal" | "tabular-nums") { const numericCtx = ctx as NumericCanvasContext; if ("fontVariantNumeric" in numericCtx) numericCtx.fontVariantNumeric = value; @@ -118,44 +138,10 @@ function firstVisibleColumn(offsets: number[], contentStart: number): number { return low; } -export function drawCanvasDataGrid(options: DrawCanvasDataGridOptions) { - const { - canvas, - scroller, - width, - height, - isDark, - rows, - renderedColumnWidths, - visibleColumnIndexes, - rowNumberWidth, - hoverCell, - isScrolling, - editingCell, - singleSelectedCell, - searchMatchKeys, - currentSearchMatch, - formatCell, - isRowActive, - isRowSelected, - rowCellsUseSelectionVisual, - cellIsSelected, - cellCanHover, - } = options; - const dpr = window.devicePixelRatio || 1; - const pixelWidth = Math.floor(width * dpr); - const pixelHeight = Math.floor(height * dpr); - if (canvas.width !== pixelWidth || canvas.height !== pixelHeight) { - canvas.width = pixelWidth; - canvas.height = pixelHeight; - } - canvas.style.width = `${width}px`; - canvas.style.height = `${height}px`; - - const ctx = canvas.getContext("2d"); - if (!ctx) return; - ctx.setTransform(dpr, 0, 0, dpr, 0, 0); - ctx.clearRect(0, 0, width, height); +function resolveCanvasRenderState(canvas: HTMLCanvasElement, isDark: boolean, styleKey?: string): CanvasRenderState { + const cacheKey = `${styleKey ?? "default"}:${isDark ? "dark" : "light"}`; + const cached = canvasRenderStateCache.get(canvas); + if (cached?.cacheKey === cacheKey) return cached; const canvasStyle = getComputedStyle(canvas); const fontFamily = @@ -186,9 +172,74 @@ export function drawCanvasDataGrid(options: DrawCanvasDataGridOptions) { getVar: (name) => canvasStyle.getPropertyValue(name), isDark, }); - const searchFill = isDark ? DATA_GRID_DARK_SEARCH_COLORS.match : theme.cellSearch; - const currentSearchFill = isDark ? DATA_GRID_DARK_SEARCH_COLORS.current : theme.cellCurrentSearch; - const currentSearchBorder = isDark ? DATA_GRID_DARK_SEARCH_COLORS.currentBorder : theme.cellCurrentSearchBorder; + const state = { + cacheKey, + normalFont, + tabularFont, + semiboldFont, + italicFont, + theme, + searchFill: isDark ? DATA_GRID_DARK_SEARCH_COLORS.match : theme.cellSearch, + currentSearchFill: isDark ? DATA_GRID_DARK_SEARCH_COLORS.current : theme.cellCurrentSearch, + currentSearchBorder: isDark ? DATA_GRID_DARK_SEARCH_COLORS.currentBorder : theme.cellCurrentSearchBorder, + }; + canvasRenderStateCache.set(canvas, state); + return state; +} + +export function drawCanvasDataGrid(options: DrawCanvasDataGridOptions) { + const { + canvas, + scroller, + width, + height, + isDark, + styleKey, + rows, + renderedColumnWidths, + renderedColumnOffsets, + visibleColumnIndexes, + rowNumberWidth, + hoverCell, + isScrolling, + editingCell, + singleSelectedCell, + searchMatchKeys, + currentSearchMatch, + formatCell, + isRowActive, + isRowSelected, + rowCellsUseSelectionVisual, + cellIsSelected, + cellCanHover, + } = options; + const dpr = window.devicePixelRatio || 1; + const pixelWidth = Math.floor(width * dpr); + const pixelHeight = Math.floor(height * dpr); + if (canvas.width !== pixelWidth || canvas.height !== pixelHeight) { + canvas.width = pixelWidth; + canvas.height = pixelHeight; + } + const canvasWidth = `${width}px`; + const canvasHeight = `${height}px`; + if (canvas.style.width !== canvasWidth) canvas.style.width = canvasWidth; + if (canvas.style.height !== canvasHeight) canvas.style.height = canvasHeight; + + const ctx = canvas.getContext("2d"); + if (!ctx) return; + ctx.setTransform(dpr, 0, 0, dpr, 0, 0); + ctx.clearRect(0, 0, width, height); + + const { + normalFont, + tabularFont, + semiboldFont, + italicFont, + theme, + searchFill, + currentSearchFill, + currentSearchBorder, + } = resolveCanvasRenderState(canvas, isDark, styleKey); const scrollTop = scroller.scrollTop; const scrollLeft = scroller.scrollLeft; @@ -200,7 +251,7 @@ export function drawCanvasDataGrid(options: DrawCanvasDataGridOptions) { ctx.font = normalFont; ctx.textBaseline = "middle"; - const offsets = columnOffsets(renderedColumnWidths); + const offsets = renderedColumnOffsets ?? columnOffsets(renderedColumnWidths); const contentStart = Math.max(0, scrollLeft - rowNumberWidth); const firstCol = firstVisibleColumn(offsets, contentStart); const columnOffset = offsets[firstCol] ?? 0;