diff --git a/packages/g-plugin-device-renderer/src/meshes/Instanced.ts b/packages/g-plugin-device-renderer/src/meshes/Instanced.ts index 66b687b5a..2242352bd 100644 --- a/packages/g-plugin-device-renderer/src/meshes/Instanced.ts +++ b/packages/g-plugin-device-renderer/src/meshes/Instanced.ts @@ -153,11 +153,6 @@ export abstract class Instanced { protected abstract createMaterial(objects: DisplayObject[]): void; - /** - * Get called when instance created or recreated. - */ - onCreate(object: DisplayObject) {} - get instance() { return this.objects[0]; } diff --git a/packages/g-plugin-device-renderer/src/meshes/InstancedPath.ts b/packages/g-plugin-device-renderer/src/meshes/InstancedPath.ts index 4bc9813f0..0f7a115f6 100644 --- a/packages/g-plugin-device-renderer/src/meshes/InstancedPath.ts +++ b/packages/g-plugin-device-renderer/src/meshes/InstancedPath.ts @@ -13,6 +13,9 @@ import { VertexAttributeLocation, } from './Instanced'; import { updateBuffer } from './Line'; +import { RenderHelper } from '../render'; +import { TexturePool } from '../TexturePool'; +import { LightPool } from '../LightPool'; enum LineVertexAttributeBufferIndex { PACKED = VertexAttributeBufferIndex.POSITION + 1, @@ -50,7 +53,13 @@ export class InstancedPathMesh extends Instanced { return false; } - onCreate(object: DisplayObject) { + constructor( + protected renderHelper: RenderHelper, + protected texturePool: TexturePool, + protected lightPool: LightPool, + object: DisplayObject, + ) { + super(renderHelper, texturePool, lightPool, object); this.segmentNum = this.calcSegmentNum(object); } diff --git a/packages/g-plugin-device-renderer/src/meshes/Text.ts b/packages/g-plugin-device-renderer/src/meshes/Text.ts index 8f54226b4..80b3bf4c0 100644 --- a/packages/g-plugin-device-renderer/src/meshes/Text.ts +++ b/packages/g-plugin-device-renderer/src/meshes/Text.ts @@ -20,6 +20,9 @@ import type GlyphAtlas from './symbol/GlyphAtlas'; import { BASE_FONT_WIDTH, GlyphManager } from './symbol/GlyphManager'; import { getGlyphQuads } from './symbol/SymbolQuad'; import { packUint8ToFloat } from '../utils/compression'; +import { LightPool } from '../LightPool'; +import { TexturePool } from '../TexturePool'; +import { RenderHelper } from '../render'; enum TextVertexAttributeBufferIndex { INSTANCED = VertexAttributeBufferIndex.POSITION + 1, @@ -50,18 +53,19 @@ export class TextMesh extends Instanced { private tmpMat4 = mat4.create(); - shouldMerge(object: DisplayObject, index: number) { - const shouldMerge = super.shouldMerge(object, index); - - if (!shouldMerge) { - return false; - } + private fontHash: string; - if (this.index !== index) { - return false; - } + constructor( + protected renderHelper: RenderHelper, + protected texturePool: TexturePool, + protected lightPool: LightPool, + object: DisplayObject, + ) { + super(renderHelper, texturePool, lightPool, object); + this.fontHash = this.calcFontHash(object); + } - const instance = this.instance; + private calcFontHash(object: DisplayObject) { const instancedAttributes = [ 'fontSize', 'fontFamily', @@ -69,22 +73,26 @@ export class TextMesh extends Instanced { 'textBaseline', 'letterSpacing', ]; + return ( + object.parsedStyle.metrics.font + + instancedAttributes.reduce((prev, cur) => { + return prev + object.parsedStyle[cur]; + }, '') + ); + } - // fontStack & fontSize should be same - // if (instance.parsedStyle.fontSize !== object.parsedStyle.fontSize) { - // return false; - // } + shouldMerge(object: DisplayObject, index: number) { + const shouldMerge = super.shouldMerge(object, index); - if ( - instance.parsedStyle.metrics.font !== object.parsedStyle.metrics.font || - instancedAttributes.some( - (name) => instance.parsedStyle[name] !== object.parsedStyle[name], - ) - ) { + if (!shouldMerge) { return false; } - return true; + if (this.index !== index) { + return false; + } + + return this.fontHash === this.calcFontHash(object); } createGeometry(objects: DisplayObject[]): void { @@ -307,7 +315,6 @@ export class TextMesh extends Instanced { name === 'lineHeight' || name === 'wordWrap' || name === 'textAlign' || - name === 'visibility' || name === 'dx' || name === 'dy' ) { @@ -323,6 +330,7 @@ export class TextMesh extends Instanced { name === 'strokeOpacity' || name === 'opacity' || name === 'lineWidth' || + name === 'visibility' || name === 'pointerEvents' ) { const vertice = this.geometry.vertices[ @@ -370,10 +378,7 @@ export class TextMesh extends Instanced { // @ts-ignore object.renderable3D?.encodedPickingColor) || [0, 0, 0]; - const modelMatrix = - name === 'modelMatrix' - ? mat4.copy(this.tmpMat4, object.getWorldTransform()) - : null; + const modelMatrix = mat4.copy(this.tmpMat4, object.getWorldTransform()); const [start, end] = this.packedBufferObjectMap.get(object); const sliced = vertice.slice(start, end); @@ -413,6 +418,7 @@ export class TextMesh extends Instanced { sliced[i + 28] = encodedPickingColor[0]; sliced[i + 29] = encodedPickingColor[1]; sliced[i + 30] = encodedPickingColor[2]; + // sliced[i + 31] = object.sortable.renderOrder * RENDER_ORDER_SCALE; } this.geometry.updateVertexBuffer( diff --git a/packages/g-plugin-device-renderer/src/renderer/BatchManager.ts b/packages/g-plugin-device-renderer/src/renderer/BatchManager.ts index 983972c5d..bca12d30d 100644 --- a/packages/g-plugin-device-renderer/src/renderer/BatchManager.ts +++ b/packages/g-plugin-device-renderer/src/renderer/BatchManager.ts @@ -13,6 +13,9 @@ import type { Batch } from './Batch'; let stencilRefCounter = 1; +/** + * Create a new batch if the number of instances exceeds. + */ const MAX_INSTANCES_PER_BATCH = 5000; export type BatchContext = { device: Device } & RenderingPluginContext; @@ -45,6 +48,7 @@ export class BatchManager { objectIndices: number[]; name: string; value: any; + // names: string[]; } > = {}; @@ -114,8 +118,8 @@ export class BatchManager { this.renderHelper, this.texturePool, this.lightPool, + object, ); - existedMesh.onCreate(object); existedMesh.renderer = renderer; existedMesh.index = i; this.meshes.push(existedMesh); @@ -202,11 +206,11 @@ export class BatchManager { this.renderHelper, this.texturePool, this.lightPool, + object, ); existedMesh.renderer = renderer; existedMesh.index = i; existedMesh.init(this.context); - existedMesh.onCreate(object); this.meshes.push(existedMesh); } else { existedMesh.geometryDirty = true; @@ -219,46 +223,46 @@ export class BatchManager { } } - if ( - shouldSubmit && - existedMesh && - existedMesh.inited && - !existedMesh.geometryDirty - ) { - const shouldMerge = existedMesh.shouldMerge(object, i); - if (shouldMerge) { - const objectIdx = existedMesh.objects.indexOf(object); - if (immediately) { - object.parsedStyle[attributeName] = newValue; - existedMesh.updateAttribute( - [object], - objectIdx, - attributeName, - newValue, - ); - } else { - const patchKey = existedMesh.id + attributeName; - if (!this.pendingUpdatePatches[patchKey]) { - this.pendingUpdatePatches[patchKey] = { - instance: existedMesh, - objectIndices: [], - name: attributeName, - value: newValue, - }; - } - if ( - this.pendingUpdatePatches[patchKey].objectIndices.indexOf( - objectIdx, - ) === -1 - ) { - this.pendingUpdatePatches[patchKey].objectIndices.push( + if (shouldSubmit && existedMesh) { + if (existedMesh.inited && !existedMesh.geometryDirty) { + const shouldMerge = existedMesh.shouldMerge(object, i); + if (shouldMerge) { + const objectIdx = existedMesh.objects.indexOf(object); + if (immediately) { + object.parsedStyle[attributeName] = newValue; + existedMesh.updateAttribute( + [object], objectIdx, + attributeName, + newValue, ); + } else { + const patchKey = existedMesh.id + attributeName; + if (!this.pendingUpdatePatches[patchKey]) { + this.pendingUpdatePatches[patchKey] = { + instance: existedMesh, + objectIndices: [], + name: attributeName, + value: newValue, + }; + } + if ( + this.pendingUpdatePatches[patchKey].objectIndices.indexOf( + objectIdx, + ) === -1 + ) { + this.pendingUpdatePatches[patchKey].objectIndices.push( + objectIdx, + ); + } } + } else { + this.remove(object); + this.add(object); } } else { - existedMesh.geometryDirty = true; - existedMesh.onCreate(object); + this.remove(object); + this.add(object); } } }); diff --git a/site/examples/perf/animation/demo/meta.json b/site/examples/perf/animation/demo/meta.json index 8b75d97cc..ffa6b10b4 100644 --- a/site/examples/perf/animation/demo/meta.json +++ b/site/examples/perf/animation/demo/meta.json @@ -27,6 +27,13 @@ "zh": "移动大量节点", "en": "Move nodes" } + }, + { + "filename": "webgl-text.js", + "title": { + "zh": "在 WebGL 中更新 Text 的位置", + "en": "Update position of Text in WebGL" + } } ] } diff --git a/site/examples/perf/animation/demo/webgl-text.js b/site/examples/perf/animation/demo/webgl-text.js index e7e29add1..122cd073c 100644 --- a/site/examples/perf/animation/demo/webgl-text.js +++ b/site/examples/perf/animation/demo/webgl-text.js @@ -11,128 +11,31 @@ const canvas = new Canvas({ renderer: new Renderer(), }); -const text1 = new Text({ - style: { - x: 100, - y: 100, - fill: 'black', - text: 'Text1', - }, -}); - -const text2 = new Text({ - style: { - x: 200, - y: 100, - fill: 'black', - text: 'Text2', - }, -}); - -const text3 = new Text({ - style: { - x: 300, - y: 100, - fill: 'black', - text: 'Text3', - }, -}); - -const text4 = new Text({ - style: { - x: 200, - y: 200, - fill: 'black', - text: 'Text3', - }, -}); - -const text5 = new Text({ - style: { - x: 300, - y: 200, - fill: 'black', - text: 'Text3', - }, -}); - canvas.addEventListener(CanvasEvent.READY, () => { - canvas.appendChild(text1); - text1.animate( - [ - { - opacity: 0, - transform: 'translate(0, 0)', + for (let i = 0; i < 1000; i++) { + const text = new Text({ + style: { + x: Math.random() * 600, + y: Math.random() * 500, + fontSize: 16, + fill: 'black', + text: `Text1${i}`, }, + }); + canvas.appendChild(text); + + text.animate( + [ + { opacity: 0, transform: 'translate(0, 0)' }, + { opacity: 1, transform: 'translate(100, 0)' }, + ], { - opacity: 1, - transform: 'translate(100, 0)', + duration: 2000, + fill: 'both', + iterations: Infinity, }, - ], - { - duration: 2000, - fill: 'both', - iterations: Infinity, - }, - ); - - canvas.appendChild(text2); - text2.animate( - [ - { opacity: 0, transform: 'translate(0, 0)' }, - { opacity: 1, transform: 'translate(100, 0)' }, - ], - { - duration: 2000, - fill: 'both', - iterations: Infinity, - }, - ); - - canvas.appendChild(text3); - text3.animate( - [ - { opacity: 0, transform: 'translate(0, 0)' }, - { opacity: 1, transform: 'translate(100, 0)' }, - ], - { - duration: 2000, - fill: 'both', - iterations: Infinity, - }, - ); - - canvas.appendChild(text4); - text4.animate( - [ - { opacity: 0, transform: 'translate(0, 0)' }, - { opacity: 1, transform: 'translate(100, 0)' }, - ], - { - duration: 2000, - fill: 'both', - iterations: Infinity, - }, - ); - - canvas.appendChild(text5); - text5.animate( - [ - { opacity: 0, transform: 'translate(0, 0)' }, - { opacity: 1, transform: 'translate(100, 0)' }, - ], - { - duration: 2000, - fill: 'both', - iterations: Infinity, - }, - ); - - text1.style.fontSize = 32; - text2.style.fontSize = 32; - text3.style.fontSize = 32; - text4.style.fontSize = 32; - text5.style.fontSize = 32; + ); + } }); // stats