@geastack/native-webgl-angle 0.1.13 → 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 +31 -4
- package/gea-native.json +16 -0
- package/geatsc-plugin-uniforms.mjs +41 -1
- package/geatsc-plugin-uploads.mjs +12 -7
- package/geatsc-plugin.mjs +135 -18
- package/native/angle_webgl_host.mm +77 -8
- package/native/audio_host.mm +26 -1
- package/native/resource_host.mm +128 -0
- package/native/win32_webgl_surface.cpp +230 -0
- package/package.json +7 -4
- package/src/nativeResources.ts +22 -0
- package/src/nativeWebGL.ts +4 -2
- package/src/nativeWebGLHost.ts +18 -0
package/README.md
CHANGED
|
@@ -50,9 +50,36 @@ native/angle_webgl_host.mm
|
|
|
50
50
|
|
|
51
51
|
## Native sources in apps
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
Apple apps name the host in `gea.nativeSources`, either app-relative or by
|
|
54
|
+
package path (`@geastack/native-webgl-angle/native/angle_webgl_host.mm`).
|
|
55
|
+
|
|
56
|
+
The Windows desktop target needs nothing in the app: `gea-native.json`
|
|
57
|
+
declares the host (`native/angle_webgl_host.mm` and `native/audio_host.mm`,
|
|
58
|
+
compiled as C++) and `native/win32_webgl_surface.cpp`, a host extension that
|
|
59
|
+
gives the program a child window filling the main window's content area,
|
|
60
|
+
attaches the WebGL context to it before the program's top level runs, makes
|
|
61
|
+
the context current around initialization and each frame, hands frame pacing
|
|
62
|
+
to the presentation, and reports keys (as macOS virtual key codes, with a
|
|
63
|
+
release held until a frame has seen the key down) and the pointer (in CSS
|
|
64
|
+
pixels).
|
|
65
|
+
|
|
66
|
+
The package requires a `gles` provider: an EGL and OpenGL ES 3
|
|
67
|
+
implementation. The app configures it, for example
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
"gea": { "targets": { "windows": { "providers": {
|
|
71
|
+
"gles": { "directory": "build/renderer", "files": { "egl": "GL.dll", "gles": "GL.dll" }, "runtimeFiles": ["d3d12/D3D12Core.dll"] }
|
|
72
|
+
} } } }
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
or `"gles": "path/to/angle"` for a directory holding ANGLE's `libEGL.dll` and
|
|
76
|
+
`libGLESv2.dll`. The build stages those files beside the executable and
|
|
77
|
+
compiles their names in (`GEA_WEBGL_EGL_LIBRARY`, `GEA_WEBGL_GLES_LIBRARY`);
|
|
78
|
+
the host loads them from the executable's directory. `GEA_ANGLE_EGL_DYLIB` and
|
|
79
|
+
`GEA_ANGLE_GLES_DYLIB` still override the paths at run time. Without a
|
|
80
|
+
provider the build stops and says so. Audio files resolve from
|
|
81
|
+
`GEA_THREE_AUDIO_DIR`, else `Resources\Sounds` beside the executable, unless
|
|
82
|
+
the app names a directory with `gea_three_audio_set_directory_uwp`.
|
|
56
83
|
|
|
57
84
|
The bridge implementation itself lives here.
|
|
58
85
|
|
|
@@ -94,7 +121,7 @@ to change them. Any present value, including an empty string, enables them.
|
|
|
94
121
|
|
|
95
122
|
### Validation
|
|
96
123
|
|
|
97
|
-
`npm run test:host-effects` checks
|
|
124
|
+
`npm run test:host-effects` checks 87 direct GL/input declarations and 15 audio
|
|
98
125
|
declarations against the compiler's host-mutation census. Their
|
|
99
126
|
`@gea-host-inert` contracts permit native state changes while excluding script
|
|
100
127
|
reference retention, script object mutation and script callbacks. The native
|
package/gea-native.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
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
|
+
"windows": {
|
|
4
|
+
"sources": ["native/angle_webgl_host.mm", "native/audio_host.mm", "native/resource_host.mm", "native/win32_webgl_surface.cpp"],
|
|
5
|
+
"includeDirs": ["native"],
|
|
6
|
+
"requires": {
|
|
7
|
+
"gles": {
|
|
8
|
+
"description": "an EGL and OpenGL ES 3 implementation (ANGLE's libEGL.dll and libGLESv2.dll, or one DLL exporting both)",
|
|
9
|
+
"files": {
|
|
10
|
+
"egl": { "define": "GEA_WEBGL_EGL_LIBRARY", "default": "libEGL.dll" },
|
|
11
|
+
"gles": { "define": "GEA_WEBGL_GLES_LIBRARY", "default": "libGLESv2.dll" }
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -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
|
|
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'],
|
|
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'],
|
|
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'],
|
|
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'],
|
|
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('../../
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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
|
-
\
|
|
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('../../
|
|
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
|
);
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
#include "webgl_readback.h"
|
|
2
|
+
#if defined(_WIN32)
|
|
3
|
+
// The EGL and OpenGL ES libraries the Windows host loads beside the executable.
|
|
4
|
+
#ifndef GEA_WEBGL_EGL_LIBRARY
|
|
5
|
+
#define GEA_WEBGL_EGL_LIBRARY "libEGL.dll"
|
|
6
|
+
#endif
|
|
7
|
+
#ifndef GEA_WEBGL_GLES_LIBRARY
|
|
8
|
+
#define GEA_WEBGL_GLES_LIBRARY "libGLESv2.dll"
|
|
9
|
+
#endif
|
|
10
|
+
#endif
|
|
2
11
|
// GEA_WEBGL_STATIC_GLES: EGL/GLES are statically linked into the program, so
|
|
3
12
|
// nothing is loaded and entry points resolve through eglGetProcAddress.
|
|
4
13
|
#if defined(_WIN32)
|
|
@@ -319,6 +328,7 @@ static constexpr GLenum GL_UNSIGNED_INT = 0x1405;
|
|
|
319
328
|
static constexpr GLenum GL_RGBA_VALUE = 0x1908;
|
|
320
329
|
static constexpr GLenum GL_VIEWPORT_VALUE = 0x0BA2;
|
|
321
330
|
static constexpr GLenum GL_FRAMEBUFFER_BINDING_VALUE = 0x8CA6;
|
|
331
|
+
static constexpr GLenum GL_INFO_LOG_LENGTH_VALUE = 0x8B84;
|
|
322
332
|
|
|
323
333
|
using PFNEGLGETPROCADDRESS = void *(*)(const char *);
|
|
324
334
|
using PFNEGLGETPLATFORMDISPLAYEXTPROC = EGLDisplay (*)(EGLenum, void *, const EGLint *);
|
|
@@ -393,6 +403,7 @@ using PFNGLGETINTEGERVPROC = void (*)(GLenum, GLint *);
|
|
|
393
403
|
using PFNGLGETPROGRAMIVPROC = void (*)(GLuint, GLenum, GLint *);
|
|
394
404
|
using PFNGLGETSHADERIVPROC = void (*)(GLuint, GLenum, GLint *);
|
|
395
405
|
using PFNGLGETSHADERINFOLOGPROC = void (*)(GLuint, GLsizei, GLsizei *, GLchar *);
|
|
406
|
+
using PFNGLGETPROGRAMINFOLOGPROC = void (*)(GLuint, GLsizei, GLsizei *, GLchar *);
|
|
396
407
|
using PFNGLGETSTRINGPROC = const unsigned char *(*)(GLenum);
|
|
397
408
|
using PFNGLGETUNIFORMLOCATIONPROC = GLint (*)(GLuint, const GLchar *);
|
|
398
409
|
using PFNGLLINEWIDTHPROC = void (*)(GLfloat);
|
|
@@ -540,6 +551,7 @@ struct AngleWebGLState {
|
|
|
540
551
|
PFNGLGETPROGRAMIVPROC glGetProgramiv = nullptr;
|
|
541
552
|
PFNGLGETSHADERIVPROC glGetShaderiv = nullptr;
|
|
542
553
|
PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog = nullptr;
|
|
554
|
+
PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog = nullptr;
|
|
543
555
|
PFNGLGETSTRINGPROC glGetString = nullptr;
|
|
544
556
|
PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation = nullptr;
|
|
545
557
|
PFNGLLINEWIDTHPROC glLineWidth = nullptr;
|
|
@@ -618,6 +630,10 @@ struct AngleWebGLState {
|
|
|
618
630
|
};
|
|
619
631
|
|
|
620
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; }
|
|
621
637
|
static GLenum hostReadbackError = 0;
|
|
622
638
|
static std::chrono::steady_clock::time_point gProfileSizeSync;
|
|
623
639
|
static bool gProfileSizeSyncPending = false;
|
|
@@ -933,9 +949,19 @@ static bool makeCurrent(const char *label) {
|
|
|
933
949
|
// reintroduced because the layer still syncs its own present.
|
|
934
950
|
// `GEA_ANGLE_SWAP_INTERVAL` overrides it (set `1` to restore the old
|
|
935
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.
|
|
936
956
|
if (gWebGL.eglSwapInterval) {
|
|
937
957
|
const char *requested = std::getenv("GEA_ANGLE_SWAP_INTERVAL");
|
|
938
|
-
const EGLint
|
|
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;
|
|
939
965
|
gWebGL.eglSwapInterval(gWebGL.display, interval);
|
|
940
966
|
}
|
|
941
967
|
if (gWebGL.eglMakeCurrent(gWebGL.display, gWebGL.surface, gWebGL.surface, gWebGL.context) == EGL_FALSE_VALUE) {
|
|
@@ -1060,6 +1086,7 @@ static bool loadGlSymbols() {
|
|
|
1060
1086
|
LOAD_GL(glGetProgramiv, PFNGLGETPROGRAMIVPROC)
|
|
1061
1087
|
LOAD_GL(glGetShaderiv, PFNGLGETSHADERIVPROC)
|
|
1062
1088
|
LOAD_GL(glGetShaderInfoLog, PFNGLGETSHADERINFOLOGPROC)
|
|
1089
|
+
LOAD_GL(glGetProgramInfoLog, PFNGLGETPROGRAMINFOLOGPROC)
|
|
1063
1090
|
LOAD_GL(glGetString, PFNGLGETSTRINGPROC)
|
|
1064
1091
|
LOAD_GL(glGetUniformLocation, PFNGLGETUNIFORMLOCATIONPROC)
|
|
1065
1092
|
LOAD_GL(glLineWidth, PFNGLLINEWIDTHPROC)
|
|
@@ -1151,22 +1178,42 @@ extern "C" bool gea_three_webgl_attach(
|
|
|
1151
1178
|
gWebGL.egl = &staticGles;
|
|
1152
1179
|
gWebGL.gles = &staticGles;
|
|
1153
1180
|
#elif defined(_WIN32)
|
|
1154
|
-
// An override
|
|
1155
|
-
//
|
|
1156
|
-
|
|
1181
|
+
// An override path from the environment first, then the packaged library of
|
|
1182
|
+
// a UWP app, then the file beside the executable. The file names default to
|
|
1183
|
+
// ANGLE's; a build that stages another EGL/GLES implementation names its
|
|
1184
|
+
// files through GEA_WEBGL_EGL_LIBRARY / GEA_WEBGL_GLES_LIBRARY (paths
|
|
1185
|
+
// relative to the executable's directory; one DLL may implement both).
|
|
1186
|
+
auto openWin32 = [](const wchar_t *overrideEnv, const char *name) -> HMODULE {
|
|
1157
1187
|
wchar_t overridePath[MAX_PATH];
|
|
1158
1188
|
const DWORD n = GetEnvironmentVariableW(overrideEnv, overridePath, MAX_PATH);
|
|
1159
1189
|
if (n > 0 && n < MAX_PATH) {
|
|
1160
1190
|
if (HMODULE m = LoadLibraryW(overridePath)) return m;
|
|
1191
|
+
smokeLog("[native-webgl-angle] %ls names a library that does not load (error %lu)", overrideEnv, GetLastError());
|
|
1161
1192
|
}
|
|
1193
|
+
std::wstring file;
|
|
1194
|
+
for (const char *c = name; *c; ++c) file.push_back(*c == '/' ? L'\\' : static_cast<wchar_t>(static_cast<unsigned char>(*c)));
|
|
1162
1195
|
#if defined(WINAPI_FAMILY) && WINAPI_FAMILY == WINAPI_FAMILY_APP
|
|
1163
|
-
return LoadPackagedLibrary(
|
|
1196
|
+
return LoadPackagedLibrary(file.c_str(), 0);
|
|
1164
1197
|
#else
|
|
1165
|
-
|
|
1198
|
+
wchar_t module[MAX_PATH] = {};
|
|
1199
|
+
const DWORD length = GetModuleFileNameW(nullptr, module, MAX_PATH);
|
|
1200
|
+
std::wstring beside(module, length < MAX_PATH ? length : 0);
|
|
1201
|
+
const size_t slash = beside.find_last_of(L"\\/");
|
|
1202
|
+
if (slash != std::wstring::npos) {
|
|
1203
|
+
beside.resize(slash + 1);
|
|
1204
|
+
beside += file;
|
|
1205
|
+
// Altered search path: the library's own dependencies resolve from its directory.
|
|
1206
|
+
if (HMODULE m = LoadLibraryExW(beside.c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH)) return m;
|
|
1207
|
+
}
|
|
1208
|
+
return LoadLibraryW(file.c_str());
|
|
1166
1209
|
#endif
|
|
1167
1210
|
};
|
|
1168
|
-
gWebGL.egl = openWin32(L"GEA_ANGLE_EGL_DYLIB",
|
|
1169
|
-
gWebGL.gles = openWin32(L"GEA_ANGLE_GLES_DYLIB",
|
|
1211
|
+
gWebGL.egl = openWin32(L"GEA_ANGLE_EGL_DYLIB", GEA_WEBGL_EGL_LIBRARY);
|
|
1212
|
+
gWebGL.gles = openWin32(L"GEA_ANGLE_GLES_DYLIB", GEA_WEBGL_GLES_LIBRARY);
|
|
1213
|
+
if (!gWebGL.egl || !gWebGL.gles) {
|
|
1214
|
+
smokeLog("[native-webgl-angle] EGL/GLES implementation not found: %s and %s beside the executable "
|
|
1215
|
+
"(GEA_ANGLE_EGL_DYLIB / GEA_ANGLE_GLES_DYLIB override them)", GEA_WEBGL_EGL_LIBRARY, GEA_WEBGL_GLES_LIBRARY);
|
|
1216
|
+
}
|
|
1170
1217
|
#elif defined(__APPLE__)
|
|
1171
1218
|
static const char *const eglPaths[] = {
|
|
1172
1219
|
"/Applications/Visual Studio Code.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libEGL.dylib",
|
|
@@ -2017,6 +2064,28 @@ extern "C" double gea_three_webgl_get_program_parameter(double program, double p
|
|
|
2017
2064
|
gWebGL.glGetProgramiv(asGLuint(program), asGLenum(pname), &value);
|
|
2018
2065
|
return value;
|
|
2019
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
|
+
}
|
|
2020
2089
|
extern "C" void gea_three_webgl_delete_program(double program) {
|
|
2021
2090
|
geaTrapCheck("gea_three_webgl_delete_program"); GEA_WEBGL_VOID("deleteProgram"); gWebGL.glDeleteProgram(asGLuint(program)); }
|
|
2022
2091
|
extern "C" void gea_three_webgl_use_program(double program) {
|
package/native/audio_host.mm
CHANGED
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
#if defined(_WIN32)
|
|
22
22
|
#define _USE_MATH_DEFINES
|
|
23
23
|
#include "audio_uwp.h"
|
|
24
|
+
// Legacy Win16 macros are not C++ identifiers and must not leak into the runtime.
|
|
25
|
+
#undef near
|
|
26
|
+
#undef far
|
|
24
27
|
#else
|
|
25
28
|
#import <AVFoundation/AVFoundation.h>
|
|
26
29
|
#import <Foundation/Foundation.h>
|
|
@@ -44,8 +47,30 @@
|
|
|
44
47
|
|
|
45
48
|
#if defined(_WIN32)
|
|
46
49
|
static std::wstring geaAudioAssetDirectory;
|
|
50
|
+
static bool geaAudioAssetDirectorySet = false;
|
|
47
51
|
extern "C" void gea_three_audio_set_directory_uwp(const wchar_t *path) {
|
|
48
52
|
geaAudioAssetDirectory = path ? path : L"";
|
|
53
|
+
geaAudioAssetDirectorySet = true;
|
|
54
|
+
}
|
|
55
|
+
// Without a driver naming the directory: GEA_THREE_AUDIO_DIR, else the
|
|
56
|
+
// Resources\Sounds folder beside the executable (where the desktop target
|
|
57
|
+
// stages an app's sounds), matching the macOS host's bundle lookup.
|
|
58
|
+
static const std::wstring &geaAudioDirectory() {
|
|
59
|
+
if (!geaAudioAssetDirectorySet) {
|
|
60
|
+
geaAudioAssetDirectorySet = true;
|
|
61
|
+
wchar_t value[MAX_PATH] = {};
|
|
62
|
+
const DWORD n = GetEnvironmentVariableW(L"GEA_THREE_AUDIO_DIR", value, MAX_PATH);
|
|
63
|
+
if (n > 0 && n < MAX_PATH) {
|
|
64
|
+
geaAudioAssetDirectory = value;
|
|
65
|
+
} else {
|
|
66
|
+
wchar_t module[MAX_PATH] = {};
|
|
67
|
+
const DWORD length = GetModuleFileNameW(nullptr, module, MAX_PATH);
|
|
68
|
+
std::wstring directory(module, length < MAX_PATH ? length : 0);
|
|
69
|
+
const size_t slash = directory.find_last_of(L"\\/");
|
|
70
|
+
if (slash != std::wstring::npos) geaAudioAssetDirectory = directory.substr(0, slash) + L"\\Resources\\Sounds";
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return geaAudioAssetDirectory;
|
|
49
74
|
}
|
|
50
75
|
#endif
|
|
51
76
|
|
|
@@ -427,7 +452,7 @@ struct GeaAudioEngine {
|
|
|
427
452
|
long loadBufferFile(const std::string &name) {
|
|
428
453
|
if (name.empty() || name.find('/') != std::string::npos || name.find("..") != std::string::npos) return 0;
|
|
429
454
|
#if defined(_WIN32)
|
|
430
|
-
if (name.find('\\') != std::string::npos ||
|
|
455
|
+
if (name.find('\\') != std::string::npos || geaAudioDirectory().empty()) return 0;
|
|
431
456
|
const int length = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, name.data(), static_cast<int>(name.size()), nullptr, 0);
|
|
432
457
|
if (length <= 0) return 0;
|
|
433
458
|
std::wstring base(static_cast<size_t>(length), L'\0');
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// The WebGL surface of a Windows desktop build: a child window filling the
|
|
3
|
+
// main window's content area, which the native WebGL host renders into, and
|
|
4
|
+
// the input the host reports to the program.
|
|
5
|
+
//
|
|
6
|
+
// It plugs into the Windows target as a host extension (gea_win32_host.h):
|
|
7
|
+
// the target attaches it before the program's top level runs -- the program
|
|
8
|
+
// creates its WebGL context while it initializes -- and brackets the
|
|
9
|
+
// initialization and every frame with begin/end, which is when the host's
|
|
10
|
+
// EGL context is current. Keys reach the host as macOS virtual key codes, the
|
|
11
|
+
// code space the program's key polling uses on every platform; pointer
|
|
12
|
+
// positions are in CSS pixels of the surface.
|
|
13
|
+
#include "gea_win32_host.h"
|
|
14
|
+
|
|
15
|
+
#include <windowsx.h>
|
|
16
|
+
|
|
17
|
+
#include <algorithm>
|
|
18
|
+
#include <cstdio>
|
|
19
|
+
#include <cstdlib>
|
|
20
|
+
|
|
21
|
+
extern "C" bool gea_three_webgl_attach_uwp(void *nativeWindow, double width, double height, double devicePixelRatio);
|
|
22
|
+
extern "C" void gea_three_webgl_update_size_uwp(double width, double height, double devicePixelRatio);
|
|
23
|
+
extern "C" bool gea_three_webgl_begin_frame_uwp();
|
|
24
|
+
extern "C" void gea_three_webgl_end_frame_uwp();
|
|
25
|
+
extern "C" void gea_three_set_key_state(int code, bool down);
|
|
26
|
+
extern "C" void gea_three_set_pointer_state(double x, double y, double width, double height, bool down);
|
|
27
|
+
|
|
28
|
+
namespace {
|
|
29
|
+
|
|
30
|
+
HWND gSurface = nullptr;
|
|
31
|
+
HWND gMainWindow = nullptr;
|
|
32
|
+
double gScale = 1.0;
|
|
33
|
+
bool gPointerDown = false;
|
|
34
|
+
// The program polls key state once per frame, so a tap whose key-down and
|
|
35
|
+
// key-up both arrive between two frames would never be seen. A release waits
|
|
36
|
+
// here until a frame has run with the key down.
|
|
37
|
+
bool gReleasePending[256] = {};
|
|
38
|
+
|
|
39
|
+
// macOS virtual key codes (Carbon kVK_*), for the keys a game typically polls.
|
|
40
|
+
int macKeyCode(WPARAM vk)
|
|
41
|
+
{
|
|
42
|
+
switch (vk) {
|
|
43
|
+
case VK_LEFT: return 123;
|
|
44
|
+
case VK_RIGHT: return 124;
|
|
45
|
+
case VK_DOWN: return 125;
|
|
46
|
+
case VK_UP: return 126;
|
|
47
|
+
case VK_SPACE: return 49;
|
|
48
|
+
case VK_RETURN: return 36;
|
|
49
|
+
case VK_ESCAPE: return 53;
|
|
50
|
+
case VK_TAB: return 48;
|
|
51
|
+
case VK_BACK: return 51;
|
|
52
|
+
case VK_DELETE: return 117;
|
|
53
|
+
case VK_HOME: return 115;
|
|
54
|
+
case VK_END: return 119;
|
|
55
|
+
case VK_PRIOR: return 116;
|
|
56
|
+
case VK_NEXT: return 121;
|
|
57
|
+
case VK_SHIFT: case VK_LSHIFT: return 56;
|
|
58
|
+
case VK_RSHIFT: return 60;
|
|
59
|
+
case VK_CONTROL: case VK_LCONTROL: return 59;
|
|
60
|
+
case VK_RCONTROL: return 62;
|
|
61
|
+
case VK_MENU: case VK_LMENU: return 58;
|
|
62
|
+
case VK_RMENU: return 61;
|
|
63
|
+
case VK_OEM_MINUS: return 27;
|
|
64
|
+
case VK_OEM_PLUS: return 24;
|
|
65
|
+
case VK_OEM_COMMA: return 43;
|
|
66
|
+
case VK_OEM_PERIOD: return 47;
|
|
67
|
+
case VK_OEM_1: return 41; // ;
|
|
68
|
+
case VK_OEM_2: return 44; // /
|
|
69
|
+
case VK_OEM_3: return 50; // `
|
|
70
|
+
case VK_OEM_4: return 33; // [
|
|
71
|
+
case VK_OEM_5: return 42; // backslash
|
|
72
|
+
case VK_OEM_6: return 30; // ]
|
|
73
|
+
case VK_OEM_7: return 39; // '
|
|
74
|
+
default: break;
|
|
75
|
+
}
|
|
76
|
+
static const int letters[26] = {0, 11, 8, 2, 14, 3, 5, 4, 34, 38, 40, 37, 46, 45, 31, 35, 12, 15, 1, 17, 32, 9, 13, 7, 16, 6};
|
|
77
|
+
if (vk >= 'A' && vk <= 'Z') return letters[vk - 'A'];
|
|
78
|
+
static const int digits[10] = {29, 18, 19, 20, 21, 23, 22, 26, 28, 25};
|
|
79
|
+
if (vk >= '0' && vk <= '9') return digits[vk - '0'];
|
|
80
|
+
static const int functions[12] = {122, 120, 99, 118, 96, 97, 98, 100, 101, 109, 103, 111};
|
|
81
|
+
if (vk >= VK_F1 && vk <= VK_F12) return functions[vk - VK_F1];
|
|
82
|
+
return -1;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
void releaseAllKeys()
|
|
86
|
+
{
|
|
87
|
+
for (int code = 0; code < 256; ++code) {
|
|
88
|
+
gReleasePending[code] = false;
|
|
89
|
+
gea_three_set_key_state(code, false);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
void reportPointer(LPARAM lParam)
|
|
94
|
+
{
|
|
95
|
+
RECT client{};
|
|
96
|
+
GetClientRect(gSurface, &client);
|
|
97
|
+
gea_three_set_pointer_state(GET_X_LPARAM(lParam) / gScale, GET_Y_LPARAM(lParam) / gScale, std::max<LONG>(1, client.right) / gScale,
|
|
98
|
+
std::max<LONG>(1, client.bottom) / gScale, gPointerDown);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
LRESULT CALLBACK surfaceProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|
102
|
+
{
|
|
103
|
+
switch (message) {
|
|
104
|
+
case WM_KEYDOWN:
|
|
105
|
+
case WM_SYSKEYDOWN:
|
|
106
|
+
if (message == WM_SYSKEYDOWN && wParam == VK_F4) break; // Alt+F4 closes the window
|
|
107
|
+
if (const int code = macKeyCode(wParam); code >= 0) {
|
|
108
|
+
gReleasePending[code] = false;
|
|
109
|
+
gea_three_set_key_state(code, true);
|
|
110
|
+
}
|
|
111
|
+
return 0; // no menu activation on Alt or F10
|
|
112
|
+
case WM_KEYUP:
|
|
113
|
+
case WM_SYSKEYUP:
|
|
114
|
+
if (const int code = macKeyCode(wParam); code >= 0) gReleasePending[code] = true;
|
|
115
|
+
return 0;
|
|
116
|
+
case WM_SYSCHAR:
|
|
117
|
+
return 0; // no beep for Alt+key
|
|
118
|
+
case WM_KILLFOCUS:
|
|
119
|
+
gPointerDown = false;
|
|
120
|
+
ReleaseCapture();
|
|
121
|
+
releaseAllKeys();
|
|
122
|
+
return 0;
|
|
123
|
+
case WM_LBUTTONDOWN:
|
|
124
|
+
SetFocus(hwnd);
|
|
125
|
+
gPointerDown = true;
|
|
126
|
+
SetCapture(hwnd);
|
|
127
|
+
reportPointer(lParam);
|
|
128
|
+
return 0;
|
|
129
|
+
case WM_LBUTTONUP:
|
|
130
|
+
gPointerDown = false;
|
|
131
|
+
ReleaseCapture();
|
|
132
|
+
reportPointer(lParam);
|
|
133
|
+
return 0;
|
|
134
|
+
case WM_MOUSEMOVE:
|
|
135
|
+
reportPointer(lParam);
|
|
136
|
+
return 0;
|
|
137
|
+
case WM_ERASEBKGND:
|
|
138
|
+
return 1; // the swap chain owns every pixel
|
|
139
|
+
case WM_PAINT:
|
|
140
|
+
ValidateRect(hwnd, nullptr);
|
|
141
|
+
return 0;
|
|
142
|
+
default:
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
return DefWindowProcW(hwnd, message, wParam, lParam);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
int attach(HWND window, RECT area, double scale)
|
|
149
|
+
{
|
|
150
|
+
gMainWindow = window;
|
|
151
|
+
gScale = scale > 0 ? scale : 1.0;
|
|
152
|
+
HINSTANCE instance = GetModuleHandleW(nullptr);
|
|
153
|
+
WNDCLASSEXW cls{};
|
|
154
|
+
cls.cbSize = sizeof(cls);
|
|
155
|
+
cls.style = CS_OWNDC | CS_DBLCLKS;
|
|
156
|
+
cls.lpfnWndProc = surfaceProc;
|
|
157
|
+
cls.hInstance = instance;
|
|
158
|
+
cls.hCursor = LoadCursorW(nullptr, IDC_ARROW);
|
|
159
|
+
cls.lpszClassName = L"GeaWebGLSurface";
|
|
160
|
+
RegisterClassExW(&cls);
|
|
161
|
+
// The main window must not paint over its swap-chain child.
|
|
162
|
+
SetWindowLongPtrW(window, GWL_STYLE, GetWindowLongPtrW(window, GWL_STYLE) | WS_CLIPCHILDREN);
|
|
163
|
+
const int width = std::max<int>(1, area.right - area.left);
|
|
164
|
+
const int height = std::max<int>(1, area.bottom - area.top);
|
|
165
|
+
gSurface = CreateWindowExW(0, cls.lpszClassName, L"", WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS, area.left, area.top, width, height, window, nullptr,
|
|
166
|
+
instance, nullptr);
|
|
167
|
+
if (!gSurface) {
|
|
168
|
+
std::fprintf(stderr, "[native-webgl-angle] could not create the surface window (error %lu)\n", GetLastError());
|
|
169
|
+
return 0;
|
|
170
|
+
}
|
|
171
|
+
SetWindowPos(gSurface, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
|
172
|
+
// Present with vsync unless the environment says otherwise.
|
|
173
|
+
if (!std::getenv("GEA_ANGLE_SWAP_INTERVAL")) _putenv_s("GEA_ANGLE_SWAP_INTERVAL", "1");
|
|
174
|
+
if (!gea_three_webgl_attach_uwp(gSurface, width, height, gScale)) {
|
|
175
|
+
std::fprintf(stderr, "[native-webgl-angle] the WebGL surface could not start: no usable EGL/OpenGL ES implementation\n");
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
// eglSwapBuffers paces the loop: it waits for the refresh with vsync on and
|
|
179
|
+
// renders unthrottled with it off (GEA_ANGLE_SWAP_INTERVAL=0).
|
|
180
|
+
gea_win32_set_frame_pacing(GEA_WIN32_FRAME_PACING_PRESENT);
|
|
181
|
+
SetFocus(gSurface);
|
|
182
|
+
return 1;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
void layout(RECT area, double scale)
|
|
186
|
+
{
|
|
187
|
+
if (!gSurface) return;
|
|
188
|
+
gScale = scale > 0 ? scale : 1.0;
|
|
189
|
+
const int width = std::max<int>(1, area.right - area.left);
|
|
190
|
+
const int height = std::max<int>(1, area.bottom - area.top);
|
|
191
|
+
SetWindowPos(gSurface, nullptr, area.left, area.top, width, height, SWP_NOZORDER | SWP_NOACTIVATE);
|
|
192
|
+
gea_three_webgl_update_size_uwp(width, height, gScale);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
int beginFrame() { return gea_three_webgl_begin_frame_uwp() ? 1 : 0; }
|
|
196
|
+
|
|
197
|
+
void endFrame()
|
|
198
|
+
{
|
|
199
|
+
gea_three_webgl_end_frame_uwp();
|
|
200
|
+
for (int code = 0; code < 256; ++code) {
|
|
201
|
+
if (!gReleasePending[code]) continue;
|
|
202
|
+
gReleasePending[code] = false;
|
|
203
|
+
gea_three_set_key_state(code, false);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
int windowMessage(HWND, UINT message, WPARAM, LPARAM, LRESULT *result)
|
|
208
|
+
{
|
|
209
|
+
// Activating the main window gives it the keyboard focus; the surface is
|
|
210
|
+
// where keys are read.
|
|
211
|
+
if (message == WM_SETFOCUS && gSurface) {
|
|
212
|
+
SetFocus(gSurface);
|
|
213
|
+
*result = 0;
|
|
214
|
+
return 1;
|
|
215
|
+
}
|
|
216
|
+
return 0;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
void detach()
|
|
220
|
+
{
|
|
221
|
+
if (gSurface) DestroyWindow(gSurface);
|
|
222
|
+
gSurface = nullptr;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const gea_win32_host_extension kSurfaceExtension = {
|
|
226
|
+
GEA_WIN32_HOST_EXTENSION_VERSION, "The WebGL surface", attach, layout, beginFrame, endFrame, windowMessage, detach,
|
|
227
|
+
};
|
|
228
|
+
const bool kRegistered = (gea_win32_register_host_extension(&kSurfaceExtension), true);
|
|
229
|
+
|
|
230
|
+
} // namespace
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geastack/native-webgl-angle",
|
|
3
|
-
"version": "0.1.
|
|
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",
|
|
@@ -28,7 +29,8 @@
|
|
|
28
29
|
"./text/native-text-types": "./src/text/native-text-types.ts",
|
|
29
30
|
"./geatsc-plugin-batched-probe": "./geatsc-plugin-batched-probe.mjs",
|
|
30
31
|
"./test/batched-render-scene": "./test/batched-render-scene.ts",
|
|
31
|
-
"./test/auto-instanced-render-scene": "./test/auto-instanced-render-scene.ts"
|
|
32
|
+
"./test/auto-instanced-render-scene": "./test/auto-instanced-render-scene.ts",
|
|
33
|
+
"./gea-native.json": "./gea-native.json"
|
|
32
34
|
},
|
|
33
35
|
"files": [
|
|
34
36
|
"src",
|
|
@@ -40,7 +42,8 @@
|
|
|
40
42
|
"geatsc-plugin-batched-probe.mjs",
|
|
41
43
|
"test/batched-render-scene.ts",
|
|
42
44
|
"test/auto-instanced-render-scene.ts",
|
|
43
|
-
"geatsc-plugin-readback.mjs"
|
|
45
|
+
"geatsc-plugin-readback.mjs",
|
|
46
|
+
"gea-native.json"
|
|
44
47
|
],
|
|
45
48
|
"publishConfig": {
|
|
46
49
|
"access": "public",
|
|
@@ -51,7 +54,7 @@
|
|
|
51
54
|
"test:host-effects": "node test/native-host-effects-contract.test.mjs",
|
|
52
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",
|
|
53
56
|
"check": "tsc -p tsconfig.json --noEmit",
|
|
54
|
-
"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",
|
|
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",
|
|
55
58
|
"test:package": "node --experimental-vm-modules test/package.test.mjs"
|
|
56
59
|
},
|
|
57
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
|
+
}
|
package/src/nativeWebGL.ts
CHANGED
|
@@ -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(
|
|
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(
|
|
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 }
|
package/src/nativeWebGLHost.ts
CHANGED
|
@@ -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
|