diff --git a/.changeset/tidy-walls-hang.md b/.changeset/tidy-walls-hang.md new file mode 100644 index 000000000..eb239120f --- /dev/null +++ b/.changeset/tidy-walls-hang.md @@ -0,0 +1,7 @@ +--- +'@antv/g-plugin-canvas-renderer': patch +'@antv/g-plugin-device-renderer': patch +'@antv/g-lite': patch +--- + +Add disableRenderHooks switch for WebGL renderer. diff --git a/packages/g-lite/src/Canvas.ts b/packages/g-lite/src/Canvas.ts index 9bc95e789..239e017fe 100644 --- a/packages/g-lite/src/Canvas.ts +++ b/packages/g-lite/src/Canvas.ts @@ -15,7 +15,6 @@ import { CustomElementRegistry } from './dom/CustomElementRegistry'; import type { ICanvas } from './dom/interfaces'; import { runtime } from './global-runtime'; import { CullingPlugin } from './plugins/CullingPlugin'; -import { DirtyCheckPlugin } from './plugins/DirtyCheckPlugin'; import { EventPlugin } from './plugins/EventPlugin'; import { FrustumCullingStrategy } from './plugins/FrustumCullingStrategy'; import { PrepareRendererPlugin } from './plugins/PrepareRendererPlugin'; @@ -506,7 +505,7 @@ export class Canvas extends EventTarget implements ICanvas { this.context.renderingPlugins.push( new EventPlugin(), new PrepareRendererPlugin(), - new DirtyCheckPlugin(), + // new DirtyCheckPlugin(), new CullingPlugin([new FrustumCullingStrategy()]), ); diff --git a/packages/g-lite/src/services/EventService.ts b/packages/g-lite/src/services/EventService.ts index d1f40ce6e..48fa3f6d2 100644 --- a/packages/g-lite/src/services/EventService.ts +++ b/packages/g-lite/src/services/EventService.ts @@ -682,7 +682,7 @@ export class EventService { hitTest(position: EventPosition): IEventTarget | null { const { viewportX, viewportY } = position; - const { width, height } = this.context.config; + const { width, height, disableHitTesting } = this.context.config; // outside canvas if ( viewportX < 0 || @@ -694,7 +694,7 @@ export class EventService { } return ( - this.pickHandler(position) || + (!disableHitTesting && this.pickHandler(position)) || this.rootTarget || // return Document null ); diff --git a/packages/g-lite/src/services/RenderingService.ts b/packages/g-lite/src/services/RenderingService.ts index 743123526..4a009fd50 100644 --- a/packages/g-lite/src/services/RenderingService.ts +++ b/packages/g-lite/src/services/RenderingService.ts @@ -172,19 +172,27 @@ export class RenderingService { this.globalRuntime.sceneGraphService.triggerPendingEvents(); if (renderingContext.renderReasons.size && this.inited) { - this.renderDisplayObject( - renderingContext.root, - canvasConfig, - renderingContext, - ); + // @ts-ignore + renderingContext.dirtyRectangleRenderingDisabled = + this.disableDirtyRectangleRendering(); + + if (!canvasConfig.disableRenderHooks) { + this.renderDisplayObject( + renderingContext.root, + canvasConfig, + renderingContext, + ); + } this.hooks.beginFrame.call(); - renderingContext.renderListCurrentFrame.forEach((object) => { - this.hooks.beforeRender.call(object); - this.hooks.render.call(object); - this.hooks.afterRender.call(object); - }); + if (!canvasConfig.disableRenderHooks) { + renderingContext.renderListCurrentFrame.forEach((object) => { + this.hooks.beforeRender.call(object); + this.hooks.render.call(object); + this.hooks.afterRender.call(object); + }); + } this.hooks.endFrame.call(); renderingContext.renderListCurrentFrame = []; @@ -212,9 +220,14 @@ export class RenderingService { // TODO: relayout // dirtycheck first + const renderable = displayObject.renderable; const objectChanged = enableDirtyCheck - ? this.hooks.dirtycheck.call(displayObject) + ? // @ts-ignore + renderable.dirty || renderingContext.dirtyRectangleRenderingDisabled + ? displayObject + : null : displayObject; + if (objectChanged) { const objectToRender = enableCulling ? this.hooks.cull.call(objectChanged, this.context.camera) diff --git a/packages/g-lite/src/types.ts b/packages/g-lite/src/types.ts index 1e954f077..df5cea8da 100644 --- a/packages/g-lite/src/types.ts +++ b/packages/g-lite/src/types.ts @@ -467,6 +467,16 @@ export interface CanvasConfig { */ alwaysTriggerPointerEventOnCanvas?: boolean; + /** + * Enable event service + */ + disableHitTesting?: boolean; + + /** + * Skip triggering hooks.beforeRender/render/afterRender + */ + disableRenderHooks?: boolean; + /** * Should we account for CSS Transform applied on container? */ diff --git a/packages/g-plugin-canvas-renderer/src/CanvasRendererPlugin.ts b/packages/g-plugin-canvas-renderer/src/CanvasRendererPlugin.ts index 04c5b5b2e..de8da5710 100644 --- a/packages/g-plugin-canvas-renderer/src/CanvasRendererPlugin.ts +++ b/packages/g-plugin-canvas-renderer/src/CanvasRendererPlugin.ts @@ -460,11 +460,10 @@ export class CanvasRendererPlugin implements RenderingPlugin { const max = aabb.getMax(); // expand the rectangle a bit to avoid artifacts // @see https://www.yuque.com/antv/ou292n/bi8nix#ExvCu - const padding = 2; - const minX = Math.floor(min[0]) - padding; - const minY = Math.floor(min[1]) - padding; - const maxX = Math.ceil(max[0]) + padding; - const maxY = Math.ceil(max[1]) + padding; + const minX = Math.floor(min[0]); + const minY = Math.floor(min[1]); + const maxX = Math.ceil(max[0]); + const maxY = Math.ceil(max[1]); const width = maxX - minX; const height = maxY - minY; diff --git a/packages/g-plugin-device-renderer/src/RenderGraphPlugin.ts b/packages/g-plugin-device-renderer/src/RenderGraphPlugin.ts index df58cdba4..e195b5455 100644 --- a/packages/g-plugin-device-renderer/src/RenderGraphPlugin.ts +++ b/packages/g-plugin-device-renderer/src/RenderGraphPlugin.ts @@ -105,8 +105,9 @@ export class RenderGraphPlugin implements RenderingPlugin { apply(context: RenderingPluginContext) { this.context = context; - const { renderingService, renderingContext } = context; + const { renderingService, renderingContext, config } = context; const canvas = renderingContext.root.ownerDocument.defaultView; + config.disableRenderHooks = true; const handleMounted = (e: FederatedEvent) => { const object = e.target as DisplayObject; @@ -251,6 +252,8 @@ export class RenderGraphPlugin implements RenderingPlugin { this.device.destroy(); this.device.checkForLeaks(); + + config.disableRenderHooks = false; }); /** diff --git a/site/examples/perf/webgl/demo/meta.json b/site/examples/perf/webgl/demo/meta.json index 826c09d47..a13ca2807 100644 --- a/site/examples/perf/webgl/demo/meta.json +++ b/site/examples/perf/webgl/demo/meta.json @@ -117,6 +117,14 @@ "en": "A large graph with 5.5w nodes" }, "screenshot": "https://gw.alipayobjects.com/mdn/rms_6ae20b/afts/img/A*dwS2Rb3mq5IAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "nodes.js", + "title": { + "zh": "大量直线", + "en": "Instanced Lines" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*GNRuTqpp6BAAAAAAAAAAAAAADmJ7AQ/original" } ] } diff --git a/site/examples/perf/webgl/demo/nodes.js b/site/examples/perf/webgl/demo/nodes.js new file mode 100644 index 000000000..43b9c5211 --- /dev/null +++ b/site/examples/perf/webgl/demo/nodes.js @@ -0,0 +1,185 @@ +import { Canvas, CanvasEvent, Circle, Line, Text, runtime } from '@antv/g'; +import { Renderer as WebGLRenderer } from '@antv/g-webgl'; +import { Plugin } from '@antv/g-plugin-dragndrop'; +import Stats from 'stats.js'; + +/** + * Compare with galaxyviz + * @see https://github.com/galaxybase/GalaxyVis/blob/main/examples/test_all.ts#L12 + */ + +runtime.enableCSSParsing = false; + +const NODE_NUM = 50000; +const EDGE_NUM = 50000; + +const webglRenderer = new WebGLRenderer(); +webglRenderer.registerPlugin( + new Plugin({ + isDocumentDraggable: true, + isDocumentDroppable: true, + dragstartDistanceThreshold: 10, + dragstartTimeThreshold: 100, + }), +); + +const canvas = new Canvas({ + container: 'container', + width: 600, + height: 500, + renderer: webglRenderer, +}); + +canvas.addEventListener(CanvasEvent.READY, () => { + let nodes = []; + let colors = [ + '#965E04', + '#C89435', + '#F7A456', + '#AFCF8A', + '#7B39DE', + '#B095C0', + '#D24556', + '#93C2FA', + '#9DB09E', + '#F8C821', + ]; + let num = Math.floor(Math.sqrt(NODE_NUM) + 0.5); + + const sourceMap = new WeakMap(); + const targetMap = new WeakMap(); + for (let i = 0; i < NODE_NUM; i++) { + const circle = new Circle({ + style: { + cx: (i % num) * 10, + cy: Math.floor(i / num) * 10, + fill: colors[Math.floor(Math.random() * colors.length) || 0], + r: 4, + }, + }); + nodes.push(circle); + sourceMap.set(circle, []); + } + + for (let i = 0; i < EDGE_NUM; i++) { + const source = nodes[Math.floor(Math.random() * NODE_NUM)]; + const target = nodes[Math.floor(Math.random() * NODE_NUM)]; + const line = new Line({ + style: { + x1: source.style.cx, + y1: source.style.cy, + x2: target.style.cx, + y2: target.style.cy, + lineWidth: 0.3, + stroke: 'grey', + }, + }); + + const sourceEdges = sourceMap.get(source); + // sourceEdges.push(line); + const targetEdges = targetMap.get(target); + // targetEdges.push(line); + + canvas.appendChild(line); + } + + nodes.forEach((circle) => { + canvas.appendChild(circle); + }); + + // stats + const stats = new Stats(); + stats.showPanel(0); + const $stats = stats.dom; + $stats.style.position = 'absolute'; + $stats.style.left = '0px'; + $stats.style.top = '0px'; + const $wrapper = document.getElementById('container'); + $wrapper.appendChild($stats); + const camera = canvas.getCamera(); + canvas.addEventListener(CanvasEvent.AFTER_RENDER, () => { + if (stats) { + stats.update(); + } + }); + + let shiftX = 0; + let shiftY = 0; + function moveAt(target, canvasX, canvasY) { + const x = canvasX - shiftX; + const y = canvasY - shiftY; + target.setPosition(x, y); + const sourceEdges = sourceMap.get(target); + const targetEdges = targetMap.get(target); + sourceEdges.forEach((edge) => { + edge.attr({ + x1: x, + y1: y, + }); + }); + targetEdges.forEach((edge) => { + edge.attr({ + x2: x, + y2: y, + }); + }); + } + + canvas.addEventListener('dragstart', function (e) { + canvas.getConfig().disableHitTesting = true; + + if (e.target === canvas.document) { + } else { + const [x, y] = e.target.getPosition(); + shiftX = e.canvasX - x; + shiftY = e.canvasY - y; + + moveAt(e.target, e.canvasX, e.canvasY); + } + }); + canvas.addEventListener('drag', function (e) { + if (e.target === canvas.document) { + camera.pan(-e.dx, -e.dy); + } else { + moveAt(e.target, e.canvasX, e.canvasY); + } + }); + canvas.addEventListener('dragend', function (e) { + console.log('dragend...'); + canvas.getConfig().disableHitTesting = false; + }); + + // handle mouse wheel event + const bindWheelHandler = () => { + // update Camera's zoom + // @see https://github.com/mrdoob/three.js/blob/master/examples/jsm/controls/OrbitControls.js + const minZoom = 0; + const maxZoom = Infinity; + canvas + .getContextService() + .getDomElement() // g-canvas/webgl 为 ,g-svg 为 + .addEventListener( + 'wheel', + (e) => { + canvas.getConfig().disableHitTesting = false; + + e.preventDefault(); + let zoom; + if (e.deltaY < 0) { + zoom = Math.max( + minZoom, + Math.min(maxZoom, camera.getZoom() / 0.95), + ); + } else { + zoom = Math.max( + minZoom, + Math.min(maxZoom, camera.getZoom() * 0.95), + ); + } + camera.setZoom(zoom); + }, + { passive: false }, + ); + }; + bindWheelHandler(); +});