Skip to content

Commit

Permalink
perf: remove unnecessary ctx.font = calls, and avoid drawing glyphs…
Browse files Browse the repository at this point in the history
… outside the viewBounds (#4775)
  • Loading branch information
lumixraku authored Mar 8, 2025
1 parent 82545bf commit cd3decd
Show file tree
Hide file tree
Showing 16 changed files with 136 additions and 60 deletions.
15 changes: 9 additions & 6 deletions e2e/perf/scroll.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { sheetData as emptySheetData } from '../__testing__/emptysheet';
import { sheetData as freezeData } from '../__testing__/freezesheet';
import { sheetData as mergeCellData } from '../__testing__/mergecell';
import { sheetData as overflowData } from '../__testing__/overflow';
import { reportToPosthog } from '../utils/report-performance';

export interface IFPSData {
fpsData: number[];
Expand All @@ -39,7 +40,7 @@ interface IFPSResult {
maxFrameTimes: number[];
}

const isCI = !!process.env.CI;
// const isCI = !!process.env.CI;
/**
* measure FPS of scrolling time.
* @param page Page from playwright
Expand Down Expand Up @@ -132,7 +133,7 @@ async function measureFPS(page: Page, testDuration = 5, deltaX: number, deltaY:
return fpsCounterPromise as Promise<IFPSResult>;
}

const createTest = (title: string, sheetData: IJsonObject, minFpsValue: number, deltaX = 0, deltaY = 0) => {
const createTest = (title: string, telemetryName: string, sheetData: IJsonObject, minFpsValue: number, deltaX = 0, deltaY = 0) => {
// Default Size Of browser: 1280x720 pixels. And default DPR is 1.
test(title, async ({ page }) => {
await page.goto('http://localhost:3000/sheets/');
Expand All @@ -154,6 +155,8 @@ const createTest = (title: string, sheetData: IJsonObject, minFpsValue: number,
console.log('FPS', resultOfFPS.fps);
console.log('medianFrameTime', resultOfFPS.medianFrameTime);
console.log('max10FrameTimes', resultOfFPS.maxFrameTimes);

await reportToPosthog(telemetryName, resultOfFPS);
expect(resultOfFPS.fps).toBeGreaterThan(minFpsValue);
});
} catch (error) {
Expand All @@ -165,7 +168,7 @@ const createTest = (title: string, sheetData: IJsonObject, minFpsValue: number,
});
};

createTest('sheet scroll empty', emptySheetData, 50, 10, 100);
createTest('sheet scroll after freeze', freezeData, 10, 10, 100);
createTest('sheet scroll in a lots of merge cell', mergeCellData, 10, 10, 50);
createTest('sheet X scroll in a lots of overflow', overflowData, 10, 50, 5);
createTest('sheet scroll empty', 'perf.sheet.scroll.empty', emptySheetData, 50, 10, 100);
createTest('sheet scroll after freeze', 'perf.sheet.scroll.freeze', freezeData, 10, 10, 100);
createTest('sheet scroll in a lots of merge cell', 'perf.sheet.scroll.mergeCell', mergeCellData, 10, 10, 50);
createTest('sheet X scroll in a lots of overflow', 'perf.sheet.scroll.overflow', overflowData, 10, 50, 5);
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"scripts": {
"prepare": "husky",
"pre-commit": "lint-staged",
"dev": "turbo dev:demo",
"dev": "turbo dev:demo -- --host 0.0.0.0",
"dev:libs": "pnpm --filter univer-examples dev:demo-libs",
"dev:e2e": "pnpm --filter univer-examples dev:e2e",
"lint:types": "turbo lint:types",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

import { awaitTime, Disposable, ICommandService, IUniverInstanceService, UniverInstanceType } from '@univerjs/core';

import { DEFAULT_WORKBOOK_DATA_DEMO, DEFAULT_WORKBOOK_DATA_DEMO_DEFAULT_STYLE } from '@univerjs/mockdata';
import { DisposeUniverCommand } from '../../commands/commands/unit.command';
import { getDefaultDocData } from './data/default-doc';
Expand Down
8 changes: 6 additions & 2 deletions packages/core/src/sheets/sheet-skeleton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export class SheetSkeleton extends Skeleton {
this._isRowStylePrecedeColumnStyle = this._configService.getConfig(IS_ROW_STYLE_PRECEDE_COLUMN_STYLE) ?? false;
}

_resetCache() {
resetCache() {
//
}

Expand Down Expand Up @@ -447,12 +447,16 @@ export class SheetSkeleton extends Skeleton {
* @param bounds
*/
calculate(): Nullable<SheetSkeleton> {
this._resetCache();
this.resetCache();
this._updateLayout();

return this;
}

resetRangeCache(_ranges: IRange[]): void {
// ...
}

private _dynamicallyUpdateRowHeaderWidth(rowHeader: {
width: number;
}): number {
Expand Down
4 changes: 2 additions & 2 deletions packages/engine-render/src/components/docs/doc-component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export abstract class DocComponent extends RenderComponent<
}
}

override render(mainCtx: UniverRenderingContext, bounds?: IViewportInfo) {
override render(mainCtx: UniverRenderingContext, bounds?: Partial<IViewportInfo>) {
if (!this.visible) {
this.makeDirty(false);
return this;
Expand Down Expand Up @@ -146,5 +146,5 @@ export abstract class DocComponent extends RenderComponent<
return false;
}

protected abstract _draw(ctx: UniverRenderingContext, bounds?: IViewportInfo): void;
protected abstract _draw(ctx: UniverRenderingContext, bounds?: Partial<IViewportInfo>): void;
}
6 changes: 4 additions & 2 deletions packages/engine-render/src/components/docs/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import type { Transform } from '../../basics/transform';
import type { IBoundRectNoAngle, IViewportInfo } from '../../basics/vector2';
import type { UniverRenderingContext } from '../../context';
import type { Scene } from '../../scene';
import type { ComponentExtension, IExtensionConfig } from '../extension';
import type { ComponentExtension, IDrawInfo, IExtensionConfig } from '../extension';
import type { IDocumentsConfig, IPageMarginLayout } from './doc-component';
import type { DocumentSkeleton } from './layout/doc-skeleton';
import { CellValueType, HorizontalAlign, VerticalAlign, WrapStrategy } from '@univerjs/core';
Expand Down Expand Up @@ -432,7 +432,9 @@ export class Documents extends DocComponent {

for (const extension of glyphExtensionsExcludeBackground) {
extension.extensionOffset = extensionOffset;
extension.draw(ctx, parentScale, glyph);
extension.draw(ctx, parentScale, glyph, [], {
viewBound: bounds?.viewBound,
} as IDrawInfo);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
*/

import type { IScale } from '@univerjs/core';
import type { IBoundRectNoAngle } from '../../../basics';
import type { IDocumentSkeletonGlyph } from '../../../basics/i-document-skeleton-cached';
import type { UniverRenderingContext } from '../../../context';
import type { IDrawInfo } from '../../extension';
import { BaselineOffset, getColorStyle } from '@univerjs/core';
import { GlyphType, hasCJK } from '../../../basics';
import { COLOR_BLACK_RGB } from '../../../basics/const';
Expand All @@ -29,14 +31,31 @@ const UNIQUE_KEY = 'DefaultDocsFontAndBaseLineExtension';

const DOC_EXTENSION_Z_INDEX = 20;

/**
* Singleton
*/
export class FontAndBaseLine extends docExtension {
override uKey = UNIQUE_KEY;

override Z_INDEX = DOC_EXTENSION_Z_INDEX;

private _preFontColor = '';

override draw(ctx: UniverRenderingContext, parentScale: IScale, glyph: IDocumentSkeletonGlyph) {
/**
* ctx.font = val; then ctx.font is not exactly the same as val
* that is because canvas would normalize the font string, remove default value and convert pt to px.
* so we need a map to store actual value and set value
*/
actualFontMap: Record<string, string> = {};

constructor() {
super();
}

// invoked by document.ts
override draw(ctx: UniverRenderingContext, _parentScale: IScale, glyph: IDocumentSkeletonGlyph, _?: IBoundRectNoAngle, more?: IDrawInfo) {
// _parentScale: IScale, _skeleton: T, _diffBounds?: V, _more?: IDrawInfo

const line = glyph.parent?.parent;
if (!line) {
return;
Expand All @@ -46,6 +65,14 @@ export class FontAndBaseLine extends docExtension {

const { spanPointWithFont = Vector2.create(0, 0) } = this.extensionOffset;

if (more) {
if (more.viewBound) {
if (spanPointWithFont.x > more.viewBound.right || spanPointWithFont.y > more.viewBound.bottom) {
return;
}
}
}

if (content == null) {
return;
}
Expand All @@ -55,8 +82,12 @@ export class FontAndBaseLine extends docExtension {
return;
}

if (ctx.font !== fontStyle?.fontString) {
ctx.font = fontStyle?.fontString || '';
const fontStringPxStr = fontStyle?.fontString || '';
if (fontStringPxStr) {
if (ctx.font !== this.actualFontMap[fontStringPxStr]) {
ctx.font = fontStringPxStr;
this.actualFontMap[fontStringPxStr] = ctx.font;
}
}

const { cl: colorStyle, va: baselineOffset } = textStyle;
Expand All @@ -71,7 +102,6 @@ export class FontAndBaseLine extends docExtension {
} else if (baselineOffset === BaselineOffset.SUBSCRIPT) {
spanPointWithFont.y += bBox.sbo;
}

this._fillText(ctx, glyph, spanPointWithFont);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,8 +218,12 @@ export class FontCache {
}, fontStyle);
}

// 获取有值单元格文本大小
// let measureTextCache = {}, measureTextCacheTimeOut = null;
/**
* Measure text on another canvas.
* @param content
* @param fontString
* @returns IMeasureTextCache
*/
static getMeasureText(content: string, fontString: string): IMeasureTextCache {
if (!this._context) {
const canvas = document.createElement('canvas');
Expand All @@ -242,7 +246,6 @@ export class FontCache {
if (mtc != null) {
return mtc;
}

ctx.font = fontString;

const textMetrics = ctx.measureText(content);
Expand Down
2 changes: 1 addition & 1 deletion packages/engine-render/src/components/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export class ComponentExtension<T, U, V> {
return this.Z_INDEX;
}

draw(ctx: UniverRenderingContext, parentScale: IScale, skeleton: T, diffBounds?: V, more?: IDrawInfo) {
draw(_ctx: UniverRenderingContext, _parentScale: IScale, _skeleton: T, _diff?: V, _more?: IDrawInfo) {
/* abstract */
}

Expand Down
31 changes: 22 additions & 9 deletions packages/engine-render/src/components/sheets/extensions/font.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
/* eslint-disable max-lines-per-function */
/* eslint-disable complexity */

import type { ICellWithCoord, IDocDrawingBase, ImageSourceType, IRange, IScale, Nullable, ObjectMatrix } from '@univerjs/core';
import type { ICellDataForSheetInterceptor, ICellWithCoord, IDocDrawingBase, ImageSourceType, IRange, IScale, Nullable, ObjectMatrix } from '@univerjs/core';
import type { IBoundRectNoAngle, IViewportInfo } from '../../../basics';
import type { UniverRenderingContext } from '../../../context';
import type { Documents } from '../../docs/document';
import type { IDrawInfo } from '../../extension';
Expand Down Expand Up @@ -190,8 +191,10 @@ export class Font extends SheetExtension {
renderFontCtx.fontCache = fontCache;

//#region overflow
// If the cell is overflowing, but the overflowRectangle has not been set,
// then overflowRectangle is set to undefined.
// e.g. cell(12, 5)'s textwrap value is overflow(which is default), and text ends at column 9,
// the overflowRange would be startRow: 12, startColumn: 5, endRow: 12, endColumn: 9
// and if column 9 is not empty, then the overflowRang e endColumn would be 8
// and if column 7 is not empty, the endColumn would be 6
const overflowRange = spreadsheetSkeleton.overflowCache.getValue(row, col);

// If it's neither an overflow nor within the current range,
Expand All @@ -209,8 +212,11 @@ export class Font extends SheetExtension {
const visibleCol = spreadsheetSkeleton.worksheet.getColVisible(col);
if (!visibleRow || !visibleCol) return true;

// const cellData = spreadsheetSkeleton.worksheet.getCell(row, col) as ICellDataForSheetInterceptor || {};
if (renderFontCtx.fontCache?.cellData?.fontRenderExtension?.isSkip) {
// Since we cannot predict when fontRenderExtension?.isSkip might change,
// we must check it every time and retrieve cell data directly from the worksheet,
// not from the cache to ensure accuracy.
const cellData = spreadsheetSkeleton.worksheet.getCell(row, col) as ICellDataForSheetInterceptor || {};
if (cellData?.fontRenderExtension?.isSkip) {
return true;
}

Expand All @@ -219,7 +225,7 @@ export class Font extends SheetExtension {

//#region text overflow
renderFontCtx.overflowRectangle = overflowRange;
this._setFontRenderBounds(renderFontCtx, row, col);
this._clipByRenderBounds(renderFontCtx, row, col);
//#endregion

ctx.translate(renderFontCtx.startX + FIX_ONE_PIXEL_BLUR_OFFSET, renderFontCtx.startY + FIX_ONE_PIXEL_BLUR_OFFSET);
Expand All @@ -232,7 +238,7 @@ export class Font extends SheetExtension {
if (documentDataModel.getDrawingsOrder()?.length) {
ctx.save();
ctx.beginPath();
this._setFontRenderBounds(renderFontCtx, row, col, 1);
this._clipByRenderBounds(renderFontCtx, row, col, 1);
this._renderImages(ctx, fontCache, renderFontCtx.startX, renderFontCtx.startY, renderFontCtx.endX, renderFontCtx.endY);
ctx.closePath();
ctx.restore();
Expand Down Expand Up @@ -324,7 +330,7 @@ export class Font extends SheetExtension {
* @param col
* @param fontCache
*/
private _setFontRenderBounds(renderFontContext: IRenderFontContext, row: number, col: number, padding = 0) {
private _clipByRenderBounds(renderFontContext: IRenderFontContext, row: number, col: number, padding = 0) {
const { ctx, scale, overflowRectangle, rowHeightAccumulation, columnWidthAccumulation, fontCache } = renderFontContext;
let { startX, endX, startY, endY } = renderFontContext;

Expand Down Expand Up @@ -470,7 +476,14 @@ export class Font extends SheetExtension {

documentSkeleton.makeDirty(false);
documents.resize(cellWidth, cellHeight);
documents.changeSkeleton(documentSkeleton).render(ctx);
documents.changeSkeleton(documentSkeleton).render(ctx, {
viewBound: {
left: 0,
top: 0,
right: cellWidth,
bottom: cellHeight,
} as IBoundRectNoAngle,
} as Partial<IViewportInfo>);
}

private _clipRectangleForOverflow(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,21 +54,6 @@ export class SheetExtension extends ComponentExtension<SpreadsheetSkeleton, SHEE
return false;
}

// isRenderDiffRangesByColumn(column: number, diffRanges?: IRange[]) {
// if (diffRanges == null || diffRanges.length === 0) {
// return true;
// }

// for (const range of diffRanges) {
// const { startColumn, endColumn } = range;
// if (column >= startColumn && column <= endColumn) {
// return true;
// }
// }

// return false;
// }

isRenderDiffRangesByColumn(curStartColumn: number, curEndColumn: number, diffRanges?: IRange[]) {
if (diffRanges == null || diffRanges.length === 0) {
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import {
isWhiteColor,
LocaleService,
ObjectMatrix,
Range,
searchArray,
SheetSkeleton,
Tools,
Expand Down Expand Up @@ -1127,15 +1128,14 @@ export class SpreadsheetSkeleton extends SheetSkeleton {
return this.worksheet.getSpanModel().getMergedCellRangeForSkeleton(range.startRow, range.startColumn, range.endRow, range.endColumn);
}

resetCache(): void {
override resetCache(): void {
this._resetCache();
}

/**
* Any changes to sheet model would reset cache.
*/
override _resetCache(): void {
super._resetCache();
_resetCache(): void {
this._stylesCache = {
background: {},
backgroundPositions: new ObjectMatrix<ICellWithCoord>(),
Expand All @@ -1149,6 +1149,16 @@ export class SpreadsheetSkeleton extends SheetSkeleton {
this._overflowCache?.reset();
}

override resetRangeCache(ranges: IRange[]): void {
for (let i = 0; i < ranges.length; i++) {
const range = ranges[i];
Range.foreach(range, (row, col) => {
this._stylesCache.fontMatrix.realDeleteValue(row, col);
});
}
this.makeDirty(true);
}

_setBorderStylesCache(row: number, col: number, style: Nullable<IStyleData>, options: {
mergeRange?: IRange;
cacheItem?: ICacheItem;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ export class Spreadsheet extends SheetComponent {
checkOutOfViewBound: true,
viewportKey: viewportInfo.viewportKey,
viewBound: viewportInfo.cacheBound,
diffBounds: viewportInfo.diffBounds,
} as IDrawInfo);
this.addRenderFrameTimeMetricToScene(timeKey, Tools.now() - st, scene);
}
Expand Down
Loading

0 comments on commit cd3decd

Please sign in to comment.