@luma.gl/webgl 9.2.5 → 9.3.0-alpha.2

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.
Files changed (49) hide show
  1. package/dist/adapter/resources/webgl-buffer.d.ts.map +1 -1
  2. package/dist/adapter/resources/webgl-buffer.js +1 -0
  3. package/dist/adapter/resources/webgl-buffer.js.map +1 -1
  4. package/dist/adapter/resources/webgl-fence.d.ts +14 -0
  5. package/dist/adapter/resources/webgl-fence.d.ts.map +1 -0
  6. package/dist/adapter/resources/webgl-fence.js +49 -0
  7. package/dist/adapter/resources/webgl-fence.js.map +1 -0
  8. package/dist/adapter/resources/webgl-render-pass.d.ts.map +1 -1
  9. package/dist/adapter/resources/webgl-render-pass.js +4 -6
  10. package/dist/adapter/resources/webgl-render-pass.js.map +1 -1
  11. package/dist/adapter/resources/webgl-texture.d.ts +21 -4
  12. package/dist/adapter/resources/webgl-texture.d.ts.map +1 -1
  13. package/dist/adapter/resources/webgl-texture.js +148 -22
  14. package/dist/adapter/resources/webgl-texture.js.map +1 -1
  15. package/dist/adapter/webgl-adapter.d.ts.map +1 -1
  16. package/dist/adapter/webgl-adapter.js +19 -19
  17. package/dist/adapter/webgl-adapter.js.map +1 -1
  18. package/dist/adapter/webgl-canvas-context.d.ts +2 -2
  19. package/dist/adapter/webgl-canvas-context.d.ts.map +1 -1
  20. package/dist/adapter/webgl-canvas-context.js +16 -6
  21. package/dist/adapter/webgl-canvas-context.js.map +1 -1
  22. package/dist/adapter/webgl-device.d.ts +2 -1
  23. package/dist/adapter/webgl-device.d.ts.map +1 -1
  24. package/dist/adapter/webgl-device.js +14 -13
  25. package/dist/adapter/webgl-device.js.map +1 -1
  26. package/dist/context/debug/webgl-developer-tools.js +4 -6
  27. package/dist/context/debug/webgl-developer-tools.js.map +1 -1
  28. package/dist/context/helpers/create-browser-context.d.ts.map +1 -1
  29. package/dist/context/helpers/create-browser-context.js +47 -35
  30. package/dist/context/helpers/create-browser-context.js.map +1 -1
  31. package/dist/dist.dev.js +468 -274
  32. package/dist/dist.min.js +2 -2
  33. package/dist/index.cjs +447 -275
  34. package/dist/index.cjs.map +4 -4
  35. package/dist/index.d.ts +1 -0
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +1 -0
  38. package/dist/index.js.map +1 -1
  39. package/package.json +4 -4
  40. package/src/adapter/resources/webgl-buffer.ts +1 -0
  41. package/src/adapter/resources/webgl-fence.ts +55 -0
  42. package/src/adapter/resources/webgl-render-pass.ts +4 -6
  43. package/src/adapter/resources/webgl-texture.ts +209 -37
  44. package/src/adapter/webgl-adapter.ts +23 -20
  45. package/src/adapter/webgl-canvas-context.ts +19 -8
  46. package/src/adapter/webgl-device.ts +15 -14
  47. package/src/context/debug/webgl-developer-tools.ts +13 -6
  48. package/src/context/helpers/create-browser-context.ts +54 -43
  49. package/src/index.ts +1 -0
package/dist/index.d.ts CHANGED
@@ -8,6 +8,7 @@ export { WEBGLTexture } from "./adapter/resources/webgl-texture.js";
8
8
  export { WEBGLShader } from "./adapter/resources/webgl-shader.js";
9
9
  export { WEBGLSampler } from "./adapter/resources/webgl-sampler.js";
10
10
  export { WEBGLFramebuffer } from "./adapter/resources/webgl-framebuffer.js";
11
+ export { WEBGLFence } from "./adapter/resources/webgl-fence.js";
11
12
  export { WEBGLRenderPipeline } from "./adapter/resources/webgl-render-pipeline.js";
12
13
  export { WEBGLCommandEncoder } from "./adapter/resources/webgl-command-encoder.js";
