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.
@@ -2,9 +2,36 @@
2
2
  'use strict'
3
3
 
4
4
  import { Style } from '../core/Style.js'
5
- import { initShaderProgram, createShader, makeWebGLCanvas } from '../utils/webGLUtils.js'
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
- /** * @type {{canvas:HTMLCanvasElement, gl:WebGLRenderingContext, width:number, height:number }}*/
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
- this.programm = undefined
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 = makeWebGLCanvas(w + '', h + '')
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
- //draw
65
- const vectorShader = `
66
- attribute vec2 pos;
67
- uniform float sizePix;
68
- uniform mat3 mat;
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
- const lutData = new Uint8Array(lutSize * 4); // RGBA for each entry
101
-
102
- // Fill lutData with your color values (e.g., rainbow, grayscale, etc.)
103
- for (let i = 0; i < lutSize; i++) {
104
- const c = color(this.colors[i])
105
- lutData[i * 4] = +c.r; // R
106
- lutData[i * 4 + 1] = c.g; // G
107
- lutData[i * 4 + 2] = c.b; // B
108
- lutData[i * 4 + 3] = c?.opacity * 255; // A
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
- // Create and bind texture
112
- const lutTexture = gl.createTexture();
113
- gl.bindTexture(gl.TEXTURE_2D, lutTexture);
114
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, lutSize, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, lutData);
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, nb = cells.length
138
- const verticesBuffer = []
139
- const iBuffer = []
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
- verticesBuffer.push(c.x + r2, c.y + r2)
153
- iBuffer.push(+i_)
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
- //bind vertice data
157
- gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer())
158
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verticesBuffer), gl.STATIC_DRAW)
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.vertexAttribPointer(
161
- position,
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
- //create canvas and webgl renderer
207
- let contextRecreated = false
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
- // mapContentChanged() is a cheap proxy for "did the content change" based
222
- // on view x/y/z and cell count alone - it has no idea the WebGL context
223
- // itself was just thrown away and recreated a few lines up (geoCanvas.w/h
224
- // changing, e.g. from a browser/devtools resize, recreates cvWGL with a
225
- // fresh, empty vertex buffer). A resize alone often leaves the geographic
226
- // view and cell count identical to the previous draw, so without forcing
227
- // a rebind here, mapContentChanged reports "unchanged" and the rebind gets
228
- // skipped - leaving the new context's vertex buffer never populated and
229
- // gl.drawArrays() drawing nothing, silently (confirmed via a resize-storm
230
- // test: reinit-without-rebind happened on 5 of 9 context recreations).
231
- const shouldRebind = this.mapContentChanged(geoCanvas.view, cells.length) || contextRecreated
232
- if (shouldRebind)
233
- //bind vertices
234
- this.bindVertices(cells, resolution, z, viewScale)
235
- //transformation
236
- gl.uniformMatrix3fv(gl.getUniformLocation(this.program, 'mat'), false, new Float32Array(geoCanvas.getWebGLTransform()))
237
-
238
- // Enable the depth test
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
- gl.drawArrays(gl.POINTS, 0, cells.length)
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 { makeWebGLCanvas } from '../utils/webGLUtils.js'
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
- //create canvas and webgl renderer
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 = makeWebGLCanvas(
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 { initShaderProgram, createShader } from './webGLUtils.js'
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
- createShader(
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
- //vertice data
83
- gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer())
84
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(this.verticesBuffer), gl.STATIC_DRAW)
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.createBuffer())
98
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(this.colorsBuffer), gl.STATIC_DRAW)
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 { initShaderProgram, createShader } from './webGLUtils.js'
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
- /** @type {WebGLShader} */
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 = initShaderProgram(gl, vShader, fShader)
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
- //vertice data
195
- gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer())
196
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verticesBuffer), gl.STATIC_DRAW)
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.createBuffer())
210
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(tBuffer), gl.STATIC_DRAW)
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);
@@ -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
  *