Skip to content

Commit

Permalink
fix: combine multiple paths into a single drawcall (#1412)
Browse files Browse the repository at this point in the history
* fix: merge simple path into a single draw call

* chore: commit changeset

* fix: return unprecise bounds before HTML appending to document #1414

* chore: commit changeset
  • Loading branch information
xiaoiver authored Jul 18, 2023
1 parent 578a440 commit 6757ccb
Show file tree
Hide file tree
Showing 13 changed files with 468 additions and 61 deletions.
5 changes: 5 additions & 0 deletions .changeset/five-fans-know.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@antv/g-plugin-device-renderer': patch
---

Merge multiple simple paths into a single draw call
6 changes: 6 additions & 0 deletions .changeset/tasty-clouds-marry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@antv/g-plugin-device-renderer': patch
'@antv/g-lite': patch
---

Return an unprecise bound of HTML before it appending to document.
26 changes: 26 additions & 0 deletions __tests__/unit/display-objects/html.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,30 @@ describe('HTML', () => {
expect(html.getLocalBounds().center[0]).toBe(230);
expect(html.getLocalBounds().center[1]).toBe(230);
});

it('should return unprecise bounding box before appending to document.', () => {
const html = new HTML({
id: 'id',
name: 'name',
className: 'classname',
style: {
x: 100,
y: 100,
width: 100,
height: 100,
innerHTML: '<h1>This is Title</h1>',
},
});

// DOM element hasn't been created yet.
const $el = html.getDomElement();
expect($el).toBe(undefined);

// Use x/y/width/height defined by user.
const bounds = html.getBounds();
expect(bounds.halfExtents[0]).toBe(50);
expect(bounds.halfExtents[1]).toBe(50);
expect(bounds.center[0]).toBe(150);
expect(bounds.center[1]).toBe(150);
});
});
44 changes: 20 additions & 24 deletions packages/g-lite/src/display-objects/HTML.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { mat4 } from 'gl-matrix';
import type { CSSUnitValue } from '../css';
import type { DisplayObjectConfig } from '../dom';
import { runtime } from '../global-runtime';
import { AABB } from '../shapes';
import { AABB, Rectangle } from '../shapes';
import type { BaseStyleProps, ParsedBaseStyleProps } from '../types';
import { Shape } from '../types';
import { DisplayObject } from './DisplayObject';
Expand All @@ -16,12 +15,12 @@ export interface HTMLStyleProps extends BaseStyleProps {
}

export interface ParsedHTMLStyleProps extends ParsedBaseStyleProps {
x: CSSUnitValue;
y: CSSUnitValue;
x: number;
y: number;
$el: HTMLElement;
innerHTML: string | HTMLElement;
width: CSSUnitValue;
height: CSSUnitValue;
width: number;
height: number;
}