13
14
  export { WEBGLRenderPass } from "./adapter/resources/webgl-render-pass.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAWA,YAAY,EAAC,iBAAiB,EAAC,wDAAqD;AAGpF,OAAO,EAAC,aAAa,EAAC,mCAAgC;AACtD,YAAY,EAAC,YAAY,EAAC,mCAAgC;AAG1D,OAAO,EAAC,WAAW,EAAC,kCAA+B;AACnD,OAAO,EAAC,kBAAkB,EAAC,0CAAuC;AAGlE,OAAO,EAAC,WAAW,EAAC,4CAAyC;AAC7D,OAAO,EAAC,YAAY,EAAC,6CAA0C;AAE/D,OAAO,EAAC,WAAW,EAAC,4CAAyC;AAC7D,OAAO,EAAC,YAAY,EAAC,6CAA0C;AAC/D,OAAO,EAAC,gBAAgB,EAAC,iDAA8C;AAEvE,OAAO,EAAC,mBAAmB,EAAC,qDAAkD;AAE9E,OAAO,EAAC,mBAAmB,EAAC,qDAAkD;AAC9E,OAAO,EAAC,eAAe,EAAC,iDAA8C;AAEtE,OAAO,EAAC,gBAAgB,EAAC,kDAA+C;AAGxE,OAAO,EAAC,sBAAsB,EAAC,wDAAqD;AAIpF,OAAO,EAAC,mBAAmB,EAAE,oBAAoB,EAAC,kDAA+C;AAGjG,OAAO,EAAC,uBAAuB,EAAC,yDAAsD;AACtF,OAAO,EAAC,iBAAiB,EAAC,uDAAoD;AAG9E,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,eAAe,EAChB,sDAAmD;AAEpD,OAAO,EAAC,gBAAgB,EAAC,mDAAgD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAWA,YAAY,EAAC,iBAAiB,EAAC,wDAAqD;AAGpF,OAAO,EAAC,aAAa,EAAC,mCAAgC;AACtD,YAAY,EAAC,YAAY,EAAC,mCAAgC;AAG1D,OAAO,EAAC,WAAW,EAAC,kCAA+B;AACnD,OAAO,EAAC,kBAAkB,EAAC,0CAAuC;AAGlE,OAAO,EAAC,WAAW,EAAC,4CAAyC;AAC7D,OAAO,EAAC,YAAY,EAAC,6CAA0C;AAE/D,OAAO,EAAC,WAAW,EAAC,4CAAyC;AAC7D,OAAO,EAAC,YAAY,EAAC,6CAA0C;AAC/D,OAAO,EAAC,gBAAgB,EAAC,iDAA8C;AACvE,OAAO,EAAC,UAAU,EAAC,2CAAwC;AAE3D,OAAO,EAAC,mBAAmB,EAAC,qDAAkD;AAE9E,OAAO,EAAC,mBAAmB,EAAC,qDAAkD;AAC9E,OAAO,EAAC,eAAe,EAAC,iDAA8C;AAEtE,OAAO,EAAC,gBAAgB,EAAC,kDAA+C;AAGxE,OAAO,EAAC,sBAAsB,EAAC,wDAAqD;AAIpF,OAAO,EAAC,mBAAmB,EAAE,oBAAoB,EAAC,kDAA+C;AAGjG,OAAO,EAAC,uBAAuB,EAAC,yDAAsD;AACtF,OAAO,EAAC,iBAAiB,EAAC,uDAAoD;AAG9E,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,eAAe,EAChB,sDAAmD;AAEpD,OAAO,EAAC,gBAAgB,EAAC,mDAAgD"}
package/dist/index.js CHANGED
@@ -13,6 +13,7 @@ export { WEBGLTexture } from "./adapter/resources/webgl-texture.js";
13
13
  export { WEBGLShader } from "./adapter/resources/webgl-shader.js";
14
14
  export { WEBGLSampler } from "./adapter/resources/webgl-sampler.js";
15
15
  export { WEBGLFramebuffer } from "./adapter/resources/webgl-framebuffer.js";
16
+ export { WEBGLFence } from "./adapter/resources/webgl-fence.js";
16
17
  export { WEBGLRenderPipeline } from "./adapter/resources/webgl-render-pipeline.js";
17
18
  // export {WEBGLComputePipeline} from './adapter/resources/webgl-compute-pipeline';
