diff --git a/.eslintrc.js b/.eslintrc.js
index 7c11b791ce..b46ba964b2 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -29,6 +29,7 @@ module.exports = {
'no-useless-catch': 0,
'@typescript-eslint/explicit-module-boundary-types': 0,
'@typescript-eslint/no-explicit-any':0,
+ "@typescript-eslint/no-unused-vars": 0,
'prefer-rest-params':0,
},
settings: {
diff --git a/.gitignore b/.gitignore
index 6a1191cbf4..c243c0dc26 100644
--- a/.gitignore
+++ b/.gitignore
@@ -71,7 +71,6 @@ dist/
es/
.DS_Store
-public
.cache
.idea
diff --git a/.umirc.ts b/.umirc.ts
index a70f871f92..95dcdb7a55 100644
--- a/.umirc.ts
+++ b/.umirc.ts
@@ -29,7 +29,7 @@ export default defineConfig({
title: '特性',
path: '/features',
},
-
+
{
title: '图库',
path: '/gallery',
@@ -101,7 +101,8 @@ export default defineConfig({
// "https://api.map.baidu.com/api?v=1.0&&type=webgl&ak=zLhopYPPERGtpGOgimcdKcCimGRyyIsh",
/** lodash */
'https://gw.alipayobjects.com/os/lib/lodash/4.17.20/lodash.min.js',
- ]
+ ],
+ publicPath: '/public/',
// more config: https://d.umijs.org/config
});
diff --git a/dev-demos/features/point/demos/circle-webgpu.tsx b/dev-demos/features/point/demos/circle-webgpu.tsx
new file mode 100644
index 0000000000..f4bc011cfb
--- /dev/null
+++ b/dev-demos/features/point/demos/circle-webgpu.tsx
@@ -0,0 +1,77 @@
+// @ts-ignore
+import { PointLayer, Scene } from '@antv/l7';
+// @ts-ignore
+import { GaodeMap } from '@antv/l7-maps';
+import React, { useEffect } from 'react';
+
+export default () => {
+ useEffect(() => {
+ const scene = new Scene({
+ id: 'point_circle',
+ pickBufferScale: 1.0,
+ renderer: 'device',
+ enableWebGPU: true,
+ shaderCompilerPath: '/glsl_wgsl_compiler_bg.wasm',
+ map: new GaodeMap({
+ style: 'light',
+ center: [-121.24357, 37.58264],
+ pitch: 0,
+ zoom: 6.45,
+ }),
+ });
+ scene.on('loaded', () => {
+ fetch(
+ 'https://gw.alipayobjects.com/os/basement_prod/6c4bb5f2-850b-419d-afc4-e46032fc9f94.csv',
+ )
+ .then((res) => res.text())
+ .then((data) => {
+ const pointLayer = new PointLayer()
+ .source(data, {
+ parser: {
+ type: 'csv',
+ x: 'Longitude',
+ y: 'Latitude',
+ },
+ })
+ .shape('circle')
+ .size(16)
+ .active(true)
+ .select({
+ color: 'red',
+ })
+ .color('Magnitude', [
+ '#0A3663',
+ '#1558AC',
+ '#3771D9',
+ '#4D89E5',
+ '#64A5D3',
+ '#72BED6',
+ '#83CED6',
+ '#A6E1E0',
+ '#B8EFE2',
+ '#D7F9F0',
+ ])
+ .style({
+ opacity: 1,
+ strokeWidth: 0,
+ stroke: '#fff',
+ });
+ scene.addLayer(pointLayer);
+ // let i =0;
+ // setInterval(() => {
+ // i++ % 2 === 0 ? pointLayer.setBlend('additive') : pointLayer.setBlend('normal');
+
+ // },20)
+ });
+ });
+ }, []);
+ return (
+
+ );
+};
diff --git a/dev-demos/features/point/demos/circlemeter.tsx b/dev-demos/features/point/demos/circlemeter.tsx
index 5f9020e0d4..03cf71e867 100644
--- a/dev-demos/features/point/demos/circlemeter.tsx
+++ b/dev-demos/features/point/demos/circlemeter.tsx
@@ -1,5 +1,5 @@
import { Scene, PointLayer,PolygonLayer } from "@antv/l7";
-import { GaodeMap } from "@antv/l7-maps";
+import { GaodeMap,Map } from "@antv/l7-maps";
import React, { useEffect } from 'react';
import * as turf from '@turf/turf'
@@ -7,6 +7,7 @@ export default () => {
useEffect( () => {
const scene = new Scene({
id: "map",
+ renderer: 'device',
map: new GaodeMap({
style: "light",
center: [120.099658370018, 30.263445807542666],
@@ -14,18 +15,9 @@ const scene = new Scene({
})
});
scene.on("loaded", () => {
- const size = 1000;
- const circle = turf.circle([120.099658370018, 30.263445807542666],size/1000,{
- steps: 60, units: 'kilometers'
- });
-const player = new PolygonLayer().source(turf.featureCollection([circle]))
-.shape('fill')
-.color('blue')
-.style({
- opacity:0.5
-})
+
const pointLayer = new PointLayer({
- autoFit: false
+ autoFit: true
})
.source({
type: "FeatureCollection",
@@ -39,18 +31,14 @@ const player = new PolygonLayer().source(turf.featureCollection([circle]))
}
}
]
- })
- .shape("triangle")
- .size(size)
+ })
+ .shape("circle")
+ .size(100)
.color("#ff0000")
- .active(true)
.style({
opacity: 1,
strokeWidth: 1,
- rotation: 30,
- unit:'meter'
});
- scene.addLayer(player);
scene.addLayer(pointLayer);
});
}, []);
diff --git a/dev-demos/features/point/demos/normal-device.tsx b/dev-demos/features/point/demos/normal-device.tsx
new file mode 100644
index 0000000000..b4b3d77e0e
--- /dev/null
+++ b/dev-demos/features/point/demos/normal-device.tsx
@@ -0,0 +1,63 @@
+// @ts-ignore
+import { PointLayer, Scene } from '@antv/l7';
+// @ts-ignore
+import { GaodeMap } from '@antv/l7-maps';
+import React, { useEffect } from 'react';
+
+export default () => {
+ useEffect(() => {
+ const scene = new Scene({
+ id: 'map',
+ renderer: 'device',
+ map: new GaodeMap({
+ // style: 'blank',
+ center: [120.099658370018, 30.263445807542666],
+ pitch: 0,
+ zoom: 11,
+ }),
+ });
+ scene.on('loaded', () => {
+ // fetch(
+ // 'https://gw.alipayobjects.com/os/rmsportal/BElVQFEFvpAKzddxFZxJ.txt',
+ // )
+ // .then((res) => res.text())
+ // .then((data) => {
+ const pointLayer = new PointLayer({ blend: 'additive' })
+ .source({
+ type: "FeatureCollection",
+ features: [
+ {
+ type: "Feature",
+ properties: {},
+ geometry: {
+ type: "Point",
+ coordinates: [120.099658370018, 30.263445807542666]
+ }
+ }
+ ]
+ })
+ .size(40)
+ .shape('dot')
+ .color('#f00')
+ .style({
+ sizeScale:0.5,
+ opacity: 0.6,
+ stroke:'#00f',
+
+
+ });
+
+ scene.addLayer(pointLayer);
+ // });
+ });
+ }, []);
+ return (
+
+ );
+};
diff --git a/dev-demos/features/point/demos/normal.tsx b/dev-demos/features/point/demos/normal.tsx
index f6ffa70aed..238aa17d50 100644
--- a/dev-demos/features/point/demos/normal.tsx
+++ b/dev-demos/features/point/demos/normal.tsx
@@ -1,51 +1,51 @@
// @ts-ignore
import { PointLayer, Scene } from '@antv/l7';
// @ts-ignore
-import { GaodeMap, Mapbox } from '@antv/l7-maps';
+import { GaodeMap } from '@antv/l7-maps';
import React, { useEffect } from 'react';
-
+
export default () => {
- useEffect( () => {
- const scene = new Scene({
- id: 'map',
- map: new GaodeMap({
- style: 'dark',
- center: [ 121.417463, 31.215175 ],
- pitch: 0,
- zoom: 11
- })
- });
- scene.on('loaded', () => {
- fetch('https://gw.alipayobjects.com/os/rmsportal/BElVQFEFvpAKzddxFZxJ.txt')
- .then(res => res.text())
- .then(data => {
- const pointLayer = new PointLayer({blend:'additive'})
- .source(data, {
- parser: {
- type: 'csv',
- y: 'lat',
- x: 'lng'
- }
- })
- .size(0.5)
- .color('#080298')
- .style({
- opacity: 1
- });
+ useEffect(() => {
+ const scene = new Scene({
+ id: 'map',
+ map: new GaodeMap({
+ style: 'dark',
+ center: [121.417463, 31.215175],
+ pitch: 0,
+ zoom: 11,
+ }),
+ });
+ scene.on('loaded', () => {
+ fetch(
+ 'https://gw.alipayobjects.com/os/rmsportal/BElVQFEFvpAKzddxFZxJ.txt',
+ )
+ .then((res) => res.text())
+ .then((data) => {
+ const pointLayer = new PointLayer({ blend: 'additive' })
+ .source(data, {
+ parser: {
+ type: 'csv',
+ y: 'lat',
+ x: 'lng',
+ },
+ })
+ .size(0.5)
+ .color('#080298')
+ .style({
+ opacity: 1,
+ });
- scene.addLayer(pointLayer);
- });
- });
-
- }, []);
- return (
-
- );
- };
-
\ No newline at end of file
+ scene.addLayer(pointLayer);
+ });
+ });
+ }, []);
+ return (
+
+ );
+};
diff --git a/dev-demos/features/point/pointCircleWebGPU.md b/dev-demos/features/point/pointCircleWebGPU.md
new file mode 100644
index 0000000000..c1bdddfbb8
--- /dev/null
+++ b/dev-demos/features/point/pointCircleWebGPU.md
@@ -0,0 +1,3 @@
+### point - circle - WebGPU
+
+
diff --git a/dev-demos/features/point/pointNormalDevice.md b/dev-demos/features/point/pointNormalDevice.md
new file mode 100644
index 0000000000..cca7565418
--- /dev/null
+++ b/dev-demos/features/point/pointNormalDevice.md
@@ -0,0 +1,3 @@
+### Point - normal - Device API
+
+
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 8b0d4d4382..ec3303ee4b 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -6,6 +6,7 @@ import container, {
} from './inversify.config';
import BasePostProcessingPass from './services/renderer/passes/BasePostProcessingPass';
import { TYPES } from './types';
+import { removeDuplicateUniforms } from './utils/shader-module';
import { packCircleVertex } from './utils/vertex-compression';
export * from './services/asset/IFontService';
@@ -54,4 +55,5 @@ export {
lazyInject,
lazyMultiInject,
packCircleVertex,
+ removeDuplicateUniforms,
};
diff --git a/packages/core/src/services/config/IConfigService.ts b/packages/core/src/services/config/IConfigService.ts
index 3aafc902ea..eb1bb4cf4d 100644
--- a/packages/core/src/services/config/IConfigService.ts
+++ b/packages/core/src/services/config/IConfigService.ts
@@ -17,6 +17,10 @@ export interface ISceneConfig extends IRenderConfig {
// TODO: 场景是否支持 stencil mask
stencil?: boolean;
debug?: boolean;
+ /**
+ * Support regl & @antv/g-device-api now
+ */
+ renderer?: 'regl' | 'device';
}
export interface IGlobalConfigService {
diff --git a/packages/core/src/services/layer/ILayerService.ts b/packages/core/src/services/layer/ILayerService.ts
index 7137177df3..dfdda8683e 100644
--- a/packages/core/src/services/layer/ILayerService.ts
+++ b/packages/core/src/services/layer/ILayerService.ts
@@ -17,6 +17,7 @@ import {
} from '../interaction/IPickingService';
import { IMapService } from '../map/IMapService';
import { IAttribute } from '../renderer/IAttribute';
+import { IBuffer } from '../renderer/IBuffer';
import {
IBlendOptions,
IModel,
@@ -87,6 +88,8 @@ export interface ILayerModelInitializationOptions {
}
export interface ILayerModel {
+ uniformBuffers: IBuffer[];
+ textures: ITexture2D[];
renderUpdate?(): void;
getBlend(): Partial;
getStencil(option?: Partial): Partial;
diff --git a/packages/core/src/services/layer/LayerService.ts b/packages/core/src/services/layer/LayerService.ts
index f56d47e01e..96369a930b 100644
--- a/packages/core/src/services/layer/LayerService.ts
+++ b/packages/core/src/services/layer/LayerService.ts
@@ -145,6 +145,8 @@ export default class LayerService
this.debugService.renderStart(renderUid);
this.alreadyInRendering = true;
this.clear();
+
+ this.renderService.beginFrame();
for (const layer of this.layerList) {
const { enableMask } = layer.getLayerConfig();
if (layer.masks.filter((m) => m.inited).length > 0 && enableMask) {
@@ -158,6 +160,7 @@ export default class LayerService
await layer.render();
}
}
+ this.renderService.endFrame();
this.debugService.renderEnd(renderUid);
this.alreadyInRendering = false;
}
diff --git a/packages/core/src/services/renderer/IAttribute.ts b/packages/core/src/services/renderer/IAttribute.ts
index ac99d6ba09..1fc60e47b4 100644
--- a/packages/core/src/services/renderer/IAttribute.ts
+++ b/packages/core/src/services/renderer/IAttribute.ts
@@ -3,6 +3,11 @@ import { IBuffer } from './IBuffer';
export interface IAttributeInitializationOptions {
buffer: IBuffer;
+ /**
+ * layout(location = x)
+ */
+ shaderLocation?: number;
+
/**
* vertexAttribPointer 单位为 byte,默认值均为 0
*/
diff --git a/packages/core/src/services/renderer/IBuffer.ts b/packages/core/src/services/renderer/IBuffer.ts
index 60d47352c4..6f37eb0eda 100644
--- a/packages/core/src/services/renderer/IBuffer.ts
+++ b/packages/core/src/services/renderer/IBuffer.ts
@@ -19,6 +19,11 @@ export interface IBufferInitializationOptions {
*/
type?: gl.FLOAT | gl.UNSIGNED_BYTE;
length?: number;
+
+ /**
+ * UniformBuffer
+ */
+ isUBO?: boolean;
}
export interface IBuffer {
diff --git a/packages/core/src/services/renderer/IModel.ts b/packages/core/src/services/renderer/IModel.ts
index 8ef43099e3..e4da22d8f2 100644
--- a/packages/core/src/services/renderer/IModel.ts
+++ b/packages/core/src/services/renderer/IModel.ts
@@ -1,6 +1,8 @@
import { gl } from './gl';
import { IAttribute } from './IAttribute';
+import { IBuffer } from './IBuffer';
import { IElements } from './IElements';
+import { ITexture2D } from './ITexture2D';
import { IUniform } from './IUniform';
export interface IBlendOptions {
@@ -160,6 +162,9 @@ export interface IModelInitializationOptions {
uniforms?: {
[key: string]: IUniform;
};
+ // UBOs
+ uniformBuffers?: IBuffer[];
+ textures?: ITexture2D[];
attributes: {
[key: string]: IAttribute;
@@ -246,6 +251,8 @@ export interface IModelDrawOptions {
};
elements?: IElements;
+ uniformBuffers?: IBuffer[];
+
blend?: Partial;
stencil?: Partial;
diff --git a/packages/core/src/services/renderer/IRendererService.ts b/packages/core/src/services/renderer/IRendererService.ts
index a25873ca1b..b6dbf5e96c 100644
--- a/packages/core/src/services/renderer/IRendererService.ts
+++ b/packages/core/src/services/renderer/IRendererService.ts
@@ -19,6 +19,14 @@ export interface IRenderConfig {
preserveDrawingBuffer?: boolean;
// Tip: 场景是否支持 stencil mask
stencil?: boolean;
+ /**
+ * Whether to use WebGPU Device.
+ */
+ enableWebGPU?: boolean;
+ /**
+ * Path of WASM shader compiler.
+ */
+ shaderCompilerPath?: string;
}
export interface IClearOptions {
@@ -47,6 +55,7 @@ export interface IExtensions {
}
export interface IRendererService {
+ uniformBuffers: IBuffer[];
extensionObject: IExtensions;
init(canvas: HTMLCanvasElement, cfg: IRenderConfig, gl: any): Promise;
testExtension(name: string): boolean;
@@ -74,4 +83,6 @@ export interface IRendererService {
setDirty(flag: boolean): void;
getDirty(): boolean;
destroy(): void;
+ beginFrame(): void;
+ endFrame(): void;
}
diff --git a/packages/core/src/services/shader/ShaderModuleService.ts b/packages/core/src/services/shader/ShaderModuleService.ts
index 3183472ba9..d4dc0ca2ca 100644
--- a/packages/core/src/services/shader/ShaderModuleService.ts
+++ b/packages/core/src/services/shader/ShaderModuleService.ts
@@ -30,7 +30,7 @@ export default class ShaderModuleService implements IShaderModuleService {
this.destroy();
this.registerModule('common', { vs: common, fs: common });
this.registerModule('decode', { vs: decode, fs: '' });
- this.registerModule('projection', { vs: projection, fs: '' });
+ this.registerModule('projection', { vs: projection, fs: projection });
this.registerModule('project', { vs: project, fs: '' });
this.registerModule('sdf_2d', { vs: '', fs: sdf2d });
this.registerModule('lighting', { vs: lighting, fs: '' });
@@ -70,7 +70,7 @@ export default class ShaderModuleService implements IShaderModuleService {
// }
let rawVS = this.rawContentCache[moduleName].vs;
- const rawFS = this.rawContentCache[moduleName].fs;
+ let rawFS = this.rawContentCache[moduleName].fs;
const inject = this.rawContentCache[moduleName].inject;
let declaredUniforms = {};
if (inject?.['vs:#decl']) {
@@ -84,6 +84,10 @@ export default class ShaderModuleService implements IShaderModuleService {
return match + inject?.['vs:#main-start'];
});
}
+ if (inject?.['fs:#decl']) {
+ // 头部注入
+ rawFS = inject?.['fs:#decl'] + rawFS;
+ }
const { content: vs, includeList: vsIncludeList } = this.processModule(
rawVS,
@@ -118,13 +122,18 @@ export default class ShaderModuleService implements IShaderModuleService {
if (!precisionRegExp.test(fs)) {
compiledFs = compiledFs + globalDefaultprecision;
}
-
compiledFs = compiledFs + fs;
+ let compiledVs = '';
+ if (!precisionRegExp.test(vs)) {
+ compiledVs = compiledVs + globalDefaultprecision;
+ }
+ compiledVs = compiledVs + vs;
+
this.moduleCache[moduleName] = {
fs: compiledFs.trim(),
uniforms,
- vs: vs.trim(),
+ vs: compiledVs.trim(),
};
return this.moduleCache[moduleName];
}
diff --git a/packages/core/src/shaders/picking.frag.glsl b/packages/core/src/shaders/picking.frag.glsl
index 7afade6481..dd771525c1 100644
--- a/packages/core/src/shaders/picking.frag.glsl
+++ b/packages/core/src/shaders/picking.frag.glsl
@@ -1,10 +1,18 @@
-varying vec4 v_PickingResult;
-uniform vec4 u_HighlightColor : [0, 0, 0, 0];
-uniform vec4 u_SelectColor : [0, 0, 0, 0];
-uniform float u_PickingStage : 0.0;
-uniform float u_shaderPick;
-uniform float u_activeMix: 0;
+in vec4 v_PickingResult;
+
+layout(std140) uniform PickingUniforms {
+ vec4 u_HighlightColor;
+ vec4 u_SelectColor;
+ vec3 u_PickingColor;
+ float u_PickingStage;
+ vec3 u_CurrentSelectedId;
+ float u_PickingThreshold;
+ float u_PickingBuffer;
+ float u_shaderPick;
+ float u_EnableSelect;
+ float u_activeMix;
+};
#define PICKING_NONE 0.0
#define PICKING_ENCODE 1.0
diff --git a/packages/core/src/shaders/picking.vert.glsl b/packages/core/src/shaders/picking.vert.glsl
index 575fdb146c..c1753b6a10 100644
--- a/packages/core/src/shaders/picking.vert.glsl
+++ b/packages/core/src/shaders/picking.vert.glsl
@@ -1,15 +1,19 @@
-attribute vec3 a_PickingColor;
-varying vec4 v_PickingResult;
+layout(location = 2) in vec3 a_PickingColor;
+out vec4 v_PickingResult;
-uniform vec3 u_PickingColor : [0, 0, 0];
-uniform vec3 u_CurrentSelectedId : [0, 0, 0];
-uniform vec4 u_HighlightColor : [0, 0, 0, 0];
-uniform vec4 u_SelectColor : [0, 0, 0, 0];
-uniform float u_PickingStage : 0.0;
-uniform float u_PickingThreshold : 1.0;
-uniform float u_PickingBuffer: 0.0;
-uniform float u_shaderPick;
-uniform float u_EnableSelect: 0.0;
+
+layout(std140) uniform PickingUniforms {
+ vec4 u_HighlightColor;
+ vec4 u_SelectColor;
+ vec3 u_PickingColor;
+ float u_PickingStage;
+ vec3 u_CurrentSelectedId;
+ float u_PickingThreshold;
+ float u_PickingBuffer;
+ float u_shaderPick;
+ float u_EnableSelect;
+ float u_activeMix;
+};
#define PICKING_NONE 0.0
#define PICKING_ENCODE 1.0
diff --git a/packages/core/src/shaders/projection.glsl b/packages/core/src/shaders/projection.glsl
index 9da3160d26..7ba43d593e 100644
--- a/packages/core/src/shaders/projection.glsl
+++ b/packages/core/src/shaders/projection.glsl
@@ -12,24 +12,26 @@
#define COORDINATE_SYSTEM_P20_2 8.0 // amap2.0
-uniform mat4 u_ViewMatrix;
-uniform mat4 u_ProjectionMatrix;
-uniform mat4 u_ViewProjectionMatrix;
-uniform float u_Zoom : 1;
-uniform float u_ZoomScale : 1;
-
-uniform float u_CoordinateSystem;
-uniform vec2 u_ViewportCenter;
-uniform vec4 u_ViewportCenterProjection;
-uniform vec3 u_PixelsPerDegree;
-uniform vec3 u_PixelsPerDegree2;
-uniform vec3 u_PixelsPerMeter;
-
-uniform vec2 u_ViewportSize;
-uniform float u_DevicePixelRatio;
-uniform float u_FocalDistance;
-uniform vec3 u_CameraPosition;
-uniform mat4 u_Mvp;
+layout(std140) uniform SceneUniforms {
+ mat4 u_ViewMatrix;
+ mat4 u_ProjectionMatrix;
+ mat4 u_ViewProjectionMatrix;
+ mat4 u_ModelMatrix;
+ mat4 u_Mvp;
+ vec4 u_ViewportCenterProjection;
+ vec3 u_PixelsPerDegree;
+ float u_Zoom;
+ vec3 u_PixelsPerDegree2;
+ float u_ZoomScale;
+ vec3 u_PixelsPerMeter;
+ float u_CoordinateSystem;
+ vec3 u_CameraPosition;
+ float u_DevicePixelRatio;
+ vec2 u_ViewportCenter;
+ vec2 u_ViewportSize;
+ vec2 u_sceneCenterMercator;
+ float u_FocalDistance;
+};
// web mercator coords -> world coords
vec2 project_mercator(vec2 lnglat) {
diff --git a/packages/core/src/utils/shader-module.ts b/packages/core/src/utils/shader-module.ts
index 57e96ac9c5..6cd672472d 100644
--- a/packages/core/src/utils/shader-module.ts
+++ b/packages/core/src/utils/shader-module.ts
@@ -27,7 +27,10 @@ export function getUniformLengthByType(type: string): number {
const uniformRegExp =
/uniform\s+(bool|float|int|vec2|vec3|vec4|ivec2|ivec3|ivec4|mat2|mat3|mat4|sampler2D|samplerCube)\s+([\s\S]*?);/g;
-export function extractUniforms(content: string): {
+function fillUniforms(
+ content: string,
+ uniformPrefix = false,
+): {
content: string;
uniforms: {
[key: string]: any;
@@ -79,10 +82,52 @@ export function extractUniforms(content: string): {
// @ts-ignore
uniforms[uniformName] = defaultValue;
- return `uniform ${type} ${uniformName};\n`;
+ return `${uniformPrefix ? 'uniform ' : ''}${type} ${uniformName};\n`;
});
+
return {
content,
uniforms,
};
}
+export function extractUniforms(content: string): {
+ content: string;
+ uniforms: {
+ [key: string]: any;
+ };
+} {
+ // eslint-disable-next-line prefer-const
+ let { content: c, uniforms: u } = fillUniforms(content, true);
+
+ c = c.replace(
+ /(\s*uniform\s*.*\s*){((?:\s*.*\s*)*?)};/g,
+ (substr, header, uniforms) => {
+ uniforms = uniforms.trim().replace(/^.*$/gm, (uniform: string) => {
+ return `uniform ${uniform}`;
+ });
+
+ const { content: cc, uniforms: uu } = fillUniforms(uniforms);
+ Object.assign(u, uu);
+
+ return `${header}{\n${cc}\n};`;
+ },
+ );
+
+ return {
+ content: c,
+ uniforms: u,
+ };
+}
+
+export function removeDuplicateUniforms(content: string) {
+ const uniforms: Record = {};
+ return content.replace(uniformRegExp, (_, type, uniformName) => {
+ const name = uniformName.trim();
+ if (!uniforms[name]) {
+ uniforms[name] = true;
+ return `uniform ${type} ${name};\n`;
+ } else {
+ return '';
+ }
+ });
+}
diff --git a/packages/layers/src/core/BaseLayer.ts b/packages/layers/src/core/BaseLayer.ts
index e8cd2b5dc3..674a737795 100644
--- a/packages/layers/src/core/BaseLayer.ts
+++ b/packages/layers/src/core/BaseLayer.ts
@@ -40,8 +40,8 @@ import {
IPass,
IPickingService,
IPostProcessingPass,
- IRendererService,
IRenderOptions,
+ IRendererService,
IScale,
IScaleOptions,
IShaderModuleService,
@@ -50,12 +50,12 @@ import {
IStyleAttributeUpdateOptions,
ITextureService,
LayerEventType,
- lazyInject,
LegendItems,
StyleAttributeField,
StyleAttributeOption,
- Triangulation,
TYPES,
+ Triangulation,
+ lazyInject,
} from '@antv/l7-core';
import Source from '@antv/l7-source';
import { encodePickingColor, lodashUtil } from '@antv/l7-utils';
@@ -496,7 +496,6 @@ export default class BaseLayer
}
}
-
public setLayerPickService(layerPickService: ILayerPickService): void {
this.layerPickService = layerPickService;
}
@@ -668,17 +667,17 @@ export default class BaseLayer
},
);
}
- // 兼容 borderColor borderWidth
- // @ts-ignore
- if(rest.borderColor) {
- // @ts-ignore
- rest.stroke = rest.borderColor;
- }
- // @ts-ignore
- if(rest.borderWidth) {
- // @ts-ignore
- rest.strokeWidth = rest.borderWidth;
- }
+ // 兼容 borderColor borderWidth
+ // @ts-ignore
+ if (rest.borderColor) {
+ // @ts-ignore
+ rest.stroke = rest.borderColor;
+ }
+ // @ts-ignore
+ if (rest.borderWidth) {
+ // @ts-ignore
+ rest.strokeWidth = rest.borderWidth;
+ }
// 兼容老版本的写法 ['field, 'value']
const newOption: { [key: string]: any } = rest;
@@ -698,8 +697,6 @@ export default class BaseLayer
}
});
-
-
this.encodeStyle(newOption);
this.updateLayerConfig(newOption);
@@ -1055,6 +1052,12 @@ export default class BaseLayer
if (this.isDestroyed) {
return;
}
+
+ // destroy all UBOs
+ this.layerModel.uniformBuffers.forEach((buffer) => {
+ buffer.destroy();
+ });
+
// remove child layer
this.layerChildren.map((child: ILayer) => child.destroy(false));
this.layerChildren = [];
@@ -1270,6 +1273,7 @@ export default class BaseLayer
inject,
});
const { vs, fs, uniforms } = this.shaderModuleService.getModule(moduleName);
+ console.log(vs, fs);
const { createModel } = this.rendererService;
return new Promise((resolve) => {
// console.log(this.encodedData)
@@ -1286,6 +1290,11 @@ export default class BaseLayer
vs,
elements,
blend: BlendTypes[BlendType.normal],
+ uniformBuffers: [
+ ...this.layerModel.uniformBuffers,
+ ...this.rendererService.uniformBuffers,
+ ],
+ textures: this.layerModel.textures,
...rest,
};
if (count) {
@@ -1375,6 +1384,10 @@ export default class BaseLayer
this.models.forEach((model) => {
model.draw(
{
+ uniformBuffers: [
+ ...this.layerModel.uniformBuffers,
+ ...this.rendererService.uniformBuffers,
+ ],
uniforms: this.layerModel.getUninforms(),
blend: this.layerModel.getBlend(),
stencil: this.layerModel.getStencil(options),
diff --git a/packages/layers/src/core/BaseModel.ts b/packages/layers/src/core/BaseModel.ts
index 8061cfc38c..80332f7356 100644
--- a/packages/layers/src/core/BaseModel.ts
+++ b/packages/layers/src/core/BaseModel.ts
@@ -3,6 +3,7 @@ import {
IAnimateOption,
IAttribute,
IBlendOptions,
+ IBuffer,
ICameraService,
IElements,
IFontService,
@@ -33,9 +34,11 @@ import {
import { rgb2arr } from '@antv/l7-utils';
import { BlendTypes } from '../utils/blend';
import { getStencil, getStencilMask } from '../utils/stencil';
-import { getCommonStyleAttributeOptions } from './CommonStyleAttribute';
-import { DefaultUniformStyleType, DefaultUniformStyleValue} from './constant'
-
+import { DefaultUniformStyleType, DefaultUniformStyleValue } from './constant'
+import {
+ getCommonStyleAttributeOptions,
+ ShaderLocation,
+} from './CommonStyleAttribute';
export type styleSingle =
| number
| string
@@ -60,11 +63,21 @@ export interface ICellProperty {
count: number;
}
+const shaderLocationMap: Record = {
+ opacity: ShaderLocation.OPACITY,
+ stroke: ShaderLocation.STROKE,
+ offsets: ShaderLocation.OFFSETS,
+ rotation: ShaderLocation.ROTATION,
+ extrusionBase: ShaderLocation.EXTRUSION_BASE,
+};
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export default class BaseModel
implements ILayerModel
{
public triangulation: Triangulation;
+ public uniformBuffers: IBuffer[] = [];
+ public textures: ITexture2D[] = [];
// style texture data mapping
public createTexture2D: (
@@ -232,33 +245,51 @@ export default class BaseModel
protected getInject(): IInject {
const encodeStyleAttribute = this.layer.encodeStyleAttribute;
let str = '';
+ // a_Position = 0
+ // a_Color = 1
+ // a_PickingColor = 2
+
+ const uniforms: string[] = [];
+ // 支持数据映射的类型
this.layer.enableShaderEncodeStyles.forEach((key: string) => {
- if (encodeStyleAttribute[key]) {
+ if (encodeStyleAttribute[key]) { // 配置了数据映射的类型
str += `#define USE_ATTRIBUTE_${key.toUpperCase()} 0.0; \n\n`;
+ } else {
+ uniforms.push(` ${DefaultUniformStyleType[key]} u_${key};`);
}
str += `
#ifdef USE_ATTRIBUTE_${key.toUpperCase()}
- attribute ${DefaultUniformStyleType[key]} a_${
+ layout(location = ${shaderLocationMap[key]}) in ${
+ DefaultUniformStyleType[key]
+ } a_${key.charAt(0).toUpperCase() + key.slice(1)};
+ #endif\n
+ `;
+ });
+ const attributeUniforms = uniforms.length
+ ? `
+layout(std140) uniform AttributeUniforms {
+${uniforms.join('\n')}
+};
+ `
+ : '';
+ str += attributeUniforms;
+
+ let innerStr = '';
+ this.layer.enableShaderEncodeStyles.forEach((key) => {
+ innerStr += `\n
+ #ifdef USE_ATTRIBUTE_${key.toUpperCase()}
+ ${DefaultUniformStyleType[key]} ${key} = a_${
key.charAt(0).toUpperCase() + key.slice(1)
};
#else
- uniform ${DefaultUniformStyleType[key]} u_${key};
+ ${DefaultUniformStyleType[key]} ${key} = u_${key};
#endif\n
`;
});
- let innerStr = '';
- this.layer.enableShaderEncodeStyles.forEach((key) => {
- innerStr += `\n
-#ifdef USE_ATTRIBUTE_${key.toUpperCase()}
- ${DefaultUniformStyleType[key]} ${key} = a_${key.charAt(0).toUpperCase() + key.slice(1)};
-#else
- ${DefaultUniformStyleType[key]} ${key} = u_${key};
-#endif\n
-`;
- });
return {
'vs:#decl': str,
+ 'fs:#decl': attributeUniforms,
'vs:#main-start': innerStr,
};
}
@@ -289,6 +320,10 @@ export default class BaseModel
const options = getCommonStyleAttributeOptions(key);
if (options) {
this.styleAttributeService.registerStyleAttribute(options);
+
+ if (options.descriptor) {
+ options.descriptor.shaderLocation = shaderLocationMap[key];
+ }
}
});
}
diff --git a/packages/layers/src/core/CommonStyleAttribute.ts b/packages/layers/src/core/CommonStyleAttribute.ts
index 2c6cc9c804..5231da7414 100644
--- a/packages/layers/src/core/CommonStyleAttribute.ts
+++ b/packages/layers/src/core/CommonStyleAttribute.ts
@@ -4,6 +4,23 @@ import {
IEncodeFeature,
IStyleAttribute,
} from '@antv/l7-core';
+
+export enum ShaderLocation {
+ POSITION = 0,
+ COLOR,
+ VERTEX_ID,
+ PICKING_COLOR,
+ STROKE,
+ OPACITY,
+ OFFSETS,
+ ROTATION,
+ EXTRUSION_BASE,
+ SIZE,
+ SHAPE,
+ EXTRUDE,
+ MAX,
+}
+
export function getCommonStyleAttributeOptions(
name: string,
): Partial | undefined {
@@ -15,6 +32,7 @@ export function getCommonStyleAttributeOptions(
type: AttributeType.Attribute,
descriptor: {
name: 'a_Rotation',
+ shaderLocation: ShaderLocation.ROTATION,
buffer: {
usage: gl.DYNAMIC_DRAW,
data: [],
@@ -35,6 +53,7 @@ export function getCommonStyleAttributeOptions(
type: AttributeType.Attribute,
descriptor: {
name: 'a_Stroke',
+ shaderLocation: ShaderLocation.STROKE,
buffer: {
// give the WebGL driver a hint that this buffer may change
usage: gl.DYNAMIC_DRAW,
@@ -54,6 +73,7 @@ export function getCommonStyleAttributeOptions(
type: AttributeType.Attribute,
descriptor: {
name: 'a_Opacity',
+ shaderLocation: ShaderLocation.OPACITY,
buffer: {
// give the WebGL driver a hint that this buffer may change
usage: gl.STATIC_DRAW,
@@ -73,6 +93,7 @@ export function getCommonStyleAttributeOptions(
type: AttributeType.Attribute,
descriptor: {
name: 'a_ExtrusionBase',
+ shaderLocation: ShaderLocation.EXTRUSION_BASE,
buffer: {
// give the WebGL driver a hint that this buffer may change
usage: gl.STATIC_DRAW,
@@ -92,6 +113,7 @@ export function getCommonStyleAttributeOptions(
type: AttributeType.Attribute,
descriptor: {
name: 'a_Offsets',
+ shaderLocation: ShaderLocation.OFFSETS,
buffer: {
// give the WebGL driver a hint that this buffer may change
usage: gl.STATIC_DRAW,
diff --git a/packages/layers/src/plugins/PixelPickingPlugin.ts b/packages/layers/src/plugins/PixelPickingPlugin.ts
index f69346fadc..bdcc35a053 100644
--- a/packages/layers/src/plugins/PixelPickingPlugin.ts
+++ b/packages/layers/src/plugins/PixelPickingPlugin.ts
@@ -14,6 +14,7 @@ import {
} from '@antv/l7-utils';
import { injectable } from 'inversify';
import 'reflect-metadata';
+import { ShaderLocation } from '../core/CommonStyleAttribute';
const PickingStage = {
NONE: 0.0,
@@ -26,12 +27,46 @@ export default class PixelPickingPlugin implements ILayerPlugin {
public apply(
layer: ILayer,
{
+ rendererService,
styleAttributeService,
}: {
rendererService: IRendererService;
styleAttributeService: IStyleAttributeService;
},
) {
+ if (!rendererService.uniformBuffers[1]) {
+ // Create a Uniform Buffer Object(UBO).
+ const uniformBuffer = rendererService.createBuffer({
+ // vec4 u_HighlightColor;
+ // vec4 u_SelectColor;
+ // vec3 u_PickingColor;
+ // float u_PickingStage;
+ // vec3 u_CurrentSelectedId;
+ // float u_PickingThreshold;
+ // float u_PickingBuffer;
+ // float u_shaderPick;
+ // float u_EnableSelect;
+ // float u_activeMix;
+ data: new Float32Array([
+ ...[1, 0, 0, 1],
+ ...[1, 0, 0, 1],
+ ...[0, 0, 0],
+ 0,
+ ...[0, 0, 0],
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ ]),
+ isUBO: true,
+ });
+ rendererService.uniformBuffers[1] = uniformBuffer;
+ }
+ // u_PickingBuffer: layer.getLayerConfig().pickingBuffer || 0,
+ // // Tip: 当前地图是否在拖动
+ // u_shaderPick: Number(layer.getShaderPickStat()),
+
// TODO: 由于 Shader 目前无法根据是否开启拾取进行内容修改,因此即使不开启也需要生成 a_PickingColor
layer.hooks.init.tapPromise('PixelPickingPlugin', () => {
const { enablePicking } = layer.getLayerConfig();
@@ -40,6 +75,7 @@ export default class PixelPickingPlugin implements ILayerPlugin {
type: AttributeType.Attribute,
descriptor: {
name: 'a_PickingColor',
+ shaderLocation: ShaderLocation.PICKING_COLOR,
buffer: {
data: [],
type: gl.FLOAT,
@@ -58,6 +94,12 @@ export default class PixelPickingPlugin implements ILayerPlugin {
layer.hooks.beforePickingEncode.tap('PixelPickingPlugin', () => {
const { enablePicking } = layer.getLayerConfig();
if (enablePicking && layer.isVisible()) {
+ // rendererService.uniformBuffers[1].subData({
+ // offset: 11,
+ // data: new Uint8Array(new Float32Array([
+ // PickingStage.ENCODE
+ // ])),
+ // })
layer.models.forEach((model) =>
model.addUniforms({
u_PickingStage: PickingStage.ENCODE,
@@ -70,6 +112,12 @@ export default class PixelPickingPlugin implements ILayerPlugin {
const { enablePicking } = layer.getLayerConfig();
// 区分选中高亮 和滑过高亮
if (enablePicking && layer.isVisible()) {
+ // rendererService.uniformBuffers[1].subData({
+ // offset: 11,
+ // data: new Uint8Array(new Float32Array([
+ // PickingStage.HIGHLIGHT
+ // ])),
+ // })
layer.models.forEach((model) =>
model.addUniforms({
u_PickingStage: PickingStage.HIGHLIGHT,
@@ -91,6 +139,12 @@ export default class PixelPickingPlugin implements ILayerPlugin {
layer.updateLayerConfig({
pickedFeatureID: decodePickingColor(new Uint8Array(pickedColor)),
});
+ // rendererService.uniformBuffers[1].subData({
+ // offset: 11,
+ // data: new Uint8Array(new Float32Array([
+ // PickingStage.HIGHLIGHT
+ // ])),
+ // })
layer.models.forEach((model) =>
model.addUniforms({
u_PickingStage: PickingStage.HIGHLIGHT,
diff --git a/packages/layers/src/plugins/RegisterStyleAttributePlugin.ts b/packages/layers/src/plugins/RegisterStyleAttributePlugin.ts
index 1240bc9077..35c2417e13 100644
--- a/packages/layers/src/plugins/RegisterStyleAttributePlugin.ts
+++ b/packages/layers/src/plugins/RegisterStyleAttributePlugin.ts
@@ -8,6 +8,7 @@ import {
} from '@antv/l7-core';
import { injectable } from 'inversify';
import 'reflect-metadata';
+import { ShaderLocation } from '../core/CommonStyleAttribute';
import { isTileGroup } from '../tile/utils/utils';
/**
@@ -55,6 +56,7 @@ export default class RegisterStyleAttributePlugin implements ILayerPlugin {
type: AttributeType.Attribute,
descriptor: {
name: 'a_Position',
+ shaderLocation: ShaderLocation.POSITION,
buffer: {
data: [],
type: gl.FLOAT,
@@ -73,29 +75,6 @@ export default class RegisterStyleAttributePlugin implements ILayerPlugin {
});
}
- private registerFilterAttribute(
- styleAttributeService: IStyleAttributeService,
- ) {
- styleAttributeService.registerStyleAttribute({
- name: 'filter',
- type: AttributeType.Attribute,
- descriptor: {
- name: 'filter',
- buffer: {
- // give the WebGL driver a hint that this buffer may change
- usage: gl.DYNAMIC_DRAW,
- data: [],
- type: gl.FLOAT,
- },
- size: 1,
- update: (feature: IEncodeFeature) => {
- const { filter } = feature;
- return filter ? [1] : [0];
- },
- },
- });
- }
-
private registerColorAttribute(
styleAttributeService: IStyleAttributeService,
) {
@@ -104,6 +83,7 @@ export default class RegisterStyleAttributePlugin implements ILayerPlugin {
type: AttributeType.Attribute,
descriptor: {
name: 'a_Color',
+ shaderLocation: ShaderLocation.COLOR,
buffer: {
// give the WebGL driver a hint that this buffer may change
usage: gl.DYNAMIC_DRAW,
@@ -128,6 +108,7 @@ export default class RegisterStyleAttributePlugin implements ILayerPlugin {
type: AttributeType.Attribute,
descriptor: {
name: 'a_vertexId',
+ shaderLocation: ShaderLocation.VERTEX_ID,
buffer: {
// give the WebGL driver a hint that this buffer may change
usage: gl.DYNAMIC_DRAW,
diff --git a/packages/layers/src/plugins/ShaderUniformPlugin.ts b/packages/layers/src/plugins/ShaderUniformPlugin.ts
index 9bae3c95cd..c5d0a88002 100644
--- a/packages/layers/src/plugins/ShaderUniformPlugin.ts
+++ b/packages/layers/src/plugins/ShaderUniformPlugin.ts
@@ -1,10 +1,12 @@
import {
CameraUniform,
CoordinateUniform,
+ IBuffer,
ICameraService,
ICoordinateSystemService,
ILayer,
ILayerPlugin,
+ ILayerService,
IMapService,
IRendererService,
TYPES,
@@ -33,11 +35,25 @@ export default class ShaderUniformPlugin implements ILayerPlugin {
@inject(TYPES.IMapService)
private readonly mapService: IMapService;
+ @inject(TYPES.ILayerService)
+ private readonly layerService: ILayerService;
+
public apply(layer: ILayer) {
const version = this.mapService.version;
let mvp = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; // default matrix (for gaode2.x)
- let sceneCenterMercator = [0, 0];
+ let sceneCenterMercator: [number, number] = [0, 0];
+
+ let uniformBuffer: IBuffer;
+ if (!this.rendererService.uniformBuffers[0]) {
+ // Create a Uniform Buffer Object(UBO).
+ uniformBuffer = this.rendererService.createBuffer({
+ data: new Float32Array(16 * 5 + 4 * 6 + 4),
+ isUBO: true,
+ });
+ this.rendererService.uniformBuffers[0] = uniformBuffer;
+ }
+
layer.hooks.beforeRender.tap('ShaderUniformPlugin', () => {
// @ts-ignore
const offset = layer.getLayerConfig().tileOrigin;
@@ -54,47 +70,31 @@ export default class ShaderUniformPlugin implements ILayerPlugin {
}
const { width, height } = this.rendererService.getViewportSize();
+
+ const { data, uniforms } = this.generateUBO(
+ mvp,
+ sceneCenterMercator,
+ width,
+ height,
+ );
+
+ if (this.layerService.alreadyInRendering && uniformBuffer) {
+ // Update only once since all models can share one UBO.
+ uniformBuffer.subData({
+ offset: 0,
+ data,
+ });
+ }
+ // For WebGL1. regl
layer.models.forEach((model) => {
model.addUniforms({
- // 相机参数,包含 VP 矩阵、缩放等级
- [CameraUniform.ProjectionMatrix]:
- this.cameraService.getProjectionMatrix(),
- [CameraUniform.ViewMatrix]: this.cameraService.getViewMatrix(),
- [CameraUniform.ViewProjectionMatrix]:
- this.cameraService.getViewProjectionMatrix(),
- [CameraUniform.Zoom]: this.cameraService.getZoom(),
- [CameraUniform.ZoomScale]: this.cameraService.getZoomScale(),
- [CameraUniform.FocalDistance]: this.cameraService.getFocalDistance(),
- [CameraUniform.CameraPosition]:
- this.cameraService.getCameraPosition(),
- // 坐标系参数
- [CoordinateUniform.CoordinateSystem]:
- this.coordinateSystemService.getCoordinateSystem(),
- [CoordinateUniform.ViewportCenter]:
- this.coordinateSystemService.getViewportCenter(),
- [CoordinateUniform.ViewportCenterProjection]:
- this.coordinateSystemService.getViewportCenterProjection(),
- [CoordinateUniform.PixelsPerDegree]:
- this.coordinateSystemService.getPixelsPerDegree(),
- [CoordinateUniform.PixelsPerDegree2]:
- this.coordinateSystemService.getPixelsPerDegree2(),
- [CoordinateUniform.PixelsPerMeter]:
- this.coordinateSystemService.getPixelsPerMeter(),
- // 坐标系是高德2.0的时候单独计算
- [CoordinateUniform.Mvp]: mvp,
- u_sceneCenterMercator: sceneCenterMercator,
- // 其他参数,例如视口大小、DPR 等
- u_ViewportSize: [width, height],
- u_ModelMatrix: this.cameraService.getModelMatrix(),
- u_DevicePixelRatio: window.devicePixelRatio,
- // u_ModelMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
+ ...uniforms,
+ // TODO: move these 2 uniforms to PixelPickingPlugin
u_PickingBuffer: layer.getLayerConfig().pickingBuffer || 0,
// Tip: 当前地图是否在拖动
u_shaderPick: Number(layer.getShaderPickStat()),
});
});
-
- // TODO:脏检查,决定是否需要渲染
});
}
@@ -110,4 +110,82 @@ export default class ShaderUniformPlugin implements ILayerPlugin {
this.mapService.setCoordCenter(layer.coordCenter);
}
}
+
+ private generateUBO(
+ u_Mvp: number[],
+ sceneCenterMercator: [number, number],
+ width: number,
+ height: number,
+ ) {
+ const u_ProjectionMatrix = this.cameraService.getProjectionMatrix();
+ const u_ViewMatrix = this.cameraService.getViewMatrix();
+ const u_ViewProjectionMatrix = this.cameraService.getViewProjectionMatrix();
+ const u_ModelMatrix = this.cameraService.getModelMatrix();
+ const u_ViewportCenterProjection =
+ this.coordinateSystemService.getViewportCenterProjection();
+ const u_PixelsPerDegree = this.coordinateSystemService.getPixelsPerDegree();
+ const u_Zoom = this.cameraService.getZoom();
+ const u_PixelsPerDegree2 =
+ this.coordinateSystemService.getPixelsPerDegree2();
+ const u_ZoomScale = this.cameraService.getZoomScale();
+ const u_PixelsPerMeter = this.coordinateSystemService.getPixelsPerMeter();
+ const u_CoordinateSystem =
+ this.coordinateSystemService.getCoordinateSystem();
+ const u_CameraPosition = this.cameraService.getCameraPosition();
+ const u_DevicePixelRatio = window.devicePixelRatio;
+ const u_ViewportCenter = this.coordinateSystemService.getViewportCenter();
+ const u_ViewportSize = [width, height];
+ const u_FocalDistance = this.cameraService.getFocalDistance();
+
+ const data: number[] = [
+ ...u_ViewMatrix, // 16
+ ...u_ProjectionMatrix, // 16
+ ...u_ViewProjectionMatrix, // 16
+ ...u_ModelMatrix, // 16
+ ...u_Mvp, // 16
+ ...u_ViewportCenterProjection, // 4
+ ...u_PixelsPerDegree, // 4
+ u_Zoom,
+ ...u_PixelsPerDegree2, // 4
+ u_ZoomScale,
+ ...u_PixelsPerMeter, // 4
+ u_CoordinateSystem,
+ ...u_CameraPosition, // 4
+ u_DevicePixelRatio,
+ ...u_ViewportCenter, // 4
+ ...u_ViewportSize, // 2
+ ...sceneCenterMercator, // 2
+ u_FocalDistance, // 1
+ 0,
+ ];
+
+ return {
+ data,
+ uniforms: {
+ // 相机参数,包含 VP 矩阵、缩放等级
+ [CameraUniform.ProjectionMatrix]: u_ProjectionMatrix,
+ [CameraUniform.ViewMatrix]: u_ViewMatrix,
+ [CameraUniform.ViewProjectionMatrix]: u_ViewProjectionMatrix,
+ [CameraUniform.Zoom]: u_Zoom,
+ [CameraUniform.ZoomScale]: u_ZoomScale,
+ [CameraUniform.FocalDistance]: u_FocalDistance,
+ [CameraUniform.CameraPosition]: u_CameraPosition,
+ // 坐标系参数
+ [CoordinateUniform.CoordinateSystem]: u_CoordinateSystem,
+ [CoordinateUniform.ViewportCenter]: u_ViewportCenter,
+ [CoordinateUniform.ViewportCenterProjection]:
+ u_ViewportCenterProjection,
+ [CoordinateUniform.PixelsPerDegree]: u_PixelsPerDegree,
+ [CoordinateUniform.PixelsPerDegree2]: u_PixelsPerDegree2,
+ [CoordinateUniform.PixelsPerMeter]: u_PixelsPerMeter,
+ // 坐标系是高德2.0的时候单独计算
+ [CoordinateUniform.Mvp]: u_Mvp,
+ u_sceneCenterMercator: sceneCenterMercator,
+ // 其他参数,例如视口大小、DPR 等
+ u_ViewportSize: u_ViewportSize,
+ u_ModelMatrix,
+ u_DevicePixelRatio: u_DevicePixelRatio,
+ },
+ };
+ }
}
diff --git a/packages/layers/src/point/index.ts b/packages/layers/src/point/index.ts
index c112197336..40f91bb472 100644
--- a/packages/layers/src/point/index.ts
+++ b/packages/layers/src/point/index.ts
@@ -6,9 +6,9 @@ import PointModels, { PointType } from './models/index';
export default class PointLayer extends BaseLayer {
public type: string = 'PointLayer';
public enableShaderEncodeStyles = [
- 'opacity',
- 'offsets',
'stroke',
+ 'offsets',
+ 'opacity',
'rotation',
];
public enableDataEncodeStyles = ['textOffset', 'textAnchor'];
diff --git a/packages/layers/src/point/models/fill.ts b/packages/layers/src/point/models/fill.ts
index 64a7dd597a..7a23635174 100644
--- a/packages/layers/src/point/models/fill.ts
+++ b/packages/layers/src/point/models/fill.ts
@@ -11,6 +11,7 @@ import {
} from '@antv/l7-core';
import { PointFillTriangulation } from '@antv/l7-utils';
import BaseModel from '../../core/BaseModel';
+import { ShaderLocation } from '../../core/CommonStyleAttribute';
import { IPointLayerStyleOptions, SizeUnitType } from '../../core/interface';
// animate pointLayer shader - support animate
import waveFillFrag from '../shaders/animate/wave_frag.glsl';
@@ -29,7 +30,8 @@ export default class FillModel extends BaseModel {
heightfixed = false,
unit = 'pixel',
} = this.layer.getLayerConfig() as IPointLayerStyleOptions;
- return {
+
+ const commonIniform = {
u_blur_height_fixed: [blur, Number(raisingHeight), Number(heightfixed)],
u_additive: blend === 'additive' ? 1.0 : 0.0,
u_stroke_opacity: strokeOpacity,
@@ -37,6 +39,33 @@ export default class FillModel extends BaseModel {
u_size_unit: SizeUnitType[unit] as SizeUnitType,
...this.getStyleAttribute(),
};
+
+ const attributes = this.getStyleAttribute();
+ this.uniformBuffers[0].subData({
+ offset: 0,
+ data: new Uint8Array(
+ new Float32Array([
+ ...attributes.u_stroke,
+ ...attributes.u_offsets,
+ attributes.u_opacity,
+ attributes.u_rotation,
+ ]).buffer,
+ ),
+ });
+
+ this.uniformBuffers[1].subData({
+ offset: 0,
+ data: new Uint8Array(
+ new Float32Array([
+ ...commonIniform.u_blur_height_fixed,
+ commonIniform.u_stroke_width,
+ commonIniform.u_stroke_opacity,
+ commonIniform.u_additive,
+ commonIniform.u_size_unit,
+ ]).buffer,
+ ),
+ });
+ return commonIniform;
}
public getAnimateUniforms(): IModelUniform {
const { animateOption = { enable: false } } =
@@ -70,6 +99,17 @@ export default class FillModel extends BaseModel {
>;
const { frag, vert, type } = this.getShaders(animateOption);
this.layer.triangulation = PointFillTriangulation;
+ const attributeUniformBuffer = this.rendererService.createBuffer({
+ data: new Float32Array(4 + 2 + 1 + 1),
+ isUBO: true,
+ });
+
+ const commonUniforms = this.rendererService.createBuffer({
+ data: new Float32Array(8),
+ isUBO: true,
+ });
+
+ this.uniformBuffers.push(attributeUniformBuffer, commonUniforms);
const model = await this.layer.buildLayerModel({
moduleName: type,
vertexShader: vert,
@@ -127,6 +167,7 @@ export default class FillModel extends BaseModel {
type: AttributeType.Attribute,
descriptor: {
name: 'a_Extrude',
+ shaderLocation: ShaderLocation.EXTRUDE,
buffer: {
// give the WebGL driver a hint that this buffer may change
usage: gl.DYNAMIC_DRAW,
@@ -157,6 +198,7 @@ export default class FillModel extends BaseModel {
type: AttributeType.Attribute,
descriptor: {
name: 'a_Size',
+ shaderLocation: ShaderLocation.SIZE,
buffer: {
// give the WebGL driver a hint that this buffer may change
usage: gl.DYNAMIC_DRAW,
@@ -177,6 +219,7 @@ export default class FillModel extends BaseModel {
type: AttributeType.Attribute,
descriptor: {
name: 'a_Shape',
+ shaderLocation: ShaderLocation.SHAPE,
buffer: {
// give the WebGL driver a hint that this buffer may change
usage: gl.DYNAMIC_DRAW,
diff --git a/packages/layers/src/point/models/normal.ts b/packages/layers/src/point/models/normal.ts
index 4b9e424c1c..aae3839463 100644
--- a/packages/layers/src/point/models/normal.ts
+++ b/packages/layers/src/point/models/normal.ts
@@ -5,12 +5,11 @@ import {
IModel,
IModelUniform,
} from '@antv/l7-core';
-import { lodashUtil } from '@antv/l7-utils';
import BaseModel from '../../core/BaseModel';
+import { ShaderLocation } from '../../core/CommonStyleAttribute';
import { IPointLayerStyleOptions } from '../../core/interface';
import normalFrag from '../shaders/normal_frag.glsl';
import normalVert from '../shaders/normal_vert.glsl';
-const { isNumber } = lodashUtil;
export function PointTriangulation(feature: IEncodeFeature) {
const coordinates = feature.coordinates as number[];
@@ -28,11 +27,32 @@ export default class NormalModel extends BaseModel {
};
}
public getUninforms(): IModelUniform {
- const { opacity = 1 } =
- this.layer.getLayerConfig() as IPointLayerStyleOptions;
+ const attributes = this.getStyleAttribute();
+ // FIXME: No need to update each frame
+ this.uniformBuffers[0].subData({
+ offset: 0,
+ data: new Uint8Array(
+ new Float32Array([
+ ...attributes.u_stroke,
+ ...attributes.u_offsets,
+ attributes.u_opacity,
+ attributes.u_rotation,
+ ]).buffer,
+ ),
+ });
+
+ this.uniformBuffers[1].subData({
+ offset: 0,
+ data: new Uint8Array(
+ new Float32Array([0.5
+ ]).buffer,
+ )
+ });
+
return {
- ...this.getStyleAttribute(),
+ u_size_scale: 0.5,
+ ...attributes,
};
}
@@ -43,6 +63,17 @@ export default class NormalModel extends BaseModel {
public async buildModels(): Promise {
this.layer.triangulation = PointTriangulation;
+ const uniformBuffer = this.rendererService.createBuffer({
+ data: new Float32Array(4 + 2 + 1 + 1),
+ isUBO: true,
+ });
+
+ const commonBuffer = this.rendererService.createBuffer({
+ data: new Float32Array(4),
+ isUBO: true,
+ });
+
+ this.uniformBuffers.push(uniformBuffer,commonBuffer);
const model = await this.layer.buildLayerModel({
moduleName: 'pointNormal',
vertexShader: normalVert,
@@ -67,6 +98,7 @@ export default class NormalModel extends BaseModel {
type: AttributeType.Attribute,
descriptor: {
name: 'a_Size',
+ shaderLocation: ShaderLocation.SIZE,
buffer: {
usage: gl.DYNAMIC_DRAW,
data: [],
diff --git a/packages/layers/src/point/shaders/fill_frag.glsl b/packages/layers/src/point/shaders/fill_frag.glsl
index 8a5e0ec075..33cca4b076 100644
--- a/packages/layers/src/point/shaders/fill_frag.glsl
+++ b/packages/layers/src/point/shaders/fill_frag.glsl
@@ -1,16 +1,22 @@
-uniform float u_additive;
-uniform float u_stroke_opacity : 1;
-uniform float u_stroke_width : 2;
-varying vec4 v_data;
-varying vec4 v_color;
-varying float v_radius;
-varying vec4 v_stroke;
+layout(std140) uniform commonUniforms {
+ vec3 u_blur_height_fixed;
+ float u_stroke_width;
+ float u_additive;
+ float u_stroke_opacity;
+ float u_size_unit;
+};
+in vec4 v_color;
+in vec4 v_stroke;
+in vec4 v_data;
+in float v_radius;
+#pragma include "projection"
#pragma include "sdf_2d"
#pragma include "picking"
+out vec4 outputColor;
void main() {
int shape = int(floor(v_data.w + 0.5));
@@ -58,20 +64,20 @@ void main() {
);
if(u_stroke_width < 0.01) {
- gl_FragColor = v_color;
+ outputColor = v_color;
} else {
- gl_FragColor = mix(v_color, v_stroke * u_stroke_opacity, color_t);
+ outputColor = mix(v_color, v_stroke * u_stroke_opacity, color_t);
}
if(u_additive > 0.0) {
- gl_FragColor *= opacity_t;
- gl_FragColor = filterColorAlpha(gl_FragColor, gl_FragColor.a);
+ outputColor *= opacity_t;
+ outputColor = filterColorAlpha(outputColor, outputColor.a);
} else {
- gl_FragColor.a *= opacity_t;
- gl_FragColor = filterColor(gl_FragColor);
+ outputColor.a *= opacity_t;
+ outputColor = filterColor(outputColor);
}
// 作为 mask 模板时需要丢弃透明的像素
- if(gl_FragColor.a < 0.01) {
+ if(outputColor.a < 0.01) {
discard;
}
}
diff --git a/packages/layers/src/point/shaders/fill_vert.glsl b/packages/layers/src/point/shaders/fill_vert.glsl
index 74dd1ff08c..5cff862dd6 100644
--- a/packages/layers/src/point/shaders/fill_vert.glsl
+++ b/packages/layers/src/point/shaders/fill_vert.glsl
@@ -1,25 +1,26 @@
-attribute vec4 a_Color;
-attribute vec3 a_Position;
-attribute vec3 a_Extrude;
-attribute float a_Size;
-attribute float a_Shape;
-uniform mat4 u_ModelMatrix;
-
-uniform int u_size_unit;
-
-varying vec4 v_data;
-varying vec4 v_color;
-varying float v_radius;
-varying vec4 v_stroke;
-uniform float u_stroke_width: 2;
-uniform vec3 u_blur_height_fixed: [0, 0, 0];
-
+layout(location = 0) in vec3 a_Position;
+layout(location = 1) in vec4 a_Color;
+layout(location = 9) in float a_Size;
+layout(location = 11) in vec3 a_Extrude;
+layout(location = 10) in float a_Shape;
+
+layout(std140) uniform commonUniforms {
+ vec3 u_blur_height_fixed;
+ float u_stroke_width;
+ float u_additive;
+ float u_stroke_opacity;
+ float u_size_unit;
+};
+
+out vec4 v_color;
+out vec4 v_stroke;
+out vec4 v_data;
+out float v_radius;
#pragma include "projection"
#pragma include "picking"
#pragma include "rotation_2d"
-
void main() {
// 透明度计算
v_stroke = stroke;
@@ -37,7 +38,7 @@ void main() {
// unpack color(vec2)
v_color = vec4(a_Color.xyz, a_Color.w * opacity);
- if(u_size_unit == 1) {
+ if(u_size_unit == 1.0) {
newSize = newSize * u_PixelsPerMeter.z;
}
diff --git a/packages/layers/src/point/shaders/normal_frag.glsl b/packages/layers/src/point/shaders/normal_frag.glsl
index 0bb78f16c0..b465f61595 100644
--- a/packages/layers/src/point/shaders/normal_frag.glsl
+++ b/packages/layers/src/point/shaders/normal_frag.glsl
@@ -1,4 +1,5 @@
-varying vec4 v_color;
+in vec4 v_color;
+out vec4 outputColor;
void main() {
- gl_FragColor = v_color;
-}
+ outputColor = v_color;
+}
\ No newline at end of file
diff --git a/packages/layers/src/point/shaders/normal_vert.glsl b/packages/layers/src/point/shaders/normal_vert.glsl
index 0a133c04bf..e7a784ed18 100644
--- a/packages/layers/src/point/shaders/normal_vert.glsl
+++ b/packages/layers/src/point/shaders/normal_vert.glsl
@@ -1,11 +1,12 @@
+layout(location = 0) in vec3 a_Position;
+layout(location = 1) in vec4 a_Color;
+layout(location = 9) in float a_Size;
-attribute vec3 a_Position;
-uniform mat4 u_ModelMatrix;
+layout(std140) uniform u_Common {
+ float u_size_scale;
+};
-attribute float a_Size;
-attribute vec4 a_Color;
-
-varying vec4 v_color;
+out vec4 v_color;
#pragma include "projection"
#pragma include "project"
@@ -13,12 +14,12 @@ varying vec4 v_color;
void main() {
v_color = vec4(a_Color.xyz, a_Color.w * opacity);
- if(u_CoordinateSystem == COORDINATE_SYSTEM_P20_2) { // gaode2.x
- gl_Position = u_Mvp * vec4(a_Position.xy, a_Position.z, 1.0);
+ if (u_CoordinateSystem == COORDINATE_SYSTEM_P20_2) { // gaode2.x
+ gl_Position = u_Mvp * vec4(a_Position, 1.0);
} else {
- vec4 project_pos = project_position(vec4(a_Position, 1.0)) + vec4(a_Size / 2.,-a_Size /2.,0.,0.);
- gl_Position = project_common_position_to_clipspace(vec4(vec2(project_pos.xy),project_pos.z,project_pos.w));
+ vec4 project_pos = project_position(vec4(a_Position, 1.0)) + vec4(a_Size / 2., -a_Size /2., 0., 0.);
+ gl_Position = project_common_position_to_clipspace(project_pos);
}
- gl_PointSize = a_Size * 2.0 * u_DevicePixelRatio;
+ gl_PointSize = a_Size * u_size_scale * 2.0 * u_DevicePixelRatio;
}
diff --git a/packages/renderer/package.json b/packages/renderer/package.json
index c7db692cad..728ebf6db8 100644
--- a/packages/renderer/package.json
+++ b/packages/renderer/package.json
@@ -2,10 +2,12 @@
"name": "@antv/l7-renderer",
"version": "2.19.10",
"description": "",
+ "license": "ISC",
+ "author": "xiaoiver",
+ "sideEffects": false,
"main": "lib/index.js",
"module": "es/index.js",
"types": "es/index.d.ts",
- "sideEffects": false,
"files": [
"lib",
"es",
@@ -13,31 +15,29 @@
],
"scripts": {
"tsc": "tsc --project tsconfig.build.json",
- "clean": "rimraf dist; rimraf es; rimraf lib;",
"build": "father build",
"build:cjs": "BABEL_ENV=cjs babel src --root-mode upward --out-dir lib --source-maps --extensions .ts,.tsx --delete-dir-on-start --no-comments",
"build:esm": "BABEL_ENV=esm babel src --root-mode upward --out-dir es --source-maps --extensions .ts,.tsx --delete-dir-on-start --no-comments",
- "watch": "BABEL_ENV=cjs babel src --watch --root-mode upward --out-dir lib --source-maps --extensions .ts,.tsx --delete-dir-on-start --no-comments",
+ "clean": "rimraf dist; rimraf es; rimraf lib;",
"sync": "tnpm sync",
"test": "umi-test --passWithNoTests",
"test-live": "umi-test --watch"
},
- "author": "xiaoiver",
- "license": "ISC",
- "devDependencies": {
- "@antv/l7-test-utils": "2.19.10",
- "gl": "^5.0.3"
- },
"dependencies": {
"@antv/l7-core": "2.19.10",
"@antv/l7-utils": "2.19.10",
"@babel/runtime": "^7.7.7",
"inversify": "^5.0.1",
"reflect-metadata": "^0.1.13",
+ "@antv/g-device-api":"^1.3.6",
"regl": "1.7.0"
},
- "gitHead": "684ba4eb806a798713496d3fc0b4d1e17517dc31",
+ "devDependencies": {
+ "@antv/l7-test-utils": "2.19.10",
+ "gl": "^6.0.2"
+ },
"publishConfig": {
"access": "public"
- }
+ },
+ "gitHead": "684ba4eb806a798713496d3fc0b4d1e17517dc31"
}
diff --git a/packages/renderer/src/device/DeviceAttribute.ts b/packages/renderer/src/device/DeviceAttribute.ts
new file mode 100644
index 0000000000..4a0009dc88
--- /dev/null
+++ b/packages/renderer/src/device/DeviceAttribute.ts
@@ -0,0 +1,72 @@
+import { Buffer, Device } from '@antv/g-device-api';
+import {
+ IAttribute,
+ IAttributeInitializationOptions,
+ IBuffer,
+} from '@antv/l7-core';
+import DeviceBuffer from './DeviceBuffer';
+
+interface AttributeConfig {
+ shaderLocation?: number;
+ /** A REGLBuffer wrapping the buffer object. (Default: null) */
+ buffer?: Buffer;
+ /** The offset of the vertexAttribPointer in bytes. (Default: 0) */
+ offset?: number | undefined;
+ /** The stride of the vertexAttribPointer in bytes. (Default: 0) */
+ stride?: number | undefined;
+ /** Whether the pointer is normalized. (Default: false) */
+ normalized?: boolean;
+ /** The size of the vertex attribute. (Default: Inferred from shader) */
+ size?: number | undefined;
+ /** Sets gl.vertexAttribDivisorANGLE. Only supported if the ANGLE_instanced_arrays extension is available. (Default: 0) */
+ divisor?: number | undefined;
+ /** Data type for attribute */
+ type?: 'uint8' | 'uint16' | 'uint32' | 'float' | 'int8' | 'int16' | 'int32';
+}
+
+export default class DeviceAttribute implements IAttribute {
+ private attribute: AttributeConfig;
+ private buffer: IBuffer;
+
+ constructor(device: Device, options: IAttributeInitializationOptions) {
+ const {
+ buffer,
+ offset,
+ stride,
+ normalized,
+ size,
+ divisor,
+ shaderLocation,
+ } = options;
+ this.buffer = buffer;
+ this.attribute = {
+ shaderLocation,
+ buffer: (buffer as DeviceBuffer).get(),
+ offset: offset || 0,
+ stride: stride || 0,
+ normalized: normalized || false,
+ divisor: divisor || 0,
+ };
+
+ if (size) {
+ this.attribute.size = size;
+ }
+ }
+
+ get() {
+ return this.buffer;
+ }
+
+ updateBuffer(options: {
+ // 用于替换的数据
+ data: number[] | number[][] | Uint8Array | Uint16Array | Uint32Array;
+ // 原 Buffer 替换位置,单位为 byte
+ offset: number;
+ }) {
+ this.buffer.subData(options);
+ }
+
+ destroy() {
+ this.buffer.destroy();
+ }
+}
diff --git a/packages/renderer/src/device/DeviceBuffer.ts b/packages/renderer/src/device/DeviceBuffer.ts
new file mode 100644
index 0000000000..31cb056587
--- /dev/null
+++ b/packages/renderer/src/device/DeviceBuffer.ts
@@ -0,0 +1,63 @@
+import { Buffer, BufferUsage, Device } from '@antv/g-device-api';
+import { IBuffer, IBufferInitializationOptions, gl } from '@antv/l7-core';
+import { hintMap, typedArrayCtorMap } from './constants';
+import { TypedArray, isTypedArray } from './utils/typedarray';
+
+/**
+ * Use Buffer from @antv/g-device-api
+ */
+export default class DeviceBuffer implements IBuffer {
+ private buffer: Buffer;
+
+ private isDestroyed: boolean = false;
+ private type;
+ private size: number;
+
+ constructor(device: Device, options: IBufferInitializationOptions) {
+ const { data, usage, type, isUBO } = options;
+
+ let typed: TypedArray;
+ if (isTypedArray(data)) {
+ typed = data;
+ } else {
+ typed = new typedArrayCtorMap[this.type || gl.FLOAT](data as number[]);
+ }
+
+ this.type = type;
+ this.size = typed.byteLength;
+
+ // @see https://www.npmjs.com/package/@antv/g-device-api#createBuffer
+ this.buffer = device.createBuffer({
+ viewOrSize: typed,
+ usage: isUBO ? BufferUsage.UNIFORM : BufferUsage.VERTEX,
+ hint: hintMap[usage || gl.STATIC_DRAW],
+ });
+ }
+
+ get() {
+ return this.buffer;
+ }
+
+ destroy() {
+ if (!this.isDestroyed) {
+ this.buffer.destroy();
+ }
+ this.isDestroyed = true;
+ }
+
+ subData({
+ data,
+ offset,
+ }: {
+ data: number[] | number[][] | Uint8Array | Uint16Array | Uint32Array;
+ offset: number;
+ }) {
+ let typed: TypedArray;
+ if (isTypedArray(data)) {
+ typed = data;
+ } else {
+ typed = new typedArrayCtorMap[this.type || gl.FLOAT](data as number[]);
+ }
+ this.buffer.setSubData(offset, new Uint8Array(typed.buffer));
+ }
+}
diff --git a/packages/renderer/src/device/DeviceElements.ts b/packages/renderer/src/device/DeviceElements.ts
new file mode 100644
index 0000000000..8d1d5a0a1a
--- /dev/null
+++ b/packages/renderer/src/device/DeviceElements.ts
@@ -0,0 +1,55 @@
+import { Buffer, BufferUsage, Device } from '@antv/g-device-api';
+import { IElements, IElementsInitializationOptions, gl } from '@antv/l7-core';
+import { typedArrayCtorMap } from './constants';
+import { TypedArray, isTypedArray } from './utils/typedarray';
+
+export default class DeviceElements implements IElements {
+ private indexBuffer: Buffer;
+ private type;
+ private count: number;
+
+ constructor(device: Device, options: IElementsInitializationOptions) {
+ const { data, type, count = 0 } = options;
+
+ let typed: TypedArray;
+ if (isTypedArray(data)) {
+ typed = data;
+ } else {
+ typed = new typedArrayCtorMap[this.type || gl.UNSIGNED_INT](
+ data as number[],
+ );
+ }
+
+ this.type = type;
+ this.count = count;
+
+ this.indexBuffer = device.createBuffer({
+ viewOrSize: typed,
+ usage: BufferUsage.INDEX,
+ });
+ }
+
+ public get() {
+ return this.indexBuffer;
+ }
+
+ public subData({
+ data,
+ }: {
+ data: number[] | number[][] | Uint8Array | Uint16Array | Uint32Array;
+ }) {
+ let typed: TypedArray;
+ if (isTypedArray(data)) {
+ typed = data;
+ } else {
+ typed = new typedArrayCtorMap[this.type || gl.UNSIGNED_INT](
+ data as number[],
+ );
+ }
+ this.indexBuffer.setSubData(0, new Uint8Array(typed.buffer));
+ }
+
+ public destroy() {
+ this.indexBuffer.destroy();
+ }
+}
diff --git a/packages/renderer/src/device/DeviceFramebuffer.ts b/packages/renderer/src/device/DeviceFramebuffer.ts
new file mode 100644
index 0000000000..653d8a79bb
--- /dev/null
+++ b/packages/renderer/src/device/DeviceFramebuffer.ts
@@ -0,0 +1,56 @@
+import { Device, Format, RenderTarget, Texture } from '@antv/g-device-api';
+import { IFramebuffer, IFramebufferInitializationOptions } from '@antv/l7-core';
+import DeviceTexture2D, { isTexture2D } from './DeviceTexture2D';
+
+export default class DeviceFramebuffer implements IFramebuffer {
+ private renderTarget: RenderTarget;
+ private width: number;
+ private height: number;
+
+ constructor(
+ private device: Device,
+ options: IFramebufferInitializationOptions,
+ ) {
+ // TODO: depth
+ const { width, height, color } = options;
+
+ if (isTexture2D(color)) {
+ this.renderTarget = device.createRenderTargetFromTexture(
+ color.get() as Texture,
+ );
+ this.width = (color as DeviceTexture2D)['width'];
+ this.height = (color as DeviceTexture2D)['height'];
+ } else if (width && height) {
+ this.renderTarget = device.createRenderTarget({
+ format: Format.U8_RGBA_RT,
+ width,
+ height,
+ });
+ this.width = width;
+ this.height = height;
+ }
+ }
+
+ public get() {
+ return this.renderTarget;
+ }
+
+ public destroy() {
+ this.renderTarget.destroy();
+ }
+
+ public resize({ width, height }: { width: number; height: number }) {
+ if (this.width !== width || this.height !== height) {
+ if (this.renderTarget) {
+ this.renderTarget.destroy();
+ }
+ this.renderTarget = this.device.createRenderTarget({
+ format: Format.U8_RGBA_RT,
+ width,
+ height,
+ });
+ this.width = width;
+ this.height = height;
+ }
+ }
+}
diff --git a/packages/renderer/src/device/DeviceModel.ts b/packages/renderer/src/device/DeviceModel.ts
new file mode 100644
index 0000000000..0e970cd6be
--- /dev/null
+++ b/packages/renderer/src/device/DeviceModel.ts
@@ -0,0 +1,474 @@
+import {
+ Bindings,
+ BlendFactor,
+ BlendMode,
+ Buffer,
+ ChannelWriteMask,
+ CompareFunction,
+ CullMode,
+ Device,
+ Format,
+ InputLayout,
+ InputLayoutBufferDescriptor,
+ Program,
+ RenderPipeline,
+ TransparentBlack,
+ VertexStepMode,
+} from '@antv/g-device-api';
+import {
+ IModel,
+ IModelDrawOptions,
+ IModelInitializationOptions,
+ IUniform,
+ gl,
+} from '@antv/l7-core';
+import { lodashUtil } from '@antv/l7-utils';
+import DeviceAttribute from './DeviceAttribute';
+import DeviceBuffer from './DeviceBuffer';
+import DeviceElements from './DeviceElements';
+import {
+ blendEquationMap,
+ blendFuncMap,
+ cullFaceMap,
+ depthFuncMap,
+ primitiveMap,
+ sizeFormatMap,
+} from './constants';
+const { isPlainObject, isTypedArray } = lodashUtil;
+
+export default class DeviceModel implements IModel {
+ private destroyed: boolean = false;
+ private uniforms: {
+ [key: string]: IUniform;
+ } = {};
+
+ private program: Program;
+ private inputLayout: InputLayout;
+ private pipeline: RenderPipeline;
+ private indexBuffer: Buffer;
+ private vertexBuffers: Buffer[] = [];
+ private bindings: Bindings;
+
+ constructor(
+ private device: Device,
+ private options: IModelInitializationOptions,
+ ) {
+ const { vs, fs, attributes, uniforms, count, elements } = options;
+ this.options = options;
+
+ const program = device.createProgram({
+ vertex: {
+ glsl: vs,
+ },
+ fragment: {
+ glsl: fs,
+ },
+ });
+ this.program = program;
+
+ // console.log(vs, fs);
+
+ if (uniforms) {
+ this.uniforms = this.extractUniforms(uniforms);
+ }
+
+ const vertexBufferDescriptors: InputLayoutBufferDescriptor[] = [];
+
+ // Infer count from data if not provided.
+ let inferredCount = 0;
+ Object.keys(attributes).forEach((name: string) => {
+ const attribute = attributes[name] as DeviceAttribute;
+
+ const buffer = attribute.get() as DeviceBuffer;
+ // Bind at each frame.
+ this.vertexBuffers.push(buffer.get());
+
+ const {
+ offset = 0,
+ stride = 0,
+ // TODO: normalized
+ size = 1,
+ divisor = 0,
+ shaderLocation = 0,
+ } = attribute['attribute'];
+
+ vertexBufferDescriptors.push({
+ arrayStride: stride || size * 4,
+ // TODO: L7 hasn't use instanced array for now.
+ stepMode: VertexStepMode.VERTEX,
+ attributes: [
+ {
+ format: sizeFormatMap[size],
+ shaderLocation,
+ offset,
+ divisor,
+ },
+ ],
+ });
+
+ inferredCount = buffer['size'] / size;
+ });
+
+ if (!count) {
+ this.options.count = inferredCount;
+ }
+
+ if (elements) {
+ this.indexBuffer = (elements as DeviceElements).get();
+ }
+
+ const inputLayout = device.createInputLayout({
+ vertexBufferDescriptors,
+ indexBufferFormat: elements ? Format.U32_R : null,
+ program,
+ });
+ this.inputLayout = inputLayout;
+
+ this.pipeline = this.createPipeline(options);
+ }
+
+ private createPipeline(options: IModelInitializationOptions) {
+ const { primitive = gl.TRIANGLES, depth, cull, blend } = options;
+
+ const depthParams = this.initDepthDrawParams({ depth });
+ const depthEnabled = !!(depthParams && depthParams.enable);
+ const cullParams = this.initCullDrawParams({ cull });
+ const cullEnabled = !!(cullParams && cullParams.enable);
+ const blendParams = this.getBlendDrawParams({ blend });
+ const blendEnabled = !!(blendParams && blendParams.enable);
+
+ return this.device.createRenderPipeline({
+ inputLayout: this.inputLayout,
+ program: this.program,
+ topology: primitiveMap[primitive],
+ colorAttachmentFormats: [Format.U8_RGBA_RT],
+ depthStencilAttachmentFormat: Format.D24_S8,
+ megaStateDescriptor: {
+ attachmentsState: [
+ {
+ channelWriteMask: ChannelWriteMask.ALL,
+ rgbBlendState: {
+ blendMode:
+ (blendEnabled && blendParams.equation.rgb) || BlendMode.ADD,
+ blendSrcFactor:
+ (blendEnabled && blendParams.func.srcRGB) ||
+ BlendFactor.SRC_ALPHA,
+ blendDstFactor:
+ (blendEnabled && blendParams.func.dstRGB) ||
+ BlendFactor.ONE_MINUS_SRC_ALPHA,
+ },
+ alphaBlendState: {
+ blendMode:
+ (blendEnabled && blendParams.equation.alpha) || BlendMode.ADD,
+ blendSrcFactor:
+ (blendEnabled && blendParams.func.srcAlpha) || BlendFactor.ONE,
+ blendDstFactor:
+ (blendEnabled && blendParams.func.dstAlpha) ||
+ BlendFactor.ONE_MINUS_SRC_ALPHA,
+ },
+ },
+ ],
+ blendConstant: TransparentBlack,
+ depthWrite: depthEnabled,
+ depthCompare:
+ (depthEnabled && depthParams.func) || CompareFunction.LESS,
+ cullMode: (cullEnabled && cullParams.face) || CullMode.NONE,
+ stencilWrite: false,
+ },
+ });
+ }
+
+ updateAttributesAndElements() // elements: IElements, // attributes: { [key: string]: IAttribute },
+ {
+ // TODO: implement
+ }
+
+ updateAttributes() { // attributes: { [key: string]: IAttribute }
+ // TODO: implement
+ // Object.keys(attributes).forEach((name: string) => {
+ // const attribute = attributes[name] as DeviceAttribute;
+ // attribute.updateBuffer();
+ // });
+ }
+
+ addUniforms(uniforms: { [key: string]: IUniform }) {
+ this.uniforms = {
+ ...this.uniforms,
+ ...this.extractUniforms(uniforms),
+ };
+ }
+
+ draw(
+ options: IModelDrawOptions,
+ // pick?: boolean
+ ) {
+ const mergedOptions = {
+ ...this.options,
+ ...options,
+ };
+ const {
+ count = 0,
+ instances,
+ elements,
+ uniforms = {},
+ uniformBuffers,
+ textures,
+ } = mergedOptions;
+
+ this.uniforms = {
+ ...this.uniforms,
+ ...this.extractUniforms(uniforms),
+ };
+
+ // @ts-ignore
+ const { width, height } = this.device;
+
+ // @ts-ignore
+ // const renderTarget = this.device.currentFramebuffer;
+ // const { onscreen } = renderTarget
+
+ // @ts-ignore
+ const renderPass = this.device.renderPass;
+ // TODO: Recreate pipeline only when blend / cull changed.
+ this.pipeline = this.createPipeline(mergedOptions);
+ renderPass.setPipeline(this.pipeline);
+ renderPass.setVertexInput(
+ this.inputLayout,
+ this.vertexBuffers.map((buffer) => ({
+ buffer,
+ })),
+ elements
+ ? {
+ buffer: this.indexBuffer,
+ offset: 0, // TODO: use defaule value
+ }
+ : null,
+ );
+ renderPass.setViewport(0, 0, width, height);
+
+ if (uniformBuffers) {
+ console.log('uniformBuffers', uniformBuffers);
+ this.bindings = this.device.createBindings({
+ pipeline: this.pipeline,
+ uniformBufferBindings: uniformBuffers.map((uniformBuffer, i) => {
+ const buffer = uniformBuffer as DeviceBuffer;
+ return {
+ binding: i,
+ buffer: buffer.get(),
+ size: buffer['size'],
+ };
+ }),
+ samplerBindings: textures?.map((t: any) => ({
+ texture: t['texture'],
+ sampler: t['sampler'],
+ })),
+ });
+ }
+
+ if (this.bindings) {
+ renderPass.setBindings(this.bindings);
+ // Compatible to WebGL1.
+ this.program.setUniformsLegacy(this.uniforms);
+ }
+
+ if (elements) {
+ const indexCount = (elements as DeviceElements)['count'];
+ if (indexCount === 0) {
+ renderPass.draw(count, instances);
+ } else {
+ renderPass.drawIndexed(indexCount, instances);
+ }
+ } else {
+ renderPass.draw(count, instances);
+ }
+ }
+
+ destroy() {
+ this.program.destroy();
+ this.vertexBuffers?.forEach((buffer) => buffer.destroy());
+ this.indexBuffer?.destroy();
+ this.bindings?.destroy();
+ this.inputLayout.destroy();
+ this.pipeline.destroy();
+ this.destroyed = true;
+ }
+
+ private initDepthDrawParams({
+ depth,
+ }: Pick) {
+ if (depth) {
+ return {
+ enable: depth.enable === undefined ? true : !!depth.enable,
+ mask: depth.mask === undefined ? true : !!depth.mask,
+ func: depthFuncMap[depth.func || gl.LESS],
+ range: depth.range || [0, 1],
+ };
+ }
+ }
+
+ private getBlendDrawParams({
+ blend,
+ }: Pick) {
+ const { enable, func, equation, color = [0, 0, 0, 0] } = blend || {};
+ return {
+ enable: !!enable,
+ func: {
+ srcRGB: blendFuncMap[(func && func.srcRGB) || gl.SRC_ALPHA],
+ srcAlpha: blendFuncMap[(func && func.srcAlpha) || gl.SRC_ALPHA],
+ dstRGB: blendFuncMap[(func && func.dstRGB) || gl.ONE_MINUS_SRC_ALPHA],
+ dstAlpha:
+ blendFuncMap[(func && func.dstAlpha) || gl.ONE_MINUS_SRC_ALPHA],
+ },
+ equation: {
+ rgb: blendEquationMap[(equation && equation.rgb) || gl.FUNC_ADD],
+ alpha: blendEquationMap[(equation && equation.alpha) || gl.FUNC_ADD],
+ },
+ color,
+ };
+ }
+
+ // /**
+ // * @see https://github.com/regl-project/regl/blob/gh-pages/API.md#stencil
+ // */
+ // private getStencilDrawParams({
+ // stencil,
+ // }: Pick) {
+ // const {
+ // enable,
+ // mask = -1,
+ // func = {
+ // cmp: gl.ALWAYS,
+ // ref: 0,
+ // mask: -1,
+ // },
+ // opFront = {
+ // fail: gl.KEEP,
+ // zfail: gl.KEEP,
+ // zpass: gl.KEEP,
+ // },
+ // opBack = {
+ // fail: gl.KEEP,
+ // zfail: gl.KEEP,
+ // zpass: gl.KEEP,
+ // },
+ // } = stencil || {};
+ // return {
+ // enable: !!enable,
+ // mask,
+ // func: {
+ // ...func,
+ // cmp: stencilFuncMap[func.cmp],
+ // },
+ // opFront: {
+ // fail: stencilOpMap[opFront.fail],
+ // zfail: stencilOpMap[opFront.zfail],
+ // zpass: stencilOpMap[opFront.zpass],
+ // },
+ // opBack: {
+ // fail: stencilOpMap[opBack.fail],
+ // zfail: stencilOpMap[opBack.zfail],
+ // zpass: stencilOpMap[opBack.zpass],
+ // },
+ // };
+ // }
+
+ // private getColorMaskDrawParams(
+ // { stencil }: Pick,
+ // pick: boolean,
+ // ) {
+ // // TODO: 重构相关参数
+ // // 掩膜模式下,颜色通道全部关闭
+ // const colorMask =
+ // stencil?.enable && stencil.opFront && !pick
+ // ? [false, false, false, false]
+ // : [true, true, true, true]; // 非掩码模式下,颜色通道全部开启
+ // return colorMask;
+ // }
+
+ /**
+ * @see https://github.com/regl-project/regl/blob/gh-pages/API.md#culling
+ */
+ private initCullDrawParams({
+ cull,
+ }: Pick) {
+ if (cull) {
+ const { enable, face = gl.BACK } = cull;
+ return {
+ enable: !!enable,
+ face: cullFaceMap[face],
+ };
+ }
+ }
+
+ /**
+ * 考虑结构体命名, eg:
+ * a: { b: 1 } -> 'a.b'
+ * a: [ { b: 1 } ] -> 'a[0].b'
+ */
+ private extractUniforms(uniforms: { [key: string]: IUniform }): {
+ [key: string]: IUniform;
+ } {
+ const extractedUniforms = {};
+ Object.keys(uniforms).forEach((uniformName) => {
+ this.extractUniformsRecursively(
+ uniformName,
+ uniforms[uniformName],
+ extractedUniforms,
+ '',
+ );
+ });
+
+ return extractedUniforms;
+ }
+
+ private extractUniformsRecursively(
+ uniformName: string,
+ uniformValue: IUniform,
+ uniforms: {
+ [key: string]: IUniform;
+ },
+ prefix: string,
+ ) {
+ if (
+ uniformValue === null ||
+ typeof uniformValue === 'number' || // u_A: 1
+ typeof uniformValue === 'boolean' || // u_A: false
+ (Array.isArray(uniformValue) && typeof uniformValue[0] === 'number') || // u_A: [1, 2, 3]
+ isTypedArray(uniformValue) || // u_A: Float32Array
+ // @ts-ignore
+ uniformValue === '' ||
+ 'resize' in uniformValue
+ ) {
+ uniforms[`${prefix && prefix + '.'}${uniformName}`] = uniformValue;
+ return;
+ }
+
+ // u_Struct.a.b.c
+ if (isPlainObject(uniformValue)) {
+ Object.keys(uniformValue).forEach((childName) => {
+ this.extractUniformsRecursively(
+ childName,
+ // @ts-ignore
+ uniformValue[childName],
+ uniforms,
+ `${prefix && prefix + '.'}${uniformName}`,
+ );
+ });
+ }
+
+ // u_Struct[0].a
+ if (Array.isArray(uniformValue)) {
+ uniformValue.forEach((child, idx) => {
+ Object.keys(child).forEach((childName) => {
+ this.extractUniformsRecursively(
+ childName,
+ // @ts-ignore
+ child[childName],
+ uniforms,
+ `${prefix && prefix + '.'}${uniformName}[${idx}]`,
+ );
+ });
+ });
+ }
+ }
+}
diff --git a/packages/renderer/src/device/DeviceTexture2D.ts b/packages/renderer/src/device/DeviceTexture2D.ts
new file mode 100644
index 0000000000..acd4b1590a
--- /dev/null
+++ b/packages/renderer/src/device/DeviceTexture2D.ts
@@ -0,0 +1,121 @@
+import {
+ Device,
+ FilterMode,
+ Format,
+ MipmapFilterMode,
+ Sampler,
+ Texture,
+ TextureUsage,
+} from '@antv/g-device-api';
+import { ITexture2D, ITexture2DInitializationOptions, gl } from '@antv/l7-core';
+import { wrapModeMap } from './constants';
+
+export function isTexture2D(t: any): t is ITexture2D {
+ return false;
+}
+
+export default class DeviceTexture2D implements ITexture2D {
+ private texture: Texture;
+ private sampler: Sampler;
+ private width: number;
+ private height: number;
+ private isDestroy: boolean = false;
+
+ constructor(device: Device, options: ITexture2DInitializationOptions) {
+ const {
+ data,
+ type = gl.UNSIGNED_BYTE,
+ width,
+ height,
+ flipY = false,
+ format = gl.RGBA,
+ wrapS = gl.CLAMP_TO_EDGE,
+ wrapT = gl.CLAMP_TO_EDGE,
+ // aniso = 0,
+ alignment = 1,
+ // mipmap = false,
+ // premultiplyAlpha = false,
+ // mag = gl.NEAREST,
+ // min = gl.NEAREST,
+ // colorSpace = gl.BROWSER_DEFAULT_WEBGL,
+ // x = 0,
+ // y = 0,
+ // copy = false,
+ } = options;
+ this.width = width;
+ this.height = height;
+
+ let pixelFormat: Format = Format.U8_RGBA_RT;
+ if (type === gl.UNSIGNED_BYTE && format === gl.RGBA) {
+ pixelFormat = Format.U8_RGBA_RT;
+ } else if (format === gl.LUMINANCE && type === gl.FLOAT) {
+ pixelFormat = Format.F32_LUMINANCE;
+ } else if (format === gl.LUMINANCE && type === gl.UNSIGNED_BYTE) {
+ pixelFormat = Format.U8_LUMINANCE;
+ } else {
+ throw new Error(`create texture error, type: ${type}, format: ${format}`);
+ }
+
+ // // copy pixels from current bind framebuffer
+ // x,
+ // y,
+ // copy,
+ // };
+
+ this.texture = device.createTexture({
+ format: pixelFormat!,
+ width,
+ height,
+ usage: TextureUsage.SAMPLED,
+ pixelStore: {
+ unpackFlipY: flipY,
+ packAlignment: alignment,
+ },
+ });
+ if (data) {
+ // @ts-ignore
+ this.texture.setImageData([data]);
+ }
+
+ this.sampler = device.createSampler({
+ addressModeU: wrapModeMap[wrapS],
+ addressModeV: wrapModeMap[wrapT],
+ minFilter: FilterMode.POINT, // TODO: use mag & min
+ magFilter: FilterMode.BILINEAR,
+ mipmapFilter: MipmapFilterMode.NO_MIP,
+ lodMinClamp: 0,
+ lodMaxClamp: 0,
+ // maxAnisotropy: aniso,
+ });
+ }
+
+ get() {
+ return this.texture;
+ }
+
+ update(props: any) {
+ const { data } = props;
+ this.texture.setImageData([data]);
+ }
+
+ bind() {
+ // this.texture._texture.bind();
+ }
+
+ resize({ width, height }: { width: number; height: number }): void {
+ // this.texture.resize(width, height);
+ this.width = width;
+ this.height = height;
+ }
+
+ getSize(): [number, number] {
+ return [this.width, this.height];
+ }
+
+ destroy() {
+ if (!this.isDestroy) {
+ this.texture?.destroy();
+ }
+ this.isDestroy = true;
+ }
+}
diff --git a/packages/renderer/src/device/constants.ts b/packages/renderer/src/device/constants.ts
new file mode 100644
index 0000000000..88b0fae9fd
--- /dev/null
+++ b/packages/renderer/src/device/constants.ts
@@ -0,0 +1,129 @@
+import {
+ AddressMode,
+ BlendFactor,
+ BlendMode,
+ BufferFrequencyHint,
+ CompareFunction,
+ CullMode,
+ Format,
+ PrimitiveTopology,
+} from '@antv/g-device-api';
+import { gl } from '@antv/l7-core';
+import { TypedArray } from './utils/typedarray';
+
+export const typedArrayCtorMap: {
+ [key: string]: new (data: number[]) => TypedArray;
+} = {
+ [gl.FLOAT]: Float32Array,
+ [gl.UNSIGNED_BYTE]: Uint8Array,
+ [gl.SHORT]: Int16Array,
+ [gl.UNSIGNED_SHORT]: Uint16Array,
+ [gl.INT]: Int32Array,
+ [gl.UNSIGNED_INT]: Uint32Array,
+};
+
+export const primitiveMap: {
+ [key: string]: PrimitiveTopology;
+} = {
+ [gl.POINTS]: PrimitiveTopology.POINTS,
+ [gl.LINES]: PrimitiveTopology.LINES,
+ [gl.LINE_LOOP]: PrimitiveTopology.LINES,
+ [gl.LINE_STRIP]: PrimitiveTopology.LINE_STRIP,
+ [gl.TRIANGLES]: PrimitiveTopology.TRIANGLES,
+ [gl.TRIANGLE_FAN]: PrimitiveTopology.TRIANGLES,
+ [gl.TRIANGLE_STRIP]: PrimitiveTopology.TRIANGLE_STRIP,
+};
+
+export const sizeFormatMap: {
+ [key: number]: Format;
+} = {
+ [1]: Format.F32_R,
+ [2]: Format.F32_RG,
+ [3]: Format.F32_RGB,
+ [4]: Format.F32_RGBA,
+};
+
+export const hintMap: {
+ [key: string]: BufferFrequencyHint;
+} = {
+ [gl.STATIC_DRAW]: BufferFrequencyHint.STATIC,
+ [gl.DYNAMIC_DRAW]: BufferFrequencyHint.DYNAMIC,
+ [gl.STREAM_DRAW]: BufferFrequencyHint.DYNAMIC,
+};
+
+export const wrapModeMap: {
+ [key: string]: AddressMode;
+} = {
+ [gl.REPEAT]: AddressMode.REPEAT,
+ [gl.CLAMP_TO_EDGE]: AddressMode.CLAMP_TO_EDGE,
+ [gl.MIRRORED_REPEAT]: AddressMode.MIRRORED_REPEAT,
+};
+
+export const depthFuncMap: {
+ [key: string]: CompareFunction;
+} = {
+ [gl.NEVER]: CompareFunction.NEVER,
+ [gl.ALWAYS]: CompareFunction.ALWAYS,
+ [gl.LESS]: CompareFunction.LESS,
+ [gl.LEQUAL]: CompareFunction.LEQUAL,
+ [gl.GREATER]: CompareFunction.GREATER,
+ [gl.GEQUAL]: CompareFunction.GEQUAL,
+ [gl.EQUAL]: CompareFunction.EQUAL,
+ [gl.NOTEQUAL]: CompareFunction.NOTEQUAL,
+};
+
+export const cullFaceMap: {
+ [key: string]: CullMode;
+} = {
+ [gl.FRONT]: CullMode.FRONT,
+ [gl.BACK]: CullMode.BACK,
+};
+
+export const blendEquationMap: {
+ [key: string]: BlendMode;
+} = {
+ [gl.FUNC_ADD]: BlendMode.ADD,
+ [gl.MIN_EXT]: BlendMode.MIN,
+ [gl.MAX_EXT]: BlendMode.MAX,
+ [gl.FUNC_SUBTRACT]: BlendMode.SUBSTRACT,
+ [gl.FUNC_REVERSE_SUBTRACT]: BlendMode.REVERSE_SUBSTRACT,
+};
+
+export const blendFuncMap: {
+ [key: string]: BlendFactor;
+} = {
+ [gl.ZERO]: BlendFactor.ZERO,
+ [gl.ONE]: BlendFactor.ONE,
+ [gl.SRC_COLOR]: BlendFactor.SRC,
+ [gl.ONE_MINUS_SRC_COLOR]: BlendFactor.ONE_MINUS_SRC,
+ [gl.SRC_ALPHA]: BlendFactor.SRC_ALPHA,
+ [gl.ONE_MINUS_SRC_ALPHA]: BlendFactor.ONE_MINUS_SRC_ALPHA,
+ [gl.DST_COLOR]: BlendFactor.DST,
+ [gl.ONE_MINUS_DST_COLOR]: BlendFactor.ONE_MINUS_DST,
+ [gl.DST_ALPHA]: BlendFactor.DST_ALPHA,
+ [gl.ONE_MINUS_DST_ALPHA]: BlendFactor.ONE_MINUS_DST_ALPHA,
+ [gl.CONSTANT_COLOR]: BlendFactor.CONST,
+ [gl.ONE_MINUS_CONSTANT_COLOR]: BlendFactor.ONE_MINUS_CONSTANT,
+ [gl.CONSTANT_ALPHA]: BlendFactor.CONST,
+ [gl.ONE_MINUS_CONSTANT_ALPHA]: BlendFactor.ONE_MINUS_CONSTANT,
+ [gl.SRC_ALPHA_SATURATE]: BlendFactor.SRC_ALPHA_SATURATE,
+};
+
+// export const filterMap: {
+// [key: string]: FilterMode;
+// } = {
+// [gl.NEAREST]: 'nearest',
+// [gl.LINEAR]: 'linear',
+// [gl.LINEAR_MIPMAP_LINEAR]: 'mipmap',
+// [gl.NEAREST_MIPMAP_LINEAR]: 'nearest mipmap linear',
+// [gl.LINEAR_MIPMAP_NEAREST]: 'linear mipmap nearest',
+// [gl.NEAREST_MIPMAP_NEAREST]: 'nearest mipmap nearest',
+// };
+
+// export const mipmapMap: {
+// [key: string]: MipmapFilterMode;
+// } = {
+// [gl.DONT_CARE]: MipmapFilterMode,
+// [gl.NICEST]: 'nice',
+// [gl.FASTEST]: 'fast',
+// };
diff --git a/packages/renderer/src/device/index.ts b/packages/renderer/src/device/index.ts
new file mode 100644
index 0000000000..f70c306a37
--- /dev/null
+++ b/packages/renderer/src/device/index.ts
@@ -0,0 +1,344 @@
+import {
+ Device,
+ Format,
+ RenderPass,
+ RenderTarget,
+ SwapChain,
+ TextureUsage,
+ TransparentBlack,
+ WebGLDeviceContribution,
+ WebGPUDeviceContribution,
+} from '@antv/g-device-api';
+import {
+ IAttribute,
+ IAttributeInitializationOptions,
+ IBuffer,
+ IBufferInitializationOptions,
+ IElements,
+ IElementsInitializationOptions,
+ IExtensions,
+ IFramebufferInitializationOptions,
+ IModel,
+ IModelInitializationOptions,
+ IRenderConfig,
+ IRendererService,
+ ITexture2D,
+ ITexture2DInitializationOptions,
+} from '@antv/l7-core';
+import { injectable } from 'inversify';
+import 'reflect-metadata';
+import DeviceAttribute from './DeviceAttribute';
+import DeviceBuffer from './DeviceBuffer';
+import DeviceElements from './DeviceElements';
+import DeviceFramebuffer from './DeviceFramebuffer';
+import DeviceModel from './DeviceModel';
+import DeviceTexture2D from './DeviceTexture2D';
+import { isWebGL2 } from './utils/webgl';
+
+/**
+ * Device API renderer
+ */
+@injectable()
+export default class DeviceRendererService implements IRendererService {
+ uniformBuffers: IBuffer[] = [];
+ extensionObject: IExtensions;
+ private device: Device;
+ private swapChain: SwapChain;
+ private $container: HTMLDivElement | null;
+ private canvas: HTMLCanvasElement;
+ private width: number;
+ private height: number;
+ private isDirty: boolean;
+ private renderPass: RenderPass;
+ private renderTarget: RenderTarget;
+ private mainDepthRT: RenderTarget;
+
+ async init(canvas: HTMLCanvasElement, cfg: IRenderConfig): Promise {
+ const { enableWebGPU, shaderCompilerPath } = cfg;
+
+ console.log(cfg);
+
+ // this.$container = $container;
+ this.canvas = canvas;
+
+ // TODO: use antialias from cfg
+ const deviceContribution = enableWebGPU
+ ? new WebGPUDeviceContribution({
+ shaderCompilerPath,
+ })
+ : new WebGLDeviceContribution({
+ // Use WebGL2 first and downgrade to WebGL1 if WebGL2 is not supported.
+ targets: ['webgl2', 'webgl1'],
+ onContextLost(e) {
+ console.warn('context lost', e);
+ },
+ onContextCreationError(e) {
+ console.warn('context creation error', e);
+ },
+ onContextRestored(e) {
+ console.warn('context restored', e);
+ },
+ });
+
+ const swapChain = await deviceContribution.createSwapChain(canvas);
+ swapChain.configureSwapChain(canvas.width, canvas.height);
+ this.device = swapChain.getDevice();
+ this.swapChain = swapChain;
+
+ // Create default RT
+ // @ts-ignore
+ // this.device.onscreenFramebuffer = this.createFramebuffer({
+ // width: canvas.width,
+ // height: canvas.height,
+ // });
+ // // @ts-ignore
+ // this.device.onscreenFramebuffer.onscreen = true;
+ // // @ts-ignore
+ // this.device.currentFramebuffer = this.device.onscreenFramebuffer;
+
+ // @ts-ignore
+ const gl = this.device['gl'];
+ this.extensionObject = {
+ // @ts-ignore
+ OES_texture_float: !isWebGL2(gl) && this.device['OES_texture_float'],
+ };
+
+ const renderTargetTexture = this.device.createTexture({
+ format: Format.U8_RGBA_RT,
+ width: canvas.width,
+ height: canvas.height,
+ usage: TextureUsage.RENDER_TARGET,
+ });
+ this.renderTarget =
+ this.device.createRenderTargetFromTexture(renderTargetTexture);
+
+ this.mainDepthRT = this.device.createRenderTargetFromTexture(
+ this.device.createTexture({
+ format: Format.D24_S8,
+ width: canvas.width,
+ height: canvas.height,
+ usage: TextureUsage.RENDER_TARGET,
+ }),
+ );
+ }
+
+ beginFrame(): void {
+ const onscreenTexture = this.swapChain.getOnscreenTexture();
+ this.renderPass = this.device.createRenderPass({
+ colorAttachment: [this.renderTarget],
+ // colorResolveTo: [onscreen ? onscreenTexture : onscreenTexture],
+ colorResolveTo: [onscreenTexture],
+ colorClearColor: [TransparentBlack],
+ depthStencilAttachment: this.mainDepthRT,
+ depthClearValue: 1,
+ });
+ // @ts-ignore
+ this.device.renderPass = this.renderPass;
+ }
+
+ endFrame(): void {
+ this.device.submitPass(this.renderPass);
+ }
+
+ getPointSizeRange() {
+ // @ts-ignore
+ const gl = this.device['gl'];
+ console.log(gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE));
+ // FIXME: implement this method in Device API.
+ return gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE);
+ }
+
+ public testExtension(name: string) {
+ // OES_texture_float
+ return !!this.getGLContext().getExtension(name);
+ }
+
+ createModel = (options: IModelInitializationOptions): IModel =>
+ new DeviceModel(this.device, options);
+
+ createAttribute = (options: IAttributeInitializationOptions): IAttribute =>
+ new DeviceAttribute(this.device, options);
+
+ createBuffer = (options: IBufferInitializationOptions): IBuffer =>
+ new DeviceBuffer(this.device, options);
+
+ createElements = (options: IElementsInitializationOptions): IElements =>
+ new DeviceElements(this.device, options);
+
+ createTexture2D = (options: ITexture2DInitializationOptions): ITexture2D =>
+ new DeviceTexture2D(this.device, options);
+
+ createFramebuffer = (options: IFramebufferInitializationOptions) =>
+ new DeviceFramebuffer(this.device, options);
+
+ useFramebuffer = () =>
+ // framebuffer: IFramebuffer | null,
+ // drawCommands: () => void,
+ {
+ // if (framebuffer == null) {
+ // // @ts-ignore
+ // this.device.currentFramebuffer = this.device.onscreenFramebuffer;
+ // } else {
+ // // @ts-ignore
+ // this.device.currentFramebuffer = framebuffer;
+ // }
+ // drawCommands();
+ };
+
+ clear = () =>
+ // options: IClearOptions
+ {
+ // @see https://github.com/regl-project/regl/blob/gh-pages/API.md#clear-the-draw-buffer
+ // const { color, depth, stencil, framebuffer = null } = options;
+ // const reglClearOptions: regl.ClearOptions = {
+ // color,
+ // depth,
+ // stencil,
+ // };
+ // reglClearOptions.framebuffer =
+ // framebuffer === null
+ // ? framebuffer
+ // : (framebuffer as DeviceFramebuffer).get();
+ // this.gl?.clear(reglClearOptions);
+ // TODO: clear
+ };
+
+ viewport = ({
+ // x,
+ // y,
+ width,
+ height,
+ }: {
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+ }) => {
+ // use WebGL context directly
+ // @see https://github.com/regl-project/regl/blob/gh-pages/API.md#unsafe-escape-hatch
+ // this.gl._gl.viewport(x, y, width, height);
+ this.width = width;
+ this.height = height;
+ // Will be used in `setViewport` from RenderPass later.
+ // @ts-ignore
+ this.device.width = width;
+ // @ts-ignore
+ this.device.height = height;
+
+ // this.gl._refresh();
+ };
+
+ readPixels = () =>
+ // options: IReadPixelsOptions
+ {
+ // const { framebuffer, x, y, width, height } = options;
+
+ // const readback = this.device.createReadback();
+
+ // if (framebuffer) {
+ // readPixelsOptions.framebuffer = (framebuffer as DeviceFramebuffer).get();
+ // }
+ // return readback.readTextureSync(null, x, y, width, height, new Uint8Array()) as Uint8Array;
+ return new Uint8Array();
+ };
+
+ getViewportSize = () => {
+ // FIXME: add viewport size in Device API.
+ return {
+ // @ts-ignore
+ width: this.device.width,
+ // @ts-ignore
+ height: this.device.height,
+ };
+ };
+
+ getContainer = () => {
+ return this.canvas?.parentElement;
+ };
+
+ getCanvas = () => {
+ // return this.$container?.getElementsByTagName('canvas')[0] || null;
+ return this.canvas;
+ };
+
+ getGLContext = () => {
+ // @ts-ignore
+ return this.device['gl'] as WebGLRenderingContext;
+ };
+
+ // TODO: 临时方法
+ setState() {
+ // this.gl({
+ // cull: {
+ // enable: false,
+ // face: 'back',
+ // },
+ // viewport: {
+ // x: 0,
+ // y: 0,
+ // height: this.width,
+ // width: this.height,
+ // },
+ // blend: {
+ // enable: true,
+ // equation: 'add',
+ // },
+ // framebuffer: null,
+ // });
+ // this.gl._refresh();
+ }
+
+ setBaseState() {
+ // this.gl({
+ // cull: {
+ // enable: false,
+ // face: 'back',
+ // },
+ // viewport: {
+ // x: 0,
+ // y: 0,
+ // height: this.width,
+ // width: this.height,
+ // },
+ // blend: {
+ // enable: false,
+ // equation: 'add',
+ // },
+ // framebuffer: null,
+ // });
+ // this.gl._refresh();
+ }
+ setCustomLayerDefaults() {
+ // const gl = this.getGLContext();
+ // gl.disable(gl.CULL_FACE);
+ }
+
+ setDirty(flag: boolean): void {
+ this.isDirty = flag;
+ }
+
+ getDirty(): boolean {
+ return this.isDirty;
+ }
+
+ destroy = () => {
+ // this.canvas = null 清除对 webgl 实例的引用
+ // @ts-ignore
+ this.canvas = null;
+
+ this.uniformBuffers?.forEach((buffer) => {
+ buffer.destroy();
+ });
+
+ this.device.destroy();
+
+ // make sure release webgl context
+ // this.gl?._gl?.getExtension('WEBGL_lose_context')?.loseContext();
+
+ // @see https://github.com/regl-project/regl/blob/gh-pages/API.md#clean-up
+ // this.gl.destroy();
+
+ // @ts-ignore
+ // this.gl = null;
+ };
+}
diff --git a/packages/renderer/src/device/utils/pipeline.ts b/packages/renderer/src/device/utils/pipeline.ts
new file mode 100644
index 0000000000..38194e0881
--- /dev/null
+++ b/packages/renderer/src/device/utils/pipeline.ts
@@ -0,0 +1 @@
+export function pipelineEquals() {}
diff --git a/packages/renderer/src/device/utils/typedarray.ts b/packages/renderer/src/device/utils/typedarray.ts
new file mode 100644
index 0000000000..e580b3aafd
--- /dev/null
+++ b/packages/renderer/src/device/utils/typedarray.ts
@@ -0,0 +1,28 @@
+const dtypes = {
+ '[object Int8Array]': 5120,
+ '[object Int16Array]': 5122,
+ '[object Int32Array]': 5124,
+ '[object Uint8Array]': 5121,
+ '[object Uint8ClampedArray]': 5121,
+ '[object Uint16Array]': 5123,
+ '[object Uint32Array]': 5125,
+ '[object Float32Array]': 5126,
+ '[object Float64Array]': 5121,
+ '[object ArrayBuffer]': 5121,
+};
+
+export type TypedArray =
+ | Int8Array
+ | Uint8Array
+ | Uint8ClampedArray
+ | Int16Array
+ | Uint16Array
+ | Int32Array
+ | Uint32Array
+ | Float32Array
+ | Float64Array;
+
+// eslint-disable-next-line
+export function isTypedArray(x: any): x is TypedArray {
+ return Object.prototype.toString.call(x) in dtypes;
+}
diff --git a/packages/renderer/src/device/utils/webgl.ts b/packages/renderer/src/device/utils/webgl.ts
new file mode 100644
index 0000000000..9f010ea55d
--- /dev/null
+++ b/packages/renderer/src/device/utils/webgl.ts
@@ -0,0 +1,14 @@
+// @see https://github.com/visgl/luma.gl/blob/30a1039573576d73641de7c1ba222e8992eb526e/modules/gltools/src/utils/webgl-checks.ts#L22
+export function isWebGL2(
+ gl: WebGL2RenderingContext | WebGLRenderingContext,
+): gl is WebGL2RenderingContext {
+ if (
+ typeof WebGL2RenderingContext !== 'undefined' &&
+ gl instanceof WebGL2RenderingContext
+ ) {
+ return true;
+ }
+ // Look for debug contexts, headless gl etc
+ // @ts-ignore
+ return Boolean(gl && gl._version === 2);
+}
diff --git a/packages/renderer/src/index.ts b/packages/renderer/src/index.ts
index 01b1f9ad66..66b481d62e 100644
--- a/packages/renderer/src/index.ts
+++ b/packages/renderer/src/index.ts
@@ -1,6 +1,7 @@
/**
*
*/
+import DeviceRendererService from './device';
import ReglRendererService from './regl';
-export { ReglRendererService };
+export { DeviceRendererService, ReglRendererService };
diff --git a/packages/renderer/src/regl/ReglModel.ts b/packages/renderer/src/regl/ReglModel.ts
index 89ee5b253f..21be561b4f 100644
--- a/packages/renderer/src/regl/ReglModel.ts
+++ b/packages/renderer/src/regl/ReglModel.ts
@@ -1,3 +1,8 @@
+import {
+ ClipSpaceNearZ,
+ preprocessShader_GLSL,
+ ViewportOrigin,
+} from '@antv/g-device-api';
import {
gl,
IAttribute,
@@ -7,6 +12,7 @@ import {
IModelDrawOptions,
IModelInitializationOptions,
IUniform,
+ removeDuplicateUniforms,
} from '@antv/l7-core';
import { lodashUtil } from '@antv/l7-utils';
import regl from 'regl';
@@ -52,6 +58,20 @@ export default class ReglModel implements IModel {
cull,
instances,
} = options;
+
+ /**
+ * try to compile GLSL 300 to 100
+ */
+ const vendorInfo = {
+ platformString: 'WebGL1',
+ glslVersion: '#version 100',
+ explicitBindingLocations: false,
+ separateSamplerTextures: false,
+ viewportOrigin: ViewportOrigin.LOWER_LEFT,
+ clipSpaceNearZ: ClipSpaceNearZ.NEGATIVE_ONE,
+ supportMRT: false,
+ };
+
const reglUniforms: { [key: string]: IUniform } = {};
this.options = options;
if (uniforms) {
@@ -69,9 +89,13 @@ export default class ReglModel implements IModel {
});
const drawParams: regl.DrawConfig = {
attributes: reglAttributes,
- frag: fs,
+ frag: removeDuplicateUniforms(
+ preprocessShader_GLSL(vendorInfo, 'frag', fs, null, false),
+ ),
uniforms: reglUniforms,
- vert: vs,
+ vert: removeDuplicateUniforms(
+ preprocessShader_GLSL(vendorInfo, 'vert', vs, null, false),
+ ),
// @ts-ignore
colorMask: reGl.prop('colorMask'),
lineWidth: 1,
diff --git a/packages/renderer/src/regl/index.ts b/packages/renderer/src/regl/index.ts
index 441671903e..c382968b58 100644
--- a/packages/renderer/src/regl/index.ts
+++ b/packages/renderer/src/regl/index.ts
@@ -36,6 +36,7 @@ import ReglTexture2D from './ReglTexture2D';
*/
@injectable()
export default class ReglRendererService implements IRendererService {
+ uniformBuffers: IBuffer[] = [];
public extensionObject: IExtensions;
private gl: regl.Regl;
private $container: HTMLDivElement | null;
@@ -277,4 +278,7 @@ export default class ReglRendererService implements IRendererService {
// @ts-ignore
this.gl = null;
};
+
+ beginFrame(): void {}
+ endFrame(): void {}
}
diff --git a/packages/scene/src/index.ts b/packages/scene/src/index.ts
index 7c88b7ce77..e75319996e 100644
--- a/packages/scene/src/index.ts
+++ b/packages/scene/src/index.ts
@@ -32,7 +32,7 @@ import {
TYPES,
} from '@antv/l7-core';
import { MaskLayer } from '@antv/l7-layers';
-import { ReglRendererService } from '@antv/l7-renderer';
+import { DeviceRendererService, ReglRendererService } from '@antv/l7-renderer';
import { DOM, IProtocolHandler, SceneConifg } from '@antv/l7-utils';
import { Container } from 'inversify';
import BoxSelect, { BoxSelectEventList } from './boxSelect';
@@ -69,7 +69,7 @@ class Scene
private container: Container;
public constructor(config: ISceneConfig) {
- const { id, map, canvas, hasBaseMap } = config;
+ const { id, map, canvas, hasBaseMap, renderer = 'regl' } = config;
// 创建场景容器
const sceneContainer = createSceneContainer();
this.container = sceneContainer;
@@ -79,7 +79,7 @@ class Scene
// 绑定渲染引擎服务
sceneContainer
.bind(TYPES.IRendererService)
- .to(ReglRendererService)
+ .to(renderer === 'regl' ? ReglRendererService : DeviceRendererService)
.inSingletonScope();
// 依赖注入
diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json
index a262c63660..4a8df35f23 100644
--- a/packages/test-utils/package.json
+++ b/packages/test-utils/package.json
@@ -42,8 +42,8 @@
"@antv/l7-map": "2.19.10",
"@antv/l7-maps": "2.19.10",
"@antv/l7-scene": "2.19.10",
- "gl": "^5.0.3",
- "regl": "1.6.1"
+ "gl": "^6.0.2",
+ "regl": "^1.7.0"
},
"homepage": "https://github.com/antvis/L7#readme"
}
diff --git a/public/antv.png b/public/antv.png
new file mode 100644
index 0000000000..2764f6117a
Binary files /dev/null and b/public/antv.png differ
diff --git a/public/glsl_wgsl_compiler_bg.wasm b/public/glsl_wgsl_compiler_bg.wasm
new file mode 100644
index 0000000000..172b691daa
Binary files /dev/null and b/public/glsl_wgsl_compiler_bg.wasm differ