gridviz 3.0.36 → 3.0.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/gridviz.js +8841 -8385
- package/dist/gridviz.min.js +2 -2
- package/package.json +74 -74
- package/src/core/Dataset.js +78 -0
- package/src/core/Legend.js +142 -8
- package/src/core/Map.js +104 -11
- package/src/core/Style.js +48 -0
- package/src/dataset/TiledGrid.js +24 -15
- package/src/layer/GridLayer.js +24 -5
- package/src/legend/ColorCategoryLegend.js +21 -9
- package/src/legend/ColorDiscreteLegend.js +18 -5
- package/src/legend/ColorLegend.js +9 -3
- package/src/legend/OrientationLegend.js +4 -2
- package/src/legend/SizeLegend.js +4 -2
- package/src/legend/TernaryLegend.js +10 -0
- package/src/style/DotDensityStyle.js +3 -3
- package/src/style/ShapeColorSizeStyle.js +4 -0
- package/src/style/SquareColorCategoryWebGLStyle.js +131 -119
- package/src/style/SquareColorWebGLStyle.js +5 -5
- package/src/utils/WebGLSquareColoring.js +28 -32
- package/src/utils/WebGLSquareColoringAdvanced.js +26 -30
- package/src/utils/webGLUtils.js +105 -0
|
@@ -2,9 +2,36 @@
|
|
|
2
2
|
'use strict'
|
|
3
3
|
|
|
4
4
|
import { Style } from '../core/Style.js'
|
|
5
|
-
import {
|
|
5
|
+
import { getSharedWebGLCanvas, getProgram, getBuffer, bufferOwner } from '../utils/webGLUtils.js'
|
|
6
6
|
import { color } from 'd3-color'
|
|
7
7
|
|
|
8
|
+
const VERTEX_SHADER = `
|
|
9
|
+
attribute vec2 pos;
|
|
10
|
+
uniform float sizePix;
|
|
11
|
+
uniform mat3 mat;
|
|
12
|
+
attribute float i;
|
|
13
|
+
varying float vi;
|
|
14
|
+
void main() {
|
|
15
|
+
gl_Position = vec4(mat * vec3(pos, 1.0), 1.0);
|
|
16
|
+
gl_PointSize = sizePix;
|
|
17
|
+
vi = i;
|
|
18
|
+
}`
|
|
19
|
+
|
|
20
|
+
const FRAGMENT_SHADER = `
|
|
21
|
+
precision highp float;
|
|
22
|
+
varying float vi;
|
|
23
|
+
uniform sampler2D colorLUT;
|
|
24
|
+
uniform float lutSize;
|
|
25
|
+
void main(void) {
|
|
26
|
+
float idx = floor(vi + 0.5);
|
|
27
|
+
float u = (idx + 0.5) / lutSize;
|
|
28
|
+
gl_FragColor = texture2D(colorLUT, vec2(u, 0.5));
|
|
29
|
+
}`
|
|
30
|
+
|
|
31
|
+
// names of the buffers this style fills in the shared context (getBuffer)
|
|
32
|
+
const POSITION_BUFFER = 'SquareColorCategoryWebGLStyle.pos'
|
|
33
|
+
const INDEX_BUFFER = 'SquareColorCategoryWebGLStyle.i'
|
|
34
|
+
|
|
8
35
|
/**
|
|
9
36
|
* Style based on webGL
|
|
10
37
|
* To show cells as colored squares, from categories.
|
|
@@ -45,98 +72,88 @@ export class SquareColorCategoryWebGLStyle extends Style {
|
|
|
45
72
|
* @type {function(number,number):number} */
|
|
46
73
|
this.size = opts.size // (resolution, z) => ...
|
|
47
74
|
|
|
48
|
-
/**
|
|
75
|
+
/** The shared WebGL canvas this style last drew into (getSharedWebGLCanvas)
|
|
76
|
+
* @type {{canvas:HTMLCanvasElement, gl:WebGLRenderingContext, width:number, height:number }}*/
|
|
49
77
|
this.cvWGL = undefined
|
|
50
|
-
|
|
78
|
+
/** @type {WebGLRenderingContext|undefined} */
|
|
79
|
+
this.gl = undefined
|
|
80
|
+
this.program = undefined
|
|
81
|
+
this.lutTexture = undefined
|
|
82
|
+
/** Number of points in the shared buffers, for drawArrays */
|
|
83
|
+
this.pointsNb = 0
|
|
51
84
|
|
|
52
85
|
this.x = undefined
|
|
53
86
|
this.y = undefined
|
|
54
87
|
this.z = undefined
|
|
55
88
|
this.cellsNb = undefined
|
|
89
|
+
/** The highlight (see Style.setHighlight) the vertices were bound with @private */
|
|
90
|
+
this.highlightKey = ''
|
|
56
91
|
}
|
|
57
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Binds this style's program, colour lookup texture and uniforms in the shared WebGL
|
|
95
|
+
* context (getSharedWebGLCanvas): compiled once per context, so a new style instance
|
|
96
|
+
* (e.g. a setting change rebuilding the style) costs no new context or program.
|
|
97
|
+
* @returns {boolean} true when the context is new to this style (its data must be re-uploaded)
|
|
98
|
+
*/
|
|
58
99
|
init(w, h) {
|
|
59
|
-
this.cvWGL =
|
|
60
|
-
if (!this.cvWGL) { console.error('No webGL'); return }
|
|
61
|
-
|
|
100
|
+
this.cvWGL = getSharedWebGLCanvas(w, h)
|
|
62
101
|
const gl = this.cvWGL.gl
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
attribute float i;
|
|
70
|
-
varying float vi;
|
|
71
|
-
void main() {
|
|
72
|
-
gl_Position = vec4(mat * vec3(pos, 1.0), 1.0);
|
|
73
|
-
gl_PointSize = sizePix;
|
|
74
|
-
vi = i;
|
|
75
|
-
}`
|
|
76
|
-
/** @type {WebGLShader} */
|
|
77
|
-
const vShader = createShader(gl, gl.VERTEX_SHADER, vectorShader)
|
|
78
|
-
|
|
79
|
-
const fragmentShader = `
|
|
80
|
-
precision highp float;
|
|
81
|
-
varying float vi;
|
|
82
|
-
uniform sampler2D colorLUT;
|
|
83
|
-
uniform float lutSize;
|
|
84
|
-
void main(void) {
|
|
85
|
-
float idx = floor(vi + 0.5);
|
|
86
|
-
float u = (idx + 0.5) / lutSize;
|
|
87
|
-
gl_FragColor = texture2D(colorLUT, vec2(u, 0.5));
|
|
88
|
-
}`
|
|
89
|
-
/** @type {WebGLShader} */
|
|
90
|
-
const fShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShader)
|
|
91
|
-
|
|
92
|
-
/** @type {WebGLProgram} */
|
|
93
|
-
this.program = initShaderProgram(gl, vShader, fShader)
|
|
102
|
+
const contextChanged = this.gl !== gl
|
|
103
|
+
if (contextChanged) {
|
|
104
|
+
this.gl = gl
|
|
105
|
+
this.program = getProgram(gl, VERTEX_SHADER, FRAGMENT_SHADER)
|
|
106
|
+
this.lutTexture = undefined
|
|
107
|
+
}
|
|
94
108
|
gl.useProgram(this.program)
|
|
109
|
+
this.bindColors()
|
|
110
|
+
return contextChanged
|
|
95
111
|
}
|
|
96
112
|
|
|
97
113
|
bindColors() {
|
|
98
114
|
const gl = this.cvWGL.gl
|
|
99
|
-
const lutSize = this.colors.length
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
115
|
+
const lutSize = this.colors.length
|
|
116
|
+
|
|
117
|
+
// created once per style and context
|
|
118
|
+
if (!this.lutTexture) {
|
|
119
|
+
const lutData = new Uint8Array(lutSize * 4) // RGBA for each entry
|
|
120
|
+
for (let i = 0; i < lutSize; i++) {
|
|
121
|
+
const c = color(this.colors[i])
|
|
122
|
+
lutData[i * 4] = +c.r
|
|
123
|
+
lutData[i * 4 + 1] = c.g
|
|
124
|
+
lutData[i * 4 + 2] = c.b
|
|
125
|
+
lutData[i * 4 + 3] = c?.opacity * 255
|
|
126
|
+
}
|
|
127
|
+
this.lutTexture = gl.createTexture()
|
|
128
|
+
gl.bindTexture(gl.TEXTURE_2D, this.lutTexture)
|
|
129
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, lutSize, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, lutData)
|
|
130
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
|
|
131
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
|
|
132
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
|
|
133
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
|
|
109
134
|
}
|
|
110
135
|
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
gl.
|
|
114
|
-
gl.
|
|
115
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
116
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
117
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
118
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
119
|
-
|
|
120
|
-
// Get uniform locations
|
|
121
|
-
const uColorLUT = gl.getUniformLocation(this.program, 'colorLUT');
|
|
122
|
-
const uLutSize = gl.getUniformLocation(this.program, 'lutSize');
|
|
123
|
-
|
|
124
|
-
// Set uniform values
|
|
125
|
-
gl.uniform1i(uColorLUT, 0); // Texture unit 0
|
|
126
|
-
gl.uniform1f(uLutSize, lutSize);
|
|
127
|
-
|
|
128
|
-
// Bind the texture to texture unit 0
|
|
129
|
-
gl.activeTexture(gl.TEXTURE0);
|
|
130
|
-
gl.bindTexture(gl.TEXTURE_2D, lutTexture);
|
|
136
|
+
gl.uniform1i(gl.getUniformLocation(this.program, 'colorLUT'), 0) // Texture unit 0
|
|
137
|
+
gl.uniform1f(gl.getUniformLocation(this.program, 'lutSize'), lutSize)
|
|
138
|
+
gl.activeTexture(gl.TEXTURE0)
|
|
139
|
+
gl.bindTexture(gl.TEXTURE_2D, this.lutTexture)
|
|
131
140
|
}
|
|
132
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Fills the shared position and colour index buffers with the cells to draw.
|
|
144
|
+
* @returns {number} the number of points uploaded (cells without a category are left out)
|
|
145
|
+
*/
|
|
133
146
|
bindVertices(cells, resolution, z, viewScale) {
|
|
134
147
|
const gl = this.cvWGL.gl
|
|
135
148
|
//add vertice and fragment data
|
|
136
149
|
const r2 = resolution / 2
|
|
137
|
-
let c,
|
|
138
|
-
|
|
139
|
-
const
|
|
150
|
+
let c,
|
|
151
|
+
nb = cells.length
|
|
152
|
+
const verticesBuffer = new Float32Array(2 * nb)
|
|
153
|
+
const iBuffer = new Float32Array(nb)
|
|
154
|
+
// categories shown while a legend swatch is hovered: those drawn in the colour of the swatch
|
|
155
|
+
const shown = this.highlightColors ? this.colors.map((c) => this.isHighlighted(c)) : null
|
|
156
|
+
let n = 0
|
|
140
157
|
for (let i = 0; i < nb; i++) {
|
|
141
158
|
c = cells[i]
|
|
142
159
|
const cat = this.code(c, resolution, z, viewScale)
|
|
@@ -149,34 +166,34 @@ export class SquareColorCategoryWebGLStyle extends Style {
|
|
|
149
166
|
console.log('Unexpected category index: ' + cat + ' ' + i_)
|
|
150
167
|
continue
|
|
151
168
|
}
|
|
152
|
-
|
|
153
|
-
|
|
169
|
+
if (shown && !shown[+i_]) continue
|
|
170
|
+
verticesBuffer[2 * n] = c.x + r2
|
|
171
|
+
verticesBuffer[2 * n + 1] = c.y + r2
|
|
172
|
+
iBuffer[n] = +i_
|
|
173
|
+
n++
|
|
154
174
|
}
|
|
155
175
|
|
|
156
|
-
|
|
157
|
-
gl.
|
|
158
|
-
gl.
|
|
176
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, getBuffer(gl, POSITION_BUFFER))
|
|
177
|
+
gl.bufferData(gl.ARRAY_BUFFER, verticesBuffer.subarray(0, 2 * n), gl.DYNAMIC_DRAW)
|
|
178
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, getBuffer(gl, INDEX_BUFFER))
|
|
179
|
+
gl.bufferData(gl.ARRAY_BUFFER, iBuffer.subarray(0, n), gl.DYNAMIC_DRAW)
|
|
180
|
+
bufferOwner(gl, POSITION_BUFFER, this)
|
|
181
|
+
return n
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Points the program's attributes at the shared buffers (whatever another style left bound). */
|
|
185
|
+
bindAttributes() {
|
|
186
|
+
const gl = this.cvWGL.gl
|
|
159
187
|
const position = gl.getAttribLocation(this.program, 'pos')
|
|
160
|
-
gl.
|
|
161
|
-
|
|
162
|
-
2, //numComponents
|
|
163
|
-
gl.FLOAT, //type
|
|
164
|
-
false, //normalise
|
|
165
|
-
0, //stride
|
|
166
|
-
0 //offset
|
|
167
|
-
)
|
|
188
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, getBuffer(gl, POSITION_BUFFER))
|
|
189
|
+
gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0)
|
|
168
190
|
gl.enableVertexAttribArray(position)
|
|
169
|
-
|
|
170
|
-
//i data
|
|
171
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer())
|
|
172
|
-
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(iBuffer), gl.STATIC_DRAW)
|
|
173
191
|
const i = gl.getAttribLocation(this.program, 'i')
|
|
192
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, getBuffer(gl, INDEX_BUFFER))
|
|
174
193
|
gl.vertexAttribPointer(i, 1, gl.FLOAT, false, 0, 0)
|
|
175
194
|
gl.enableVertexAttribArray(i)
|
|
176
195
|
}
|
|
177
196
|
|
|
178
|
-
|
|
179
|
-
|
|
180
197
|
// check if the vertices have to be bound again
|
|
181
198
|
mapContentChanged(v, cellsNb) {
|
|
182
199
|
if (v.x == this.x && v.y == this.y && v.z == this.z && this.cellsNb == cellsNb) return false
|
|
@@ -203,13 +220,8 @@ export class SquareColorCategoryWebGLStyle extends Style {
|
|
|
203
220
|
//get view scale
|
|
204
221
|
const viewScale = this.viewScale ? this.viewScale(cells, resolution, z) : undefined
|
|
205
222
|
|
|
206
|
-
//
|
|
207
|
-
|
|
208
|
-
if (!this.cvWGL || geoCanvas.w != this.cvWGL.width || geoCanvas.h != this.cvWGL.height) {
|
|
209
|
-
contextRecreated = true
|
|
210
|
-
this.init(geoCanvas.w, geoCanvas.h)
|
|
211
|
-
this.bindColors()
|
|
212
|
-
}
|
|
223
|
+
//bind this style's program in the shared WebGL context
|
|
224
|
+
const contextChanged = this.init(geoCanvas.w, geoCanvas.h)
|
|
213
225
|
const gl = this.cvWGL.gl
|
|
214
226
|
const canvas = this.cvWGL.canvas
|
|
215
227
|
|
|
@@ -217,32 +229,32 @@ export class SquareColorCategoryWebGLStyle extends Style {
|
|
|
217
229
|
const sizePix = this.size ? this.size(resolution, z) / z : resolution / z + 0.2
|
|
218
230
|
gl.uniform1f(gl.getUniformLocation(this.program, 'sizePix'), 1.0 * sizePix)
|
|
219
231
|
|
|
220
|
-
//
|
|
221
|
-
//
|
|
222
|
-
//
|
|
223
|
-
//
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
this.
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
//gl.enable(gl.DEPTH_TEST);
|
|
232
|
+
// mapContentChanged() is a cheap proxy for "did the content change", based on view
|
|
233
|
+
// x/y/z and cell count alone. The upload is also needed when the shared buffers
|
|
234
|
+
// hold another style's data (bufferOwner), or the context is new to this style
|
|
235
|
+
// (e.g. replaced after being lost).
|
|
236
|
+
const highlightKey = this.highlightColors ? this.highlightColors.join('|') : ''
|
|
237
|
+
const highlightChanged = highlightKey !== this.highlightKey
|
|
238
|
+
this.highlightKey = highlightKey
|
|
239
|
+
const contentChanged = this.mapContentChanged(geoCanvas.view, cells.length) || highlightChanged
|
|
240
|
+
if (contentChanged || contextChanged || bufferOwner(gl, POSITION_BUFFER) !== this)
|
|
241
|
+
this.pointsNb = this.bindVertices(cells, resolution, z, viewScale)
|
|
242
|
+
this.bindAttributes()
|
|
243
|
+
|
|
244
|
+
//transformation
|
|
245
|
+
gl.uniformMatrix3fv(
|
|
246
|
+
gl.getUniformLocation(this.program, 'mat'),
|
|
247
|
+
false,
|
|
248
|
+
new Float32Array(geoCanvas.getWebGLTransform())
|
|
249
|
+
)
|
|
250
|
+
|
|
240
251
|
// Clear the color buffer bit
|
|
252
|
+
gl.clearColor(0, 0, 0, 0)
|
|
241
253
|
gl.clear(gl.COLOR_BUFFER_BIT)
|
|
242
|
-
// Set the view port
|
|
243
|
-
//gl.viewport(0, 0, cg.w, cg.h);
|
|
244
254
|
|
|
245
|
-
|
|
255
|
+
// only the points actually uploaded: cells without a category are left out, and
|
|
256
|
+
// drawing past the end of the buffer is a WebGL error that draws nothing at all
|
|
257
|
+
gl.drawArrays(gl.POINTS, 0, this.pointsNb)
|
|
246
258
|
|
|
247
259
|
//draw in canvas geo
|
|
248
260
|
geoCanvas.initCanvasTransform()
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
'use strict'
|
|
3
3
|
|
|
4
4
|
import { Style } from '../core/Style.js'
|
|
5
|
-
import {
|
|
5
|
+
import { getSharedWebGLCanvas } from '../utils/webGLUtils.js'
|
|
6
6
|
import { WebGLSquareColoringAdvanced } from '../utils/WebGLSquareColoringAdvanced.js'
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -80,11 +80,11 @@ export class SquareColorWebGLStyle extends Style {
|
|
|
80
80
|
//get view scale
|
|
81
81
|
const viewScale = this.viewScale ? this.viewScale(cells, resolution, z) : undefined
|
|
82
82
|
|
|
83
|
-
//
|
|
83
|
+
//the shared webgl canvas (one context for all styles, instead of a new one per draw)
|
|
84
84
|
//for opacity control, see: https://webglfundamentals.org/webgl/lessons/webgl-and-alpha.html
|
|
85
|
-
const cvWGL =
|
|
86
|
-
geoCanvas.w
|
|
87
|
-
geoCanvas.h
|
|
85
|
+
const cvWGL = getSharedWebGLCanvas(
|
|
86
|
+
geoCanvas.w,
|
|
87
|
+
geoCanvas.h,
|
|
88
88
|
this.opacity != undefined ? { premultipliedAlpha: false } : undefined
|
|
89
89
|
)
|
|
90
90
|
if (!cvWGL) {
|
|
@@ -1,28 +1,10 @@
|
|
|
1
1
|
//@ts-check
|
|
2
2
|
'use strict'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { getProgram, getBuffer } from './webGLUtils.js'
|
|
5
5
|
import { color } from 'd3-color'
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
* Everything to easily draw colored squares with webGL.
|
|
9
|
-
* All the same size, but different fill color.
|
|
10
|
-
*/
|
|
11
|
-
export class WebGLSquareColoring {
|
|
12
|
-
/**
|
|
13
|
-
*
|
|
14
|
-
* @param {WebGLRenderingContext} gl
|
|
15
|
-
*/
|
|
16
|
-
constructor(gl, sizePix) {
|
|
17
|
-
this.gl = gl
|
|
18
|
-
this.sizePix = sizePix || 10.0
|
|
19
|
-
|
|
20
|
-
this.program = initShaderProgram(
|
|
21
|
-
gl,
|
|
22
|
-
createShader(
|
|
23
|
-
gl,
|
|
24
|
-
gl.VERTEX_SHADER,
|
|
25
|
-
`
|
|
7
|
+
const VERTEX_SHADER = `
|
|
26
8
|
attribute vec2 pos;
|
|
27
9
|
uniform float sizePix;
|
|
28
10
|
uniform mat3 mat;
|
|
@@ -34,11 +16,8 @@ export class WebGLSquareColoring {
|
|
|
34
16
|
vColor = color;
|
|
35
17
|
}
|
|
36
18
|
`
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
gl,
|
|
40
|
-
gl.FRAGMENT_SHADER,
|
|
41
|
-
`
|
|
19
|
+
|
|
20
|
+
const FRAGMENT_SHADER = `
|
|
42
21
|
precision mediump float;
|
|
43
22
|
varying vec4 vColor;
|
|
44
23
|
void main(void) {
|
|
@@ -46,8 +25,22 @@ export class WebGLSquareColoring {
|
|
|
46
25
|
vColor_[3] = 255.0 * vColor_[3];
|
|
47
26
|
gl_FragColor = vColor_;
|
|
48
27
|
}`
|
|
49
|
-
|
|
50
|
-
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Everything to easily draw colored squares with webGL.
|
|
31
|
+
* All the same size, but different fill color.
|
|
32
|
+
*/
|
|
33
|
+
export class WebGLSquareColoring {
|
|
34
|
+
/**
|
|
35
|
+
*
|
|
36
|
+
* @param {WebGLRenderingContext} gl
|
|
37
|
+
*/
|
|
38
|
+
constructor(gl, sizePix) {
|
|
39
|
+
this.gl = gl
|
|
40
|
+
this.sizePix = sizePix || 10.0
|
|
41
|
+
|
|
42
|
+
// compiled once per context, then reused
|
|
43
|
+
this.program = getProgram(gl, VERTEX_SHADER, FRAGMENT_SHADER)
|
|
51
44
|
gl.useProgram(this.program)
|
|
52
45
|
|
|
53
46
|
//buffer data
|
|
@@ -79,9 +72,11 @@ export class WebGLSquareColoring {
|
|
|
79
72
|
draw(transfoMat) {
|
|
80
73
|
const gl = this.gl
|
|
81
74
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
75
|
+
gl.useProgram(this.program)
|
|
76
|
+
|
|
77
|
+
//vertice data, in a buffer reused from draw to draw
|
|
78
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, getBuffer(gl, 'WebGLSquareColoring.pos'))
|
|
79
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(this.verticesBuffer), gl.DYNAMIC_DRAW)
|
|
85
80
|
const position = gl.getAttribLocation(this.program, 'pos')
|
|
86
81
|
gl.vertexAttribPointer(
|
|
87
82
|
position,
|
|
@@ -94,8 +89,8 @@ export class WebGLSquareColoring {
|
|
|
94
89
|
gl.enableVertexAttribArray(position)
|
|
95
90
|
|
|
96
91
|
//color data
|
|
97
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, gl.
|
|
98
|
-
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(this.colorsBuffer), gl.
|
|
92
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, getBuffer(gl, 'WebGLSquareColoring.color'))
|
|
93
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(this.colorsBuffer), gl.DYNAMIC_DRAW)
|
|
99
94
|
var color = gl.getAttribLocation(this.program, 'color')
|
|
100
95
|
gl.vertexAttribPointer(color, 4, gl.FLOAT, false, 0, 0)
|
|
101
96
|
gl.enableVertexAttribArray(color)
|
|
@@ -109,6 +104,7 @@ export class WebGLSquareColoring {
|
|
|
109
104
|
// Enable the depth test
|
|
110
105
|
//gl.enable(gl.DEPTH_TEST);
|
|
111
106
|
// Clear the color buffer bit
|
|
107
|
+
gl.clearColor(0, 0, 0, 0)
|
|
112
108
|
gl.clear(gl.COLOR_BUFFER_BIT)
|
|
113
109
|
// Set the view port
|
|
114
110
|
//gl.viewport(0, 0, cg.w, cg.h);
|
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
//@ts-check
|
|
2
2
|
'use strict'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { getProgram, getBuffer } from './webGLUtils.js'
|
|
5
5
|
import { color } from 'd3-color'
|
|
6
6
|
|
|
7
|
+
const VERTEX_SHADER = `
|
|
8
|
+
attribute vec2 pos;
|
|
9
|
+
uniform float sizePix;
|
|
10
|
+
uniform mat3 mat;
|
|
11
|
+
|
|
12
|
+
attribute float t;
|
|
13
|
+
varying float vt;
|
|
14
|
+
|
|
15
|
+
void main() {
|
|
16
|
+
gl_Position = vec4(mat * vec3(pos, 1.0), 1.0);
|
|
17
|
+
gl_PointSize = sizePix;
|
|
18
|
+
vt = t;
|
|
19
|
+
}
|
|
20
|
+
`
|
|
21
|
+
|
|
7
22
|
/**
|
|
8
23
|
* Everything to easily draw colored squares with webGL.
|
|
9
24
|
* All the same size, but different fill color.
|
|
@@ -31,26 +46,6 @@ export class WebGLSquareColoringAdvanced {
|
|
|
31
46
|
//gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
32
47
|
//gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
33
48
|
|
|
34
|
-
/** @type {WebGLShader} */
|
|
35
|
-
const vShader = createShader(
|
|
36
|
-
gl,
|
|
37
|
-
gl.VERTEX_SHADER,
|
|
38
|
-
`
|
|
39
|
-
attribute vec2 pos;
|
|
40
|
-
uniform float sizePix;
|
|
41
|
-
uniform mat3 mat;
|
|
42
|
-
|
|
43
|
-
attribute float t;
|
|
44
|
-
varying float vt;
|
|
45
|
-
|
|
46
|
-
void main() {
|
|
47
|
-
gl_Position = vec4(mat * vec3(pos, 1.0), 1.0);
|
|
48
|
-
gl_PointSize = sizePix;
|
|
49
|
-
vt = t;
|
|
50
|
-
}
|
|
51
|
-
`
|
|
52
|
-
)
|
|
53
|
-
|
|
54
49
|
//prepare fragment shader code
|
|
55
50
|
//declare the uniform and other variables
|
|
56
51
|
const fshBuff = []
|
|
@@ -157,11 +152,9 @@ export class WebGLSquareColoringAdvanced {
|
|
|
157
152
|
//let fshString = fshBuff.join("\n")
|
|
158
153
|
//console.log(fshString)
|
|
159
154
|
|
|
160
|
-
|
|
161
|
-
const fShader = createShader(gl, gl.FRAGMENT_SHADER, fshString)
|
|
162
|
-
|
|
155
|
+
// compiled once per context and fragment source (colours, stretching), then reused
|
|
163
156
|
/** @type {WebGLProgram} */
|
|
164
|
-
this.program =
|
|
157
|
+
this.program = getProgram(gl, VERTEX_SHADER, fshString)
|
|
165
158
|
gl.useProgram(this.program)
|
|
166
159
|
|
|
167
160
|
//set uniforms
|
|
@@ -191,9 +184,11 @@ export class WebGLSquareColoringAdvanced {
|
|
|
191
184
|
const gl = this.gl
|
|
192
185
|
const program = this.program
|
|
193
186
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
187
|
+
gl.useProgram(program)
|
|
188
|
+
|
|
189
|
+
//vertice data, in a buffer reused from draw to draw
|
|
190
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, getBuffer(gl, 'WebGLSquareColoringAdvanced.pos'))
|
|
191
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verticesBuffer), gl.DYNAMIC_DRAW)
|
|
197
192
|
const position = gl.getAttribLocation(program, 'pos')
|
|
198
193
|
gl.vertexAttribPointer(
|
|
199
194
|
position,
|
|
@@ -206,8 +201,8 @@ export class WebGLSquareColoringAdvanced {
|
|
|
206
201
|
gl.enableVertexAttribArray(position)
|
|
207
202
|
|
|
208
203
|
//t data
|
|
209
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, gl.
|
|
210
|
-
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(tBuffer), gl.
|
|
204
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, getBuffer(gl, 'WebGLSquareColoringAdvanced.t'))
|
|
205
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(tBuffer), gl.DYNAMIC_DRAW)
|
|
211
206
|
const t = gl.getAttribLocation(program, 't')
|
|
212
207
|
gl.vertexAttribPointer(t, 1, gl.FLOAT, false, 0, 0)
|
|
213
208
|
gl.enableVertexAttribArray(t)
|
|
@@ -218,6 +213,7 @@ export class WebGLSquareColoringAdvanced {
|
|
|
218
213
|
// Enable the depth test
|
|
219
214
|
//gl.enable(gl.DEPTH_TEST);
|
|
220
215
|
// Clear the color buffer bit
|
|
216
|
+
gl.clearColor(0, 0, 0, 0)
|
|
221
217
|
gl.clear(gl.COLOR_BUFFER_BIT)
|
|
222
218
|
// Set the view port
|
|
223
219
|
//gl.viewport(0, 0, cg.w, cg.h);
|
package/src/utils/webGLUtils.js
CHANGED
|
@@ -20,6 +20,111 @@ export function makeWebGLCanvas(width, height, opts = {}) {
|
|
|
20
20
|
return { canvas: canvas, gl: gl, width: width, height: height }
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* The shared, off-page WebGL canvases, one per set of context options.
|
|
25
|
+
* Styles draw into one of these and copy the result onto the map's canvas, one style at a
|
|
26
|
+
* time, so they never need a context each. Browsers keep only ~16 live WebGL contexts and
|
|
27
|
+
* drop the oldest beyond that, so one context per style instance (or worse, per draw)
|
|
28
|
+
* eventually blanks other WebGL content on the page.
|
|
29
|
+
* @type {Map<string, {canvas:HTMLCanvasElement, gl:WebGLRenderingContext, width:number, height:number}>} */
|
|
30
|
+
const sharedCanvases = new Map()
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Returns the shared WebGL canvas for these context options, resized to width x height.
|
|
34
|
+
* Resizing keeps the context (and its compiled programs and buffers); only its drawing
|
|
35
|
+
* buffer is reallocated. A lost context is replaced.
|
|
36
|
+
*
|
|
37
|
+
* @param {number} width
|
|
38
|
+
* @param {number} height
|
|
39
|
+
* @param {object} [opts] WebGL context attributes, e.g. { premultipliedAlpha: false }
|
|
40
|
+
* @returns {{canvas:HTMLCanvasElement, gl:WebGLRenderingContext, width:number, height:number }}
|
|
41
|
+
*/
|
|
42
|
+
export function getSharedWebGLCanvas(width, height, opts = undefined) {
|
|
43
|
+
const key = JSON.stringify(opts || {})
|
|
44
|
+
let cv = sharedCanvases.get(key)
|
|
45
|
+
if (!cv || cv.gl.isContextLost()) {
|
|
46
|
+
cv = makeWebGLCanvas(width + '', height + '', opts)
|
|
47
|
+
cv.width = +width
|
|
48
|
+
cv.height = +height
|
|
49
|
+
sharedCanvases.set(key, cv)
|
|
50
|
+
}
|
|
51
|
+
if (cv.width != width || cv.height != height) {
|
|
52
|
+
cv.canvas.width = width
|
|
53
|
+
cv.canvas.height = height
|
|
54
|
+
cv.width = +width
|
|
55
|
+
cv.height = +height
|
|
56
|
+
}
|
|
57
|
+
cv.gl.viewport(0, 0, cv.width, cv.height)
|
|
58
|
+
return cv
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** @type {WeakMap<WebGLRenderingContext, {programs: Map<string, WebGLProgram>, buffers: Map<string, WebGLBuffer>, owners: Map<string, any>}>} */
|
|
62
|
+
const glResources = new WeakMap()
|
|
63
|
+
function resourcesOf(gl) {
|
|
64
|
+
let r = glResources.get(gl)
|
|
65
|
+
if (!r) {
|
|
66
|
+
r = { programs: new Map(), buffers: new Map(), owners: new Map() }
|
|
67
|
+
glResources.set(gl, r)
|
|
68
|
+
}
|
|
69
|
+
return r
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* A shader program for these sources, compiled once per context and reused.
|
|
74
|
+
*
|
|
75
|
+
* @param {WebGLRenderingContext} gl
|
|
76
|
+
* @param {string} vertexSource
|
|
77
|
+
* @param {string} fragmentSource
|
|
78
|
+
* @returns {WebGLProgram}
|
|
79
|
+
*/
|
|
80
|
+
export function getProgram(gl, vertexSource, fragmentSource) {
|
|
81
|
+
const programs = resourcesOf(gl).programs
|
|
82
|
+
const key = vertexSource + '\u0000' + fragmentSource
|
|
83
|
+
let program = programs.get(key)
|
|
84
|
+
if (!program) {
|
|
85
|
+
program = initShaderProgram(
|
|
86
|
+
gl,
|
|
87
|
+
createShader(gl, gl.VERTEX_SHADER, vertexSource),
|
|
88
|
+
createShader(gl, gl.FRAGMENT_SHADER, fragmentSource)
|
|
89
|
+
)
|
|
90
|
+
programs.set(key, program)
|
|
91
|
+
}
|
|
92
|
+
return program
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* A named buffer of a context, created once and reused: refill it with bufferData instead
|
|
97
|
+
* of creating a new buffer per draw (buffers are never garbage collected while their
|
|
98
|
+
* context lives).
|
|
99
|
+
*
|
|
100
|
+
* @param {WebGLRenderingContext} gl
|
|
101
|
+
* @param {string} name
|
|
102
|
+
* @returns {WebGLBuffer}
|
|
103
|
+
*/
|
|
104
|
+
export function getBuffer(gl, name) {
|
|
105
|
+
const buffers = resourcesOf(gl).buffers
|
|
106
|
+
let buffer = buffers.get(name)
|
|
107
|
+
if (!buffer) {
|
|
108
|
+
buffer = gl.createBuffer()
|
|
109
|
+
buffers.set(name, buffer)
|
|
110
|
+
}
|
|
111
|
+
return buffer
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Who last filled a named buffer, so a style can tell whether the data it uploaded is
|
|
116
|
+
* still there or another style has overwritten it since.
|
|
117
|
+
* @param {WebGLRenderingContext} gl
|
|
118
|
+
* @param {string} name
|
|
119
|
+
* @param {any} [owner] sets the owner when given
|
|
120
|
+
* @returns {any} the owner
|
|
121
|
+
*/
|
|
122
|
+
export function bufferOwner(gl, name, owner = undefined) {
|
|
123
|
+
const owners = resourcesOf(gl).owners
|
|
124
|
+
if (owner !== undefined) owners.set(name, owner)
|
|
125
|
+
return owners.get(name)
|
|
126
|
+
}
|
|
127
|
+
|
|
23
128
|
/**
|
|
24
129
|
* Initialize a shader program, so WebGL knows how to draw our data
|
|
25
130
|
*
|