18
19
  export { WEBGLCommandEncoder } from "./adapter/resources/webgl-command-encoder.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,UAAU;AACV,+BAA+B;AAC/B,oCAAoC;AAWpC,wBAAwB;AACxB,OAAO,EAAC,aAAa,EAAC,mCAAgC;AAGtD,uBAAuB;AACvB,OAAO,EAAC,WAAW,EAAC,kCAA+B;AACnD,OAAO,EAAC,kBAAkB,EAAC,0CAAuC;AAElE,yBAAyB;AACzB,OAAO,EAAC,WAAW,EAAC,4CAAyC;AAC7D,OAAO,EAAC,YAAY,EAAC,6CAA0C;AAC/D,mFAAmF;AACnF,OAAO,EAAC,WAAW,EAAC,4CAAyC;AAC7D,OAAO,EAAC,YAAY,EAAC,6CAA0C;AAC/D,OAAO,EAAC,gBAAgB,EAAC,iDAA8C;AAEvE,OAAO,EAAC,mBAAmB,EAAC,qDAAkD;AAC9E,mFAAmF;AACnF,OAAO,EAAC,mBAAmB,EAAC,qDAAkD;AAC9E,OAAO,EAAC,eAAe,EAAC,iDAA8C;AACtE,2EAA2E;AAC3E,OAAO,EAAC,gBAAgB,EAAC,kDAA+C;AAExE,wBAAwB;AACxB,OAAO,EAAC,sBAAsB,EAAC,wDAAqD;AAEpF,wBAAwB;AAExB,OAAO,EAAC,mBAAmB,EAAE,oBAAoB,EAAC,kDAA+C;AAEjG,yBAAyB;AACzB,OAAO,EAAC,uBAAuB,EAAC,yDAAsD;AACtF,OAAO,EAAC,iBAAiB,EAAC,uDAAoD;AAE9E,0BAA0B;AAC1B,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,eAAe,EAChB,sDAAmD;AAEpD,OAAO,EAAC,gBAAgB,EAAC,mDAAgD"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,UAAU;AACV,+BAA+B;AAC/B,oCAAoC;AAWpC,wBAAwB;AACxB,OAAO,EAAC,aAAa,EAAC,mCAAgC;AAGtD,uBAAuB;AACvB,OAAO,EAAC,WAAW,EAAC,kCAA+B;AACnD,OAAO,EAAC,kBAAkB,EAAC,0CAAuC;AAElE,yBAAyB;AACzB,OAAO,EAAC,WAAW,EAAC,4CAAyC;AAC7D,OAAO,EAAC,YAAY,EAAC,6CAA0C;AAC/D,mFAAmF;AACnF,OAAO,EAAC,WAAW,EAAC,4CAAyC;AAC7D,OAAO,EAAC,YAAY,EAAC,6CAA0C;AAC/D,OAAO,EAAC,gBAAgB,EAAC,iDAA8C;AACvE,OAAO,EAAC,UAAU,EAAC,2CAAwC;AAE3D,OAAO,EAAC,mBAAmB,EAAC,qDAAkD;AAC9E,mFAAmF;AACnF,OAAO,EAAC,mBAAmB,EAAC,qDAAkD;AAC9E,OAAO,EAAC,eAAe,EAAC,iDAA8C;AACtE,2EAA2E;AAC3E,OAAO,EAAC,gBAAgB,EAAC,kDAA+C;AAExE,wBAAwB;AACxB,OAAO,EAAC,sBAAsB,EAAC,wDAAqD;AAEpF,wBAAwB;AAExB,OAAO,EAAC,mBAAmB,EAAE,oBAAoB,EAAC,kDAA+C;AAEjG,yBAAyB;AACzB,OAAO,EAAC,uBAAuB,EAAC,yDAAsD;AACtF,OAAO,EAAC,iBAAiB,EAAC,uDAAoD;AAE9E,0BAA0B;AAC1B,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,eAAe,EAChB,sDAAmD;AAEpD,OAAO,EAAC,gBAAgB,EAAC,mDAAgD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luma.gl/webgl",
3
- "version": "9.2.5",
3
+ "version": "9.3.0-alpha.2",
4
4
  "description": "WebGL2 adapter for the luma.gl core API",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -40,12 +40,12 @@
40
40
  "prepublishOnly": "npm run build-minified-bundle && npm run build-dev-bundle"
41
41
  },
42
42
  "peerDependencies": {
43
- "@luma.gl/core": "~9.2.0"
43
+ "@luma.gl/core": "9.2.0-alpha.6"
44
44
  },
45
45
  "dependencies": {
46
- "@luma.gl/constants": "9.2.5",
46
+ "@luma.gl/constants": "9.3.0-alpha.2",
47
47
  "@math.gl/types": "^4.1.0",
48
48
  "@probe.gl/env": "^4.0.8"
49
49
  },
50
- "gitHead": "a8efd26edd0c61c7bb29ea700c6c38f544f60326"
50
+ "gitHead": "7fedf8d8902f58490a4ffca9a873daee3c732f24"
51
51
  }
