@geastack/native-webgl-angle 0.1.14 → 0.1.15

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/README.md CHANGED
@@ -121,7 +121,7 @@ to change them. Any present value, including an empty string, enables them.
121
121
 
122
122
  ### Validation
123
123
 
124
- `npm run test:host-effects` checks 85 direct GL/input declarations and 15 audio
124
+ `npm run test:host-effects` checks 87 direct GL/input declarations and 15 audio
125
125
  declarations against the compiler's host-mutation census. Their
126
126
  `@gea-host-inert` contracts permit native state changes while excluding script
127
127
  reference retention, script object mutation and script callbacks. The native
package/gea-native.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$comment": "What a GeaStack native target needs in order to build this package into an app. Paths are relative to the package root.",
3
3
  "windows": {
4
- "sources": ["native/angle_webgl_host.mm", "native/audio_host.mm", "native/win32_webgl_surface.cpp"],
4
+ "sources": ["native/angle_webgl_host.mm", "native/audio_host.mm", "native/resource_host.mm", "native/win32_webgl_surface.cpp"],
5
5
  "includeDirs": ["native"],
6
6
  "requires": {
7
7
  "gles": {
@@ -44,7 +44,9 @@ export const nativeLightUniformClassNames = [
44
44
  export const nativeUniformTypedefs = `/**
45
45
  * Every value a shader uniform slot can hold, as ONE disjoint union: the math
46
46
  * classes three's setters duck-type (\`v.x\`, \`v.r\`, \`v.elements\`), textures,
47
- * the flat numeric arrays, and the light-uniform arrays \`WebGLLights\` builds.
47
+ * the flat numeric arrays, the vector and matrix arrays a custom
48
+ * \`ShaderMaterial\` declares (\`uniform vec4 u[ N ]\`, flattened by
49
+ * \`copyUniformElement\`), and the light-uniform arrays \`WebGLLights\` builds.
48
50
  * A slot is therefore one native tagged union, and every consumer narrows it
49
51
  * with \`instanceof\`/\`typeof\`/\`Array.isArray\` instead of reading a box.
50
52
  * @typedef {number|boolean|null|undefined
@@ -57,7 +59,9 @@ export const nativeUniformTypedefs = `/**
57
59
  * |import('../../math/Matrix4.js').Matrix4
58
60
  * |import('../../textures/Texture.js').Texture
59
61
  * |number[]|Float32Array|Int32Array|Uint32Array
62
+ * |Array<import('../../math/Vector2.js').Vector2>
60
63
  * |Array<import('../../math/Vector3.js').Vector3>
64
+ * |Array<import('../../math/Vector4.js').Vector4>
61
65
  * |Array<import('../../math/Matrix4.js').Matrix4>
62
66
  * |Array<import('../../textures/Texture.js').Texture|null>
63
67
  ${nativeLightUniformClassNames.map((name) => ` * |${name}|${name}[]`).join('\n')}
@@ -345,6 +349,13 @@ export function isUniformNumberArray( value ) {
345
349
 
346
350
  }
347
351
 
352
+ /** @param {NativeUniformValue} value @return {value is Vector2[]} */
353
+ function isVector2Array( value ) {
354
+
355
+ \treturn Array.isArray( value ) && value.length > 0 && value[ 0 ] instanceof Vector2;
356
+
357
+ }
358
+
348
359
  /** @param {NativeUniformValue} value @return {value is Vector3[]} */
349
360
  function isVector3Array( value ) {
350
361
 
@@ -352,6 +363,13 @@ function isVector3Array( value ) {
352
363
 
353
364
  }
354
365
 
366
+ /** @param {NativeUniformValue} value @return {value is Vector4[]} */
367
+ function isVector4Array( value ) {
368
+
369
+ \treturn Array.isArray( value ) && value.length > 0 && value[ 0 ] instanceof Vector4;
370
+
371
+ }
372
+
355
373
  /** @param {NativeUniformValue} value @return {value is Matrix4[]} */
356
374
  function isMatrix4Array( value ) {
357
375
 
@@ -405,6 +423,17 @@ export function cloneUniformValue( value ) {
405
423
 
406
424
  \tif ( isUniformNumberArray( value ) ) return value.slice();
407
425
 
426
+ \tif ( isVector2Array( value ) ) {
427
+
428
+ \t\t/** @type {Vector2[]} */
429
+ \t\tconst cloned = [];
430
+
431
+ \t\tfor ( let i = 0, l = value.length; i < l; i ++ ) cloned.push( value[ i ].clone() );
432
+
433
+ \t\treturn cloned;
434
+
435
+ \t}
436
+
408
437
  \tif ( isVector3Array( value ) ) {
409
438
 
410
439
  \t\t/** @type {Vector3[]} */
@@ -416,6 +445,17 @@ export function cloneUniformValue( value ) {
416
445
 
417
446
  \t}
418
447
 
448
+ \tif ( isVector4Array( value ) ) {
449
+
450
+ \t\t/** @type {Vector4[]} */
451
+ \t\tconst cloned = [];
452
+
453
+ \t\tfor ( let i = 0, l = value.length; i < l; i ++ ) cloned.push( value[ i ].clone() );
454
+
455
+ \t\treturn cloned;
456
+
457
+ \t}
458
+
419
459
  \tif ( isMatrix4Array( value ) ) {
420
460
 
421
461
  \t\t/** @type {Matrix4[]} */
@@ -1,16 +1,21 @@
1
+ // A texture upload hands GL the view's own bytes, and the GL type names the view
2
+ // (WebGL 2 3.7.6, OES_texture_half_float): UNSIGNED_SHORT, HALF_FLOAT and the
3
+ // packed 16-bit types read a Uint16Array; UNSIGNED_INT and the packed 32-bit
4
+ // types a Uint32Array; BYTE, SHORT and INT their signed views; FLOAT a Float32Array.
5
+ const textureElementKind = '(type == 0x1401 ? 4 : type == 0x1400 ? 5 : type == 0x1402 ? 6 : type == 0x1404 ? 7 : ' +
6
+ 'type == 0x1403 || type == 0x140B || type == 0x8D61 || type == 0x8033 || type == 0x8034 || type == 0x8363 ? 2 : ' +
7
+ 'type == 0x1405 || type == 0x8368 || type == 0x8C3B || type == 0x8C3E || type == 0x84FA ? 3 : 1)'
8
+
1
9
  // The host owns the physical upload format. The compiler only preserves the
2
10
  // native carriers at these explicitly registered synchronous boundaries.
3
- // Element type of the client pixels, by GL type: half floats travel as their
4
- // 16-bit patterns, never widened to float32.
5
- const pixelKind = '(type == 0x1403 || type == 0x140B || type == 0x8D61 ? 2 : type == 0x1405 ? 3 : type == 0x1401 ? 4 : type == 0x1400 ? 5 : type == 0x1402 ? 6 : type == 0x1404 ? 7 : 1)';
6
11
  export const nativeUploadFunctions = [
7
12
  ['threeWebGLBufferData', 'gea_three_webgl_buffer_data_native', ['target', 'usage', 'typeCode'], 'gea_three_webgl_buffer_data_bytes', ['target', 'usage'], 'typeCode'],
8
13
  ['threeWebGLBufferSubData', 'gea_three_webgl_buffer_sub_data_native', ['target', 'offset', 'typeCode'], 'gea_three_webgl_buffer_sub_data_bytes', ['target', 'offset'], 'typeCode'],
9
14
  ['threeWebGLBufferSubDataRange', 'gea_three_webgl_buffer_sub_data_range_native', ['target', 'offset', 'typeCode'], 'gea_three_webgl_buffer_sub_data_bytes', ['target', 'offset'], 'typeCode', true],
10
- ['threeWebGLTexImage2D', 'gea_three_webgl_tex_image_2d_native', ['target', 'level', 'internalFormat', 'width', 'height', 'format', 'type'], 'gea_three_webgl_tex_image_2d_bytes', ['target', 'level', 'internalFormat', 'width', 'height', 'format', 'type'], pixelKind],
11
- ['threeWebGLTexImage3D', 'gea_three_webgl_tex_image_3d_native', ['target', 'level', 'internalFormat', 'width', 'height', 'depth', 'format', 'type'], 'gea_three_webgl_tex_image_3d_bytes', ['target', 'level', 'internalFormat', 'width', 'height', 'depth', 'format', 'type'], pixelKind],
12
- ['threeWebGLTexSubImage3D', 'gea_three_webgl_tex_sub_image_3d_native', ['target', 'level', 'xoffset', 'yoffset', 'zoffset', 'width', 'height', 'depth', 'format', 'type'], 'gea_three_webgl_tex_sub_image_3d_bytes', ['target', 'level', 'xoffset', 'yoffset', 'zoffset', 'width', 'height', 'depth', 'format', 'type'], pixelKind],
13
- ['threeWebGLTexSubImage2D', 'gea_three_webgl_tex_sub_image_2d_native', ['target', 'level', 'xoffset', 'yoffset', 'width', 'height', 'format', 'type'], 'gea_three_webgl_tex_sub_image_2d_bytes', ['target', 'level', 'xoffset', 'yoffset', 'width', 'height', 'format', 'type'], pixelKind],
15
+ ['threeWebGLTexImage2D', 'gea_three_webgl_tex_image_2d_native', ['target', 'level', 'internalFormat', 'width', 'height', 'format', 'type'], 'gea_three_webgl_tex_image_2d_bytes', ['target', 'level', 'internalFormat', 'width', 'height', 'format', 'type'], textureElementKind],
16
+ ['threeWebGLTexImage3D', 'gea_three_webgl_tex_image_3d_native', ['target', 'level', 'internalFormat', 'width', 'height', 'depth', 'format', 'type'], 'gea_three_webgl_tex_image_3d_bytes', ['target', 'level', 'internalFormat', 'width', 'height', 'depth', 'format', 'type'], textureElementKind],
17
+ ['threeWebGLTexSubImage3D', 'gea_three_webgl_tex_sub_image_3d_native', ['target', 'level', 'xoffset', 'yoffset', 'zoffset', 'width', 'height', 'depth', 'format', 'type'], 'gea_three_webgl_tex_sub_image_3d_bytes', ['target', 'level', 'xoffset', 'yoffset', 'zoffset', 'width', 'height', 'depth', 'format', 'type'], textureElementKind],
18
+ ['threeWebGLTexSubImage2D', 'gea_three_webgl_tex_sub_image_2d_native', ['target', 'level', 'xoffset', 'yoffset', 'width', 'height', 'format', 'type'], 'gea_three_webgl_tex_sub_image_2d_bytes', ['target', 'level', 'xoffset', 'yoffset', 'width', 'height', 'format', 'type'], textureElementKind],
14
19
  ].map(([name, binding, parameters, raw, rawParameters, kind, range]) => {
15
20
  const declaration = `extern "C" void ${raw}(${rawParameters.map(p => `double ${p}`).join(', ')}, const void* bytes, double byteLength);
16
21
  template <typename Source> inline void ${binding}(${parameters.map(p => `double ${p}`).join(', ')}, const Source& values${range ? ', double sourceOffset, double length' : ''}) {
package/geatsc-plugin.mjs CHANGED
@@ -97,6 +97,14 @@ const hostFunctions = [
97
97
  ],
98
98
  ...nativeUploadFunctions,
99
99
  ...nativeReadbackFunctions,
100
+ // Resources files (native/resource_host.mm): read whole, in place, into the caller's Uint8Array.
101
+ ['threeResourceSize','gea_three_resource_size','double','extern "C" double gea_three_resource_size(std::string name);'],
102
+ ['threeResourceRead','gea_three_resource_read_native','double',`
103
+ extern "C" double gea_three_resource_read_bytes(std::string name, void* bytes, double byteLength);
104
+ inline double gea_three_resource_read_native(std::string name, const gea::Ref<gea::TypedArray<std::uint8_t>>& destination) {
105
+ if (!destination) return -1;
106
+ return gea_three_resource_read_bytes(std::move(name), destination->data(), static_cast<double>(destination->size()));
107
+ }`],
100
108
  ['threeWebGLBlitFramebuffer','gea_three_webgl_blit_framebuffer','void','extern "C" void gea_three_webgl_blit_framebuffer(double,double,double,double,double,double,double,double,double,double);'],
101
109
  ['threeWebGLReadPixelsPack','gea_three_webgl_read_pixels_pack','void','extern "C" void gea_three_webgl_read_pixels_pack(double,double,double,double,double,double,double);'],
102
110
  ['threeWebGLFenceSync','gea_three_webgl_fence_sync','double','extern "C" double gea_three_webgl_fence_sync();'],
@@ -148,6 +156,12 @@ const hostFunctions = [
148
156
  "void",
149
157
  'extern "C" void gea_three_webgl_swap();',
150
158
  ],
159
+ [
160
+ "threeWebGLSetVsync",
161
+ "gea_three_webgl_set_vsync",
162
+ "void",
163
+ 'extern "C" void gea_three_webgl_set_vsync(double on);',
164
+ ],
151
165
  [
152
166
  "threeAngleSmokeLog",
153
167
  "gea_three_angle_smoke_log",
@@ -222,6 +236,12 @@ const hostFunctions = [
222
236
  "double",
223
237
  'extern "C" double gea_three_webgl_get_shader_parameter(double shader, double pname);',
224
238
  ],
239
+ [
240
+ "threeWebGLGetShaderInfoLog",
241
+ "gea_three_webgl_get_shader_info_log",
242
+ "std::string",
243
+ 'extern "C" std::string gea_three_webgl_get_shader_info_log(double shader);',
244
+ ],
225
245
  [
226
246
  "threeWebGLDeleteShader",
227
247
  "gea_three_webgl_delete_shader",
@@ -253,6 +273,12 @@ const hostFunctions = [
253
273
  "double",
254
274
  'extern "C" double gea_three_webgl_get_program_parameter(double program, double pname);',
255
275
  ],
276
+ [
277
+ "threeWebGLGetProgramInfoLog",
278
+ "gea_three_webgl_get_program_info_log",
279
+ "std::string",
280
+ 'extern "C" std::string gea_three_webgl_get_program_info_log(double program);',
281
+ ],
256
282
  [
257
283
  "threeWebGLDeleteProgram",
258
284
  "gea_three_webgl_delete_program",
@@ -1698,6 +1724,12 @@ const nativeDataTextureOverlayUnpackAlignmentSource =
1698
1724
  "\t/** @type {number} */\n\tunpackAlignment;";
1699
1725
  const overlayToJSONParameterSource =
1700
1726
  /(\/\*\*\n\t \* @param )\{[^\n]+\}( \[meta\]\n\t \*\/\n\ttoJSON\( meta \) \{)/g;
1727
+ const nativeObject3DJSONType =
1728
+ "{ metadata?: { version: number, type: string, generator: string }, geometries?: Array<Object>, materials?: Array<Object>, textures?: Array<Object>, images?: Array<Object>, shapes?: Array<Object>, skeletons?: Array<Object>, animations?: Array<Object>, nodes?: Array<Object>, object: Object }";
1729
+ const nativeBufferGeometryJSONType =
1730
+ "{ metadata: { version: number, type: string, generator: string }, uuid?: string, type?: string, name?: string, userData?: Object, data?: Object, instanceCount?: number, isInstancedBufferGeometry?: boolean, [key: string]: any }";
1731
+ const nativeFogJSONType =
1732
+ "{ type: string, name: string, color: number, near?: number, far?: number, density?: number }";
1701
1733
  const rendererReceiverMethods = [
1702
1734
  "setPixelRatio",
1703
1735
  "setSize",
@@ -2032,6 +2064,37 @@ const transformSource = ({ fileName, text }) => {
2032
2064
  object3DRaycastSource,
2033
2065
  nativeObject3DRaycastSource,
2034
2066
  );
2067
+ // `JSON.stringify` on a declared-any value keeps every class's `toJSON`
2068
+ // (three's own `copy()` deep-copies `userData` that way), so these bodies
2069
+ // compile whenever a material or texture is cloned. `@return {Object}`
2070
+ // states nothing, so every `super.toJSON( meta )`, `child.toJSON( meta )`
2071
+ // and `camera.toJSON( false )` result boxed and then had to be read back
2072
+ // as the record the body builds, which no conversion can do. The envelope
2073
+ // is the record the body builds; `object` stays `Object` because each
2074
+ // subclass adds its own fields to it (`data.object.fov = ...`).
2075
+ transformed = replaceOne(
2076
+ transformed,
2077
+ "\t * @return {Object} A JSON object representing the serialized 3D object.",
2078
+ `\t * @return {${nativeObject3DJSONType}} A JSON object representing the serialized 3D object.`,
2079
+ fileName,
2080
+ "Object3D.toJSON envelope",
2081
+ );
2082
+ }
2083
+ if (
2084
+ normalized.endsWith("/three/src/scenes/Fog.js") ||
2085
+ normalized.endsWith("/three/src/scenes/FogExp2.js")
2086
+ ) {
2087
+ // `Scene.toJSON` calls `toJSON` on `?(Fog|FogExp2)`. Each body's own
2088
+ // literal made the two methods two function types, and the read over the
2089
+ // union had to convert one into the other. One record both literals fit,
2090
+ // spelled once, makes them one type.
2091
+ transformed = replaceOne(
2092
+ transformed,
2093
+ "\t * @return {Object} A JSON object representing the serialized fog",
2094
+ `\t * @return {${nativeFogJSONType}} A JSON object representing the serialized fog`,
2095
+ fileName,
2096
+ "Fog toJSON shared record",
2097
+ );
2035
2098
  }
2036
2099
  if (normalized.endsWith("/three/src/renderers/WebGLRenderer.js")) {
2037
2100
  transformed =
@@ -2215,6 +2278,15 @@ class WebGLRenderer {
2215
2278
  rendererCompileReturnSource,
2216
2279
  nativeRendererCompileReturnSource,
2217
2280
  );
2281
+ // `if ( targetScene === null ) targetScene = scene;` stores the Object3D
2282
+ // being compiled; every use after it is an Object3D use.
2283
+ transformed = replaceOne(
2284
+ transformed,
2285
+ `@param {?Scene} [targetScene=null] - The target scene.\n\t\t * ${nativeRendererCompileReturnSource}`,
2286
+ `@param {?Object3D} [targetScene=null] - The target scene.\n\t\t * ${nativeRendererCompileReturnSource}`,
2287
+ fileName,
2288
+ "WebGLRenderer compile target root",
2289
+ );
2218
2290
  transformed = transformed.replace(
2219
2291
  rendererCompileAssignmentSource,
2220
2292
  nativeRendererCompileAssignmentSource,
@@ -3718,6 +3790,33 @@ class BufferGeometry extends EventDispatcher {`,
3718
3790
  fileName,
3719
3791
  "BufferGeometry morph-attribute table",
3720
3792
  );
3793
+ // `toJSON` fills each morph entry from `const array = []`, which the
3794
+ // checker types `any[]` (the table's entry type) while the census lays it
3795
+ // out from its pushes, the union of the two attribute `toJSON` records.
3796
+ // Two carriers for one array, and an array cannot be converted in place.
3797
+ // State the element three's attribute `toJSON`s state: `Object`.
3798
+ transformed = replaceOne(
3799
+ transformed,
3800
+ "\t\t\tconst attributeArray = this.morphAttributes[ key ];\n\n\t\t\tconst array = [];",
3801
+ "\t\t\tconst attributeArray = this.morphAttributes[ key ];\n\n\t\t\t/** @type {Array<Object>} */\n\t\t\tconst array = [];",
3802
+ fileName,
3803
+ "BufferGeometry.toJSON morph entry array",
3804
+ );
3805
+ // `JSON.stringify` on a declared-any value keeps every `toJSON` (see
3806
+ // Object3D's envelope), so this envelope is published to dynamic code,
3807
+ // which may write any of its fields. Laid out from the body, its `data`
3808
+ // field is the record `data.data = { attributes: {} }` and the writes
3809
+ // after it build, and a dynamic value has no reader into that record.
3810
+ // State the envelope, with `data` left `Object` as Object3D's `object`
3811
+ // is: the body fills it key by key. The index signature is the
3812
+ // parameters branch, which copies each parameter in by name.
3813
+ transformed = replaceOne(
3814
+ transformed,
3815
+ "\t * @return {Object} A JSON object representing the serialized geometry.\n\t */\n\ttoJSON() {\n\n\t\tconst data = {",
3816
+ `\t * @return {${nativeBufferGeometryJSONType}} A JSON object representing the serialized geometry.\n\t */\n\ttoJSON() {\n\n\t\t/** @type {${nativeBufferGeometryJSONType}} */\n\t\tconst data = {`,
3817
+ fileName,
3818
+ "BufferGeometry.toJSON envelope",
3819
+ );
3721
3820
  transformed = replaceOne(
3722
3821
  transformed,
3723
3822
  `\tgetAttribute( name ) {
@@ -5455,6 +5554,18 @@ function requireWebGLStateTextureImageSource( value ) {
5455
5554
  fileName,
5456
5555
  "WebGLBindingStates interleaved attribute import",
5457
5556
  );
5557
+ // The binding-state fast paths below test `object instanceof InstancedMesh`; three's own
5558
+ // file reads `object.isInstancedMesh` and imports no InstancedMesh. An app that patches
5559
+ // three on disk may have added the import already.
5560
+ if (!transformed.includes("import { InstancedMesh } from '../../objects/InstancedMesh.js';")) {
5561
+ transformed = replaceOne(
5562
+ transformed,
5563
+ "import { IntType } from '../../constants.js';",
5564
+ "import { IntType } from '../../constants.js';\nimport { InstancedMesh } from '../../objects/InstancedMesh.js';",
5565
+ fileName,
5566
+ "WebGLBindingStates instanced mesh import",
5567
+ );
5568
+ }
5458
5569
  transformed = replaceOne(
5459
5570
  transformed,
5460
5571
  "\t\t\tif ( index !== null ) {\n\n\t\t\t\tgl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, attributes.get( index ).buffer );",
@@ -5641,7 +5752,7 @@ function requireWebGLStateTextureImageSource( value ) {
5641
5752
  transformed = replaceOne(
5642
5753
  transformed,
5643
5754
  "\tfunction get( scene, renderCallDepth ) {",
5644
- "\t/** @param {import('../../scenes/Scene.js').Scene} scene @param {number} renderCallDepth */\n\tfunction get( scene, renderCallDepth ) {",
5755
+ "\t/** @param {import('../../core/Object3D.js').Object3D} scene @param {number} renderCallDepth */\n\tfunction get( scene, renderCallDepth ) {",
5645
5756
  fileName,
5646
5757
  "WebGLRenderLists lookup inputs",
5647
5758
  );
@@ -6373,7 +6484,10 @@ function WebGLShadowMap( renderer, objects, capabilities ) {`,
6373
6484
  transformed = replaceOne(
6374
6485
  transformed,
6375
6486
  "this.render = function ( lights, scene, camera ) {",
6376
- "this.render = /** @this {WebGLShadowMap} @param {Array<import('../../lights/Light.js').Light>} lights */ function ( lights, scene, camera ) {",
6487
+ // `scene` is the root WebGLRenderer.render was handed, any Object3D;
6488
+ // the body only traverses it. This inline block outranks the
6489
+ // declaration overlay's `@param {Scene} scene` above the assignment.
6490
+ "this.render = /** @this {WebGLShadowMap} @param {Array<import('../../lights/Light.js').Light>} lights @param {import('../../core/Object3D.js').Object3D} scene */ function ( lights, scene, camera ) {",
6377
6491
  fileName,
6378
6492
  "WebGLShadowMap.render receiver",
6379
6493
  );
@@ -6741,21 +6855,21 @@ class WebGLBackgroundPlaneMaterial extends ShaderMaterial {
6741
6855
  transformed = replaceOne(
6742
6856
  transformed,
6743
6857
  "\tfunction getBackground( scene ) {",
6744
- "\t/** @param {Scene} scene */\n\tfunction getBackground( scene ) {",
6858
+ "\t/** @param {import('../../core/Object3D.js').Object3D} scene */\n\tfunction getBackground( scene ) {",
6745
6859
  fileName,
6746
6860
  "WebGLBackground background scene input",
6747
6861
  );
6748
6862
  transformed = replaceOne(
6749
6863
  transformed,
6750
6864
  "\tfunction render( scene ) {",
6751
- "\t/** @param {Scene} scene */\n\tfunction render( scene ) {",
6865
+ "\t/** @param {import('../../core/Object3D.js').Object3D} scene */\n\tfunction render( scene ) {",
6752
6866
  fileName,
6753
6867
  "WebGLBackground scene input",
6754
6868
  );
6755
6869
  transformed = replaceOne(
6756
6870
  transformed,
6757
6871
  "\tfunction addToRenderList( renderList, scene ) {",
6758
- "\t/** @param {Scene} scene */\n\tfunction addToRenderList( renderList, scene ) {",
6872
+ "\t/** @param {import('../../core/Object3D.js').Object3D} scene */\n\tfunction addToRenderList( renderList, scene ) {",
6759
6873
  fileName,
6760
6874
  "WebGLBackground render-list scene input",
6761
6875
  );
@@ -7092,6 +7206,15 @@ class WebGLBackgroundPlaneMaterial extends ShaderMaterial {
7092
7206
  backgroundRotation: "uniformMatrix3",
7093
7207
  uvTransform: "uniformMatrix3",
7094
7208
  });
7209
+ // The renderer renders any Object3D (WebGLRenderer.render states
7210
+ // `@param {Object3D} scene`), so the scene inputs above state Object3D.
7211
+ // Only a Scene has a background: each read sits behind
7212
+ // `scene.isScene === true`, or behind a background only a Scene yields,
7213
+ // and names the Scene it reads.
7214
+ transformed = transformed.replaceAll(
7215
+ "scene.background",
7216
+ "/** @type {Scene} */ ( scene ).background",
7217
+ );
7095
7218
  }
7096
7219
  if (normalized.endsWith("/three/src/renderers/shaders/UniformsLib.js")) {
7097
7220
  transformed = transformUniformsLib(transformed, fileName, replaceOne);
@@ -8511,17 +8634,6 @@ function WebGLProperties() {
8511
8634
 
8512
8635
  \t}
8513
8636
 
8514
- \t/**
8515
- \t * @param {WebGLPropertyOwner} object
8516
- \t * @param {keyof NativeWebGLPropertyBag} key
8517
- \t * @param {NativeWebGLPropertyBag[keyof NativeWebGLPropertyBag]} value
8518
- \t */
8519
- \tfunction update( object, key, value ) {
8520
-
8521
- \t\tget( object )[ key ] = value;
8522
-
8523
- \t}
8524
-
8525
8637
  \tfunction dispose() {
8526
8638
 
8527
8639
  \t\tmaterials.clear();
@@ -8533,7 +8645,12 @@ function WebGLProperties() {
8533
8645
 
8534
8646
  \t}
8535
8647
 
8536
- \treturn { has, get, remove, update, dispose };
8648
+ \t// Three's \`update( object, key, value )\` is left out: nothing in three
8649
+ \t// calls it, and its \`get( object )[ key ] = value\` stores a union of every
8650
+ \t// slot's type into the one slot \`key\` names. TypeScript rejects that store
8651
+ \t// (the target is \`never\`), and geatsc certifies no conversion from the
8652
+ \t// union into a single slot, so the C++ emitter refused the body.
8653
+ \treturn { has, get, remove, dispose };
8537
8654
 
8538
8655
  }
8539
8656
 
@@ -9238,7 +9355,7 @@ function WebGLRenderState( extensions ) {`,
9238
9355
  transformed = replaceOne(
9239
9356
  transformed,
9240
9357
  "\tfunction get( scene, renderCallDepth = 0 ) {",
9241
- "\t/** @param {import('../../scenes/Scene.js').Scene} scene @param {number} [renderCallDepth=0] */\n\tfunction get( scene, renderCallDepth = 0 ) {",
9358
+ "\t/** @param {import('../../core/Object3D.js').Object3D} scene @param {number} [renderCallDepth=0] */\n\tfunction get( scene, renderCallDepth = 0 ) {",
9242
9359
  fileName,
9243
9360
  "WebGLRenderStates lookup inputs",
9244
9361
  );
@@ -328,6 +328,7 @@ static constexpr GLenum GL_UNSIGNED_INT = 0x1405;
328
328
  static constexpr GLenum GL_RGBA_VALUE = 0x1908;
329
329
  static constexpr GLenum GL_VIEWPORT_VALUE = 0x0BA2;
330
330
  static constexpr GLenum GL_FRAMEBUFFER_BINDING_VALUE = 0x8CA6;
331
+ static constexpr GLenum GL_INFO_LOG_LENGTH_VALUE = 0x8B84;
331
332
 
332
333
  using PFNEGLGETPROCADDRESS = void *(*)(const char *);
333
334
  using PFNEGLGETPLATFORMDISPLAYEXTPROC = EGLDisplay (*)(EGLenum, void *, const EGLint *);
@@ -402,6 +403,7 @@ using PFNGLGETINTEGERVPROC = void (*)(GLenum, GLint *);
402
403
  using PFNGLGETPROGRAMIVPROC = void (*)(GLuint, GLenum, GLint *);
403
404
  using PFNGLGETSHADERIVPROC = void (*)(GLuint, GLenum, GLint *);
404
405
  using PFNGLGETSHADERINFOLOGPROC = void (*)(GLuint, GLsizei, GLsizei *, GLchar *);
406
+ using PFNGLGETPROGRAMINFOLOGPROC = void (*)(GLuint, GLsizei, GLsizei *, GLchar *);
405
407
  using PFNGLGETSTRINGPROC = const unsigned char *(*)(GLenum);
406
408
  using PFNGLGETUNIFORMLOCATIONPROC = GLint (*)(GLuint, const GLchar *);
407
409
  using PFNGLLINEWIDTHPROC = void (*)(GLfloat);
@@ -549,6 +551,7 @@ struct AngleWebGLState {
549
551
  PFNGLGETPROGRAMIVPROC glGetProgramiv = nullptr;
550
552
  PFNGLGETSHADERIVPROC glGetShaderiv = nullptr;
551
553
  PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog = nullptr;
554
+ PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog = nullptr;
552
555
  PFNGLGETSTRINGPROC glGetString = nullptr;
553
556
  PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation = nullptr;
554
557
  PFNGLLINEWIDTHPROC glLineWidth = nullptr;
@@ -627,6 +630,10 @@ struct AngleWebGLState {
627
630
  };
628
631
 
629
632
  static AngleWebGLState gWebGL;
633
+ // Vertical sync as the program chose it (nativeWebGLSetVsync): 1 on, 0 off, -1 not chosen
634
+ // (then GEA_ANGLE_SWAP_INTERVAL, else 0; see makeCurrent).
635
+ static int gVsyncChoice = -1;
636
+ extern "C" void gea_three_webgl_set_vsync(double on) { gVsyncChoice = on != 0 ? 1 : 0; }
630
637
  static GLenum hostReadbackError = 0;
631
638
  static std::chrono::steady_clock::time_point gProfileSizeSync;
632
639
  static bool gProfileSizeSyncPending = false;
@@ -942,9 +949,19 @@ static bool makeCurrent(const char *label) {
942
949
  // reintroduced because the layer still syncs its own present.
943
950
  // `GEA_ANGLE_SWAP_INTERVAL` overrides it (set `1` to restore the old
944
951
  // double-paced behaviour) so both arms can be measured from one binary.
952
+ // A program's choice (nativeWebGLSetVsync) wins: off is interval 0; on is
953
+ // interval 1 where nothing else paces the frames (a desktop window with no
954
+ // display link), and the default above on Apple platforms, where the display
955
+ // link and the layer's own sync already do.
945
956
  if (gWebGL.eglSwapInterval) {
946
957
  const char *requested = std::getenv("GEA_ANGLE_SWAP_INTERVAL");
947
- const EGLint interval = requested ? static_cast<EGLint>(std::atoi(requested)) : 0;
958
+ const EGLint fallback = requested ? static_cast<EGLint>(std::atoi(requested)) : 0;
959
+ #if defined(__APPLE__)
960
+ const EGLint paced = fallback;
961
+ #else
962
+ const EGLint paced = 1;
963
+ #endif
964
+ const EGLint interval = gVsyncChoice == 0 ? 0 : gVsyncChoice == 1 ? paced : fallback;
948
965
  gWebGL.eglSwapInterval(gWebGL.display, interval);
949
966
  }
950
967
  if (gWebGL.eglMakeCurrent(gWebGL.display, gWebGL.surface, gWebGL.surface, gWebGL.context) == EGL_FALSE_VALUE) {
@@ -1069,6 +1086,7 @@ static bool loadGlSymbols() {
1069
1086
  LOAD_GL(glGetProgramiv, PFNGLGETPROGRAMIVPROC)
1070
1087
  LOAD_GL(glGetShaderiv, PFNGLGETSHADERIVPROC)
1071
1088
  LOAD_GL(glGetShaderInfoLog, PFNGLGETSHADERINFOLOGPROC)
1089
+ LOAD_GL(glGetProgramInfoLog, PFNGLGETPROGRAMINFOLOGPROC)
1072
1090
  LOAD_GL(glGetString, PFNGLGETSTRINGPROC)
1073
1091
  LOAD_GL(glGetUniformLocation, PFNGLGETUNIFORMLOCATIONPROC)
1074
1092
  LOAD_GL(glLineWidth, PFNGLLINEWIDTHPROC)
@@ -2046,6 +2064,28 @@ extern "C" double gea_three_webgl_get_program_parameter(double program, double p
2046
2064
  gWebGL.glGetProgramiv(asGLuint(program), asGLenum(pname), &value);
2047
2065
  return value;
2048
2066
  }
2067
+ // The driver's compile or link log, as WebGL's getShaderInfoLog and
2068
+ // getProgramInfoLog return it.
2069
+ static std::string geaInfoLog(double object, PFNGLGETSHADERIVPROC queryLength, PFNGLGETSHADERINFOLOGPROC readLog) {
2070
+ GLint length = 0;
2071
+ queryLength(asGLuint(object), GL_INFO_LOG_LENGTH_VALUE, &length);
2072
+ if (length <= 0) return std::string();
2073
+ std::string log(static_cast<std::size_t>(length), '\0');
2074
+ GLsizei written = 0;
2075
+ readLog(asGLuint(object), length, &written, log.data());
2076
+ log.resize(static_cast<std::size_t>(std::clamp(written, 0, length)));
2077
+ return log;
2078
+ }
2079
+ extern "C" std::string gea_three_webgl_get_shader_info_log(double shader) {
2080
+ geaTrapCheck("gea_three_webgl_get_shader_info_log");
2081
+ if (!ensureWebGLCurrent("getShaderInfoLog")) return std::string();
2082
+ return geaInfoLog(shader, gWebGL.glGetShaderiv, gWebGL.glGetShaderInfoLog);
2083
+ }
2084
+ extern "C" std::string gea_three_webgl_get_program_info_log(double program) {
2085
+ geaTrapCheck("gea_three_webgl_get_program_info_log");
2086
+ if (!ensureWebGLCurrent("getProgramInfoLog")) return std::string();
2087
+ return geaInfoLog(program, gWebGL.glGetProgramiv, gWebGL.glGetProgramInfoLog);
2088
+ }
2049
2089
  extern "C" void gea_three_webgl_delete_program(double program) {
2050
2090
  geaTrapCheck("gea_three_webgl_delete_program"); GEA_WEBGL_VOID("deleteProgram"); gWebGL.glDeleteProgram(asGLuint(program)); }
2051
2091
  extern "C" void gea_three_webgl_use_program(double program) {
@@ -0,0 +1,128 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Files an app ships in its Resources folder, read whole into a typed array the
3
+ // program allocated (src/nativeResources.ts). Resources is the app bundle's
4
+ // resource directory on macOS and iOS, and the Resources folder beside the
5
+ // executable elsewhere (where the desktop targets stage an app's
6
+ // nativeResources); GEA_RESOURCE_DIR names another directory.
7
+ //
8
+ // A name is relative to that directory, with '/' between its parts; an
9
+ // absolute name, a backslash or a '..' part is refused, so a name never reaches
10
+ // outside it.
11
+ #include <cstdint>
12
+ #include <cstdio>
13
+ #include <cstdlib>
14
+ #include <string>
15
+
16
+ #if defined(_WIN32)
17
+ #include <windows.h>
18
+ #elif defined(__APPLE__)
19
+ #import <Foundation/Foundation.h>
20
+ #else
21
+ #include <unistd.h>
22
+ #include <climits>
23
+ #endif
24
+
25
+ namespace {
26
+
27
+ bool geaResourceNameValid(const std::string &name) {
28
+ if (name.empty() || name.front() == '/' || name.find('\\') != std::string::npos || name.find(':') != std::string::npos) return false;
29
+ size_t start = 0;
30
+ while (start <= name.size()) {
31
+ const size_t end = name.find('/', start);
32
+ const std::string part = name.substr(start, end == std::string::npos ? std::string::npos : end - start);
33
+ if (part.empty() || part == "." || part == "..") return false;
34
+ if (end == std::string::npos) break;
35
+ start = end + 1;
36
+ }
37
+ return true;
38
+ }
39
+
40
+ #if defined(_WIN32)
41
+ const std::wstring &geaResourceDirectory() {
42
+ static const std::wstring directory = [] {
43
+ wchar_t value[MAX_PATH] = {};
44
+ const DWORD n = GetEnvironmentVariableW(L"GEA_RESOURCE_DIR", value, MAX_PATH);
45
+ if (n > 0 && n < MAX_PATH) return std::wstring(value);
46
+ wchar_t module[MAX_PATH] = {};
47
+ const DWORD length = GetModuleFileNameW(nullptr, module, MAX_PATH);
48
+ std::wstring path(module, length < MAX_PATH ? length : 0);
49
+ const size_t slash = path.find_last_of(L"\\/");
50
+ return slash == std::wstring::npos ? std::wstring() : path.substr(0, slash) + L"\\Resources";
51
+ }();
52
+ return directory;
53
+ }
54
+
55
+ FILE *geaOpenResource(const std::string &name) {
56
+ if (!geaResourceNameValid(name) || geaResourceDirectory().empty()) return nullptr;
57
+ const int length = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, name.data(), static_cast<int>(name.size()), nullptr, 0);
58
+ if (length <= 0) return nullptr;
59
+ std::wstring relative(static_cast<size_t>(length), L'\0');
60
+ MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, name.data(), static_cast<int>(name.size()), relative.data(), length);
61
+ for (wchar_t &c : relative) if (c == L'/') c = L'\\';
62
+ FILE *file = nullptr;
63
+ return _wfopen_s(&file, (geaResourceDirectory() + L"\\" + relative).c_str(), L"rb") == 0 ? file : nullptr;
64
+ }
65
+ #else
66
+ const std::string &geaResourceDirectory() {
67
+ static const std::string directory = [] {
68
+ if (const char *value = std::getenv("GEA_RESOURCE_DIR")) return std::string(value);
69
+ #if defined(__APPLE__)
70
+ @autoreleasepool {
71
+ NSString *resources = [[NSBundle mainBundle] resourcePath];
72
+ return resources ? std::string(resources.UTF8String) : std::string();
73
+ }
74
+ #else
75
+ char path[PATH_MAX] = {};
76
+ const ssize_t length = readlink("/proc/self/exe", path, sizeof(path) - 1);
77
+ if (length <= 0) return std::string();
78
+ const std::string executable(path, static_cast<size_t>(length));
79
+ const size_t slash = executable.find_last_of('/');
80
+ return slash == std::string::npos ? std::string() : executable.substr(0, slash) + "/Resources";
81
+ #endif
82
+ }();
83
+ return directory;
84
+ }
85
+
86
+ FILE *geaOpenResource(const std::string &name) {
87
+ if (!geaResourceNameValid(name) || geaResourceDirectory().empty()) return nullptr;
88
+ return std::fopen((geaResourceDirectory() + "/" + name).c_str(), "rb");
89
+ }
90
+ #endif
91
+
92
+ long long geaResourceLength(FILE *file) {
93
+ if (std::fseek(file, 0, SEEK_END) != 0) return -1;
94
+ #if defined(_WIN32)
95
+ const long long length = _ftelli64(file);
96
+ #else
97
+ const long long length = static_cast<long long>(ftello(file));
98
+ #endif
99
+ if (length < 0 || std::fseek(file, 0, SEEK_SET) != 0) return -1;
100
+ return length;
101
+ }
102
+
103
+ } // namespace
104
+
105
+ // The file's size in bytes, or -1 when it is missing or the name is refused.
106
+ extern "C" double gea_three_resource_size(std::string name) {
107
+ FILE *file = geaOpenResource(name);
108
+ if (!file) return -1;
109
+ const long long length = geaResourceLength(file);
110
+ std::fclose(file);
111
+ return static_cast<double>(length);
112
+ }
113
+
114
+ // Reads the whole file into `bytes` when it holds exactly `byteLength` bytes.
115
+ // Answers the bytes read, or -1 (missing file, refused name, other size, short read).
116
+ extern "C" double gea_three_resource_read_bytes(std::string name, void *bytes, double byteLength) {
117
+ if (!bytes && byteLength != 0) return -1;
118
+ FILE *file = geaOpenResource(name);
119
+ if (!file) return -1;
120
+ const long long length = geaResourceLength(file);
121
+ double result = -1;
122
+ if (length >= 0 && static_cast<double>(length) == byteLength) {
123
+ const size_t wanted = static_cast<size_t>(length);
124
+ if (wanted == 0 || std::fread(bytes, 1, wanted, file) == wanted) result = static_cast<double>(length);
125
+ }
126
+ std::fclose(file);
127
+ return result;
128
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geastack/native-webgl-angle",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "Typed native WebGL compatibility layer backed by ANGLE/Metal",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -21,6 +21,7 @@
21
21
  "./nativeWebXRManager": "./src/nativeWebXRManager.ts",
22
22
  "./troika-three-text": "./src/troika-three-text.ts",
23
23
  "./inflate": "./src/inflate.ts",
24
+ "./nativeResources": "./src/nativeResources.ts",
24
25
  "./geatsc-plugin": "./geatsc-plugin.mjs",
25
26
  "./native/*": "./native/*",
26
27
  "./package.json": "./package.json",
@@ -53,7 +54,7 @@
53
54
  "test:host-effects": "node test/native-host-effects-contract.test.mjs",
54
55
  "test:allocation-contracts": "node test/render-sort-contract.test.mjs && node test/upload-array-contract.test.mjs && node test/draw-dispatch-contract.test.mjs && node test/native-upload-transport.test.mjs && node test/fixed-uniform-contract.test.mjs && node test/uniform-binding-contract.test.mjs && node test/buffer-streaming-contract.test.mjs",
55
56
  "check": "tsc -p tsconfig.json --noEmit",
56
- "test": "node test/native-blending-contract.test.mjs && node test/native-transform-contract.test.mjs && node test/troika-three-text.test.mjs && node test/typed-void-contract.test.mjs && node test/instancing-transport-contract.test.mjs && node test/automatic-instancing-contract.test.mjs && node test/jsdoc-fold-contract.test.mjs && node test/gea-native-declaration.test.mjs",
57
+ "test": "node test/native-blending-contract.test.mjs && node test/native-transform-contract.test.mjs && node test/troika-three-text.test.mjs && node test/typed-void-contract.test.mjs && node test/info-log-contract.test.mjs && node test/resource-host-contract.test.mjs && node test/instancing-transport-contract.test.mjs && node test/automatic-instancing-contract.test.mjs && node test/jsdoc-fold-contract.test.mjs && node test/gea-native-declaration.test.mjs",
57
58
  "test:package": "node --experimental-vm-modules test/package.test.mjs"
58
59
  },
59
60
  "dependencies": {
@@ -0,0 +1,22 @@
1
+ // Files the app ships in its Resources folder (native/resource_host.mm): the
2
+ // app bundle's resource directory on macOS, the Resources folder beside the
3
+ // executable elsewhere. Names are relative to it, with '/' between parts.
4
+
5
+ /** @gea-host-inert Reads its argument and the file system only; never calls back into script. */
6
+ declare function threeResourceSize(name: string): number
7
+ /** @gea-host-typed-array-element-writes Stores the file's bytes into the destination typed array's elements only; no other property of any object, no callbacks. */
8
+ declare function threeResourceRead(name: string, destination: Uint8Array): number
9
+
10
+ /** The size in bytes of a file in Resources, or -1 when there is none. */
11
+ export function nativeResourceSize(name: string): number {
12
+ return threeResourceSize(name)
13
+ }
14
+
15
+ /** Reads a whole file from Resources; throws when it is missing or cannot be read. */
16
+ export function nativeReadResource(name: string): Uint8Array {
17
+ const size = threeResourceSize(name)
18
+ if (size < 0) throw new Error(`Missing resource: ${name}`)
19
+ const bytes = new Uint8Array(size)
20
+ if (threeResourceRead(name, bytes) !== size) throw new Error(`Could not read resource: ${name}`)
21
+ return bytes
22
+ }
@@ -59,7 +59,9 @@ import {
59
59
  nativeWebGLGetError,
60
60
  nativeWebGLGetActiveAttribInfo,
61
61
  nativeWebGLGetActiveUniformInfo,
62
+ nativeWebGLGetProgramInfoLog,
62
63
  nativeWebGLGetProgramParameter,
64
+ nativeWebGLGetShaderInfoLog,
63
65
  nativeWebGLGetShaderParameter,
64
66
  nativeWebGLGetUniformLocation,
65
67
  nativeWebGLHeight,
@@ -1036,7 +1038,7 @@ export class NativeWebGL2RenderingContext {
1036
1038
  if (pname === this.DELETE_STATUS) return false
1037
1039
  return nativeWebGLGetShaderParameter(nativeHandle(shader), pname) !== 0
1038
1040
  }
1039
- getShaderInfoLog(_shader: NativeHandle | null): string { return '' }
1041
+ getShaderInfoLog(shader: NativeShader): string { return nativeWebGLGetShaderInfoLog(nativeHandle(shader)) }
1040
1042
  getShaderSource(shader: NativeShader): string { return shaderSourceFor(shader) }
1041
1043
  deleteShader(shader: NativeShader | null): void { if (shader) { shaderSourceByHandle.delete(nativeHandle(shader)); nativeWebGLDeleteShader(nativeHandle(shader)) } }
1042
1044
 
@@ -1065,7 +1067,7 @@ export class NativeWebGL2RenderingContext {
1065
1067
  if (pname === this.VALIDATE_STATUS) return true
1066
1068
  return nativeWebGLGetProgramParameter(nativeHandle(program), pname) !== 0
1067
1069
  }
1068
- getProgramInfoLog(_program: NativeProgram | null): string { return '' }
1070
+ getProgramInfoLog(program: NativeProgram): string { return nativeWebGLGetProgramInfoLog(nativeHandle(program)) }
1069
1071
  deleteProgram(program: NativeProgram | null): void { if (program) { nativeWebGLDeleteProgram(nativeHandle(program)) } }
1070
1072
  validateProgram(_program: NativeProgram): void {}
1071
1073
  getAttachedShaders(program: NativeProgram): NativeShader[] { return program.shaders }
@@ -35,6 +35,16 @@ export function nativeWebGLSwap(): void {
35
35
  threeWebGLSwap()
36
36
  }
37
37
 
38
+ /**
39
+ * Vertical sync from the next frame on. Off presents at once, so the frame rate shows what
40
+ * a frame costs. On paces presents to the display: interval 1 where nothing else paces the
41
+ * frames (a desktop window with no display link), the host's own pacing on Apple platforms,
42
+ * where a second waiter would only make frames miss refreshes.
43
+ */
44
+ export function nativeWebGLSetVsync(on: boolean): void {
45
+ threeWebGLSetVsync(on ? 1 : 0)
46
+ }
47
+
38
48
  // GPU time-elapsed timers (GL_EXT_disjoint_timer_query): begin returns a timer id (0 when
39
49
  // unsupported), end closes the open timer, and result is the elapsed nanoseconds, or -1 while the
40
50
  // GPU has not finished it. A timer is released once its result has been read.
@@ -88,12 +98,14 @@ export function nativeWebGLCreateShader(type: number): number { return threeWebG
88
98
  export function nativeWebGLShaderSource(shader: number, source: string): void { threeWebGLShaderSource(shader, source) }
89
99
  export function nativeWebGLCompileShader(shader: number): void { threeWebGLCompileShader(shader) }
90
100
  export function nativeWebGLGetShaderParameter(shader: number, pname: number): number { return threeWebGLGetShaderParameter(shader, pname) }
101
+ export function nativeWebGLGetShaderInfoLog(shader: number): string { return threeWebGLGetShaderInfoLog(shader) }
91
102
  export function nativeWebGLDeleteShader(shader: number): void { threeWebGLDeleteShader(shader) }
92
103
 
93
104
  export function nativeWebGLCreateProgram(): number { return threeWebGLCreateProgram() }
94
105
  export function nativeWebGLAttachShader(program: number, shader: number): void { threeWebGLAttachShader(program, shader) }
95
106
  export function nativeWebGLLinkProgram(program: number): void { threeWebGLLinkProgram(program) }
96
107
  export function nativeWebGLGetProgramParameter(program: number, pname: number): number { return threeWebGLGetProgramParameter(program, pname) }
108
+ export function nativeWebGLGetProgramInfoLog(program: number): string { return threeWebGLGetProgramInfoLog(program) }
97
109
  export function nativeWebGLDeleteProgram(program: number): void { threeWebGLDeleteProgram(program) }
98
110
  export function nativeWebGLUseProgram(program: number): void { threeWebGLUseProgram(program) }
99
111
  export function nativeWebGLGetAttribLocation(program: number, name: string): number { return threeWebGLGetAttribLocation(program, name) }
@@ -187,6 +199,8 @@ declare function threeWebGLDevicePixelRatio(): number
187
199
  /** Lifecycle entry: presents the native surface and may run platform hooks; not an inert call. */
188
200
  declare function threeWebGLSwap(): void
189
201
  /** @gea-host-inert Native state only; retains no script references and invokes no script. */
202
+ declare function threeWebGLSetVsync(on: number): void
203
+ /** @gea-host-inert Native state only; retains no script references and invokes no script. */
190
204
  declare function threeAngleSmokeLog(message: string): void
191
205
  /** @gea-host-inert Native state only; retains no script references and invokes no script. */
192
206
  declare function threeKeyPressed(code: number): boolean
@@ -207,6 +221,8 @@ declare function threeWebGLCompileShader(shader: number): void
207
221
  /** @gea-host-inert Native state only; retains no script references and invokes no script. */
208
222
  declare function threeWebGLGetShaderParameter(shader: number, pname: number): number
209
223
  /** @gea-host-inert Native state only; retains no script references and invokes no script. */
224
+ declare function threeWebGLGetShaderInfoLog(shader: number): string
225
+ /** @gea-host-inert Native state only; retains no script references and invokes no script. */
210
226
  declare function threeWebGLDeleteShader(shader: number): void
211
227
  /** @gea-host-inert Native state only; retains no script references and invokes no script. */
212
228
  declare function threeWebGLCreateProgram(): number
@@ -217,6 +233,8 @@ declare function threeWebGLLinkProgram(program: number): void
217
233
  /** @gea-host-inert Native state only; retains no script references and invokes no script. */
218
234
  declare function threeWebGLGetProgramParameter(program: number, pname: number): number
219
235
  /** @gea-host-inert Native state only; retains no script references and invokes no script. */
236
+ declare function threeWebGLGetProgramInfoLog(program: number): string
237
+ /** @gea-host-inert Native state only; retains no script references and invokes no script. */
220
238
  declare function threeWebGLDeleteProgram(program: number): void
221
239
  /** @gea-host-inert Native state only; retains no script references and invokes no script. */
222
240
  declare function threeWebGLUseProgram(program: number): void