/**
Expand Down Expand Up @@ -63,8 +62,13 @@ export class HTML extends DisplayObject<HTMLStyleProps, ParsedHTMLStyleProps> {
* override with $el.getBoundingClientRect
* @see https://developer.mozilla.org/zh-CN/docs/Web/API/Element/getBoundingClientRect
*/
getBoundingClientRect() {
return this.parsedStyle.$el.getBoundingClientRect();
getBoundingClientRect(): Rectangle {
if (this.parsedStyle.$el) {
return this.parsedStyle.$el.getBoundingClientRect();
} else {
const { x, y, width, height } = this.parsedStyle;
return new Rectangle(x, y, width, height);
}
}

getClientRects() {
Expand All @@ -78,23 +82,15 @@ export class HTML extends DisplayObject<HTMLStyleProps, ParsedHTMLStyleProps> {
const canvasRect = this.ownerDocument?.defaultView
?.getContextService()
.getBoundingClientRect();
if (canvasRect) {
const minX = clientRect.left - canvasRect.left;
const minY = clientRect.top - canvasRect.top;

const aabb = new AABB();
// aabb.setMinMax(
// vec3.fromValues(minX, minY, 0),
// vec3.fromValues(minX + clientRect.width, minY + clientRect.height, 0),
// );
aabb.setMinMax(
[minX, minY, 0],
[minX + clientRect.width, minY + clientRect.height, 0],
);

return aabb;
}
return null;
const aabb = new AABB();
const minX = clientRect.left - (canvasRect?.left || 0);
const minY = clientRect.top - (canvasRect?.top || 0);
aabb.setMinMax(
[minX, minY, 0],
[minX + clientRect.width, minY + clientRect.height, 0],
);
return aabb;
}

getLocalBounds() {
Expand Down
4 changes: 2 additions & 2 deletions packages/g-plugin-device-renderer/src/PickingPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ export class PickingPlugin implements RenderingPlugin {

renderingService.hooks.pick.tapPromise(
PickingPlugin.tag,
(result: PickingResult) => {
throw new Error('Async version is not implemented.');
async (result: PickingResult) => {
return this.pick(result);
},
);
}
Expand Down
97 changes: 78 additions & 19 deletions packages/g-plugin-device-renderer/src/meshes/Instanced.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type {
Tuple4Number,
} from '@antv/g-lite';
import { CSSRGB, isPattern, isCSSRGB, parseColor, Shape } from '@antv/g-lite';
import { mat4 } from 'gl-matrix';
import { mat4, vec3 } from 'gl-matrix';
import { BufferGeometry, GeometryEvent } from '../geometries';
import type { LightPool } from '../LightPool';
import type { Fog } from '../lights';
Expand Down Expand Up @@ -80,7 +80,7 @@ export enum VertexAttributeLocation {
}

/**
* Instanced mesh
* Draw call.
*/
export abstract class Instanced {
/**
Expand Down Expand Up @@ -141,10 +141,20 @@ export abstract class Instanced {
protected lightReceived = false;

/**
*
* Divisor of instanced array.
*/
protected divisor = 1;

/**
* Account for anchor and merge it into modelMatrix.
*/
protected mergeAnchorIntoModelMatrix = false;

/**
* Create a new instance when exceed.
*/
protected maxInstance = 5000;

protected abstract createMaterial(objects: DisplayObject[]): void;

get instance() {
Expand Down Expand Up @@ -235,6 +245,10 @@ export abstract class Instanced {
return true;
}

if (this.objects.length >= this.maxInstance) {
return false;
}

// Path / Polyline could be rendered as Line
if (
this.instance.nodeName !== object.nodeName &&
Expand Down Expand Up @@ -303,18 +317,18 @@ export abstract class Instanced {
];
}

if (this.clipPathTarget) {
// account for target's rts
mat4.copy(modelMatrix, object.getLocalTransform());
fillColor = [255, 255, 255, 255];
mat4.mul(
modelMatrix,
this.clipPathTarget.getWorldTransform(),
modelMatrix,
);
} else {
mat4.copy(modelMatrix, object.getWorldTransform());
}
// if (this.clipPathTarget) {
// // account for target's rts
// mat4.copy(modelMatrix, object.getLocalTransform());
// fillColor = [255, 255, 255, 255];
// mat4.mul(
// modelMatrix,
// this.clipPathTarget.getWorldTransform(),
// modelMatrix,
// );
// } else {
// mat4.copy(modelMatrix, object.getWorldTransform());
// }
mat4.mul(
modelViewMatrix,
this.context.camera.getViewTransform(),
Expand All @@ -325,6 +339,30 @@ export abstract class Instanced {
// @ts-ignore
object.renderable3D?.encodedPickingColor) || [0, 0, 0];

if (this.mergeAnchorIntoModelMatrix) {
const { anchor } = object.parsedStyle as ParsedBaseStyleProps;
let translateX = 0;
let translateY = 0;
let translateZ = 0;
const contentBounds = object.getGeometryBounds();
if (contentBounds) {
const { halfExtents } = contentBounds;
translateX = -halfExtents[0] * anchor[0] * 2;
translateY = -halfExtents[1] * anchor[1] * 2;
translateZ = -halfExtents[2] * (anchor[2] || 0) * 2;
}

mat4.mul(
modelMatrix,
object.getWorldTransform(), // apply anchor
mat4.fromTranslation(
modelMatrix,
vec3.fromValues(translateX, translateY, translateZ),
),
);
} else {
mat4.copy(modelMatrix, object.getWorldTransform());
}
packedModelMatrix.push(...modelMatrix);
packedFillStroke.push(
packUint8ToFloat(fillColor[0], fillColor[1]),
Expand Down Expand Up @@ -798,11 +836,32 @@ export abstract class Instanced {
);
} else if (name === 'modelMatrix') {
const packed: number[] = [];
const modelMatrix = mat4.create();
objects.forEach((object) => {
const modelMatrix = mat4.copy(
mat4.create(),
object.getWorldTransform(),
);
if (this.mergeAnchorIntoModelMatrix) {
const { anchor } = object.parsedStyle;
let translateX = 0;
let translateY = 0;
let translateZ = 0;
const contentBounds = object.getGeometryBounds();
if (contentBounds) {
const { halfExtents } = contentBounds;
translateX = -halfExtents[0] * anchor[0] * 2;
translateY = -halfExtents[1] * anchor[1] * 2;
translateZ = -halfExtents[2] * (anchor[2] || 0) * 2;
}

mat4.mul(
modelMatrix,
object.getWorldTransform(), // apply anchor
mat4.fromTranslation(
modelMatrix,
vec3.fromValues(translateX, translateY, translateZ),
),
);
} else {
mat4.copy(modelMatrix, object.getWorldTransform());
}
packed.push(...modelMatrix);
});

Expand Down
40 changes: 32 additions & 8 deletions packages/g-plugin-device-renderer/src/meshes/InstancedPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,26 +43,50 @@ export class InstancedPathMesh extends Instanced {
const {
path: { absolutePath },
} = object.parsedStyle as ParsedPathStyleProps;
if (
absolutePath.length === 2 &&
absolutePath[0][0] === 'M' &&
(absolutePath[1][0] === 'C' ||
absolutePath[1][0] === 'A' ||
absolutePath[1][0] === 'Q')
) {
if (absolutePath.length >= 2 && absolutePath.length <= 5) {
return true;
}
}
return false;
}

protected mergeAnchorIntoModelMatrix = true;

private segmentNum = -1;

private calcSegmentNum(object: DisplayObject) {
return (
object.parsedStyle as ParsedPathStyleProps
).path.absolutePath.reduce((prev, cur) => {
let segment = 0;
if (cur[0] === 'M') {
segment = 0;
} else if (cur[0] === 'L') {
segment = 1;
} else if (cur[0] === 'A' || cur[0] === 'Q' || cur[0] === 'C') {
segment = SEGMENT_NUM;
} else if (cur[0] === 'Z') {
segment = 1;
}
return prev + segment;
}, 0);
}
/**
* Paths with the same number of vertices should be merged.
*/
shouldMerge(object: DisplayObject, index: number) {
const shouldMerge = super.shouldMerge(object, index);
if (!shouldMerge) {
return false;
}

return true;
if (this.segmentNum === -1) {
this.segmentNum = this.calcSegmentNum(this.instance);
}

const segmentNum = this.calcSegmentNum(object);

return this.segmentNum === segmentNum;
}

createMaterial(objects: DisplayObject[]): void {
Expand Down
7 changes: 3 additions & 4 deletions packages/g-plugin-device-renderer/src/meshes/Line.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
import { arcToCubic } from '@antv/util';
import earcut from 'earcut';
import { mat4, vec3 } from 'gl-matrix';
import { CullMode, Format, VertexBufferFrequency } from '../platform';
import { Format, VertexBufferFrequency } from '../platform';
import { RENDER_ORDER_SCALE } from '../renderer/Batch';
import frag from '../shader/line.frag';
import vert from '../shader/line.vert';
Expand Down Expand Up @@ -190,8 +190,8 @@ export class LineMesh extends Instanced {
});
} else if (name === 'lineDash') {
this.material.setUniforms({
[Uniform.DASH]: lineDash[0] || 0,
[Uniform.GAP]: lineDash[1] || 0,
[Uniform.DASH]: (lineDash && lineDash[0]) || 0,
[Uniform.GAP]: (lineDash && lineDash[1]) || 0,
});
} else if (name === 'lineDashOffset') {
this.material.setUniforms({
Expand Down Expand Up @@ -299,7 +299,6 @@ export class LineMesh extends Instanced {
[Uniform.VISIBLE]: visibility === 'visible' ? 1 : 0,
[Uniform.Z_INDEX]: instance.sortable.renderOrder * RENDER_ORDER_SCALE,
});
this.material.cullMode = CullMode.None;
}

createGeometry(objects: DisplayObject[]): void {
Expand Down
2 changes: 1 addition & 1 deletion packages/g-plugin-device-renderer/src/renderer/Path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export class PathRenderer extends Batch {
}

if (index === 3) {
return isOneCommandCurve;
return !isLine && isOneCommandCurve;
}

return true;
Expand Down
Loading

0 comments on commit 6757ccb

Please sign in to comment.