@@ -42,6 +42,7 @@ export class WEBGLBuffer extends Buffer {
42
42
  // - In WebGL2, we can use GL.COPY_READ_BUFFER which avoids locking the type here
43
43
  this.glTarget = getWebGLTarget(this.props.usage);
44
44
  this.glUsage = getWebGLUsage(this.props.usage);
45
+ // Note: uint8 indices are converted to uint16 during device normalization for WebGPU compatibility
45
46
  this.glIndexType = this.props.indexType === 'uint32' ? GL.UNSIGNED_INT : GL.UNSIGNED_SHORT;
46
47
 
47
48
  // Set data: (re)initializes the buffer
@@ -0,0 +1,55 @@
1
+ // luma.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {Fence, type FenceProps} from '@luma.gl/core';
6
+ import {WebGLDevice} from '../webgl-device';
7
+
8
+ /** WebGL fence implemented with gl.fenceSync */
9
+ export class WEBGLFence extends Fence {
10
+ readonly device: WebGLDevice;
11
+ readonly gl: WebGL2RenderingContext;
12
+ readonly handle: WebGLSync;
13
+ readonly signaled: Promise<void>;
14
+ private _signaled = false;
15
+
16
+ constructor(device: WebGLDevice, props: FenceProps = {}) {
17
+ super(device, {});
18
+ this.device = device;
19
+ this.gl = device.gl;
20
+
21
+ const sync = this.props.handle || this.gl.fenceSync(this.gl.SYNC_GPU_COMMANDS_COMPLETE, 0);
22
+ if (!sync) {
23
+ throw new Error('Failed to create WebGL fence');
24
+ }
25
+ this.handle = sync;
26
+
27
+ this.signaled = new Promise(resolve => {
28
+ const poll = () => {
29
+ const status = this.gl.clientWaitSync(this.handle, 0, 0);
30
+ if (status === this.gl.ALREADY_SIGNALED || status === this.gl.CONDITION_SATISFIED) {
31
+ this._signaled = true;
32
+ resolve();
33
+ } else {
34
+ setTimeout(poll, 1);
35
+ }
36
+ };
37
+ poll();
38
+ });
39
+ }
40
+
41
+ isSignaled(): boolean {
42
+ if (this._signaled) {
43
+ return true;
44
+ }
45
+ const status = this.gl.getSyncParameter(this.handle, this.gl.SYNC_STATUS);
46
+ this._signaled = status === this.gl.SIGNALED;
47
+ return this._signaled;
48
+ }
49
+
50
+ destroy(): void {
51
+ if (!this.destroyed) {
52
+ this.gl.deleteSync(this.handle);
53
+ }
54
+ }
55
+ }
@@ -50,7 +50,8 @@ export class WEBGLRenderPass extends RenderPass {
50
50
  (_, i) => GL.COLOR_ATTACHMENT0 + i
51
51
  );
52
52
  this.device.gl.drawBuffers(drawBuffers);
53
- } else {
53
+ } else if (!this.props.framebuffer) {
54
+ // Default framebuffer only supports GL.BACK/GL.NONE draw buffers
54
55
  this.device.gl.drawBuffers([GL.BACK]);
55
56
  }
56
57
 
