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