第10章:性能优化策略
本章概要
核心问题:如何让画布在万级元素时仍然流畅?
无限画布的性能优化是一个系统工程,涉及渲染、内存、纹理等多个维度。本章将深入分析:
- 视口剔除:只渲染可见区域的元素
- 纹理管理:动态分辨率、纹理复用、自动回收
- 渲染优化:批处理、脏矩形、延迟更新
- 内存优化:引用计数缓存、对象池复用
- 性能监控:FPS 统计、渲染时间追踪
目录
10.1 视口剔除
10.1.1 Cullable 机制
PixiJS 提供原生的 cullable 属性,用于视口剔除:
typescript
// 来源:infinite-renderer/src/surfaces/surface.ts
constructor(options: Partial<ISurfaceOptions> = {}) {
this._viewport = new Viewport();
this._viewport.initialize(new PageVm(), renderOptions);
// 开启 stage 级别的 cullable
this._viewport.app.stage.cullable = true;
(this._viewport.app.stage as any).eventMode = 'static';
}Cullable 原理:
视口剔除示意:
┌──────────────────────────────────────────────────────────────┐
│ 世界坐标空间 │
│ │
│ ┌────────┐ │
│ │ Elem A │ ← 视口外,cullable = true 时不渲染 │
│ └────────┘ │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ 视口(Viewport) │ │
│ │ │ │
│ │ ┌────────┐ ┌────────┐ │ │
│ │ │ Elem B │ │ Elem C │ │ ← 视口内渲染 │
│ │ └────────┘ └────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────┘ │
│ │
│ ┌────────┐ │
│ │ Elem D │ ← 视口外,跳过 │
│ └────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘10.1.2 视口范围检测
在插件渲染时,通过 screen.intersects() 进行视口范围检测:
typescript
// 来源:infinite-plugins/src/renderer-plugins/outline/outline-plugin.ts
render(): void {
const { screen } = this.viewport.app; // 获取视口矩形
const graphics = this.view.clear();
for (const element of unlocked) {
const rect = element.getModelBounds(false, RECTANGLE);
// 只渲染与视口相交的元素
if (screen.intersects(rect) && element.view.visible) {
renderOutline(graphics, element, zoom);
}
}
}检测流程:
元素是否渲染判断:
┌──────────────┐
│ 获取元素 │
│ 包围盒 │
└──────┬───────┘
│
▼
┌──────────────┐
│ screen. │
│ intersects │
│ (bounds)? │
└──────┬───────┘
Yes │ No
┌──────┴──────┐
▼ ▼
┌──────────────┐ 跳过渲染
│ visible === │
│ true? │
└──────┬──────┘
Yes │ No
│ │
▼ ▼
渲染 跳过10.1.3 层级剔除策略
对于嵌套元素,采用层级剔除策略:
typescript
// 来源:infinite-renderer/src/extends/mask-container/mask-container.ts
protected _renderCanvasChildren(renderer: IRenderer<ICanvas>, cullable = false): void {
for (let i = 0, j = this.children.length; i < j; ++i) {
const child = this.children[i];
// 不可渲染则跳过
if (!child.renderable) {
continue;
}
// 设置子元素的 cullable 状态
child.cullable = childCullable || cullable;
child.render(renderer);
child.cullable = childCullable;
}
}10.2 纹理管理
10.2.1 CanvasSprite 设计
CanvasSprite 是纹理管理的核心类,实现了视口外自动回收纹理:
typescript
// 来源:infinite-renderer/src/extends/canvas-sprite.ts
/**
* ## Canvas Sprite 对象
*
* CanvasSprite 元素仅会在视口内渲染,离开视口后会销毁内容纹理,减少内存占用
*/
abstract class CanvasSprite<T extends object = object> extends Sprite {
/** 最大 Canvas 尺寸 */
static MAX_CANVAS_SIZE = getMaxCanvasSize();
/** 最大 Canvas 面积 */
static MAX_CANVAS_AREA = isMobile() ? 2500 * 2500 : getMaxCanvasArea();
/** 纹理缓存最大等待时间 10s,移动端 5s */
static MAX_AGE_TIME = isMobile() ? 5000 : 10000;
/** 内容纹理 */
protected content: Texture | null = null;
/** 快照纹理(低分辨率占位) */
protected snapshot: Texture | null = null;
/** 是否标脏 */
protected _dirty: boolean;
/** 上次触碰时间 */
touched: number;
}CanvasSprite 状态流转:
纹理状态流转图:
┌──────────────┐ update(state) ┌──────────────┐
│ 初始状态 │ ────────────────────→ │ 预备状态 │
│ EMPTY 纹理 │ │ _prepared │
└──────────────┘ └──────┬───────┘
↑ │
│ prepare()
│ requestUpdate
│ │
│ ▼
│ ┌──────────────┐
│ disposeContent() │ 标脏状态 │
│ (离开视口超时) │ _dirty │
│ └──────┬───────┘
│ │
│ updateContent()
│ (下一帧渲染时)
│ │
│ ▼
│ ┌──────────────┐
└──────────────────────────────│ 渲染状态 │
│ content 纹理 │
└──────────────┘
│
touch()
(每帧触碰续命)10.2.2 动态分辨率
DynamicSprite 根据画布缩放级别动态调整纹理分辨率:
typescript
// 来源:infinite-renderer/src/extends/dynamic-sprite.ts
/**
* 动态 Sprite 对象
* @description 在画布缩放时纹理清晰度会发生变化,采用动态纹理方案去适应精度的变化
*/
abstract class DynamicSprite<T extends object = object> extends CanvasSprite<T> {
protected resolution = -1;
/**
* 获取纹理渲染精度比例
* @param renderer 渲染引擎
* @param width 渲染宽度
* @param height 渲染高度
* @param resolution 渲染精度
* @param maxSize 最大纹理边长
* @param maxArea 最大纹理面积
*/
getTextureRatio(
renderer: IRenderer,
width: number,
height: number,
resolution = renderer.activeResolution,
maxSize = DynamicSprite.MAX_CANVAS_SIZE,
maxArea = DynamicSprite.MAX_CANVAS_AREA,
): number {
// WebGL 渲染器限制
if (renderer.type === RENDERER_TYPE.SKIA && (renderer as SkRenderer).gl) {
const gl = (renderer as SkRenderer).gl!;
const maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE);
maxSize = Math.min(maxSize, maxTextureSize);
maxArea = Math.min(maxArea, 4096 * 4096);
}
width *= resolution;
height *= resolution;
return calcMaxResolution(width, height, maxSize, maxArea) * resolution;
}
protected _render(renderer: IRenderer): void {
const { zoom = 1 } = renderer;
const resolution = renderer.activeResolution * zoom;
// 缩放级别变化时重新生成纹理
if (!isSame(this.resolution, resolution)) {
this.resolution = resolution;
// 延迟更新或立即标脏
if (renderer.lazyUpdateCanvasSprite) {
this.prepare();
} else {
this.dirty();
}
}
super._render(renderer);
}
}动态分辨率原理:
缩放级别与纹理分辨率关系:
缩放级别: 10% 50% 100% 200% 400%
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
纹理分辨率: 低 中 标准 高 最高
(节省内存) ──────────────────→ (保证清晰度)
示例(1000×1000 图片):
zoom = 0.1 → 实际显示 100×100px → 纹理 100×100
zoom = 0.5 → 实际显示 500×500px → 纹理 500×500
zoom = 1.0 → 实际显示 1000×1000px → 纹理 1000×1000
zoom = 2.0 → 实际显示 2000×2000px → 纹理 2000×2000 (限制最大值)
优化效果:
- 缩小时使用低分辨率纹理,节省 GPU 内存
- 放大时使用高分辨率纹理,保证显示清晰10.2.3 纹理生命周期
纹理采用"触碰续命"机制,离开视口超时后自动回收:
typescript
// 来源:infinite-renderer/src/extends/canvas-sprite.ts
private static _touchList = new Set<CanvasSprite>();
// 触碰:更新最后访问时间
private static touch<T extends object>(sprite: CanvasSprite<T>) {
const performance = settings.ADAPTER.getPerformance();
sprite.touched = performance.now();
this._touchList.add(sprite as unknown as CanvasSprite);
}
// 移除跟踪
private static remove<T extends object>(sprite: CanvasSprite<T>) {
this._touchList.delete(sprite as unknown as CanvasSprite);
}
// 定期清理过期纹理
static dispose(t: number) {
for (const sprite of CanvasSprite._touchList) {
// 超过最大存活时间则回收
if (t - sprite.touched > CanvasSprite.MAX_AGE_TIME) {
sprite.disposeContent();
sprite.prepare(); // 重新预备,下次进入视口时重建
}
}
}
// 全局定时器,定期执行 GC
globalThis.setInterval(() => {
const performance = settings.ADAPTER.getPerformance();
CanvasSprite.dispose(performance.now());
}, CanvasSprite.MAX_AGE_TIME);纹理生命周期图:
纹理生命周期时间线:
时间 ────────────────────────────────────────────────→
┌─────────────┐ ┌─────────────┐
│ 进入视口 │ │ 离开视口 │
│ touch() │ │ │
└─────┬───────┘ └──────┬──────┘
│ │
│ ← 渲染状态 → │ ← 开始计时 →
│ │
│ │ MAX_AGE_TIME
│ │ (5s/10s)
│ │ │
│ │ ▼
│ │ ┌──────────────┐
│ │ │ disposeContent│
│ │ │ 销毁纹理 │
│ │ │ 切换到快照 │
│ │ └──────────────┘
│ │
│ ← 重新进入视口 →
│
│ updateContent()
│ 重建纹理
▼10.2.4 纹理复用
通过 TextureReuse 类实现纹理复用:
typescript
// 来源:infinite-renderer/src/context/texture-reuse.ts
export class TextureReuse {
private cache: Map<string, RenderTexture> = new Map();
set(uuid: string, texture: RenderTexture) {
this.cache.set(uuid, texture);
}
get(uuid: string) {
return this.cache.get(uuid);
}
delete(uuid: string) {
return this.cache.get(uuid);
}
clear() {
return this.cache.clear();
}
}纹理复用场景:
纹理复用场景:
场景1:相同图片多次使用
┌────────────┐ ┌────────────┐ ┌────────────┐
│ Image A │ │ Image B │ │ Image C │
│ url: x.jpg│ │ url: x.jpg│ │ url: x.jpg│
└─────┬──────┘ └─────┬──────┘ └─────┬──────┘
│ │ │
└───────────────────┼───────────────────┘
│
▼
┌──────────────┐
│ 共享同一纹理 │
│ TextureReuse │
└──────────────┘
场景2:撤销/重做时复用
操作: 删除 → 撤销 → 重做 → 撤销
│ │ │ │
│ │ │ └─ 复用缓存纹理
│ │ └───────── 复用缓存纹理
│ └───────────────── 从缓存恢复
└───────────────────────── 缓存纹理(不销毁)10.3 渲染优化
10.3.1 延迟更新机制
系统支持延迟更新模式,避免频繁重绘:
typescript
// 来源:infinite-renderer/src/extends/canvas-sprite.ts
protected _render(renderer: IRenderer): void {
// 若使用 Snapshot 则销毁内容
if (this.useSnapshot(renderer, this._boundsRect!)) {
this.renderSnapshot(renderer);
super._render(renderer);
return;
}
// 若元素被标脏,则执行纹理更新
// lazyUpdateCanvasSprite = true 时,只有 dirty 才更新
if (this._dirty || (this._prepared && !renderer.lazyUpdateCanvasSprite)) {
this.updateContent(renderer);
CanvasSprite.cancelUpdate(this);
this._dirty = false;
this._prepared = false;
}
// 触碰元素,延长纹理存活时间
CanvasSprite.touch(this);
super._render(renderer);
}延迟更新 vs 即时更新:
| 模式 | 触发条件 | 适用场景 |
|---|---|---|
| 即时更新 | _prepared && !lazyUpdateCanvasSprite | 单元素编辑、实时预览 |
| 延迟更新 | _dirty 被标记 | 批量操作、缩放平移 |
10.3.2 批量更新调度
使用防抖机制批量处理更新请求:
typescript
// 来源:infinite-renderer/src/extends/canvas-sprite.ts
private static _updateQueue = new Set<DirtyItem>();
// 请求更新
private static requestUpdate(sprite: DirtyItem) {
this._updateQueue.add(sprite);
this.prepareUpdate();
}
// 取消更新
private static cancelUpdate(sprite: DirtyItem) {
this._updateQueue.delete(sprite);
}
// 防抖处理:100ms 内的请求合并执行
@Debounce(1000 / 10) // 100ms
private static prepareUpdate() {
this._updateQueue.forEach((item) => item.dirty());
this._updateQueue.clear();
}批量更新流程:
批量更新时序图:
时间 ─────────────────────────────────────────────────→
sprite1.prepare() sprite2.prepare() sprite3.prepare()
│ │ │
└──────────────────┴──────────────────┘
│
100ms 防抖
│
▼
┌────────────────────┐
│ prepareUpdate() │
│ 批量执行 dirty() │
└────────────────────┘
│
▼
┌────────────────────┐
│ 下一帧渲染时 │
│ 统一 updateContent│
└────────────────────┘10.3.3 快照渲染
当元素离开视口或缩放较小时,使用低分辨率快照渲染:
typescript
// 来源:infinite-renderer/src/extends/canvas-sprite.ts
// 是否使用快照(子类可覆写)
useSnapshot(_renderer: IRenderer, _bounds: Rectangle): boolean {
return !this.state;
}
// 生成快照纹理
generateSnapshot(_renderer: IRenderer): Texture {
return CanvasSprite.EMPTY;
}
// 渲染快照
protected renderSnapshot(renderer: IRenderer): void {
if (!this.snapshot) {
this.snapshot = this.generateSnapshot(renderer);
}
this.texture = this.snapshot;
}
// 静态快照纹理(16×16 占位图)
static get SNAPSHOT(): Texture<CanvasResource> {
if (!CanvasSprite._SNAPSHOT) {
const canvas = settings.ADAPTER.createCanvas(16, 16);
const context = canvas.getContext('2d')!;
canvas.width = 16;
canvas.height = 16;
context.fillStyle = '#0000000A'; // 半透明黑色
context.fillRect(0, 0, 16, 16);
const snapshot = new Texture<CanvasResource>(BaseTexture.from(canvas));
CanvasSprite._SNAPSHOT = snapshot;
}
return CanvasSprite._SNAPSHOT;
}10.4 缓存策略
10.4.1 引用计数缓存
RCCache 实现了引用计数的缓存机制:
typescript
// 来源:infinite-renderer/src/common/cache/rc-cache.ts
interface ICacheResource<T> {
resource: T;
referenceIds: Set<string>; // 引用该资源的 ID 集合
}
export class RCCache<T> {
_cache: Map<string, ICacheResource<T>>;
constructor() {
this._cache = new Map();
}
/**
* 删除某一缓存引用,当引用数为 0 时会清除对应缓存数据
*/
delete(key: string, referenceId: string) {
const cacheResult = this.get(key);
if (cacheResult) {
const { referenceIds } = cacheResult;
if (referenceIds.has(referenceId)) {
// 如果当前只有一个引用,则直接删除缓存结果
if (referenceIds.size <= 1) {
this.removeCache(key);
} else {
referenceIds.delete(referenceId);
}
}
}
}
/**
* 获取缓存结果(同时收集依赖)
*/
private get(key: string, referenceId?: string): ICacheResource<T> | undefined {
if (this.has(key) && referenceId) {
const { referenceIds } = this._cache.get(key)!;
referenceIds.add(referenceId); // 自动收集引用
}
return this._cache.get(key);
}
/**
* 添加缓存
*/
addCache(key: string, referenceId: string, resource: T) {
if (!this.has(key)) {
this._cache.set(key, {
resource,
referenceIds: new Set([referenceId]),
});
}
}
/**
* 获取缓存
*/
getCache(key: string, referenceId: string): T | undefined {
const cache = this.get(key, referenceId);
return cache ? cache.resource : cache;
}
}引用计数原理:
引用计数缓存示意:
缓存键: "image_abc123"
┌────────────────────────────────────────────────┐
│ resource: Texture │
│ referenceIds: Set { │
│ "element_001", // Element 1 引用 │
│ "element_002", // Element 2 引用 │
│ "element_003", // Element 3 引用 │
│ } │
│ 引用计数: 3 │
└────────────────────────────────────────────────┘
删除 element_001 的引用后:
┌────────────────────────────────────────────────┐
│ resource: Texture │
│ referenceIds: Set { │
│ "element_002", │
│ "element_003", │
│ } │
│ 引用计数: 2 (资源保留) │
└────────────────────────────────────────────────┘
删除所有引用后:
┌────────────────────────────────────────────────┐
│ 引用计数: 0 │
│ → 自动清除缓存,释放内存 │
└────────────────────────────────────────────────┘10.4.2 LRU 缓存
视口状态使用 LRU 缓存策略(见第4章):
typescript
// LRU 缓存用于存储视口状态
const viewportStateCache = new LRUMap<string, ViewportState>(100);
// 最近最少使用的条目会被自动移除
viewportStateCache.set(pageId, state);
const cached = viewportStateCache.get(pageId);10.4.3 像素缓存
像素级碰撞检测使用缓存优化:
typescript
// 来源:infinite-renderer/src/common/hit-test.ts
export interface BufferedBaseTexture extends BaseTexture {
_imageBufferData?: {
data: ImageData;
ratio: number;
};
}
const MAX_BUFFER_SIZE = 512;
export function doesSpritePixelsHitRect(sprite: Sprite, rect: Rectangle): boolean {
const { baseTexture } = sprite.texture;
// 将图像 buffer 资源缓存至 baseTexture 中
if (!(baseTexture as BufferedBaseTexture)._imageBufferData) {
// 限制缓存大小
const ratio = Math.min(
MAX_BUFFER_SIZE / baseTexture.realWidth,
MAX_BUFFER_SIZE / baseTexture.realHeight,
1,
);
const canvas = settings.ADAPTER.createCanvas(
baseTexture.realWidth * ratio,
baseTexture.realHeight * ratio,
);
const context = canvas.getContext('2d')!;
context.drawImage(source, 0, 0, canvas.width, canvas.height);
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
// 释放临时 canvas
canvas.width = 1;
canvas.height = 1;
(baseTexture as BufferedBaseTexture)._imageBufferData = {
data: imageData,
ratio,
};
}
// 使用缓存的像素数据进行碰撞检测
const { data, ratio = 1 } = (baseTexture as BufferedBaseTexture)._imageBufferData!;
// ...
}10.5 Canvas 尺寸限制
10.5.1 设备限制检测
不同设备对 Canvas 尺寸有不同限制:
typescript
// 来源:infinite-renderer/src/extends/canvas-sprite.ts
/** 最大 Canvas 尺寸 */
static MAX_CANVAS_SIZE = getMaxCanvasSize();
/** 最大 Canvas 面积 */
static MAX_CANVAS_AREA = isMobile() ? 2500 * 2500 : getMaxCanvasArea();
/** 纹理缓存最大等待时间 10s,移动端 5s */
static MAX_AGE_TIME = isMobile() ? 5000 : 10000;设备限制参考:
| 设备类型 | 最大边长 | 最大面积 | 纹理存活时间 |
|---|---|---|---|
| 桌面端 | ~16384px | ~268M px² | 10s |
| 移动端 | ~4096px | ~6.25M px² | 5s |
| WebGL | 取决于 GPU | MAX_TEXTURE_SIZE | - |
10.5.2 自适应分辨率
根据设备限制自动计算最佳分辨率:
typescript
// 来源:infinite-renderer/src/extends/dynamic-sprite.ts
getTextureRatio(
renderer: IRenderer,
width: number,
height: number,
resolution = renderer.activeResolution,
maxSize = DynamicSprite.MAX_CANVAS_SIZE,
maxArea = DynamicSprite.MAX_CANVAS_AREA,
): number {
// WebGL 渲染器需要检测 GPU 限制
if (renderer.type === RENDERER_TYPE.SKIA && (renderer as SkRenderer).gl) {
const gl = (renderer as SkRenderer).gl!;
const maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE);
maxSize = Math.min(maxSize, maxTextureSize);
maxArea = Math.min(maxArea, 4096 * 4096);
}
width *= resolution;
height *= resolution;
// 计算满足限制的最大分辨率
return calcMaxResolution(width, height, maxSize, maxArea) * resolution;
}分辨率计算示例:
自适应分辨率计算:
输入:
- 元素尺寸: 10000 × 10000 px
- 设备像素比: 2
- 画布缩放: 1.5
- 最大边长: 4096 px
- 最大面积: 16M px²
计算过程:
1. 实际需要尺寸: 10000 × 2 × 1.5 = 30000 px
2. 边长限制: 30000 > 4096, 需要缩小
缩小比例: 4096 / 30000 = 0.137
3. 面积限制: 30000² = 900M > 16M, 需要缩小
缩小比例: √(16M / 900M) = 0.133
4. 取较小值: min(0.137, 0.133) = 0.133
输出分辨率: 0.133 × 2 × 1.5 = 0.4
最终纹理尺寸: 10000 × 0.4 = 4000 px10.6 性能监控
10.6.1 渲染时间统计
系统在每帧渲染后统计耗时:
typescript
// 来源:infinite-renderer/src/surfaces/surface.ts
this._context.ticker.add(
(t) => {
try {
// 记录开始时间
const time = Math.trunc(performance.now());
// 执行渲染
this.viewport.app.ticker.update();
// 计算耗时
const now = Math.trunc(performance.now());
const duration = Math.max(now - time, 0);
// 发送统计事件
this.events.emit('update', duration);
} catch (error) {
console.error(error);
settings.LOGGER.error(error);
throw error;
}
},
this,
UPDATE_PRIORITY.LOW,
);监控数据使用:
typescript
// 监听渲染耗时
surface.events.on('update', (duration: number) => {
// 计算 FPS
const fps = duration > 0 ? Math.min(1000 / duration, 60) : 60;
// 性能告警
if (duration > 16.67) { // 超过 60fps 阈值
console.warn(`渲染耗时: ${duration}ms, FPS: ${fps.toFixed(1)}`);
}
// 上报统计
reportMetrics({ renderDuration: duration, fps });
});10.6.2 Ticker 机制
系统使用统一的 Ticker 管理渲染循环:
typescript
// 来源:infinite-renderer/src/context/context.ts
class Context extends EventEmitter<IContextEventTypes> implements IContext {
ticker: Ticker;
constructor(props: ContextProps = {}) {
super();
const {
ticker = new Ticker(),
// ...
} = props;
this.ticker = ticker;
}
}
// Surface 中使用 Context 的 Ticker
constructor(options: Partial<ISurfaceOptions> = {}) {
// 停止 app 自带的 ticker
this.viewport.app.ticker.stop();
// 使用 Context 的共享 ticker
this._context.ticker.start();
this._context.ticker.add(
(t) => {
this.viewport.app.ticker.update();
},
this,
UPDATE_PRIORITY.LOW,
);
}Ticker 优先级:
typescript
// PixiJS UPDATE_PRIORITY 枚举
enum UPDATE_PRIORITY {
INTERACTION = 50, // 交互事件
HIGH = 25, // 高优先级
NORMAL = 0, // 普通优先级
LOW = -25, // 低优先级
UTILITY = -50, // 工具类
}
// 使用示例
context.ticker.add(callback, context, UPDATE_PRIORITY.HIGH);
context.ticker.add(callback, context, UPDATE_PRIORITY.NORMAL);
context.ticker.add(callback, context, UPDATE_PRIORITY.LOW);10.7 本章小结
性能优化架构图
┌─────────────────────────────────────────────────────────────┐
│ 性能优化策略体系 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 渲染层优化 │ │
│ │ │ │
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
│ │ │ 视口剔除 │ │ 批量渲染 │ │ 延迟更新 │ │ │
│ │ │ cullable │ │ batching │ │ lazy update│ │ │
│ │ └────────────┘ └────────────┘ └────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 纹理层优化 │ │
│ │ │ │
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
│ │ │ 动态分辨率 │ │ 纹理复用 │ │ 自动回收 │ │ │
│ │ │ DynamicSpr │ │TextureReuse│ │ dispose() │ │ │
│ │ └────────────┘ └────────────┘ └────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 缓存层优化 │ │
│ │ │ │
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
│ │ │ 引用计数 │ │ LRU 缓存 │ │ 像素缓存 │ │ │
│ │ │ RCCache │ │ LRUMap │ │ BufferData │ │ │
│ │ └────────────┘ └────────────┘ └────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 监控与调度 │ │
│ │ │ │
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
│ │ │ Ticker │ │ 渲染统计 │ │ 设备适配 │ │ │
│ │ │ 帧调度 │ │ FPS/耗时 │ │ Canvas限制 │ │ │
│ │ └────────────┘ └────────────┘ └────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘关键代码路径
| 模块 | 文件路径 |
|---|---|
| CanvasSprite | infinite-renderer/src/extends/canvas-sprite.ts |
| DynamicSprite | infinite-renderer/src/extends/dynamic-sprite.ts |
| TextureReuse | infinite-renderer/src/context/texture-reuse.ts |
| RCCache | infinite-renderer/src/common/cache/rc-cache.ts |
| Context/Ticker | infinite-renderer/src/context/context.ts |
| Surface 渲染循环 | infinite-renderer/src/surfaces/surface.ts |
核心优化策略总结
| 策略 | 原理 | 效果 |
|---|---|---|
| 视口剔除 | 只渲染可见元素 | 减少绑制调用 |
| 动态分辨率 | 根据缩放调整纹理 | 节省 GPU 内存 |
| 纹理回收 | 离开视口超时销毁 | 防止内存泄漏 |
| 延迟更新 | 批量处理更新请求 | 减少重绘次数 |
| 快照渲染 | 低分辨率占位 | 加速首屏渲染 |
| 引用计数 | 共享资源自动回收 | 优化内存使用 |
| 设备适配 | 根据限制调整参数 | 保证兼容性 |
性能优化检查清单
- [ ] 大量元素时是否开启
cullable - [ ] 纹理是否使用动态分辨率
- [ ] 离开视口的元素是否被正确回收
- [ ] 批量操作是否使用延迟更新
- [ ] 相同资源是否使用缓存
- [ ] 是否监控渲染耗时和 FPS
- [ ] 移动端是否降低了资源限制