@@ -110,12 +111,9 @@ export class WEBGLRenderPass extends RenderPass {
110
111
  if (parameters.blendConstant) {
111
112
  glParameters.blendColor = parameters.blendConstant;
112
113
  }
113
- if (parameters.stencilReference) {
114
- // eslint-disable-next-line no-console
115
- console.warn('RenderPassParameters.stencilReference not yet implemented in WebGL');
116
- // parameters.stencilFunc = [func, ref, mask];
117
- // Does this work?
114
+ if (parameters.stencilReference !== undefined) {
118
115
  glParameters[GL.STENCIL_REF] = parameters.stencilReference;
116
+ glParameters[GL.STENCIL_BACK_REF] = parameters.stencilReference;
119
117
  }
120
118
 
121
119
  if ('colorMask' in parameters) {
@@ -2,33 +2,45 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
- import type {
6
- Device,
7
- TextureProps,
8
- TextureViewProps,
9
- Sampler,
10
- SamplerProps,
11
- CopyExternalImageOptions,
12
- CopyImageDataOptions,
13
- TypedArray
5
+ // @ts-nocheck
6
+
7
+ import {
8
+ type Device,
9
+ type TextureProps,
10
+ type TextureViewProps,
11
+ type Sampler,
12
+ type SamplerProps,
13
+ type CopyExternalImageOptions,
14
+ type CopyImageDataOptions,
15
+ type TextureReadOptions,
16
+ type TextureWriteOptions,
17
+ type TextureFormat,
18
+ type TypedArray,
19
+ Buffer,
20
+ Texture,
21
+ log
14
22
  } from '@luma.gl/core';
15
- import {Texture, log} from '@luma.gl/core';
23
+
16
24
  import {
25
+ GLSamplerParameters,
26
+ GLValueParameters,
17
27
  GL,
18
28
  GLTextureTarget,
19
29
  GLTextureCubeMapTarget,
20
30
  GLTexelDataFormat,
21
- GLPixelType,
22
- // GLDataType,
23
- GLSamplerParameters,
24
- GLValueParameters
31
+ GLPixelType
25
32
  } from '@luma.gl/constants';
33
+
26
34
  import {getTextureFormatWebGL} from '../converters/webgl-texture-table';
27
35
  import {convertSamplerParametersToWebGL} from '../converters/sampler-parameters';
28
36
  import {withGLParameters} from '../../context/state-tracker/with-parameters';
29
37
  import {WebGLDevice} from '../webgl-device';
38
+ import {WEBGLFramebuffer} from './webgl-framebuffer';
30
39
  import {WEBGLSampler} from './webgl-sampler';
31
40
  import {WEBGLTextureView} from './webgl-texture-view';
41
+ import {convertDataTypeToGLDataType} from '../converters/webgl-shadertypes';
42
+ import {convertGLDataTypeToDataType} from '../converters/shader-formats';
43
+ import {getTypedArrayConstructor, getDataType} from '@luma.gl/core';
32
44
 
33
45
  /**
34
46
  * WebGL... the texture API from hell... hopefully made simpler
@@ -65,9 +77,12 @@ export class WEBGLTexture extends Texture {
65
77
  // state
66
78
  /** Texture binding slot - TODO - move to texture view? */
67
79
  _textureUnit: number = 0;
80
+ /** Chached framebuffer */
81
+ _framebuffer: WEBGLFramebuffer | null = null;
68
82
 
69
83
  constructor(device: Device, props: TextureProps) {
70
- super(device, props);
84
+ // const byteAlignment = this._getRowByteAlignment(props.format, props.width);
85
+ super(device, props, {byteAlignment: 1});
71
86
 
72
87
  this.device = device as WebGLDevice;
73
88
  this.gl = this.device.gl;
@@ -119,6 +134,10 @@ export class WEBGLTexture extends Texture {
119
134
 
120
135
  override destroy(): void {
121
136
  if (this.handle) {
137
+ // Destroy any cached framebuffer
138
+ this._framebuffer?.destroy();
139
+ this._framebuffer = null;
140
+
122
141
  this.gl.deleteTexture(this.handle);
123
142
  this.removeStats();
124
143
  this.trackDeallocatedMemory('Texture');
@@ -138,12 +157,51 @@ export class WEBGLTexture extends Texture {
138
157
  this._setSamplerParameters(parameters);
139
158
  }
140
159
 
160
+ copyExternalImage(options_: CopyExternalImageOptions): {width: number; height: number} {
161
+ const options = this._normalizeCopyExternalImageOptions(options_);
162
+
163
+ if (options.sourceX || options.sourceY) {
164
+ // requires copyTexSubImage2D from a framebuffer'
165
+ throw new Error('WebGL does not support sourceX/sourceY)');
166
+ }
167
+
168
+ const {glFormat, glType} = this;
169
+ const {image, depth, mipLevel, x, y, z, width, height} = options;
170
+
171
+ // WebGL cube maps specify faces by overriding target instead of using the z parameter
172
+ const glTarget = getWebGLCubeFaceTarget(this.glTarget, this.dimension, z);
173
+ const glParameters: GLValueParameters = options.flipY ? {[GL.UNPACK_FLIP_Y_WEBGL]: true} : {};
174
+
175
+ this.gl.bindTexture(this.glTarget, this.handle);
176
+
177
+ withGLParameters(this.gl, glParameters, () => {
178
+ switch (this.dimension) {
179
+ case '2d':
180
+ case 'cube':
181
+ // prettier-ignore
182
+ this.gl.texSubImage2D(glTarget, mipLevel, x, y, width, height, glFormat, glType, image);
183
+ break;
184
+ case '2d-array':
185
+ case '3d':
186
+ // prettier-ignore
187
+ this.gl.texSubImage3D(glTarget, mipLevel, x, y, z, width, height, depth, glFormat, glType, image);
188
+ break;
189
+ default:
190
+ // Can never happen in WebGL
191
+ }
192
+ });
193
+
194
+ this.gl.bindTexture(this.glTarget, null);
195
+
196
+ return {width: options.width, height: options.height};
197
+ }
198
+
141
199
  copyImageData(options_: CopyImageDataOptions): void {
142
200
  const options = this._normalizeCopyImageDataOptions(options_);
143
201
 
144
202
  const typedArray = options.data as TypedArray;
145
- const {width, height, depth} = this;
146
- const {mipLevel = 0, byteOffset = 0, x = 0, y = 0, z = 0} = options;
203
+ const {width, height, depth, z = 0} = options;
204
+ const {mipLevel = 0, byteOffset = 0, x = 0, y = 0} = options;
147
205
  const {glFormat, glType, compressed} = this;
148
206
 
149
207
  // Target used for face updates, but not for binding
@@ -164,12 +222,13 @@ export class WEBGLTexture extends Texture {
164
222
 
165
223
  const glParameters: GLValueParameters = !this.compressed
166
224
  ? {
225
+ [GL.UNPACK_ALIGNMENT]: this.byteAlignment,
167
226
  ...(unpackRowLength !== undefined ? {[GL.UNPACK_ROW_LENGTH]: unpackRowLength} : {}),
168
227
  [GL.UNPACK_IMAGE_HEIGHT]: options.rowsPerImage
169
228
  }
170
229
  : {};
171
230
 
172
- this.gl.bindTexture(glTarget, this.handle);
231
+ this.gl.bindTexture(this.glTarget, this.handle);
173
232
 
174
233
  withGLParameters(this.gl, glParameters, () => {
175
234
  switch (this.dimension) {
@@ -198,51 +257,163 @@ export class WEBGLTexture extends Texture {
198
257
  }
199
258
  });
200
259
 
201
- this.gl.bindTexture(glTarget, null);
260
+ this.gl.bindTexture(this.glTarget, null);
202
261
  }
203
262
 
204
- copyExternalImage(options_: CopyExternalImageOptions): {width: number; height: number} {
205
- const options = this._normalizeCopyExternalImageOptions(options_);
263
+ readBuffer(options: TextureReadOptions = {}, buffer?: Buffer): Buffer {
264
+ throw new Error('readBuffer not implemented');
265
+ }
206
266
 
207
- if (options.sourceX || options.sourceY) {
208
- // requires copyTexSubImage2D from a framebuffer'
209
- throw new Error('WebGL does not support sourceX/sourceY)');
210
- }
267
+ async readDataAsync(options: TextureReadOptions = {}): Promise<ArrayBuffer> {
268
+ return this.readDataSyncWebGL(options);
269
+ }
211
270
 
212
- const {glFormat, glType} = this;
213
- const {image, depth, mipLevel, x, y, z, width, height} = options;
271
+ writeBuffer(buffer: Buffer, options_: TextureWriteOptions = {}) {}
214
272
 
215
- // WebGL cube maps specify faces by overriding target instead of using the depth parameter
273
+ writeData(data: ArrayBuffer | ArrayBufferView, options_: TextureWriteOptions = {}): void {
274
+ const options = this._normalizeTextureWriteOptions(options_);
275
+
276
+ const typedArray = ArrayBuffer.isView(data) ? data : new Uint8Array(data);
277
+ const {} = this;
278
+ const {width, height, mipLevel, x, y, z} = options;
279
+ const {glFormat, glType, compressed} = this;
280
+ const depth = 0; // TODO - fix
216
281
  const glTarget = getWebGLCubeFaceTarget(this.glTarget, this.dimension, depth);
217
- const glParameters: GLValueParameters = options.flipY ? {[GL.UNPACK_FLIP_Y_WEBGL]: true} : {};
218
282
 
219
- this.gl.bindTexture(this.glTarget, this.handle);
283
+ // const byteOffset = 0;
284
+ // const {bytesPerRow, rowsPerImage} = this.computeMemoryLayout(options);
285
+
286
+ const glParameters: GLValueParameters = !this.compressed
287
+ ? {
288
+ // WebGL does not require byte alignment, but allows it to be specified
289
+ [GL.UNPACK_ALIGNMENT]: this.byteAlignment
290
+ // [GL.UNPACK_ROW_LENGTH]: bytesPerRow,
291
+ // [GL.UNPACK_IMAGE_HEIGHT]: rowsPerImage
292
+ }
293
+ : {};
294
+
295
+ this.gl.bindTexture(glTarget, this.handle);
296
+ this.gl.bindBuffer(GL.PIXEL_UNPACK_BUFFER, null);
220
297
 
221
298
  withGLParameters(this.gl, glParameters, () => {
222
299
  switch (this.dimension) {
223
300
  case '2d':
224
301
  case 'cube':
225
- // prettier-ignore
226
- this.gl.texSubImage2D(glTarget, mipLevel, x, y, width, height, glFormat, glType, image);
302
+ if (compressed) {
303
+ // prettier-ignore
304
+ this.gl.compressedTexSubImage2D(glTarget, mipLevel, x, y, width, height, glFormat, typedArray);
305
+ } else {
306
+ // prettier-ignore
307
+ this.gl.texSubImage2D(glTarget, mipLevel, x, y, width, height, glFormat, glType, typedArray);
308
+ }
227
309
  break;
228
310
  case '2d-array':
229
311
  case '3d':
230
- // prettier-ignore
231
- this.gl.texSubImage3D(glTarget, mipLevel, x, y, z, width, height, depth, glFormat, glType, image);
312
+ if (compressed) {
313
+ // prettier-ignore
314
+ this.gl.compressedTexSubImage3D(glTarget, mipLevel, x, y, z, width, height, depth, glFormat, typedArray);
315
+ } else {
316
+ // prettier-ignore
317
+ this.gl.texSubImage3D(glTarget, mipLevel, x, y, z, width, height, depth, glFormat, glType, typedArray);
318
+ }
232
319
  break;
233
320
  default:
234
321
  // Can never happen in WebGL
235
322
  }
236
323
  });
237
324
 
238
- this.gl.bindTexture(this.glTarget, null);
325
+ this.gl.bindTexture(glTarget, null);
326
+ }
239
327
 
240
- return {width: options.width, height: options.height};
328
+ // IMPLEMENTATION SPECIFIC
329
+
330
+ /** @todo - for now we always use 1 for maximum compatibility, we can fine tune later */
331
+ private _getRowByteAlignment(format: TextureFormat, width: number): 1 | 2 | 4 | 8 {
332
+ // For best texture data read/write performance, calculate the biggest pack/unpack alignment
333
+ // that fits with the provided texture row byte length
334
+ // Note: Any RGBA or 32 bit type will be at least 4 bytes, which should result in good performance.
335
+ // const info = this.device.getTextureFormatInfo(format);
336
+ // const rowByteLength = width * info.bytesPerPixel;
337
+ // if (rowByteLength % 8 === 0) return 8;
338
+ // if (rowByteLength % 4 === 0) return 4;
339
+ // if (rowByteLength % 2 === 0) return 2;
340
+ return 1;
341
+ }
342
+
343
+ /**
344
+ * Wraps a given texture into a framebuffer object, that can be further used
345
+ * to read data from the texture object.
346
+ */
347
+ _getFramebuffer() {
348
+ this._framebuffer ||= this.device.createFramebuffer({
349
+ id: `framebuffer-for-${this.id}`,
350
+ width: this.width,
351
+ height: this.height,
352
+ colorAttachments: [this]
353
+ });
354
+ return this._framebuffer;
241
355
  }
242
356
 
243
357
  // WEBGL SPECIFIC
244
358
 
245
- generateMipmapsWebGL(options?: {force?: boolean}): void {
359
+ override readDataSyncWebGL(options_: TextureReadOptions = {}): ArrayBuffer {
360
+ const options = this._normalizeTextureReadOptions(options_);
361
+
362
+ const memoryLayout = this.computeMemoryLayout(options);
363
+
364
+ // const formatInfo = getTextureFormatInfo(format);
365
+ // Allocate pixel array if not already available, using supplied type
366
+ const shaderType = convertGLDataTypeToDataType(this.glType);
367
+
368
+ const ArrayType = getTypedArrayConstructor(shaderType);
369
+ // const components = glFormatToComponents(this.glFormat);
370
+ // TODO - check for composite type (components = 1).
371
+
372
+ const targetArray = new ArrayType(memoryLayout.byteLength) as
373
+ | Uint8Array
374
+ | Uint16Array
375
+ | Float32Array;
376
+
377
+ // Pixel array available, if necessary, deduce type from it.
378
+ const signedType = getDataType(targetArray);
379
+ const sourceType = convertDataTypeToGLDataType(signedType);
380
+
381
+ // There is a lot of hedging in the WebGL2 spec about what formats are guaranteed to be readable
382
+ // (It should always be possible to read RGBA/UNSIGNED_BYTE, but most other combinations are not guaranteed)
383
+ // Querying is possible but expensive:
384
+ // const {device} = framebuffer;
385
+ // texture.glReadFormat ||= gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_FORMAT);
386
+ // texture.glReadType ||= gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_TYPE);
387
+ // console.log('params', device.getGLKey(texture.glReadFormat), device.getGLKey(texture.glReadType));
388
+
389
+ const framebuffer = this._getFramebuffer();
390
+
391
+ // Note: luma.gl overrides bindFramebuffer so that we can reliably restore the previous framebuffer (this is the only function for which we do that)
392
+ const prevHandle = this.gl.bindFramebuffer(
393
+ GL.FRAMEBUFFER,
394
+ framebuffer.handle
395
+ ) as unknown as WebGLFramebuffer | null;
396
+
397
+ // Select the color attachment to read from
398
+ this.gl.readBuffer(GL.COLOR_ATTACHMENT0);
399
+ this.gl.readPixels(
400
+ options.x,
401
+ options.y,
402
+ options.width,
403
+ options.height,
404
+ this.glFormat,
405
+ sourceType,
406
+ targetArray
407
+ );
408
+ this.gl.bindFramebuffer(GL.FRAMEBUFFER, prevHandle || null);
409
+
410
+ return targetArray.buffer as ArrayBuffer;
411
+ }
412
+
413
+ /**
414
+ * @note - this is used by the DynamicTexture class to generate mipmaps on WebGL
415
+ */
416
+ override generateMipmapsWebGL(options?: {force?: boolean}): void {
246
417
  const isFilterableAndRenderable =
247
418
  this.device.isTextureFormatRenderable(this.props.format) &&
248
419
  this.device.isTextureFormatFilterable(this.props.format);
@@ -312,6 +483,7 @@ export class WEBGLTexture extends Texture {
312
483
 
313
484
  this.gl.bindTexture(this.glTarget, null);
314
485
  }
486
+
315
487
  _getActiveUnit(): number {
316
488
  return this.gl.getParameter(GL.ACTIVE_TEXTURE) - GL.TEXTURE0;
317
489
  }
@@ -78,40 +78,44 @@ export class WebGLAdapter extends Adapter {
78
78
  async create(props: DeviceProps = {}): Promise<WebGLDevice> {
79
79
  const {WebGLDevice} = await import('./webgl-device');
80
80
 
81
- log.groupCollapsed(LOG_LEVEL, 'WebGLDevice created')();
82
- try {
83
- const promises: Promise<unknown>[] = [];
81
+ const promises: Promise<unknown>[] = [];
84
82
 
85
- // Load webgl and spector debug scripts from CDN if requested
86
- if (props.debugWebGL || props.debug) {
87
- promises.push(loadWebGLDeveloperTools());
88
- }
83
+ // Load webgl and spector debug scripts from CDN if requested
84
+ if (props.debugWebGL || props.debug) {
85
+ promises.push(loadWebGLDeveloperTools());
86
+ }
89
87
 
90
- if (props.debugSpectorJS) {
91
- promises.push(loadSpectorJS(props));
92
- }
88
+ if (props.debugSpectorJS) {
89
+ promises.push(loadSpectorJS(props));
90
+ }
93
91
 
94
- // Wait for all the loads to settle before creating the context.
95
- // The Device.create() functions are async, so in contrast to the constructor, we can `await` here.
96
- const results = await Promise.allSettled(promises);
97
- for (const result of results) {
98
- if (result.status === 'rejected') {
99
- log.error(`Failed to initialize debug libraries ${result.reason}`)();
100
- }
92
+ // Wait for all the loads to settle before creating the context.
93
+ // The Device.create() functions are async, so in contrast to the constructor, we can `await` here.
94
+ const results = await Promise.allSettled(promises);
95
+ for (const result of results) {
96
+ if (result.status === 'rejected') {
97
+ log.error(`Failed to initialize debug libraries ${result.reason}`)();
101
98
  }
99
+ }
102
100
 
101
+ try {
103
102
  const device = new WebGLDevice(props);
104
103
 
104
+ log.groupCollapsed(LOG_LEVEL, `WebGLDevice ${device.id} created`)();
105
105
  // Log some debug info about the newly created context
106
106
  const message = `\
107
107
  ${device._reused ? 'Reusing' : 'Created'} device with WebGL2 ${device.props.debug ? 'debug ' : ''}context: \
108
108
  ${device.info.vendor}, ${device.info.renderer} for canvas: ${device.canvasContext.id}`;
109
109
  log.probe(LOG_LEVEL, message)();
110
110
  log.table(LOG_LEVEL, device.info)();
111
-
112
111
  return device;
113
112
  } finally {
114
113
  log.groupEnd(LOG_LEVEL)();
114
+ log.info(
115
+ LOG_LEVEL,
116
+ `%cWebGL call tracing: luma.log.set('debug-webgl') `,
117
+ 'color: white; background: blue; padding: 2px 6px; border-radius: 3px;'
118
+ )();
115
119
  }
116
120
  }
117
121
  }
@@ -121,8 +125,7 @@ function isWebGL(gl: any): gl is WebGL2RenderingContext {
121
125
  if (typeof WebGL2RenderingContext !== 'undefined' && gl instanceof WebGL2RenderingContext) {
122
126
  return true;
123
127
  }
124
- // Look for debug contexts, headless gl etc
125
- return Boolean(gl && Number.isFinite(gl._version));
128
+ return Boolean(gl && typeof gl.createVertexArray === 'function');
126
129
  }
127
130
 
128
131
  export const webgl2Adapter = new WebGLAdapter();
@@ -27,16 +27,27 @@ export class WebGLCanvasContext extends CanvasContext {
27
27
 
28
28
  // Base class constructor cannot access derived methods/fields, so we need to call these functions in the subclass constructor
29
29
  this._setAutoCreatedCanvasId(`${this.device.id}-canvas`);
30
- this._updateDevice();
31
- }
32
-
33
- getCurrentFramebuffer(): WEBGLFramebuffer {
34
- // Setting handle to null returns a reference to the default framebuffer
35
- this._framebuffer = this._framebuffer || new WEBGLFramebuffer(this.device, {handle: null});
36
- return this._framebuffer;
30
+ this._configureDevice();
37
31
  }
38
32
 
39
33
  // IMPLEMENTATION OF ABSTRACT METHODS
40
34
 
41
- _updateDevice(): void {}
35
+ _configureDevice(): void {
36
+ const shouldResize =
37
+ this.drawingBufferWidth !== this._framebuffer?.width ||
38
+ this.drawingBufferHeight !== this._framebuffer?.height;
39
+ if (shouldResize) {
40
+ this._framebuffer?.resize([this.drawingBufferWidth, this.drawingBufferHeight]);
41
+ }
42
+ }
43
+
44
+ _getCurrentFramebuffer(): WEBGLFramebuffer {
45
+ this._framebuffer ||= new WEBGLFramebuffer(this.device, {
46
+ id: 'canvas-context-framebuffer',
47
+ handle: null, // Setting handle to null returns a reference to the default WebGL framebuffer
48
+ width: this.drawingBufferWidth,
49
+ height: this.drawingBufferHeight
50
+ });
51
+ return this._framebuffer;
52
+ }
42
53
  }