From cfd48d4d008dc4cb8a09547806d94cc777e365a4 Mon Sep 17 00:00:00 2001 From: Andrey Kuznecov Date: Fri, 13 Oct 2023 14:28:47 +0700 Subject: [PATCH] deckPlugin2 --- README.md | 4 +- demo/index.ts | 11 +- package-lock.json | 143 +++----------- package.json | 4 +- src/2gl/Buffer.ts | 371 +++++++++++++++++++++++++++++++++++++ src/2gl/BufferChannel.ts | 40 ++++ src/2gl/Object3D.ts | 159 ++++++++++++++++ src/2gl/RenderTarget.ts | 208 +++++++++++++++++++++ src/2gl/Renderer.ts | 157 ++++++++++++++++ src/2gl/Shader.ts | 144 ++++++++++++++ src/2gl/ShaderAttribute.ts | 90 +++++++++ src/2gl/ShaderProgram.ts | 185 ++++++++++++++++++ src/2gl/ShaderUniform.ts | 125 +++++++++++++ src/2gl/Texture.ts | 300 ++++++++++++++++++++++++++++++ src/2gl/Vao.ts | 249 +++++++++++++++++++++++++ src/2gl/types.ts | 2 + src/deckgl2gisLayer.ts | 261 +++++++++----------------- src/index.ts | 4 +- src/types.ts | 9 +- src/utils.ts | 141 ++++++++++++-- tsconfig.json | 2 +- tsconfig.module.json | 3 +- 22 files changed, 2290 insertions(+), 322 deletions(-) create mode 100644 src/2gl/Buffer.ts create mode 100644 src/2gl/BufferChannel.ts create mode 100644 src/2gl/Object3D.ts create mode 100644 src/2gl/RenderTarget.ts create mode 100644 src/2gl/Renderer.ts create mode 100644 src/2gl/Shader.ts create mode 100644 src/2gl/ShaderAttribute.ts create mode 100644 src/2gl/ShaderProgram.ts create mode 100644 src/2gl/ShaderUniform.ts create mode 100644 src/2gl/Texture.ts create mode 100644 src/2gl/Vao.ts create mode 100644 src/2gl/types.ts diff --git a/README.md b/README.md index f6d48e9..bfb3b1a 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ npm install @2gis/deck2gis-layer Import the deck2gisLayer plugin to your project and use it: ```typescript -import { Deck2gisLayer, initDeck2gisProps } from '@2gis/deck2gis-layer'; +import { Deck2gisLayer, initDeck } from '@2gis/deck2gis-layer'; // Init mapgl const map = new mapgl.Map('container', { center: [55.31878, 25.23584], @@ -22,7 +22,7 @@ const map = new mapgl.Map('container', { key: 'Your API access key', }); // Init deck.gl -const deck = new Deck(initDeck2gisProps(map)); +const deck = initDeck(map, Deck, { antialiasing: 'msaa' }); // create Deck2gisLayer const layer = new Deck2gisLayer>({ diff --git a/demo/index.ts b/demo/index.ts index 1c4d944..71af703 100644 --- a/demo/index.ts +++ b/demo/index.ts @@ -3,7 +3,7 @@ import { HeatmapLayer, HexagonLayer } from '@deck.gl/aggregation-layers/typed'; import { TextLayer } from '@deck.gl/layers'; import { Color, Deck } from '@deck.gl/core/typed'; import { data } from './data'; -import { initDeck2gisProps } from '../src/utils'; +import { initDeck } from '../src/utils'; declare const mapgl: any; @@ -15,9 +15,10 @@ const map = new mapgl.Map('container', { webglVersion: 2, }); -const deck = new Deck(initDeck2gisProps(map, { antialiasing: 'none' })); -map.once('styleload', () => { - setTimeout(() => initDeckGL(), 3000); +let deck; +map.once('idle', () => { + deck = initDeck(map, Deck, { antialiasing: 'msaa' }); + initDeckGL(); }); function initDeckGL() { @@ -29,6 +30,8 @@ function initDeckGL() { map.addLayer(deckLayer3); const deckLayer4 = createTextlayer(data); map.addLayer(deckLayer4); + map.removeLayer('deckgl-HexagonLayer'); + map.addLayer(deckLayer2); } const COLOR_RANGE: Color[] = [ diff --git a/package-lock.json b/package-lock.json index 1396ded..0b2d467 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,16 @@ { "name": "@2gis/deck2gis-layer", - "version": "1.2.1", + "version": "2.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@2gis/deck2gis-layer", - "version": "1.2.1", + "version": "2.0.0", "license": "BSD-2-Clause", "dependencies": { - "@deck.gl/core": "8.8.17", - "2gl": "0.10.2" + "@2gis/gl-matrix": "^2.4.6", + "@deck.gl/core": "8.8.17" }, "devDependencies": { "@2gis/mapgl": "1.32.1", @@ -88,9 +88,8 @@ }, "node_modules/@deck.gl/aggregation-layers": { "version": "8.8.17", - "resolved": "https://registry.npmjs.org/@deck.gl/aggregation-layers/-/aggregation-layers-8.8.17.tgz", - "integrity": "sha512-VO56dYe2C6l3mkk865kdfezHwHr7xlbP7EwCITQGbnBeXS0KUKGMik5AZqJMtayLf87+dSnTn6yDw1BXrQYVEA==", "dev": true, + "license": "MIT", "dependencies": { "@luma.gl/constants": "^8.5.16", "@luma.gl/shadertools": "^8.5.16", @@ -105,8 +104,7 @@ }, "node_modules/@deck.gl/core": { "version": "8.8.17", - "resolved": "https://registry.npmjs.org/@deck.gl/core/-/core-8.8.17.tgz", - "integrity": "sha512-lBJrO4iPa5bQUhLYXKP2C1Xwe/YaGZ/gXfviWzGHblJ972mSdrvE3Ro13fYyODCjmCRtUvdW4AfQ4TQBLLeetQ==", + "license": "MIT", "dependencies": { "@loaders.gl/core": "^3.2.10", "@loaders.gl/images": "^3.2.10", @@ -125,9 +123,8 @@ }, "node_modules/@deck.gl/layers": { "version": "8.8.17", - "resolved": "https://registry.npmjs.org/@deck.gl/layers/-/layers-8.8.17.tgz", - "integrity": "sha512-6p1F90DqN567lFGlHClY/8d2o7rBiw42WZzoXg35YgRAzPxZr+vuRd41Vji1j1pDnXKdD1l1oXMF9qTwFF6pJg==", "dev": true, + "license": "MIT", "dependencies": { "@loaders.gl/images": "^3.2.10", "@loaders.gl/schema": "^3.2.10", @@ -175,8 +172,7 @@ }, "node_modules/@loaders.gl/core": { "version": "3.4.6", - "resolved": "https://registry.npmjs.org/@loaders.gl/core/-/core-3.4.6.tgz", - "integrity": "sha512-9nang7w+McOg3ZippyFNz0+FuJu4bSkQkwpnIjUEr61JUiwpmRy5IakZP/LnmhZuzCy5+lDFj921md/l8Tduaw==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.3.1", "@loaders.gl/loader-utils": "3.4.6", @@ -186,16 +182,14 @@ }, "node_modules/@loaders.gl/core/node_modules/@probe.gl/env": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@probe.gl/env/-/env-4.0.4.tgz", - "integrity": "sha512-sYNGqesDfWD6dFP5oNZtTeFA4Z6ak5T4a8BNPdNhoqy7PK9w70JHrb6mv+RKWqKXq33KiwCDWL7fYxx2HuEH2w==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.0.0" } }, "node_modules/@loaders.gl/core/node_modules/@probe.gl/log": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@probe.gl/log/-/log-4.0.4.tgz", - "integrity": "sha512-WpmXl6njlBMwrm8HBh/b4kSp/xnY1VVmeT4PWUKF+RkVbFuKQbsU11dA1IxoMd7gSY+5DGIwxGfAv1H5OMzA4A==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.0.0", "@probe.gl/env": "4.0.4" @@ -203,16 +197,14 @@ }, "node_modules/@loaders.gl/images": { "version": "3.4.6", - "resolved": "https://registry.npmjs.org/@loaders.gl/images/-/images-3.4.6.tgz", - "integrity": "sha512-zwM6lXATGxxIe27X1gPyqcgXb6ql9gPkoNlb5pWOrF1D8DrFFjTJSLV9p6WGrVKMZKeioJ2PK8ZnkeRiOYkveA==", + "license": "MIT", "dependencies": { "@loaders.gl/loader-utils": "3.4.6" } }, "node_modules/@loaders.gl/loader-utils": { "version": "3.4.6", - "resolved": "https://registry.npmjs.org/@loaders.gl/loader-utils/-/loader-utils-3.4.6.tgz", - "integrity": "sha512-AN56Tvomas7bOZlrMEDforJWwS6DuXXFT/MsxOR/2vvzhutLDxYSuG4dZ9g5ejqJkiboDLAL3aghXXWekyToaw==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.3.1", "@loaders.gl/worker-utils": "3.4.6", @@ -221,38 +213,33 @@ }, "node_modules/@loaders.gl/loader-utils/node_modules/@probe.gl/stats": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@probe.gl/stats/-/stats-4.0.4.tgz", - "integrity": "sha512-SDuSY/D4yDL6LQDa69l/GCcnZLRiGYdyvYkxWb0CgnzTPdPrcdrzGkzkvpC3zsA4fEFw2smlDje370QGHwlisg==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.0.0" } }, "node_modules/@loaders.gl/schema": { "version": "3.4.6", - "resolved": "https://registry.npmjs.org/@loaders.gl/schema/-/schema-3.4.6.tgz", - "integrity": "sha512-uvToDknGCEL0TIZY1Rn76lQ0m22kUtVxE0HbgfgzwkT7dlXeT92F5yD4VVVL8qTX4oqh2xUySPQIaiAqdkGCvg==", "dev": true, + "license": "MIT", "dependencies": { "@types/geojson": "^7946.0.7" } }, "node_modules/@loaders.gl/worker-utils": { "version": "3.4.6", - "resolved": "https://registry.npmjs.org/@loaders.gl/worker-utils/-/worker-utils-3.4.6.tgz", - "integrity": "sha512-1Ns2d5HbJ22u9PDfEqoDWhg2F3zyYZUCQBmfmSW9Yuxblt2M0pRyB01VvAhPCnLovldvesHu+o16FsgkqieybQ==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.3.1" } }, "node_modules/@luma.gl/constants": { "version": "8.5.20", - "resolved": "https://registry.npmjs.org/@luma.gl/constants/-/constants-8.5.20.tgz", - "integrity": "sha512-5yG+ybkUZ4j6kLPWMZjN4Hun2yLB0MyEpNCRKAUN9/yS9UIWA7unyVxjSf2vnE7k/7dywtxlbXegASNFgNVGxw==" + "license": "MIT" }, "node_modules/@luma.gl/core": { "version": "8.5.20", - "resolved": "https://registry.npmjs.org/@luma.gl/core/-/core-8.5.20.tgz", - "integrity": "sha512-xJr96G6vhYcznYHC84fbeOG3fgNM4lFwj9bd0VPcg/Kfe8otUeN1Hl0AKHCCtNn48PiMSg3LKbaiRfNUMhaffQ==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.0.0", "@luma.gl/constants": "8.5.20", @@ -264,8 +251,7 @@ }, "node_modules/@luma.gl/engine": { "version": "8.5.20", - "resolved": "https://registry.npmjs.org/@luma.gl/engine/-/engine-8.5.20.tgz", - "integrity": "sha512-+0ryJ/4gL1pWaEgZimY21jUPt1LYiO6Cqte8TNUprCfAHoAStsuzD7jwgEqnM6jJOUEdIxQ3w0z3Dzw/0KIE+w==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.0.0", "@luma.gl/constants": "8.5.20", @@ -280,8 +266,7 @@ }, "node_modules/@luma.gl/gltools": { "version": "8.5.20", - "resolved": "https://registry.npmjs.org/@luma.gl/gltools/-/gltools-8.5.20.tgz", - "integrity": "sha512-5pP6ph9FSX5gHiVWQM1DmYRUnriklzKUG9yaqlQsKEqCFsOcKB0EfK3MfBVXIfsOdP/1bJZ9Dlz/zV19soWVhg==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.0.0", "@luma.gl/constants": "8.5.20", @@ -292,8 +277,7 @@ }, "node_modules/@luma.gl/shadertools": { "version": "8.5.20", - "resolved": "https://registry.npmjs.org/@luma.gl/shadertools/-/shadertools-8.5.20.tgz", - "integrity": "sha512-q1lrCZy1ncIFb4mMjsYgISLzNP6eMnhLUY+Oltj/qjAMcPEssCeHN2+XGfP/CVtU+O7sC+5JY2bQGaTs6HQ/Qw==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.0.0", "@math.gl/core": "^3.5.0" @@ -301,8 +285,7 @@ }, "node_modules/@luma.gl/webgl": { "version": "8.5.20", - "resolved": "https://registry.npmjs.org/@luma.gl/webgl/-/webgl-8.5.20.tgz", - "integrity": "sha512-p/kt9KztywH4l+09XHoZ4cPFOoE7xlZXIBMT8rxRVgfe1w0lvi7QYh4tOG7gk+iixQ34EyDQacoHCsabdpmqQg==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.0.0", "@luma.gl/constants": "8.5.20", @@ -313,9 +296,8 @@ }, "node_modules/@mapbox/tiny-sdf": { "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-1.2.5.tgz", - "integrity": "sha512-cD8A/zJlm6fdJOk6DqPUV8mcpyJkRz2x2R+/fYcWDYG3oWbG7/L7Yl/WqQ1VZCjnL9OTIMAn6c+BC5Eru4sQEw==", - "dev": true + "dev": true, + "license": "BSD-2-Clause" }, "node_modules/@math.gl/core": { "version": "3.6.3", @@ -410,8 +392,7 @@ }, "node_modules/@types/offscreencanvas": { "version": "2019.7.0", - "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.0.tgz", - "integrity": "sha512-PGcyveRIpL1XIqK8eBsmRBt76eFgtzuPiSTyKHZxnGemp2yzGzWpjYKAfK3wIMiU7eH+851yEpiuP8JZerTmWg==" + "license": "MIT" }, "node_modules/@webassemblyjs/ast": { "version": "1.9.0", @@ -580,17 +561,6 @@ "dev": true, "license": "Apache-2.0" }, - "node_modules/2gl": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/2gl/-/2gl-0.10.2.tgz", - "integrity": "sha512-06Lr1DRJXDMgBop8ObHk73RPv5VY7X+btXbTfgAZiEdtqKmeCOtUqHy1QlInqMdF7mHMwvCUFI4YHJntoVh4xw==", - "dependencies": { - "@2gis/gl-matrix": "^2.4.1" - }, - "bin": { - "buildShaders": "buildShaders.js" - } - }, "node_modules/a-sync-waterfall": { "version": "1.0.1", "dev": true, @@ -934,9 +904,8 @@ }, "node_modules/bindings": { "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "file-uri-to-path": "1.0.0" @@ -2473,9 +2442,8 @@ }, "node_modules/file-uri-to-path": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", "dev": true, + "license": "MIT", "optional": true }, "node_modules/fill-range": { @@ -4168,9 +4136,8 @@ }, "node_modules/nan": { "version": "2.17.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", - "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==", "dev": true, + "license": "MIT", "optional": true }, "node_modules/nanomatch": { @@ -7804,8 +7771,6 @@ }, "@deck.gl/aggregation-layers": { "version": "8.8.17", - "resolved": "https://registry.npmjs.org/@deck.gl/aggregation-layers/-/aggregation-layers-8.8.17.tgz", - "integrity": "sha512-VO56dYe2C6l3mkk865kdfezHwHr7xlbP7EwCITQGbnBeXS0KUKGMik5AZqJMtayLf87+dSnTn6yDw1BXrQYVEA==", "dev": true, "requires": { "@luma.gl/constants": "^8.5.16", @@ -7816,8 +7781,6 @@ }, "@deck.gl/core": { "version": "8.8.17", - "resolved": "https://registry.npmjs.org/@deck.gl/core/-/core-8.8.17.tgz", - "integrity": "sha512-lBJrO4iPa5bQUhLYXKP2C1Xwe/YaGZ/gXfviWzGHblJ972mSdrvE3Ro13fYyODCjmCRtUvdW4AfQ4TQBLLeetQ==", "requires": { "@loaders.gl/core": "^3.2.10", "@loaders.gl/images": "^3.2.10", @@ -7836,8 +7799,6 @@ }, "@deck.gl/layers": { "version": "8.8.17", - "resolved": "https://registry.npmjs.org/@deck.gl/layers/-/layers-8.8.17.tgz", - "integrity": "sha512-6p1F90DqN567lFGlHClY/8d2o7rBiw42WZzoXg35YgRAzPxZr+vuRd41Vji1j1pDnXKdD1l1oXMF9qTwFF6pJg==", "dev": true, "requires": { "@loaders.gl/images": "^3.2.10", @@ -7870,8 +7831,6 @@ }, "@loaders.gl/core": { "version": "3.4.6", - "resolved": "https://registry.npmjs.org/@loaders.gl/core/-/core-3.4.6.tgz", - "integrity": "sha512-9nang7w+McOg3ZippyFNz0+FuJu4bSkQkwpnIjUEr61JUiwpmRy5IakZP/LnmhZuzCy5+lDFj921md/l8Tduaw==", "requires": { "@babel/runtime": "^7.3.1", "@loaders.gl/loader-utils": "3.4.6", @@ -7881,16 +7840,12 @@ "dependencies": { "@probe.gl/env": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@probe.gl/env/-/env-4.0.4.tgz", - "integrity": "sha512-sYNGqesDfWD6dFP5oNZtTeFA4Z6ak5T4a8BNPdNhoqy7PK9w70JHrb6mv+RKWqKXq33KiwCDWL7fYxx2HuEH2w==", "requires": { "@babel/runtime": "^7.0.0" } }, "@probe.gl/log": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@probe.gl/log/-/log-4.0.4.tgz", - "integrity": "sha512-WpmXl6njlBMwrm8HBh/b4kSp/xnY1VVmeT4PWUKF+RkVbFuKQbsU11dA1IxoMd7gSY+5DGIwxGfAv1H5OMzA4A==", "requires": { "@babel/runtime": "^7.0.0", "@probe.gl/env": "4.0.4" @@ -7900,16 +7855,12 @@ }, "@loaders.gl/images": { "version": "3.4.6", - "resolved": "https://registry.npmjs.org/@loaders.gl/images/-/images-3.4.6.tgz", - "integrity": "sha512-zwM6lXATGxxIe27X1gPyqcgXb6ql9gPkoNlb5pWOrF1D8DrFFjTJSLV9p6WGrVKMZKeioJ2PK8ZnkeRiOYkveA==", "requires": { "@loaders.gl/loader-utils": "3.4.6" } }, "@loaders.gl/loader-utils": { "version": "3.4.6", - "resolved": "https://registry.npmjs.org/@loaders.gl/loader-utils/-/loader-utils-3.4.6.tgz", - "integrity": "sha512-AN56Tvomas7bOZlrMEDforJWwS6DuXXFT/MsxOR/2vvzhutLDxYSuG4dZ9g5ejqJkiboDLAL3aghXXWekyToaw==", "requires": { "@babel/runtime": "^7.3.1", "@loaders.gl/worker-utils": "3.4.6", @@ -7918,8 +7869,6 @@ "dependencies": { "@probe.gl/stats": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@probe.gl/stats/-/stats-4.0.4.tgz", - "integrity": "sha512-SDuSY/D4yDL6LQDa69l/GCcnZLRiGYdyvYkxWb0CgnzTPdPrcdrzGkzkvpC3zsA4fEFw2smlDje370QGHwlisg==", "requires": { "@babel/runtime": "^7.0.0" } @@ -7928,8 +7877,6 @@ }, "@loaders.gl/schema": { "version": "3.4.6", - "resolved": "https://registry.npmjs.org/@loaders.gl/schema/-/schema-3.4.6.tgz", - "integrity": "sha512-uvToDknGCEL0TIZY1Rn76lQ0m22kUtVxE0HbgfgzwkT7dlXeT92F5yD4VVVL8qTX4oqh2xUySPQIaiAqdkGCvg==", "dev": true, "requires": { "@types/geojson": "^7946.0.7" @@ -7937,21 +7884,15 @@ }, "@loaders.gl/worker-utils": { "version": "3.4.6", - "resolved": "https://registry.npmjs.org/@loaders.gl/worker-utils/-/worker-utils-3.4.6.tgz", - "integrity": "sha512-1Ns2d5HbJ22u9PDfEqoDWhg2F3zyYZUCQBmfmSW9Yuxblt2M0pRyB01VvAhPCnLovldvesHu+o16FsgkqieybQ==", "requires": { "@babel/runtime": "^7.3.1" } }, "@luma.gl/constants": { - "version": "8.5.20", - "resolved": "https://registry.npmjs.org/@luma.gl/constants/-/constants-8.5.20.tgz", - "integrity": "sha512-5yG+ybkUZ4j6kLPWMZjN4Hun2yLB0MyEpNCRKAUN9/yS9UIWA7unyVxjSf2vnE7k/7dywtxlbXegASNFgNVGxw==" + "version": "8.5.20" }, "@luma.gl/core": { "version": "8.5.20", - "resolved": "https://registry.npmjs.org/@luma.gl/core/-/core-8.5.20.tgz", - "integrity": "sha512-xJr96G6vhYcznYHC84fbeOG3fgNM4lFwj9bd0VPcg/Kfe8otUeN1Hl0AKHCCtNn48PiMSg3LKbaiRfNUMhaffQ==", "requires": { "@babel/runtime": "^7.0.0", "@luma.gl/constants": "8.5.20", @@ -7963,8 +7904,6 @@ }, "@luma.gl/engine": { "version": "8.5.20", - "resolved": "https://registry.npmjs.org/@luma.gl/engine/-/engine-8.5.20.tgz", - "integrity": "sha512-+0ryJ/4gL1pWaEgZimY21jUPt1LYiO6Cqte8TNUprCfAHoAStsuzD7jwgEqnM6jJOUEdIxQ3w0z3Dzw/0KIE+w==", "requires": { "@babel/runtime": "^7.0.0", "@luma.gl/constants": "8.5.20", @@ -7979,8 +7918,6 @@ }, "@luma.gl/gltools": { "version": "8.5.20", - "resolved": "https://registry.npmjs.org/@luma.gl/gltools/-/gltools-8.5.20.tgz", - "integrity": "sha512-5pP6ph9FSX5gHiVWQM1DmYRUnriklzKUG9yaqlQsKEqCFsOcKB0EfK3MfBVXIfsOdP/1bJZ9Dlz/zV19soWVhg==", "requires": { "@babel/runtime": "^7.0.0", "@luma.gl/constants": "8.5.20", @@ -7991,8 +7928,6 @@ }, "@luma.gl/shadertools": { "version": "8.5.20", - "resolved": "https://registry.npmjs.org/@luma.gl/shadertools/-/shadertools-8.5.20.tgz", - "integrity": "sha512-q1lrCZy1ncIFb4mMjsYgISLzNP6eMnhLUY+Oltj/qjAMcPEssCeHN2+XGfP/CVtU+O7sC+5JY2bQGaTs6HQ/Qw==", "requires": { "@babel/runtime": "^7.0.0", "@math.gl/core": "^3.5.0" @@ -8000,8 +7935,6 @@ }, "@luma.gl/webgl": { "version": "8.5.20", - "resolved": "https://registry.npmjs.org/@luma.gl/webgl/-/webgl-8.5.20.tgz", - "integrity": "sha512-p/kt9KztywH4l+09XHoZ4cPFOoE7xlZXIBMT8rxRVgfe1w0lvi7QYh4tOG7gk+iixQ34EyDQacoHCsabdpmqQg==", "requires": { "@babel/runtime": "^7.0.0", "@luma.gl/constants": "8.5.20", @@ -8012,8 +7945,6 @@ }, "@mapbox/tiny-sdf": { "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-1.2.5.tgz", - "integrity": "sha512-cD8A/zJlm6fdJOk6DqPUV8mcpyJkRz2x2R+/fYcWDYG3oWbG7/L7Yl/WqQ1VZCjnL9OTIMAn6c+BC5Eru4sQEw==", "dev": true }, "@math.gl/core": { @@ -8094,9 +8025,7 @@ "dev": true }, "@types/offscreencanvas": { - "version": "2019.7.0", - "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.0.tgz", - "integrity": "sha512-PGcyveRIpL1XIqK8eBsmRBt76eFgtzuPiSTyKHZxnGemp2yzGzWpjYKAfK3wIMiU7eH+851yEpiuP8JZerTmWg==" + "version": "2019.7.0" }, "@webassemblyjs/ast": { "version": "1.9.0", @@ -8245,14 +8174,6 @@ "version": "4.2.2", "dev": true }, - "2gl": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/2gl/-/2gl-0.10.2.tgz", - "integrity": "sha512-06Lr1DRJXDMgBop8ObHk73RPv5VY7X+btXbTfgAZiEdtqKmeCOtUqHy1QlInqMdF7mHMwvCUFI4YHJntoVh4xw==", - "requires": { - "@2gis/gl-matrix": "^2.4.1" - } - }, "a-sync-waterfall": { "version": "1.0.1", "dev": true @@ -8472,8 +8393,6 @@ }, "bindings": { "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", "dev": true, "optional": true, "requires": { @@ -9575,8 +9494,6 @@ }, "file-uri-to-path": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", "dev": true, "optional": true }, @@ -10672,8 +10589,6 @@ }, "nan": { "version": "2.17.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", - "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==", "dev": true, "optional": true }, diff --git a/package.json b/package.json index 70c1512..8b17ed7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@2gis/deck2gis-layer", - "version": "1.2.1", + "version": "2.0.0", "description": "", "main": "dist/deck2gislayer.js", "typings": "dist/types/index.d.ts", @@ -39,7 +39,7 @@ }, "dependencies": { "@deck.gl/core": "8.8.17", - "2gl": "0.10.2" + "@2gis/gl-matrix": "^2.4.6" }, "optionalDependencies": { "fsevents": "^2.3.2" diff --git a/src/2gl/Buffer.ts b/src/2gl/Buffer.ts new file mode 100644 index 0000000..e287c01 --- /dev/null +++ b/src/2gl/Buffer.ts @@ -0,0 +1,371 @@ +import { checkAttributeLocationCount } from './ShaderAttribute'; +import { GLContext } from './types'; + +/** + * Используется для хранения и подготовки данных для передачи в атрибуты шейдера + */ +export class Buffer { + public static readonly ArrayBuffer = 1; + public static readonly ElementArrayBuffer = 2; + + public static readonly StaticDraw = 10; + public static readonly DynamicDraw = 11; + + public static readonly Float = 20; + public static readonly UnsignedByte = 21; + public static readonly UnsignedShort = 22; + public static readonly UnsignedInt = 23; + public static readonly Byte = 24; + public static readonly Short = 25; + public static readonly Int = 26; + + public static readonly defaultOptions: BufferBindOptions = { + itemSize: 3, + dataType: this.Float, + stride: 0, + offset: 0, + normalized: false, + instanceDivisor: 0, + drawType: Buffer.StaticDraw, + }; + + /** + * Размер данных в буфере в байтах + */ + public byteLength: number; + /** + * Тип буфера. Буфер может использоваться для передачи массива данных, + * так и для передачи индексов элементов из данных. + * @type {Buffer.ArrayBuffer | Buffer.ElementArrayBuffer} + */ + public type: number; + + /** + * Параметры для связывания буфера + */ + public options: BufferBindOptions; + + /** + * Указывает, как часто данные буфера будут изменяться. + * @type {Buffer.StaticDraw | Buffer.DynamicDraw} + */ + public drawType: number; + + /** + * Тип элементов в индeксном буфере. Применим только к буферам типа ElementArrayBuffer + * UNSIGNED_INT поддерживается при поддержке расширения OES_element_index_uint (core в WebGL2) + * Определяется автоматически на основе наибольшего элемента в буфере + * @type {Buffer.UnsignedByte | Buffer.UnsignedShort | Buffer.UnsignedInt | null} + */ + public elementsType: number | null = null; + + private _initData: number | BufferSource | null; + /** + * Исходный WebGL буфер + */ + private _glBuffer: WebGLBuffer | null = null; + + /** + * Контекст WebGL, в котором был инициализирован буфер. + * Используется только для удаления буфера, подумать хорошо, прежде чем использовать для чего-то ещё. + */ + private _glContext: GLContext | null = null; + + /** + * @param initData Данные для инита буфера: содержимое буфера или его размер + * @param options Параметры передачи буфера в видеокарту, + * могут быть переопределены из {@link BufferChannel} + * @param isElementArray Флаг определяющий является ли буффер индексным (если true) + * или повертексным (если false) + */ + constructor( + initData: DataView | TypedArray | ArrayBuffer | number, + options?: Partial, + isElementArray = false, + ) { + this._initData = initData; + this.byteLength = typeof initData === 'number' ? initData : initData.byteLength; + this.type = isElementArray ? Buffer.ElementArrayBuffer : Buffer.ArrayBuffer; + this.options = Object.assign({}, Buffer.defaultOptions, options); + this.drawType = options?.drawType ?? Buffer.StaticDraw; + + const supportedElementArrayTypes = [ + Buffer.UnsignedByte, + Buffer.UnsignedShort, + Buffer.UnsignedInt, + ]; + if (isElementArray && !supportedElementArrayTypes.includes(this.options.dataType)) { + console.warn( + 'Please provide dataType of one of the following values:' + + 'Buffer.UnsignedByte, Buffer.UnsignedShort, Buffer.UnsignedInt. Defaulting to UnsignedInt', + ); + this.options.dataType = Buffer.UnsignedInt; + } + } + + /** + * Возвращает размер типа данных в байтах + * @param {number} dataType + * @ignore + */ + private static _dataTypeSize(dataType: number) { + switch (dataType) { + case Buffer.Byte: + case Buffer.UnsignedByte: + return 1; + case Buffer.UnsignedShort: + case Buffer.Short: + return 2; + case Buffer.Float: + case Buffer.Int: + case Buffer.UnsignedInt: + return 4; + } + + throw new Error(`[2gl] Unsupported Buffer data type: ${String(dataType)}`); + } + + /** + * Связывает данные с контекстом WebGL. + * + * В случае Buffer.ArrayBuffer связывает с атрибутами шейдера. + * А в случае Buffer.ElementArrayBuffer связывает массив индексов. + * + * Если используется первый раз, добавляет данные в контекст WebGL. + * + * @param gl Контекст WebGL + * @param location Положение аттрибута для связывания данных с переменными в шейдере + * @param options Параметры передаваемые в функцию vertexAttribPointer, если их нет, + * то используются параметры конкретного буфера. Параметры должны быть переданы все. + * @param instancesExt Экстеншн для работы с instanced буферами, + * @param locationsCount Количество слотов необходимых атрибуту. По умолчанию равен 1. + */ + public bind( + gl: GLContext, + location?: number | null, + options?: BufferBindOptions | null, + instancesExt?: ANGLE_instanced_arrays | null, + locationsCount?: number, + ) { + if (!this._glBuffer) { + this.prepare(gl); + } + + if (this.type === Buffer.ArrayBuffer) { + gl.bindBuffer(gl.ARRAY_BUFFER, this._glBuffer); + + location = location || 0; + options = options || this.options; + locationsCount = locationsCount ?? 1; + checkAttributeLocationCount(locationsCount); + const type = this._toGlParam(gl, options.dataType) || gl.FLOAT; + + if (locationsCount === 1) { + gl.vertexAttribPointer( + location, + options.itemSize, + type, + options.normalized, + options.stride, + options.offset, + ); + this.bindVertexAttribDivisor(gl, location, options, instancesExt); + } else { + /** Размер элемента в байтах */ + const componentSize = Buffer._dataTypeSize(options.dataType); + + for (let i = 0; i < locationsCount; i++) { + gl.vertexAttribPointer( + location + i, + locationsCount, + type, + options.normalized, + options.stride, + options.offset + i * locationsCount * componentSize, + ); + this.bindVertexAttribDivisor(gl, location + i, options, instancesExt); + } + } + } else if (this.type === Buffer.ElementArrayBuffer) { + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._glBuffer); + } + + return this; + } + + /** + * Удаляет данные из контекста WebGL. + */ + public remove() { + this._unprepare(); + + return this; + } + + /** + * Заменяет часть буфера новыми данными и отправляет их в видеокарту + * @param {WebGLRenderingContext} gl + * @param {Number} index Индекс, с которого начать замену + * @param {TypedArray} data Новые данные + */ + public subData(gl: WebGLRenderingContext, index: number, data: TypedArray) { + const type = this._toGlParam(gl, this.type) || gl.ARRAY_BUFFER; + gl.bindBuffer(type, this._glBuffer); + gl.bufferSubData(type, index, data); + + return this; + } + + /** + * Кладёт данные в видеокарту + * @param gl WebGL Контекст + * @ignore + */ + public prepare(gl: GLContext) { + this._glContext = gl; + this._glBuffer = gl.createBuffer(); + const type = this._toGlParam(gl, this.type) || gl.ARRAY_BUFFER; + const drawType = this._toGlParam(gl, this.drawType) || gl.STATIC_DRAW; + gl.bindBuffer(type, this._glBuffer); + // @ts-ignore + // Типы верны, но в TS перегруженные функции не понимают union-типы + gl.bufferData(type, this._initData, drawType); + this._initData = null; + return this; + } + /** + * Возвращает GL-тип буфера + * @param {WebGLRenderingContext} gl + * @returns {number | null} GL-тип буфера + * @ignore + */ + public getGLType(gl: GLContext) { + return this._toGlParam(gl, this.options.dataType); + } + + /** + * Удаляет данные из видеокарты + * @ignore + */ + private _unprepare() { + if (this._glBuffer && this._glContext) { + this._glContext.deleteBuffer(this._glBuffer); + this._glBuffer = null; + this._glContext = null; + } + } + + /** + * Преобразовывает параметры буфера в параметры WebGL + * @param {WebGLRenderingContext | WebGL2RenderingContext} gl + * @param {Buffer.ArrayBuffer | Buffer.ElementArrayBuffer} param + * @ignore + */ + private _toGlParam(gl: GLContext, param: number) { + if (param === Buffer.ArrayBuffer) { + return gl.ARRAY_BUFFER; + } + if (param === Buffer.ElementArrayBuffer) { + return gl.ELEMENT_ARRAY_BUFFER; + } + if (param === Buffer.StaticDraw) { + return gl.STATIC_DRAW; + } + if (param === Buffer.DynamicDraw) { + return gl.DYNAMIC_DRAW; + } + if (param === Buffer.Byte) { + return gl.BYTE; + } + if (param === Buffer.Short) { + return gl.SHORT; + } + if (param === Buffer.Int) { + return gl.INT; + } + if (param === Buffer.Float) { + return gl.FLOAT; + } + if (param === Buffer.UnsignedByte) { + return gl.UNSIGNED_BYTE; + } + if (param === Buffer.UnsignedShort) { + return gl.UNSIGNED_SHORT; + } + if (param === Buffer.UnsignedInt) { + return gl.UNSIGNED_INT; + } + return null; + } + + private _hasRealWebGLContext() { + return ( + typeof window !== 'undefined' && + ('WebGLRenderingContext' in window || 'WebGL2RenderingContext' in window) + ); + } + + private bindVertexAttribDivisor( + gl: GLContext, + location: number, + options: BufferBindOptions, + instancesExt?: ANGLE_instanced_arrays | null, + ) { + if (options.instanceDivisor && this._hasRealWebGLContext()) { + if (gl instanceof WebGLRenderingContext) { + if (instancesExt) { + instancesExt.vertexAttribDivisorANGLE(location, options.instanceDivisor); + } else { + console.error( + "Can't set up instanced attribute divisor. " + + 'Missing ANGLE_instanced_arrays extension', + ); + } + } else { + gl.vertexAttribDivisor(location, options.instanceDivisor); + } + } + } +} + +/** + * Параметры передаваемые в функцию vertexAttribPointer. + * + */ +export interface BufferBindOptions { + /** + * @property {Number} Размерность элементов в атрибуте. Возможные значения: + * 1 - `float`, + * 2 - `vec2`, + * 3 - `vec3`, + * 4 - `vec4`, + * 9 - `mat3`, + * 16 - `mat4`, + */ + itemSize: number; + /** + * @property {Buffer.Float | Buffer.UnsignedByte} dataType Тип данных в буфере + */ + dataType: number; + /** + * @property {Boolean} normalized Используется для целочисленных типов. Если выставлен в true, то + * значения имеющие тип BYTE от -128 до 128 будут переведены от -1.0 до 1.0. + */ + normalized: boolean; + /** + * @property {Number} stride + */ + stride: number; + /** + * @property {Number} offset + */ + offset: number; + /** + * @property {Number} instanceDivisor + */ + instanceDivisor: number; + /** + * @property {Buffer.StaticDraw | Buffer.DynamicDraw} drawType + */ + drawType: number; +} diff --git a/src/2gl/BufferChannel.ts b/src/2gl/BufferChannel.ts new file mode 100644 index 0000000..827fab2 --- /dev/null +++ b/src/2gl/BufferChannel.ts @@ -0,0 +1,40 @@ +import { Buffer, BufferBindOptions } from './Buffer'; +import { GLContext } from './types'; + +/** + * Класс BufferChannel используется, если данные в обычном буфере имееют разные типы + * и предназначены для разных атрибутов шейдера, т.е. нужно использовать webgl параметры stride и offset. + * При инициализации классу передаётся {@link Buffer}. Несколько BufferChannel могут использовать один и тот же Buffer. + * Во время рендеринга BufferChannel связывает полученный буфер с нужными параметрами. + * + * @param {Buffer} buffer Типизированный массив данных, например, координат вершин + * @param {BufferBindOptions} options + */ +export class BufferChannel { + /** + * Параметры для связывания буфера + */ + public options: BufferBindOptions; + /** + * Исходный буфер + */ + private _buffer: Buffer; + constructor(buffer: Buffer, options = {}) { + this._buffer = buffer; + this.options = Object.assign({}, Buffer.defaultOptions, options); + } + + /** + * Связывает данные с контекстом WebGL с нужными параметрами. + * Вызывает {@link Buffer#bind} исходного буфера. + */ + public bind( + gl: GLContext, + location: number, + options?: BufferBindOptions, + instancesExt?: ANGLE_instanced_arrays, + locationsCount?: number, + ) { + this._buffer.bind(gl, location, options || this.options, instancesExt, locationsCount); + } +} diff --git a/src/2gl/Object3D.ts b/src/2gl/Object3D.ts new file mode 100644 index 0000000..65cbf4b --- /dev/null +++ b/src/2gl/Object3D.ts @@ -0,0 +1,159 @@ +import * as vec3 from '@2gis/gl-matrix/vec3'; +import * as mat4 from '@2gis/gl-matrix/mat4'; +import * as quat from '@2gis/gl-matrix/quat'; + +/** + * Базовый класс для 3D объектов. + */ +export class Object3D { + /** + * Каждый Object3D может включать в себя другие объекты. + * Позиция, поворот и масштаб дочерних объектов будет зависеть от родителя. + */ + public children: Object3D[] = []; + /** + * Родитель, т.е. объект в котором данный Object3D будет дочерним + */ + public parent: Object3D | null = null; + /** + * Будет ли объект отображаться на сцене, если нет, то все дочерние объекты тоже не будут отображаться. + */ + public visible = true; + /** + * Масштаб объекта + */ + public scale = vec3.fromValues(1, 1, 1); + /** + * Позиция объекта в локальной системе координат относительно родителя + */ + public position = vec3.create(); + /** + * Отвечает за поворот объекта + */ + public quaternion = quat.create(); + /** + * Матрица определяющая поворот, масштаб и позицию объекта в локальной системе координат + * относительно родителя. + */ + public localMatrix = mat4.create(); + /** + * Матрица определяющая поворот, масштаб и позицию объекта в глобальной системе координат. + */ + public worldMatrix = mat4.create(); + /** + * Если true, то worldMatrix будет обновлена перед рендерингом + */ + public worldMatrixNeedsUpdate = false; + + /** + * Добавляет дочерний объект + * @param object Дочерний объект + */ + public add(object: Object3D) { + if (object.parent) { + object.parent.remove(object); + } + + object.parent = this; + this.children.push(object); + + return this; + } + + /** + * Убирает дочерний объект + * @param object Дочерний объект + */ + public remove(object: Object3D) { + const index = this.children.indexOf(object); + + if (index !== -1) { + object.parent = null; + this.children.splice(index, 1); + } + + return this; + } + + /** + * Вызывается рендером для подготовки и отрисовки объекта. + */ + public render() { + if (!this.visible) { + return this; + } + + if (this.worldMatrixNeedsUpdate) { + this.updateWorldMatrix(); + } + + return this; + } + + /** + * Обновляет локальную матрицу объекта. Необходимо использовать каждый раз после изменения position, scale + * и quaternion. + * */ + public updateLocalMatrix() { + mat4.fromRotationTranslationScale( + this.localMatrix, + this.quaternion, + this.position, + this.scale, + ); + + this.worldMatrixNeedsUpdate = true; + + return this; + } + + /** + * Обновляет глобальную матрицу объекта. + * */ + public updateWorldMatrix() { + if (this.parent) { + mat4.mul(this.worldMatrix, this.parent.worldMatrix, this.localMatrix); + } else { + mat4.copy(this.worldMatrix, this.localMatrix); + } + + this.children.forEach((child) => child.updateWorldMatrix()); + + this.worldMatrixNeedsUpdate = false; + + return this; + } + + /** + * Возвращает позицию объекта относительно глобальных координат. + */ + public getWorldPosition() { + return vec3.fromValues(this.worldMatrix[12], this.worldMatrix[13], this.worldMatrix[14]); + } + + /** + * Вызывает переданный callback для себя и для каждого дочернего класса. + */ + public traverse(callback: (obj: Object3D) => void) { + callback(this); + + this.children.forEach((child) => child.traverse(callback)); + + return this; + } + + /** + * Работает также как и {@link Object3D#traverse}, но только для объектов с visible = true + */ + public traverseVisible(callback: (obj: Object3D) => void) { + if (!this.visible) { + return this; + } + + callback(this); + + this.children.forEach((child) => child.traverseVisible(callback)); + + return this; + } +} diff --git a/src/2gl/RenderTarget.ts b/src/2gl/RenderTarget.ts new file mode 100644 index 0000000..10a653f --- /dev/null +++ b/src/2gl/RenderTarget.ts @@ -0,0 +1,208 @@ +import { Texture, TextureOptions } from './Texture'; +import { GLContext } from './types'; + +/** + * Используется для создания фреймбуфера, куда можно отрендерить кадр. + */ +export class RenderTarget { + public static defaultOptions = Object.assign({}, Texture.defaultOptions, { + size: [0, 0], + generateMipmaps: false, + depthTexture: false, + }); + /** + * Параметры для связывания фреймбуфера + * @type {RenderTargetOptions} + */ + public readonly options: RenderTargetOptions; + /** + * Текстура создается в конструкторе, чтобы можно было сразу получить на нее ссылку. + */ + private _texture: Texture | null = null; + /** + * Контекст WebGL, в котором был инициализирован фреймбуфер. + * Используется только для удаления, подумать хорошо, прежде чем использовать для чего-то ещё. + */ + private _glContext: GLContext | null = null; + private _frameBuffer: WebGLFramebuffer | null = null; + private _depthBuffer: Texture | WebGLRenderbuffer | null = null; + + constructor(options: Partial = {}) { + this.options = Object.assign({}, RenderTarget.defaultOptions, options); + this._texture = new Texture(null, this.options); + } + + /** + * Связывает компоненты с контекстом WebGL + * @param {WebGLRenderingContext} gl + */ + public bind(gl: GLContext) { + if (!this._frameBuffer) { + this._prepare(gl); + } + + gl.bindFramebuffer(gl.FRAMEBUFFER, this._frameBuffer); + + return this; + } + + /** + * Устанавливает пустой фреймбуфер у контекста WebGL + * @param {WebGLRenderingContext} gl + */ + public unbind(gl: GLContext) { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + return this; + } + + /** + * Удаляет фреймбуфер из видеокарты + */ + public remove() { + this._unprepare(); + return this; + } + + /** + * Устанавливает размер фреймбуферу + * @param {vec2} size + */ + public setSize(size: Vec2) { + this.options.size = size; + this._unprepare(); + return this; + } + + /** + * Возвращает текущую текстуру фреймбуфера + */ + public getTexture(): Texture | null { + return this._texture; + } + + /** + * Возвращает текущие буфер или текстуру глубины фреймбуфера + */ + public getDepthBuffer(): Texture | WebGLRenderbuffer | null { + return this._depthBuffer; + } + + /** + * Инициализирует фреймбуфер, текстуры и рендербуфер + * @param {WebGLRenderingContext} gl + * @ignore + */ + private _prepare(gl: GLContext) { + this._glContext = gl; + + // Проверяем наличие текстуры, т.к. она может быть удалена через метод _unprepare. + if (!this._texture) { + this._texture = new Texture(null, this.options); + } + this._texture.prepare(gl); + + this._frameBuffer = gl.createFramebuffer(); + gl.bindFramebuffer(gl.FRAMEBUFFER, this._frameBuffer); + gl.framebufferTexture2D( + gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0, + gl.TEXTURE_2D, + this._texture.getTexture(), + 0, + ); + + if (this.options.depthTexture) { + const depthBuffer = (this._depthBuffer = new Texture(null, { + magFilter: Texture.NearestFilter, + minFilter: Texture.NearestFilter, + format: Texture.DepthComponentFormat, + size: this.options.size, + premultiplyAlpha: false, + generateMipmaps: false, + type: Texture.UnsignedInt, + })); + depthBuffer.prepare(gl); + gl.framebufferTexture2D( + gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthBuffer.getTexture(), + 0, + ); + } else { + this._depthBuffer = gl.createRenderbuffer(); + gl.bindRenderbuffer(gl.RENDERBUFFER, this._depthBuffer); + gl.renderbufferStorage( + gl.RENDERBUFFER, + gl.DEPTH_COMPONENT16, + this.options.size[0], + this.options.size[1], + ); + gl.framebufferRenderbuffer( + gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.RENDERBUFFER, + this._depthBuffer, + ); + } + + this._checkComplete(gl); + + gl.bindRenderbuffer(gl.RENDERBUFFER, null); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + } + + /** + * Удаляет данные из видеокарты + * @ignore + */ + private _unprepare() { + if (this._texture) { + this._texture.remove(); + this._texture = null; + } + + if (this._glContext && this._depthBuffer) { + this._depthBuffer instanceof Texture + ? this._depthBuffer.remove() + : this._glContext.deleteRenderbuffer(this._depthBuffer); + this._depthBuffer = null; + } + + if (this._glContext && this._frameBuffer) { + this._glContext.deleteFramebuffer(this._frameBuffer); + this._frameBuffer = null; + } + } + + /** + * Проверяет инициализацию фреймбуфера + * @param {WebGLRenderingContext} gl + * @ignore + */ + private _checkComplete(gl: GLContext) { + const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER); + + if (status === gl.FRAMEBUFFER_COMPLETE) { + return; + } else if (status === gl.FRAMEBUFFER_UNSUPPORTED) { + console.log('Framebuffer is unsupported'); + } else if (status === gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT) { + console.log('Framebuffer incomplete attachment'); + } else if (status === gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS) { + console.log('Framebuffer incomplete dimensions'); + } else if (status === gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT) { + console.log('Framebuffer incomplete missing attachment'); + } else { + console.log(`Unexpected framebuffer status: ${status}`); + } + } +} + +/** + * Параметры связывания текстуры + */ +interface RenderTargetOptions extends TextureOptions { + size: Vec2; + depthTexture: boolean; +} diff --git a/src/2gl/Renderer.ts b/src/2gl/Renderer.ts new file mode 100644 index 0000000..a125966 --- /dev/null +++ b/src/2gl/Renderer.ts @@ -0,0 +1,157 @@ +import { GLContext } from './types'; + +/** + * Используется для инициализация WebGL контекста и отрисовки объектов. + * Для некоторых объектов может использовать специфичные рендеры. + * + * @param {Object} options + * @param {HTMLElement} [options.canvas] Элемент canvas + * @param {GLContext} [options.gl] Если элемент canvas не указан, то можно напрямую передать WebGL контекст + * @param {Number} [options.pixelRatio=1] Pixel ratio экрана + * @param {Boolean} [options.antialias=true] Использовать ли антиалиасинг + * @param {Boolean} [options.stencil=false] Использовать ли stencil buffer + * @param {Number[]} [options.clearColor=[1,1,1,1]] Цвет заливки в формате RGBA + * @param {Boolean} [options.preserveDrawingBuffer=false] Сохранять ли содержимое Drawing Buffer + * (может влиять на производительность) + * */ +export class Renderer { + public clearColor: Vec4; + public webGlExtensions: Record = {}; + protected _gl: GLContext; + private _canvasElement: HTMLCanvasElement | null = null; + private _pixelRatio: number; + private _size: Vec2 = [1, 1]; + constructor(options: RendererOptions) { + options = options || {}; + + if ('canvas' in options) { + this._canvasElement = + typeof options.canvas === 'string' + ? (document.getElementById(options.canvas) as HTMLCanvasElement) + : options.canvas; + + const attributes = { + antialias: options.antialias !== undefined ? options.antialias : true, + stencil: options.stencil !== undefined ? options.stencil : false, + failIfMajorPerformanceCaveat: + options.failIfMajorPerformanceCaveat !== undefined + ? options.failIfMajorPerformanceCaveat + : false, + preserveDrawingBuffer: + options.preserveDrawingBuffer !== undefined + ? options.preserveDrawingBuffer + : false, + }; + + this._gl = ( + options.version === 2 + ? this._canvasElement.getContext('webgl2', attributes) + : this._canvasElement.getContext('webgl', attributes) || + this._canvasElement.getContext('experimental-webgl', attributes) + ) as GLContext; + } else { + this._gl = options.gl; + } + + this._pixelRatio = options.pixelRatio || 1; + + /** + * Цвет заливки в формате RGBA + * @type {Array} + */ + this.clearColor = options.clearColor || [1, 1, 1, 1]; + + /** + * Список включенных WebGL расширений + * @type {Object} + */ + this.webGlExtensions = {}; + } + + /** + * Устанавливает параметр pixel ratio + * @param {Number} value + */ + public setPixelRatio(value: number) { + this._pixelRatio = value; + + return this; + } + + /** + * Возвращает текущий pixel ratio + * @returns {Number} + */ + public getPixelRatio() { + return this._pixelRatio; + } + + /** + * Устанавливает размеры элементу canvas и viewport для WebGL + * @param {Number} width Ширина в пикселях + * @param {Number} height Высота в пикселях + */ + public setSize(width: number, height: number) { + this._size = [width * this._pixelRatio, height * this._pixelRatio]; + + if (this._canvasElement) { + this._canvasElement.width = this._size[0]; + this._canvasElement.height = this._size[1]; + this._canvasElement.style.width = `${width}px`; + this._canvasElement.style.height = `${height}px`; + } + + this.setViewport(); + + return this; + } + + /** + * Устанавливает viewport для WebGL + * Если размеры не указаны, то выставляет размеры указанные в функции {@link Renderer#setSize} + * @param {Number} [width] Ширина в пикселях + * @param {Number} [height] Высота в пикселях + */ + public setViewport(width?: number, height?: number) { + if (width !== undefined && height !== undefined) { + this._gl.viewport(0, 0, width, height); + } else { + this._gl.viewport(0, 0, this._size[0], this._size[1]); + } + + return this; + } + + /** + * Возвращает текущий viewport WebGL + * @returns {Array} + */ + public getSize() { + return this._size; + } + + /** + * Включает расширение WebGL + * + * @param {String} name Название расширения + */ + public addExtension(name: string) { + this.webGlExtensions[name] = this._gl.getExtension(name); + return this; + } +} + +interface RendererOptsWithGL { + pixelRatio?: number; + clearColor?: [number, number, number, number]; + gl: GLContext; +} + +interface RendererOptsWithCanvas extends WebGLContextAttributes { + version?: number; + pixelRatio?: number; + clearColor?: [number, number, number, number]; + canvas: HTMLCanvasElement | string; +} + +type RendererOptions = RendererOptsWithGL | RendererOptsWithCanvas; diff --git a/src/2gl/Shader.ts b/src/2gl/Shader.ts new file mode 100644 index 0000000..b87c6ae --- /dev/null +++ b/src/2gl/Shader.ts @@ -0,0 +1,144 @@ +import { GLContext, ShaderDefinitions } from './types'; + +/** + * Шейдер компилирует код и хранит его в видеокарте. + * Один шейдер может быть использован для нескольких программ. + * + * @param type Тип шейдера: или vertex, или fragment + * @param code Код шейдера написанный на языке GLSL. + * Можно передать несколько строк в виде массива, тогда перед компиляцией строки сложатся. + * @param definitions Список #define попадающих в код шейдера + */ +export class Shader { + public static Vertex = 1; + public static Fragment = 2; + /** + * Тип шейдера + * @type {Shader.Vertex | Shader.Fragment} + */ + public type: number; + + /** + * Код шейдера + */ + private sourceCode: string | string[]; + private shader: WebGLShader | null = null; + private defines: ShaderDefinitions; + + constructor(type: string, code: string | string[], definitions: ShaderDefinitions = []) { + this.type = type === 'vertex' ? Shader.Vertex : Shader.Fragment; + this.sourceCode = code; + this.defines = definitions; + } + + /** + * Возвращает webgl шейдер для связывания с программой. + * Если шейдер используюется первый раз, то компилирует его. + * @param gl Контекст WebGL + * @param [externalDefinitions] Внешние #define, которые могут перезаписать существующие определения + */ + public get(gl: GLContext, externalDefinitions?: ShaderDefinitions) { + if (!this.shader) { + this.compile(gl, externalDefinitions); + } + return this.shader; + } + + /** + * Удаляет шейдер из видеокарты + * @param gl Контекст WebGl + */ + public remove(gl: GLContext) { + if (this.shader) { + gl.deleteShader(this.shader); + } + } + + /** + * Возвращает текстовый код шейдера + */ + public getCode() { + return this.assembleShaderText().slice(); + } + + /** + * Компилирует данный шейдер + */ + private compile(gl: GLContext, externalDefinitions?: ShaderDefinitions) { + const glType = this.type === Shader.Vertex ? gl.VERTEX_SHADER : gl.FRAGMENT_SHADER; + const shader = (this.shader = gl.createShader(glType)); + + if (!shader || gl.isContextLost()) { + throw new Error( + `[2gl] Failed to create shader. Shader is null: ${String( + !shader, + )}. Context is lost: ${String(gl.isContextLost())}`, + ); + } + + if (externalDefinitions) { + this.combineDefintions(externalDefinitions); + } + const code = this.assembleShaderText(); + gl.shaderSource(shader, code); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { + const infoLog = gl.getShaderInfoLog(shader); + const codeLines = (code || '').split('\n'); + throw new Error( + infoLog + ? infoLog.replace( + /^ERROR:\s*(\d+):(\d+):\s*(.*?)\n/, + // It's useful to inject erroneous line of code + // in the error message to concise what happened + function (wholeMatch: string, col: number, row: number, message: string) { + const line = codeLines[Number(row) - 1]; + if (line) { + return `ERROR ${col}:${row}: ${message}\nErroneous line: <<${line}>>\n`; + } else { + return wholeMatch; + } + }, + ) + : 'Unknown shader compilation error', + ); + } + } + + private combineDefintions(externalDefinitions: ShaderDefinitions) { + for (const { type, value } of externalDefinitions) { + const existingDef = this.defines.find((d) => d.type === type); + if (existingDef) { + existingDef.value = value; + } else { + this.defines.push({ type, value }); + } + } + } + + private assembleShaderText() { + const code = this.sourceCode; + const result = this.defines.map((def) => { + if (def.value !== undefined) { + return '#define ' + def.type + ' ' + def.value; + } else { + return '#define ' + def.type; + } + }); + + const lines = Array.isArray(code) ? code : [code || '']; + let firstLine = true; + for (const line of lines) { + // Если в шейдерах указана версия, то ее нужно обязательно + // поместить первой строкой + if (firstLine && line.indexOf('#version') !== -1) { + result.unshift(line); + } else { + result.push(line); + } + firstLine = false; + } + return result.join('\n'); + } +} diff --git a/src/2gl/ShaderAttribute.ts b/src/2gl/ShaderAttribute.ts new file mode 100644 index 0000000..2241a50 --- /dev/null +++ b/src/2gl/ShaderAttribute.ts @@ -0,0 +1,90 @@ +import { Buffer } from './Buffer'; +import { BufferChannel } from './BufferChannel'; +import { GLContext } from './types'; + +/** + * Шейдерный атрибут, используется только {@link ShaderProgram} + * @param {AttributeDefinition} options Опции создания атрибута + */ +export class ShaderAttribute { + public name: string; + public index: boolean | undefined; + public location: number; + public locationsCount: number; + private _enable = false; + constructor(options: AttributeDefinition) { + this.name = options.name; + this.index = options.index; + this.location = options.location !== undefined ? options.location : -1; + this.locationsCount = options.locationsCount ?? 1; + checkAttributeLocationCount(this.locationsCount); + } + public bindLocation(gl: GLContext, shaderProgram: WebGLProgram) { + if (this.location !== -1 && this.index !== true) { + gl.bindAttribLocation(shaderProgram, this.location, this.name); + } + return this; + } + + public getLocation(gl: GLContext, shaderProgram: WebGLProgram) { + if (this.location === -1 && this.index !== true) { + this.location = gl.getAttribLocation(shaderProgram, this.name); + } + return this; + } + + public bind(gl: GLContext, buffer: Buffer | BufferChannel) { + if (!this._enable && this.index !== true) { + for (let i = 0; i < this.locationsCount; i++) { + gl.enableVertexAttribArray(this.location + i); + } + this._enable = true; + } + buffer.bind(gl, this.location, undefined); + return this; + } + + public disable(gl: GLContext) { + if (this._enable && this.index !== true) { + for (let i = 0; i < this.locationsCount; i++) { + gl.disableVertexAttribArray(this.location + i); + } + this._enable = false; + } + return this; + } +} + +/** + * Описание шейдерного атрибута + */ +export interface AttributeDefinition { + /** + * @property name Название атрибута + */ + name: string; + /** + * @property [index] Если атрибут используется для передачи индексов, то true + */ + index?: boolean; + /** + * @property [location] Можно напрямую выставить location атрибуту, чтобы не вызывался getAttributeLocation + */ + location?: number; + /** + * @property [locationsCount] Количество слотов, которые нужны атрибуту. + * Для атрибутов типа `float`, `vec2`, `vec3` и `vec4` оно равно 1 (значение по умолчанию). + * Для атрибутов типа `mat2`, `mat3` и `mat4` оно равно 2, 3, и 4 соответственно. + */ + locationsCount?: number; +} + +/** + * Проверяет принадлежность количества локаций диапазону 1-4. + * В случае несоответствия генерирует ошибку. + */ +export function checkAttributeLocationCount(locationCount: number) { + if (locationCount < 0 || locationCount > 4) { + throw new Error(`[2gl] Invalid attribute location count. Must be 1, 2, 3 or 4`); + } +} diff --git a/src/2gl/ShaderProgram.ts b/src/2gl/ShaderProgram.ts new file mode 100644 index 0000000..3683da9 --- /dev/null +++ b/src/2gl/ShaderProgram.ts @@ -0,0 +1,185 @@ +import { Shader } from './Shader'; +import { AttributeDefinition, ShaderAttribute } from './ShaderAttribute'; +import { ShaderUniform, UniformDefinition } from './ShaderUniform'; +import { Buffer } from './Buffer'; +import { GLContext, ShaderDefinitions } from './types'; +import { BufferChannel } from './BufferChannel'; + +/** + * Шейдерная программа инициализирует шейдеры, подготавливает и связывает данные с WebGL. + * + * @param {Object} options + * @param {Shader} vertex Вершинный шейдер + * @param {Shader} fragment Фрагментный шейдер + * @param {UniformDefinition[]} [options.uniforms=[]] Описание юниформ + * @param {AttributeDefinition[]} [options.attributes=[]] Описание атрибутов + */ +export class ShaderProgram { + public uniforms: Record = {}; + public attributes: Record = {}; + private _vertexShader: Shader; + private _fragmentShader: Shader; + private _webglProgram: WebGLProgram | null = null; + private _linked = false; + private _located = false; + private _error = false; + constructor(options: { + vertex: Shader; + fragment: Shader; + uniforms?: UniformDefinition[]; + attributes?: AttributeDefinition[]; + }) { + options = options || {}; + + this._vertexShader = options.vertex; + this._fragmentShader = options.fragment; + + options.uniforms = options.uniforms || []; + options.uniforms.forEach((obj) => { + this.uniforms[obj.name] = new ShaderUniform(obj); + }); + + options.attributes = options.attributes || []; + options.attributes.forEach((obj) => { + this.attributes[obj.name] = new ShaderAttribute(obj); + }); + } + + /** + * Инициализирует программу с контекстом WebGl + * @param gl + */ + public enable(gl: GLContext, externalDefinitions?: ShaderDefinitions) { + if (this._error) { + return this; + } + this.link(gl, externalDefinitions); + this.locate(gl); + if (this._error) { + return this; + } + + gl.useProgram(this._webglProgram); + + return this; + } + + /** + * Связывает юниформы и атрибуты программы с контекстом WebGl + * + * @param gl + * @param [uniforms] Key-value объект содержащий значения юниформ + * @param [attributes] Key-value объект содержащий значения атрибутов + */ + public bind( + gl: GLContext, + uniforms?: Record, + attributes?: Record, + ) { + if (this._error) { + return this; + } + if (uniforms) { + for (const name in uniforms) { + this.uniforms[name].bind(gl, uniforms[name]); + } + } + + if (attributes) { + for (const name in attributes) { + this.attributes[name].bind(gl, attributes[name]); + } + } + + return this; + } + + /** + * Выключает программу + * @param gl + */ + public disable(gl: GLContext) { + if (this._error) { + return this; + } + + for (const name in this.attributes) { + this.attributes[name].disable(gl); + } + + return this; + } + + /** + * Компилирует шейдеры и слинковывает программу. + * Одна из двух необходимых функций для работы шейдерной программы. + * @param gl + */ + public link(gl: GLContext, externalDefinitions?: ShaderDefinitions) { + // Сейчас ничего не произойдет, если после линковки повторно вызвать link с другими externalDefinitions. + // По-идее, должна произойти пересборка шейдеров + новая линковка + if (this._linked || this._error) { + return this; + } + + try { + this._webglProgram = gl.createProgram(); + if (!this._webglProgram) { + throw new Error('Failed to create shader program'); + } + + const vs = this._vertexShader.get(gl, externalDefinitions); + const fs = this._fragmentShader.get(gl, externalDefinitions); + + if (vs) { + gl.attachShader(this._webglProgram, vs); + } + + if (fs) { + gl.attachShader(this._webglProgram, fs); + } + + for (const name in this.attributes) { + this.attributes[name].bindLocation(gl, this._webglProgram); + } + + gl.linkProgram(this._webglProgram); + if (!gl.getProgramParameter(this._webglProgram, gl.LINK_STATUS)) { + throw new Error( + gl.getProgramInfoLog(this._webglProgram) || + "Couldn't get shader program Info Log", + ); + } + + this._linked = true; + } catch (error) { + this._error = true; + throw error; + } + + return this; + } + + /** + * Лоцирует атрибуты и юниформе на основе шейдера. + * Одна из двух необходимых функций для работы шейдерной программы. + * @param gl + */ + public locate(gl: GLContext) { + if (this._located || this._error || !this._webglProgram) { + return this; + } + + for (const name in this.attributes) { + this.attributes[name].getLocation(gl, this._webglProgram); + } + + for (const name in this.uniforms) { + this.uniforms[name].getLocation(gl, this._webglProgram); + } + + this._located = true; + + return this; + } +} diff --git a/src/2gl/ShaderUniform.ts b/src/2gl/ShaderUniform.ts new file mode 100644 index 0000000..db69a98 --- /dev/null +++ b/src/2gl/ShaderUniform.ts @@ -0,0 +1,125 @@ +import { GLContext } from './types'; + +export type UniformTypeNumber = '1f' | '1i'; + +export type UniformTypeArray = + | '2i' + | '3i' + | '4i' + | '2f' + | '3f' + | '4f' + | '1fv' + | '2fv' + | '3fv' + | '4fv' + | '1iv' + | '2iv' + | '3iv' + | '4iv' + | 'mat2' + | 'mat3' + | 'mat4'; + +export type UniformType = UniformTypeNumber | UniformTypeArray; + +/** + * Шейдерная юниформа, используется только {@link ShaderProgram} + * + * @param {UniformDefinition} options + * @ignore + */ +export class ShaderUniform { + public name: string; + public type: string; + public location: WebGLUniformLocation | null = null; + constructor(options: UniformDefinition) { + this.name = options.name; + this.type = options.type; + } + + public getLocation(gl: GLContext, webglProgram: WebGLProgram) { + this.location = gl.getUniformLocation(webglProgram, this.name); + return this; + } + + public bind(gl: GLContext, value: any) { + switch (this.type) { + case '1i': + gl.uniform1i(this.location, value); + break; + case '1f': + gl.uniform1f(this.location, value); + break; + case '2i': + gl.uniform2i(this.location, value[0], value[1]); + break; + case '2f': + gl.uniform2f(this.location, value[0], value[1]); + break; + case '3i': + gl.uniform3i(this.location, value[0], value[1], value[2]); + break; + case '3f': + gl.uniform3f(this.location, value[0], value[1], value[2]); + break; + case '4i': + gl.uniform4i(this.location, value[0], value[1], value[2], value[3]); + break; + case '4f': + gl.uniform4f(this.location, value[0], value[1], value[2], value[3]); + break; + case '1iv': + gl.uniform1iv(this.location, value); + break; + case '1fv': + gl.uniform1fv(this.location, value); + break; + case '2iv': + gl.uniform2iv(this.location, value); + break; + case '2fv': + gl.uniform2fv(this.location, value); + break; + case '3iv': + gl.uniform3iv(this.location, value); + break; + case '3fv': + gl.uniform3fv(this.location, value); + break; + case '4iv': + gl.uniform4iv(this.location, value); + break; + case '4fv': + gl.uniform4fv(this.location, value); + break; + case 'mat2': + gl.uniformMatrix2fv(this.location, false, value); + break; + case 'mat3': + gl.uniformMatrix3fv(this.location, false, value); + break; + case 'mat4': + gl.uniformMatrix4fv(this.location, false, value); + break; + default: + throw new Error(`[2gl] Unsupported uniform type: '${this.type}'`); + } + + return this; + } +} + +/** + * Описание шейдерной юниформы + */ +export interface UniformDefinition { + /** + * Название юниформы + */ + name: string; + /** + * Тип юниформы, может быть: mat[234], [1234][fi], [1234][fi]v + */ + type: UniformType; +} diff --git a/src/2gl/Texture.ts b/src/2gl/Texture.ts new file mode 100644 index 0000000..224ba0b --- /dev/null +++ b/src/2gl/Texture.ts @@ -0,0 +1,300 @@ +import { GLContext } from './types'; + +/** + * Текстуры используются для отрисовки изображений в WebGL + */ +export class Texture { + public static readonly ClampToEdgeWrapping = 8; + public static readonly Repeat = 9; + public static readonly MirroredRepeat = 10; + + public static readonly NearestFilter = 1; + public static readonly NearestMipMapNearestFilter = 2; + public static readonly NearestMipMapLinearFilter = 3; + public static readonly LinearFilter = 4; + public static readonly LinearMipMapNearestFilter = 5; + public static readonly LinearMipMapLinearFilter = 6; + + public static readonly DepthComponentFormat = 7; + public static readonly RgbaFormat = 11; + public static readonly AlphaFormat = 12; + public static readonly RgbFormat = 13; + + public static readonly UnsignedByte = 14; + public static readonly Float = 15; + public static readonly UnsignedInt = 16; + + public static readonly RedFormat = 17; + + public static readonly defaultOptions: TextureOptions = { + magFilter: Texture.LinearFilter, + minFilter: Texture.LinearMipMapLinearFilter, + wrapS: Texture.ClampToEdgeWrapping, + wrapT: Texture.ClampToEdgeWrapping, + format: Texture.RgbaFormat, + generateMipmaps: true, + flipY: true, + premultiplyAlpha: true, + type: Texture.UnsignedByte, + }; + + /** + * Параметры для связывания текстуры + */ + public readonly options: TextureOptions; + + private _src: TexImageSource | ArrayBufferView | null = null; + + /** + * Контекст WebGL, в котором была инициализирована текстура. + * Используется только для удаления, подумать хорошо, прежде чем использовать для чего-то ещё. + * @ignore + */ + private _glContext: GLContext | null = null; + private _texture: WebGLTexture | null = null; + + /** + * @param {TexImageSource} [src=null] В качестве + * изображения может быть либо элемент img, либо canvas + * @param {?TextureOptions} options + */ + constructor( + src: TexImageSource | ArrayBufferView | null = null, + options: TextureOptions | Record = {}, + ) { + this._src = src; + this.options = Object.assign({}, Texture.defaultOptions, options); + } + + /** + * Связывает WebGL и данные текстуры. + * При первом вызов происходит инициализация. + * + * @param {WebGLRenderingContext} gl + * @param {?Number} index Номер текстуры в контексте WebGL. + * Если его нет, используется уже активированный юнит текстуры. + */ + public enable(gl: GLContext, index?: number) { + const unit = index ?? this.options.unit; + + if (unit !== undefined) { + gl.activeTexture(gl.TEXTURE0 + unit); + } + + if (!this._texture) { + this.prepare(gl); + } + + gl.bindTexture(gl.TEXTURE_2D, this._texture); + + return this; + } + + /** + * Удаляет текстуру из видеокарты + */ + public remove() { + if (this._texture && this._glContext) { + this._glContext.deleteTexture(this._texture); + this._glContext = null; + this._texture = null; + } + + return this; + } + + /** + * Возвращает WebGL текстуру + * @return {WebGLTexture} + */ + public getTexture() { + return this._texture; + } + + /** + * Обновляет часть текстуры + * + * @param {WebGLRenderingContext} gl + * @param {HTMLImageElement | HTMLCanvasElement | ImageBitmap | ImageData | TypedArray} src + * @param {number} x Горизонтальное смещение, с которого записываем в текстуру + * @param {number} y Вертикальное смещение, с которого записываем в текстуру + */ + public subImage(gl: GLContext, src: TexImageSource, x: number, y: number) { + gl.bindTexture(gl.TEXTURE_2D, this._texture); + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, this.options.flipY); + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this.options.premultiplyAlpha); + const format = this._toGlParam(gl, this.options.format); + const type = this._toGlParam(gl, this.options.type ?? Texture.UnsignedByte); + if (format === null || type === null) { + return this; + } + + gl.texSubImage2D(gl.TEXTURE_2D, 0, x, y, format, type, src); + + return this; + } + + /** + * Кладёт текстуру в видеокарту + * @param {WebGLRenderingContext} gl + */ + public prepare(gl: GLContext) { + this._glContext = gl; + this._texture = gl.createTexture(); + + gl.bindTexture(gl.TEXTURE_2D, this._texture); + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, this.options.flipY); + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this.options.premultiplyAlpha); + + const format = this._toGlParam(gl, this.options.format); + const type = this._toGlParam(gl, this.options.type ?? Texture.UnsignedByte); + const wrapS = this._toGlParam(gl, this.options.wrapS); + const wrapT = this._toGlParam(gl, this.options.wrapT); + const magFilter = this._toGlParam(gl, this.options.magFilter ?? Texture.LinearFilter); + const minFilter = this._toGlParam( + gl, + this.options.minFilter ?? Texture.LinearMipMapLinearFilter, + ); + + if (format !== null && type !== null) { + const internalFormat = this.hookInternalFormat(format, type, gl); + // В случае, если options.size определен, то _src должен быть либо null либо ArrayBufferView + if (ArrayBuffer.isView(this._src) || this._src === null) { + if (this.options.size) { + gl.texImage2D( + gl.TEXTURE_2D, + 0, + internalFormat, + this.options.size[0], + this.options.size[1], + 0, + format, + type, + this._src, + ); + } + } else if (this._src) { + gl.texImage2D(gl.TEXTURE_2D, 0, format, format, type, this._src); + } + } + + if (wrapS !== null && wrapT !== null) { + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrapS); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrapT); + } + + if (magFilter !== null && minFilter !== null) { + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, magFilter); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, minFilter); + } + + if ( + this.options.generateMipmaps && + this.options.minFilter !== Texture.NearestFilter && + this.options.minFilter !== Texture.LinearFilter + ) { + gl.generateMipmap(gl.TEXTURE_2D); + } + + gl.bindTexture(gl.TEXTURE_2D, null); + + return this; + } + + private _toGlParam(gl: GLContext, param: number) { + if (param === Texture.ClampToEdgeWrapping) { + return gl.CLAMP_TO_EDGE; + } + if (param === Texture.Repeat) { + return gl.REPEAT; + } + if (param === Texture.MirroredRepeat) { + return gl.MIRRORED_REPEAT; + } + + if (param === Texture.NearestFilter) { + return gl.NEAREST; + } + if (param === Texture.NearestMipMapNearestFilter) { + return gl.NEAREST_MIPMAP_NEAREST; + } + if (param === Texture.NearestMipMapLinearFilter) { + return gl.NEAREST_MIPMAP_LINEAR; + } + + if (param === Texture.LinearFilter) { + return gl.LINEAR; + } + if (param === Texture.LinearMipMapNearestFilter) { + return gl.LINEAR_MIPMAP_NEAREST; + } + if (param === Texture.LinearMipMapLinearFilter) { + return gl.LINEAR_MIPMAP_LINEAR; + } + if (param === Texture.RgbaFormat) { + return gl.RGBA; + } + if (param === Texture.AlphaFormat) { + return gl.ALPHA; + } + if (param === Texture.RgbFormat) { + return gl.RGB; + } + if (param === Texture.DepthComponentFormat) { + return gl.DEPTH_COMPONENT; + } + + if (param === Texture.UnsignedByte) { + return gl.UNSIGNED_BYTE; + } + if (param === Texture.Float) { + return gl.FLOAT; + } + if (param === Texture.UnsignedInt) { + return gl.UNSIGNED_INT; + } + if (param === Texture.RedFormat && !(gl instanceof WebGLRenderingContext)) { + return gl.RED; + } + + return null; + } + + private hookInternalFormat( + format: number, + type: number, + gl: WebGL2RenderingContext | WebGLRenderingContext, + ) { + if (gl instanceof WebGLRenderingContext) { + return format; + } + if (format === gl.DEPTH_COMPONENT) { + return gl.DEPTH_COMPONENT24; + } + if (type === gl.FLOAT && format === gl.RGBA) { + return gl.RGBA32F; + } + if (type === gl.FLOAT && format === gl.RED) { + return gl.R32F; + } + return format; + } +} + +/** + * Параметры связывания текстуры + */ +export interface TextureOptions { + magFilter: number; + minFilter: number; + wrapS: number; + wrapT: number; + format: number; + generateMipmaps: boolean; + flipY: boolean; + premultiplyAlpha: boolean; + size?: Vec2; + unit?: number; + type?: number; + texcoord?: number; +} diff --git a/src/2gl/Vao.ts b/src/2gl/Vao.ts new file mode 100644 index 0000000..1bfd3c6 --- /dev/null +++ b/src/2gl/Vao.ts @@ -0,0 +1,249 @@ +import { Buffer } from './Buffer'; +import { BufferChannel } from './BufferChannel'; +import { ShaderProgram } from './ShaderProgram'; +import { GLContext } from './types'; + +export interface AttributesAliases { + [shaderAttributeName: string]: string; +} + +export type VaoAttributes = Record; + +/** + * Обертка над vertex array object. + * https://developer.mozilla.org/ru/docs/Web/API/OES_vertex_array_object + * + * Для использования необходимо включить расширение renderer.addExtension('OES_vertex_array_object') + * + */ +export class Vao { + public indicesBuffer: Buffer | null; + private _vao: WebGLVertexArrayObject | null = null; + private _attributes: VaoAttributes; + private _shaderProgram: ShaderProgram; + + /** + * WebGL экстеншен, в котором был инициализирован буфер. + */ + private _vaoExt: OES_vertex_array_object | null = null; + private _gl: GLContext | null = null; + + private attributesAliases: AttributesAliases = {}; + + /** + * @param shaderProgram Шейдерная программа, каждый Vao привязан к одной шейдерной программе. + * @param attributes Key-value объект содержащий данные атрибутов. + * @param indicesBuffer Буффер индексов. + */ + constructor( + shaderProgram: ShaderProgram, + attributes: VaoAttributes, + indicesBuffer: Buffer | null = null, + ) { + this._attributes = attributes; + this._shaderProgram = shaderProgram; + this.indicesBuffer = indicesBuffer; + } + + public static merge(vaos: Vao[]) { + const first = vaos[0]; + const attrs = {}; + const aliases = {}; + for (const vao of vaos) { + Object.assign(attrs, vao._attributes); + Object.assign(aliases, vao.attributesAliases); + } + const vao = new Vao(first._shaderProgram, attrs, first.indicesBuffer); + vao.setAttributesAliases(aliases); + return vao; + } + + /** Копирует Vao */ + public copy() { + const vao = new Vao(this._shaderProgram, this._attributes, this.indicesBuffer); + vao.setAttributesAliases(this.attributesAliases); + return vao; + } + + /** + * Связывает vao с контекстом WebGL. + * + * @param state Стейт рендера + */ + public bind(state: { + gl: GLContext; + extensions: { + OES_vertex_array_object?: OES_vertex_array_object; + ANGLE_instanced_arrays?: ANGLE_instanced_arrays; + }; + }) { + const vaoExt = state.extensions.OES_vertex_array_object; + const instancesExt = state.extensions.ANGLE_instanced_arrays; + + this._bind(state.gl, vaoExt, instancesExt); + + return this; + } + + /** + * Отвязывает vao от контекста WebGL. + * ВНИМАНИЕ: Этот метод нужно вызывать всегда, перед тем как будет использоваться + * стандартный подход для связывания атрибутов через {@link ShaderProgram#bind}. + */ + public unbind() { + this._glBindVertexArray(null); + + return this; + } + + public setAttribute(name: string, buffer: Buffer | BufferChannel) { + this._attributes[name] = buffer; + this.remove(); + } + + /** + * Удаляет vao. + */ + public remove() { + if (this._vao) { + this._glDeleteVertexArray(this._vao); + this._vao = null; + } + + return this; + } + + /** + * Возвращает GL-тип индексного буфера или null + * @param {WebGLRenderingContext | WebGL2RenderingContext} gl Gl-контекст + * @returns {number | null} GL-тип индексного буфера + */ + public getElementsGLType(gl: GLContext) { + if (this.indicesBuffer) { + return this.indicesBuffer.getGLType(gl); + } + return null; + } + + /** + * Карта псевдонимов вида { 'shader_att_name': 'vao_attr_name' }. + * Эти псевдонимы могут использоваться при связке буферов с шейдерными атрибутами в методе bind. + * Например, в Vao задан буфер 'texcoord_0'. В шейдере он может использоваться под именем 'texcoord_color'. + * @param {AttributesAliases} aliases Карта пвседонимов + */ + public setAttributesAliases(aliases: AttributesAliases) { + Object.assign(this.attributesAliases, aliases); + // Инвалидируем текщуий VAO, если он был создан. Нужно привязать атрибуты заново + this.remove(); + } + + private setAttributes(gl: GLContext, instancesExt?: ANGLE_instanced_arrays) { + const shaderAttributes = this._shaderProgram.attributes; + const attributes = this._attributes; + + // Биндим атрибуты переданные в конструктор, их параметры берём из шейдерной программы + for (const attrNameInShader in shaderAttributes) { + const alias = this.attributesAliases[attrNameInShader]; + const attrNameInVao = alias || attrNameInShader; + const vaoAttributeBuffer = attributes[attrNameInVao]; + if (!vaoAttributeBuffer) { + // TODO: TILES-5322: Некоторые шейдерные программы ожидают атрибуты, которые не включаются, + // поскольку они отсутствуют в VAO. В шейдере из таких атрибутов читаются нули, что, видимо, + // нас пока устраивает. В целях консистентности данных стоит добавить все недостающие буферы, + // или подключать шейдерные программы, не требующие отсутвующих в VAO атрибутов. + // Чтобы увидеть все такие атрибуты - раскомментируй console.error ниже. + + // console.error( + // `VAO doesn't have an attribute named "${attrNameInVao}" that is requested by shader program.`, + // ); + continue; + } + const shaderAttribute = shaderAttributes[attrNameInShader]; + if (shaderAttribute.index !== true) { + for (let i = 0; i < shaderAttribute.locationsCount; i++) { + gl.enableVertexAttribArray(shaderAttribute.location + i); + } + } + vaoAttributeBuffer.bind( + gl, + shaderAttribute.location, + undefined, + instancesExt, + shaderAttribute.locationsCount, + ); + } + if (this.indicesBuffer) { + this.indicesBuffer.bind(gl); + } + } + + private _bind( + gl: GLContext, + vaoExt?: OES_vertex_array_object, + instancesExt?: ANGLE_instanced_arrays, + ) { + if (!this._vao) { + this._prepare(gl, vaoExt, instancesExt); + } else { + this._glBindVertexArray(this._vao); + } + } + + private _prepare( + gl: GLContext, + vaoExt?: OES_vertex_array_object, + instancesExt?: ANGLE_instanced_arrays, + ) { + this._gl = gl; + if (vaoExt) { + this._vaoExt = vaoExt; + } + + this._vao = this._glCreateVertexArray(); + this._glBindVertexArray(this._vao); + + this.setAttributes(gl, instancesExt); + } + + private _glCreateVertexArray() { + const gl = this._gl; + const ext = this._vaoExt; + if (gl && this._isWebGL2(gl)) { + return gl.createVertexArray(); + } else if (ext) { + return ext.createVertexArrayOES(); + } + return null; + } + + private _glBindVertexArray(vao: WebGLVertexArrayObject | null) { + const gl = this._gl; + const ext = this._vaoExt; + if (gl && this._isWebGL2(gl)) { + gl.bindVertexArray(vao); + } else if (ext) { + ext.bindVertexArrayOES(vao); + } else if (gl) { + // В случае фоллбека - биндим атрибуты прямо из шейдерной программы + this._shaderProgram.bind(gl, undefined, this._attributes); + } + } + + private _glDeleteVertexArray(vao: WebGLVertexArrayObject) { + const gl = this._gl; + const ext = this._vaoExt; + if (gl && this._isWebGL2(gl)) { + gl.deleteVertexArray(vao); + } else if (ext) { + ext.deleteVertexArrayOES(vao); + } + } + + private _isWebGL2(gl: GLContext): gl is WebGL2RenderingContext { + return ( + typeof window !== 'undefined' && + 'WebGL2RenderingContext' in window && + gl instanceof WebGL2RenderingContext + ); + } +} diff --git a/src/2gl/types.ts b/src/2gl/types.ts new file mode 100644 index 0000000..4b0cd85 --- /dev/null +++ b/src/2gl/types.ts @@ -0,0 +1,2 @@ +export type GLContext = WebGLRenderingContext | WebGL2RenderingContext; +export type ShaderDefinitions = Array<{ value?: string; type: string }>; diff --git a/src/deckgl2gisLayer.ts b/src/deckgl2gisLayer.ts index 4194801..8212557 100644 --- a/src/deckgl2gisLayer.ts +++ b/src/deckgl2gisLayer.ts @@ -1,24 +1,11 @@ // Use fork mapbox layer in deck.gl // https://github.com/visgl/deck.gl/tree/master/modules/mapbox -import { - prepareDeckInstance, - addLayer, - removeLayer, - updateLayer, - drawLayer, - initDeck2gisProps, - onMapResize, -} from './utils'; +import { addLayer, removeLayer, updateLayer, drawLayer, initDeck, onMapResize } from './utils'; import type { Deck, Layer } from '@deck.gl/core/typed'; import { CustomRenderInternalProps, CustomRenderProps, DeckCustomLayer } from './types'; import type { Map } from '@2gis/mapgl/types'; -import RenderTarget from '2gl/RenderTarget'; -import Texture from '2gl/Texture'; -import type Vao from '2gl/Vao'; -import type ShaderProgram from '2gl/ShaderProgram'; - /** * Any Layer class from deck.gl. */ @@ -48,8 +35,6 @@ export class Deck2gisLayer implements DeckCustomLayer { id: string; type: 'custom'; renderingMode: '2d' | '3d'; - map: Map | null; - deck: Deck | null; props: LayerProps | undefined; gl?: WebGLRenderingContext | WebGL2RenderingContext; antialiasing: boolean; @@ -60,13 +45,8 @@ export class Deck2gisLayer implements DeckCustomLayer { * @param map The map instance. * @param deckProps CustomRenderProps initialization options. */ - static initDeck2gisProps = (map: Map, deckProps?: CustomRenderProps) => - initDeck2gisProps(map, deckProps); - - private renderTarget?: RenderTarget; - private msaaFrameBuffer?: WebGLFramebuffer | null; - private program?: ShaderProgram; - private vao?: Vao; + static initDeck = (map: Map, Deck: any, deckProps?: CustomRenderProps) => + initDeck(map, Deck, deckProps); /* eslint-disable no-this-before-super */ /** @@ -93,9 +73,10 @@ export class Deck2gisLayer implements DeckCustomLayer { this.id = props.id; this.type = 'custom'; this.renderingMode = props.renderingMode || '3d'; - this.map = null; - this.deck = null; this.props = props; + this.gl = ( + this.props.deck.props as CustomRenderInternalProps + )._2gisData._2gisMap.getWebGLContext(); this.antialiasing = Boolean(props.antialiasing); } @@ -105,36 +86,9 @@ export class Deck2gisLayer implements DeckCustomLayer { * MapGL calls this method after adding a layer to a map. */ public onAdd = () => { - const map: Map = (this.props?.deck.props as CustomRenderInternalProps)._2gisData._2gisMap; - - // fix wrong initRender use when add layer in map on move - const initBeforeAdd = () => { - if (!this.map && this.props?.deck && !this.isDestroyed) { - const deck = this.props?.deck; - this.map = map; - const gl = (this.gl = map.getWebGLContext()); - if ((map as any).__deck) { - this.deck = (map as any).__deck; - this.renderTarget = (this.deck as any).props._2glRenderTarget; - this.msaaFrameBuffer = (this.deck as any).props._2glMsaaFrameBuffer; - } - if (!this.renderTarget || !this.deck) { - this.initRenderTarget(gl, map, deck); - } - if (this.deck) { - this.program = (this.deck as any).props._2glProgram; - this.vao = (this.deck as any).props._2glVao; - } - } - - if (this.deck && !this.isDestroyed) { - addLayer(this.deck, this); - } - }; - - if ((map as any).__deck) { - initBeforeAdd(); - } else [map.once('idle', () => initBeforeAdd())]; + if (this.props?.deck && !this.isDestroyed) { + addLayer(this.props.deck, this); + } }; /** * @hidden @@ -142,11 +96,9 @@ export class Deck2gisLayer implements DeckCustomLayer { * MapGL calls this method after removing a layer from a map. */ public onRemove = () => { - if (this.deck) { - removeLayer(this.deck, this); + if (this.props && this.props.deck) { + removeLayer(this.props.deck, this); } - this.deck = null; - this.map = null; }; /** @@ -159,8 +111,8 @@ export class Deck2gisLayer implements DeckCustomLayer { Object.assign(this.props, props, { id: this.id }); this.antialiasing = Boolean(props.antialiasing); // safe guard in case setProps is called before onAdd - if (this.deck) { - updateLayer(this.deck, this); + if (this.props.deck) { + updateLayer(this.props.deck, this); } } } @@ -169,11 +121,6 @@ export class Deck2gisLayer implements DeckCustomLayer { * Destroys the layer and frees all related resources. */ public destroy = () => { - this.deck = null; - this.map = null; - this.renderTarget = undefined; - this.program = undefined; - this.vao = undefined; this.gl = undefined; this.isDestroyed = true; this.props = undefined; @@ -185,59 +132,82 @@ export class Deck2gisLayer implements DeckCustomLayer { * MapGL calls this method on each map frame rendering. */ public render = () => { + if (!this.props || !(this.props?.deck as any)?.props) { + return; + } + const renderTarget = (this.props.deck as any).props._2glRenderTarget; + const msaaFrameBuffer = (this.props.deck as any).props._2glMsaaFrameBuffer; + const program = (this.props.deck as any).props._2glProgram; + const vao = (this.props.deck as any).props._2glVao; + if ( this.isDestroyed || - !this.deck || - !(this.deck as any).layerManager || - !this.map || - !this.renderTarget || - !this.program || - !this.vao || + !this.props.deck || + !(this.props.deck as any)?.layerManager || + !(this.props.deck.props as CustomRenderInternalProps)._2gisData._2gisMap || + !renderTarget || + !program || + !vao || !this.gl || !this.props || - !(this.deck.props as CustomRenderInternalProps)._2glRenderTarget + !(this.props.deck.props as CustomRenderInternalProps)._2glRenderTarget || + !(this.props.deck.props as CustomRenderInternalProps)._2gisInitDeck ) { return; } - const mapSize = this.map.getSize(); - const { _2gisData } = this.deck.props as CustomRenderInternalProps; + + const mapSize = ( + this.props.deck.props as CustomRenderInternalProps + )._2gisData._2gisMap.getSize(); + const { _2gisData } = this.props.deck.props as CustomRenderInternalProps; const gl = this.gl; if (_2gisData._2gisFramestart) { - if (this.deck.width !== mapSize[0] || this.deck.height !== mapSize[1]) { - (this.deck as any).animationLoop._resizeCanvasDrawingBuffer(); - (this.deck as any).animationLoop._resizeViewport(); - const renderTarget = this.renderTarget.bind(this.gl); - onMapResize(this.map, this.deck, renderTarget, this.msaaFrameBuffer); + if (this.props.deck.width !== mapSize[0] || this.props.deck.height !== mapSize[1]) { + (this.props.deck as any).animationLoop._resizeCanvasDrawingBuffer(); + (this.props.deck as any).animationLoop._resizeViewport(); + renderTarget.bind(this.gl); + onMapResize( + (this.props.deck.props as CustomRenderInternalProps)._2gisData._2gisMap, + this.props.deck, + renderTarget, + msaaFrameBuffer, + ); } - this.msaaFrameBuffer - ? gl.bindFramebuffer(gl.FRAMEBUFFER, this.msaaFrameBuffer) - : this.renderTarget.bind(gl); + msaaFrameBuffer + ? gl.bindFramebuffer(gl.FRAMEBUFFER, msaaFrameBuffer) + : renderTarget.bind(gl); gl.clearColor(1, 1, 1, 0); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); _2gisData._2gisCurrentViewport = undefined; _2gisData._2gisFramestart = false; } else { - this.msaaFrameBuffer - ? gl.bindFramebuffer(gl.FRAMEBUFFER, this.msaaFrameBuffer) - : this.renderTarget.bind(gl); + msaaFrameBuffer + ? gl.bindFramebuffer(gl.FRAMEBUFFER, msaaFrameBuffer) + : renderTarget.bind(gl); gl.clearColor(1, 1, 1, 0); gl.clear(gl.COLOR_BUFFER_BIT); } - this.renderTarget.unbind(gl); - - drawLayer(this.deck, this.map, this); + renderTarget.unbind(gl); - if (this.msaaFrameBuffer) { + const isDrawed = drawLayer( + this.props.deck, + (this.props.deck.props as CustomRenderInternalProps)._2gisData._2gisMap, + this, + ); + if (!isDrawed) { + return; + } + if (msaaFrameBuffer) { this.blitMsaaFrameBuffer(); } gl.bindFramebuffer(gl.FRAMEBUFFER, null); - const texture = this.renderTarget.getTexture(); + const texture = renderTarget.getTexture(); texture.enable(gl, 0); - this.program.enable(gl); + program.enable(gl); this.programmBinder(); @@ -245,14 +215,24 @@ export class Deck2gisLayer implements DeckCustomLayer { }; private programmBinder() { - if (!this.deck || !this.map || !this.program || !this.vao || !this.gl) { + const program = (this.props?.deck as any)?.props?._2glProgram; + const vao = (this.props?.deck as any)?.props?._2glVao; + if ( + !this.props?.deck || + !(this.props.deck.props as CustomRenderInternalProps)._2gisData._2gisMap || + !program || + !vao || + !this.gl + ) { return; } - const mapSize = this.map.getSize(); + const mapSize = ( + this.props.deck.props as CustomRenderInternalProps + )._2gisData._2gisMap.getSize(); const gl = this.gl; if (this.currentAntialiasingMode() === 'fxaa') { - this.program.bind(gl, { + program.bind(gl, { iResolution: [ mapSize[0] * window.devicePixelRatio, mapSize[1] * window.devicePixelRatio, @@ -261,12 +241,12 @@ export class Deck2gisLayer implements DeckCustomLayer { enabled: 1, }); } else { - this.program.bind(gl, { + program.bind(gl, { u_sr2d_texture: 0, }); } - this.vao.bind({ + vao.bind({ gl, extensions: { OES_vertex_array_object: gl.getExtension('OES_vertex_array_object') }, }); @@ -274,11 +254,15 @@ export class Deck2gisLayer implements DeckCustomLayer { private blitMsaaFrameBuffer() { const gl = this.gl; - const mapSize = this.map?.getSize(); - if (this.msaaFrameBuffer && mapSize && gl && !(gl instanceof WebGLRenderingContext)) { - gl.bindFramebuffer(gl.READ_FRAMEBUFFER, this.msaaFrameBuffer); + const mapSize = ( + this.props?.deck.props as CustomRenderInternalProps + )._2gisData._2gisMap?.getSize(); + const msaaFrameBuffer = (this.props?.deck as any)?.props?._2glMsaaFrameBuffer; + const renderTarget = (this.props?.deck as any)?.props?._2glRenderTarget; + if (msaaFrameBuffer && mapSize && gl && !(gl instanceof WebGLRenderingContext)) { + gl.bindFramebuffer(gl.READ_FRAMEBUFFER, msaaFrameBuffer); - gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, (this.renderTarget as any)._frameBuffer); + gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, (renderTarget as any)._frameBuffer); gl.clearBufferfv(gl.COLOR, 0, [0.0, 0.0, 0.0, 0.0]); @@ -297,81 +281,6 @@ export class Deck2gisLayer implements DeckCustomLayer { } } - private initRenderTarget( - gl: WebGL2RenderingContext | WebGLRenderingContext, - map: Map, - deck: Deck, - ) { - const mapSize = map.getSize(); - const targetTextureWidth = Math.ceil(mapSize[0] * window.devicePixelRatio); - const targetTextureHeight = Math.ceil(mapSize[1] * window.devicePixelRatio); - this.renderTarget = new RenderTarget({ - size: [targetTextureWidth, targetTextureHeight], - magFilter: Texture.LinearFilter, - minFilter: Texture.LinearFilter, - wrapS: Texture.ClampToEdgeWrapping, - wrapT: Texture.ClampToEdgeWrapping, - }); - this.renderTarget.bind(gl); - this.renderTarget.unbind(gl); - - if ( - !(gl instanceof WebGLRenderingContext) && - this.currentAntialiasingMode() === 'msaa' && - gl.getContextAttributes()?.antialias === false - ) { - const msaaFrameBuffer = gl.createFramebuffer(); - const depthRenderBuffer = gl.createRenderbuffer(); - gl.bindRenderbuffer(gl.RENDERBUFFER, depthRenderBuffer); - gl.renderbufferStorageMultisample( - gl.RENDERBUFFER, - 4, - gl.DEPTH_COMPONENT16, - targetTextureWidth, - targetTextureHeight, - ); - - const colorRenderBuffer = gl.createRenderbuffer(); - gl.bindRenderbuffer(gl.RENDERBUFFER, colorRenderBuffer); - - gl.renderbufferStorageMultisample( - gl.RENDERBUFFER, - 4, - gl.RGBA8, - targetTextureWidth, - targetTextureHeight, - ); - - gl.bindFramebuffer(gl.FRAMEBUFFER, msaaFrameBuffer); - - gl.framebufferRenderbuffer( - gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0, - gl.RENDERBUFFER, - colorRenderBuffer, - ); - - gl.framebufferRenderbuffer( - gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.RENDERBUFFER, - depthRenderBuffer, - ); - - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - this.msaaFrameBuffer = msaaFrameBuffer; - } - - this.deck = prepareDeckInstance({ - map, - gl, - deck, - renderTarget: this.renderTarget, - msaaFrameBuffer: this.msaaFrameBuffer, - }); - } - private currentAntialiasingMode() { return (this.props?.deck.props as CustomRenderProps).antialiasing; } diff --git a/src/index.ts b/src/index.ts index 4070c9d..554ad4c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ import { Deck2gisLayer } from './deckgl2gisLayer'; -import { initDeck2gisProps } from './utils'; +import { initDeck } from './utils'; if (typeof window !== 'undefined') { if ('mapgl' in window) { @@ -15,4 +15,4 @@ if (typeof window !== 'undefined') { } } -export { Deck2gisLayer, initDeck2gisProps }; +export { Deck2gisLayer, initDeck }; diff --git a/src/types.ts b/src/types.ts index dc7685d..2cbc130 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,7 +1,7 @@ -import type RenderTarget from '2gl/RenderTarget'; -import type Vao from '2gl/Vao'; -import type ShaderProgram from '2gl/ShaderProgram'; -import { DeckProps } from '@deck.gl/core/typed'; +import type { DeckProps } from '@deck.gl/core/typed'; +import { RenderTarget } from './2gl/RenderTarget'; +import { ShaderProgram } from './2gl/ShaderProgram'; +import { Vao } from './2gl/Vao'; export interface DeckCustomLayer { type: 'custom'; @@ -43,6 +43,7 @@ export type CustomRenderInternalProps = Partial & { _2glVao?: Vao; _2gisFramestart?: boolean; _antialiasing?: AntiAliasingMode; + _2gisInitDeck?: boolean; }; export type AntiAliasingMode = 'fxaa' | 'msaa' | 'none'; diff --git a/src/utils.ts b/src/utils.ts index 885f7c6..d8ce191 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -4,19 +4,24 @@ import type { Map } from '@2gis/mapgl/types'; import { Deck2gisLayer } from './deckgl2gisLayer'; import { getViewState, MapglMercatorViewport } from './viewport'; -import Vao from '2gl/Vao'; -import Buffer from '2gl/Buffer'; -import ShaderProgram from '2gl/ShaderProgram'; -import RenderTarget from '2gl/RenderTarget'; -import Shader from '2gl/Shader'; - import fill_fxaa_fsh from './shaders/fillTextureFXAA.fsh'; import fill_fxaa_vsh from './shaders/fillTextureFXAA.vsh'; import fill_fsh from './shaders/fillTexture.fsh'; import fill_vsh from './shaders/fillTexture.vsh'; -import { AntiAliasingMode, CustomRenderInternalProps, CustomRenderProps } from './types'; +import { + AntiAliasingMode, + CustomRenderInternalProps, + CustomRenderProps, + DeckRenderProps, +} from './types'; import { DeckProps } from '@deck.gl/core/typed'; +import { RenderTarget } from './2gl/RenderTarget'; +import { Texture } from './2gl/Texture'; +import { Vao } from './2gl/Vao'; +import { ShaderProgram } from './2gl/ShaderProgram'; +import { Shader } from './2gl/Shader'; +import { Buffer } from './2gl/Buffer'; /** * @hidden @@ -59,7 +64,7 @@ export function prepareDeckInstance({ map.on('move', () => onMapMove(deckInstance, map)); } - if (deck?.['layerManager']) { + if (deck?.['animationLoop']) { deckInstance = deck as Deck; deckInstance.setProps(deckProps); } else { @@ -105,7 +110,7 @@ export function updateLayer(deck: Deck, _layer: Deck2gisLayer): void { * @hidden * @internal */ -export function drawLayer(deck: Deck, map: Map, layer: Deck2gisLayer): void { +export function drawLayer(deck: Deck, map: Map, layer: Deck2gisLayer): boolean { let currentViewport = (deck.props as CustomRenderInternalProps)._2gisData._2gisCurrentViewport; if (!currentViewport) { // This is the first layer drawn in this render cycle. @@ -114,8 +119,8 @@ export function drawLayer(deck: Deck, map: Map, layer: Deck2gisLayer): void (deck.props as CustomRenderInternalProps)._2gisData._2gisCurrentViewport = currentViewport; } - if (!(deck as any).layerManager) { - return; + if (!isIncludeLayer(deck, layer)) { + return false; } stateBinder(map.getWebGLContext(), layer); @@ -125,6 +130,24 @@ export function drawLayer(deck: Deck, map: Map, layer: Deck2gisLayer): void layerFilter: ({ layer: deckLayer }) => layer.id === deckLayer.id, clearCanvas: false, }); + + return true; +} + +/** + * @hidden + * @internal + */ +function isIncludeLayer(deck: Deck, layer: Deck2gisLayer): boolean { + if (!(deck as any).layerManager) { + return false; + } + + if (!(deck as any).layerManager.layers.some((deckLayer) => layer.id === deckLayer.id)) { + return false; + } + + return true; } /** @@ -144,7 +167,7 @@ function getViewport(map: Map): MapglMercatorViewport | undefined { * @internal */ function onMapMove(deck: Deck, map: Map): void { - if (deck['layerManager']) { + if (deck['animationLoop']) { deck.setProps({ viewState: getViewState(map), }); @@ -225,11 +248,13 @@ export function onMapResize( * @internal */ function updateLayers(deck: Deck): void { - if (deck['layerManager']) { + if (deck['animationLoop']) { const layers: Layer[] = []; let layerIndex = 0; - const gl = deck.props.gl; - gl && stateBinder(gl); + const gl = (deck.props as CustomRenderInternalProps)._2gisData._2gisMap.getWebGLContext(); + if (gl) { + stateBinder(gl); + } (deck.props as CustomRenderInternalProps)._2gisData._2gisCustomLayers.forEach( (deckLayer) => { const LayerType = deckLayer.props.type; @@ -293,7 +318,8 @@ function reInitDeck2gisProps( }, _antialiasing: (deck?.props as CustomRenderProps).antialiasing || 'none', _2gisData: { - _2gisCustomLayers: new Set(), + _2gisCustomLayers: + (deck?.props as CustomRenderInternalProps)._2gisData._2gisCustomLayers || new Set(), _2gisMap: map, }, views: [new MapView({ id: '2gis' })], @@ -310,6 +336,86 @@ function reInitDeck2gisProps( return deck2gisProps; } +export function initDeck(map: Map, Deck: any, deckProps?: DeckRenderProps): Deck { + const deck = new Deck(initDeck2gisProps(map, deckProps)); + const gl = map.getWebGLContext() as WebGL2RenderingContext | WebGLRenderingContext; + const mapSize = map.getSize(); + const targetTextureWidth = Math.ceil(mapSize[0] * window.devicePixelRatio); + const targetTextureHeight = Math.ceil(mapSize[1] * window.devicePixelRatio); + const renderTarget = new RenderTarget({ + size: [targetTextureWidth, targetTextureHeight], + magFilter: Texture.LinearFilter, + minFilter: Texture.LinearFilter, + wrapS: Texture.ClampToEdgeWrapping, + wrapT: Texture.ClampToEdgeWrapping, + }); + renderTarget.bind(gl); + renderTarget.unbind(gl); + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false); + + let msaaFrameBuffer; + + if ( + !(gl instanceof WebGLRenderingContext) && + gl && + deckProps?.antialiasing === 'msaa' && + gl.getContextAttributes()?.antialias === false + ) { + msaaFrameBuffer = gl.createFramebuffer(); + const depthRenderBuffer = gl.createRenderbuffer(); + gl.bindRenderbuffer(gl.RENDERBUFFER, depthRenderBuffer); + gl.renderbufferStorageMultisample( + gl.RENDERBUFFER, + 4, + gl.DEPTH_COMPONENT16, + targetTextureWidth, + targetTextureHeight, + ); + + const colorRenderBuffer = gl.createRenderbuffer(); + gl.bindRenderbuffer(gl.RENDERBUFFER, colorRenderBuffer); + + gl.renderbufferStorageMultisample( + gl.RENDERBUFFER, + 4, + gl.RGBA8, + targetTextureWidth, + targetTextureHeight, + ); + + gl.bindFramebuffer(gl.FRAMEBUFFER, msaaFrameBuffer); + + gl.framebufferRenderbuffer( + gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0, + gl.RENDERBUFFER, + colorRenderBuffer, + ); + + gl.framebufferRenderbuffer( + gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.RENDERBUFFER, + depthRenderBuffer, + ); + + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + } + + const deckReInit = prepareDeckInstance({ + map, + gl, + deck, + renderTarget: renderTarget, + msaaFrameBuffer: msaaFrameBuffer, + }); + map.triggerRerender(); + (deckReInit?.props as CustomRenderInternalProps)._2gisInitDeck = true; + + return deckReInit as Deck; +} + /** * Initializes deck.gl properties for working with the MapGL map. * @param map The map instance. @@ -333,6 +439,7 @@ export function initDeck2gisProps(map: Map, deckProps?: CustomRenderProps): Deck _2gisMap: map, }, _antialiasing: deckProps?.antialiasing || 'none', + // _framebuffer: ((new RenderTarget({ size: [1, 1] })).bind(gl).unbind(gl) as any)._frameBuffer, views: [new MapView({ id: '2gis' })], }; // deck is using the WebGLContext created by 2gis @@ -362,6 +469,7 @@ function stateBinder( gl.clearDepth(1); gl.clear(gl.DEPTH_BUFFER_BIT); gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false); } /** @@ -370,6 +478,7 @@ function stateBinder( */ export function createVao(program: ShaderProgram) { const screenVertices = [-1, -1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1]; + return new Vao(program, { a_vec2_position: new Buffer(new Int8Array(screenVertices), { itemSize: 2, diff --git a/tsconfig.json b/tsconfig.json index f851ceb..af5ff59 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "types": ["2gl"], + "types": ["@2gis/gl-matrix"], "target": "es6", "module": "es6", "moduleResolution": "node", diff --git a/tsconfig.module.json b/tsconfig.module.json index 8d9263d..2ab6f52 100644 --- a/tsconfig.module.json +++ b/tsconfig.module.json @@ -1,7 +1,8 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "outDir": "./dist/types" + "outDir": "./dist/types", + "types": ["@2gis/gl-matrix"] }, "include": ["index.d.ts", "project.d.ts", "deckgl.d.ts", "src/**/*.ts"], }