@geastack/native-webgl-angle 0.1.6

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/LICENSE ADDED
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,184 @@
1
+ # Native WebGL ANGLE
2
+
3
+ `@geastack/native-webgl-angle` owns the native WebGL compatibility layer used by
4
+ the real Three.js macOS path.
5
+
6
+ ## Installation
7
+
8
+ Install `@geastack/native-webgl-angle` and its Three.js peer. The package
9
+ requires compiler 1.0.9 or later within the 1.x line and Three.js 0.185.x, and
10
+ resolves Apple bindings from npm; it does not require a sibling Apple
11
+ checkout.
12
+
13
+ ```sh
14
+ npm install @geastack/native-webgl-angle three
15
+ ```
16
+
17
+ The exports intentionally contain TypeScript sources for geatsc compilation,
18
+ not precompiled browser JavaScript. Load the compiler plugin from
19
+ `@geastack/native-webgl-angle/geatsc-plugin`. Native source files ship under
20
+ `native/` and can be located with, for example,
21
+ `require.resolve('@geastack/native-webgl-angle/native/angle_webgl_host.mm')`.
22
+ The platform build still supplies the generated host headers, platform SDK,
23
+ and ANGLE libraries; this package does not bundle those binaries.
24
+
25
+ The experimental BatchedMesh plugin is excluded from the npm archive.
26
+ Packaging validation loads the plugin and all of its eager imports directly
27
+ from the npm archive in memory, without falling back to the source checkout.
28
+ Run it with `npm run test:package -- <archive.tgz>`.
29
+
30
+ ```text
31
+ upstream three
32
+ -> WebGLRenderingContext / WebGL2RenderingContext facade
33
+ -> geatsc host shims
34
+ -> native ANGLE host
35
+ -> Metal
36
+ ```
37
+
38
+ This package is infrastructure. Examples should consume it; they should not
39
+ define their own ANGLE/WebGL bridge.
40
+
41
+ ## Contents
42
+
43
+ ```text
44
+ src/nativeWebGL.ts current TS WebGL facade used by Three.WebGLRenderer
45
+ src/nativeWebGLHost.ts typed declarations for geatsc native host functions
46
+ geatsc-plugin.mjs host shim metadata for geatsc
47
+ native/angle_webgl_host.mm
48
+ Objective-C++ ANGLE/Metal host implementation
49
+ ```
50
+
51
+ ## Current Compromise
52
+
53
+ Gea app manifests currently accept only app-relative `nativeSources`. Until
54
+ that resolver can consume package native sources directly, app consumers keep a
55
+ small app-local `.mm` wrapper that includes this package's native implementation.
56
+
57
+ The bridge implementation itself lives here.
58
+
59
+ ## Direction
60
+
61
+ The current TS facade still routes many WebGL calls through transitional
62
+ opcode-style host functions. The intended end state is direct typed native
63
+ methods and typed WebGL handles, with no `gea_cpp_value`/`gea_cpp_key` in the
64
+ bridge hot path.
65
+
66
+
67
+ ### Synchronous numeric uploads
68
+
69
+ GL upload functions opt into `hostShims.hostNativeArrayFunctions`. The compiler
70
+ preserves their native array carriers; the plugin owns the physical element
71
+ format and buffer/texture dispatch in `geatsc-plugin-uploads.mjs`.
72
+
73
+ `HostNumericArgument<E>` converts ordinary arrays directly to the requested
74
+ float/integer format, using inline storage through 16 elements and one heap
75
+ allocation beyond that. Matching typed arrays retain and borrow their backing
76
+ storage, preserving view offsets. Shared views use synchronized reads into an
77
+ owned copy. Uniform arrays and buffer subranges no longer pass through a
78
+ JavaScript number-array temporary. Sparse arrays fail closed.
79
+
80
+ These hosts must consume the input synchronously without reentering JavaScript
81
+ or retaining pointers. Reentrant hosts must use the compiler's separate owned
82
+ snapshot protocol. Rebuild generated declarations and native objects together
83
+ when changing this ABI; unmarked hosts retain their original vector ABI.
84
+
85
+ Profiling builds (`GEA_PROFILE_ALLOCATIONS` or `GEA_PROFILE_NATIVE_FRAMES`)
86
+ aggregate native GL call scopes and scene-transform markers. The frame-only
87
+ option requires defining the macro only for `angle_webgl_host.mm` and avoids
88
+ allocation instrumentation. Their elapsed times include instrumentation and
89
+ any driver wait; they are attribution data, not uninstrumented CPU speedups.
90
+
91
+ `GEA_WEBGL_TRAP`, `GEA_WEBGL_BLEND_DEBUG`, and `GEA_WEBGL_SHADOW_COLOR_AUDIT`
92
+ are launch settings, cached on first use to avoid environment lookups in draw
93
+ and uniform paths. Set them before the first host call and restart the process
94
+ to change them. Any present value, including an empty string, enables them.
95
+
96
+ ### Validation
97
+
98
+ `npm run test:host-effects` checks 85 direct GL/input declarations and 15 audio
99
+ declarations against the compiler's host-mutation census. Their
100
+ `@gea-host-inert` contracts permit native state changes while excluding script
101
+ reference retention, script object mutation and script callbacks. The native
102
+ implementations in `angle_webgl_host.mm` and `audio_host.mm` were audited for
103
+ those effects; audio channel uploads copy samples synchronously into native
104
+ storage. Removing a scalar or typed-array contract must restore conservative
105
+ prototype taint in the test.
106
+
107
+ View attachment, size synchronization and presentation remain outside that
108
+ contract pending a separate lifecycle/reentry audit. A native function's name
109
+ or numeric return type alone does not establish its effects. These contracts
110
+ remove false opaque-call edges; they do not establish zero boxing for an
111
+ application as a whole.
112
+
113
+ `npm test` includes the native void-call regression. Three.js comes from this
114
+ package's own `three` devDependency, so `npm install` here is enough; the
115
+ compiler comes from a checkout of
116
+ [geastack/compiler](https://github.com/geastack/compiler) with `dist/` built.
117
+ `clang++` (or `CXX`) supplies ASan/UBSan. `GEATSC_COMPILER_ROOT` and
118
+ `GEASTACK_ROOT` point at that checkout when it is not beside this repo.
119
+
120
+ The test extracts Three.js's actual `bindVertexArrayObject` function before
121
+ and after the plugin transform, then compiles each against the real native
122
+ WebGL facade and host bridge. It checks certified direct calls, physical void
123
+ results, absence of executable boxing, nullable handle forwarding, and the
124
+ observable `undefined` return. Only the external GL functions are mocked for
125
+ execution. Source fixtures stay in memory; the test executable uses the
126
+ compiler's existing ignored build output. No archived compiler APIs or scratch
127
+ directories are needed.
128
+
129
+ ### Streaming diagnostics and uniform names
130
+
131
+ Windows builds can select `GEA_WEBGL_BUFFER_STREAMING=0..9` to compare original
132
+ subdata, orphaning, replacement, dynamic usage, and a three-buffer staging ring.
133
+ Modes 1–4 qualify complete ARRAY_BUFFER updates. Modes 5/6 extend dynamic
134
+ replacement/staging rotation to complete ELEMENT_ARRAY_BUFFER updates; mode 7
135
+ streams indices alone; mode 8 combines vertex staging with dynamic indices.
136
+ Mode 9 uses uniform-buffer staging for eligible small full vertex updates.
137
+ Although its measured upload wait is shorter, total CPU cost increased;
138
+ it remains a diagnostic option and mode 8 remains the default.
139
+ Partial writes always preserve existing storage.
140
+ Windows defaults to the hybrid policy (8); mode 0 keeps the original path and
141
+ bypasses streaming bookkeeping in normal builds. Other platforms retain the
142
+ original path. Per-upload timing rows compile only with allocation or native
143
+ frame profiling.
144
+
145
+ Common renderer uniforms use fixed-name typed setters that still read the map
146
+ on every call. Replacing/deleting entries remains observable. Shadow-map
147
+ setters also forward the texture allocator so sampler ordering is preserved.
148
+ General uploads classify built-in long names once when the program's uniform
149
+ nodes are created, then read the current material value cell through a named
150
+ lookup. Custom names retain dynamic dictionary lookup. No material value cell
151
+ is cached; replacement, deletion and `needsUpdate` remain observable.
152
+
153
+ `GEA_WEBGL_UNIFORM_BINDINGS=baseline` disables the new name classification and
154
+ shadow setters for comparison with the previous implementation.
155
+ `GEA_WEBGL_UNIFORM_NAMES=baseline` disables fixed-name renderer setters.
156
+ An earlier explicit local ID snapshot increased allocations by discarding
157
+ reusable native string capacity; this implementation does not repeat it.
158
+ `npm run test:allocation-contracts` covers streaming, fixed uniforms and live
159
+ uniform bindings. `node test/uniform-binding-native.test.mjs` additionally
160
+ executes the actual lookup helpers under ASan/UBSan and requires no emitted
161
+ boxing.
162
+
163
+ The experimental BatchedMesh adaptation remains separate from the standard
164
+ plugin. `node test/batched-mesh-contract.test.mjs` checks its generic geometry,
165
+ transform, color, resize and clone behavior. Clones receive independent texture
166
+ sources and arrays. Native callback, clone and visibility checks now pass;
167
+ the full ANGLE comparison also matches ordinary mesh pixels exactly. Without
168
+ `WEBGL_multi_draw`, its fallback retains separate draw calls. See
169
+ [the native validation record](test/BATCHED-MESH-NATIVE.md) for evidence.
170
+
171
+ The separate `GEA_WEBGL_AUTO_INSTANCE=1` experiment groups adjacent compatible
172
+ opaque meshes into actual instanced draws. It is disabled by default while
173
+ end-to-end validation and platform measurements continue. Its eligibility rules,
174
+ fallbacks and validation commands are documented in
175
+ [the instancing contract](test/INSTANCING.md). Both experiments are generic;
176
+ the plugin contains no application-specific batching logic.
177
+
178
+ ## License
179
+
180
+ Apache-2.0 (see `LICENSE`). Use it, change it, ship closed-source products on
181
+ it, no strings attached. The only GeaStack code under a different license is
182
+ the embedded board support (`targets` and `@geastack/chips`, GPL-3.0-only):
183
+ shipping closed-source firmware through those needs a commercial license.
184
+ Contact [contact@geastack.com](mailto:contact@geastack.com) for commercial terms, support and hosted builds.
@@ -0,0 +1,98 @@
1
+ // Verification-only wrapper plugin for BatchedMesh native typing work.
2
+ //
3
+ // It imports the base plugin's default export, wraps `configure()` so its
4
+ // `hostShims.transformSource` runs first (every other transform the base
5
+ // plugin applies still happens), and then layers these adaptations on top:
6
+ //
7
+ // - `core/Object3D.js` -> expose the documented native callback arguments
8
+ // - `math/FrustumArray.js` -> check pool extent before native element reads
9
+ // - `objects/BatchedMesh.js` -> `transformBatchedMesh`
10
+ // - `renderers/WebGLRenderer.js` -> `transformBatchedRendererBranch`
11
+ // (replacing the base plugin's current "BatchedMesh is not supported"
12
+ // stub with the typed uniform branch)
13
+ //
14
+ // These rewrites remain experimental until native execution and renderer
15
+ // callback dispatch pass. This wrapper is excluded from the published package.
16
+
17
+ import basePlugin from './geatsc-plugin.mjs'
18
+ import { transformBatchedMesh, transformBatchedRendererBranch } from './geatsc-plugin-batched-mesh.mjs'
19
+
20
+ const replaceOne = (text, before, after, fileName, description) => {
21
+ if (!text.includes(before)) throw new Error(`Three ${description} source at ${fileName} no longer matches the native adaptation`)
22
+ return text.replace(before, after)
23
+ }
24
+
25
+ export default {
26
+ name: 'native-webgl-angle-host-batched-probe',
27
+ configure() {
28
+ const base = basePlugin.configure()
29
+ const baseTransformSource = base.hostShims.transformSource
30
+
31
+ const transformSource = ({ fileName, text }) => {
32
+ const normalized = fileName.replaceAll('\\', '/')
33
+ const baseResult = baseTransformSource({ fileName, text })
34
+ let transformed = baseResult === null ? text : baseResult
35
+
36
+ if (process.env.GEA_WEBGL_AUTO_INSTANCE_TEST === '1' && normalized.endsWith('/three-batched-mesh/index.tsx')) {
37
+ transformed = replaceOne(transformed, 'native-webgl-angle/test/batched-render-scene',
38
+ 'native-webgl-angle/test/auto-instanced-render-scene', fileName, 'automatic instancing validation scene');
39
+ }
40
+
41
+ // Three documents these callback arguments but comments them out of the
42
+ // base no-op definitions. Keep the real callback ABI visible to native
43
+ // override dispatch and to callers holding an Object3D reference.
44
+ if (normalized.endsWith('/three/src/core/Object3D.js')) {
45
+ transformed = transformed.replaceAll('@param {Object} group - The geometry group data.',
46
+ '@param {{start:number,count:number,materialIndex?:number}|null} group - The geometry group data.')
47
+ for (const name of ['onBeforeRender', 'onAfterRender', 'onBeforeShadow', 'onAfterShadow']) {
48
+ const shadow = name.endsWith('Shadow')
49
+ const parameters = shadow
50
+ ? 'renderer, object, camera, shadowCamera, geometry, depthMaterial, group'
51
+ : 'renderer, scene, camera, geometry, material, group'
52
+ transformed = replaceOne(transformed, `${name}( /* ${parameters} */ ) {}`,
53
+ `${name}( ${parameters} ) {}`, fileName, `Object3D ${name} declared callback arguments`)
54
+ if (!shadow) {
55
+ // Scene calls this inherited no-op with a render target in slot
56
+ // four and omits material/group. Mesh calls supply all six slots.
57
+ const signature = `${name}( ${parameters} ) {}`
58
+ const start = transformed.lastIndexOf('/**', transformed.indexOf(signature))
59
+ const end = transformed.indexOf(signature)
60
+ const comment = transformed.slice(start, end)
61
+ .replace('@param {BufferGeometry} geometry', "@param {BufferGeometry|import('../renderers/WebGLRenderTarget.js').WebGLRenderTarget|null} geometry")
62
+ .replace('@param {Material} material', '@param {Material} [material]')
63
+ .replace('} group -', '} [group] -')
64
+ transformed = transformed.slice(0, start) + comment + transformed.slice(end)
65
+ }
66
+ }
67
+ }
68
+
69
+ if (normalized.endsWith('/three/src/objects/BatchedMesh.js')) {
70
+ transformed = transformBatchedMesh(transformed, fileName, replaceOne)
71
+ }
72
+
73
+ if (normalized.endsWith('/three/src/math/FrustumArray.js')) {
74
+ // The pool grows sequentially. Test its extent before reading a native
75
+ // element; retain Three's missing-entry test for existing JS slots.
76
+ const before = 'if ( frustums[ i ] === undefined ) frustums[ i ] = new Frustum();'
77
+ const after = 'if ( i >= frustums.length || frustums[ i ] === undefined ) frustums[ i ] = new Frustum();'
78
+ for (let occurrence = 0; occurrence < 2; occurrence++) {
79
+ transformed = replaceOne(transformed, before, after, fileName, 'FrustumArray pool extent before element read')
80
+ }
81
+ }
82
+
83
+ if (normalized.endsWith('/three/src/renderers/WebGLRenderer.js')) {
84
+ transformed = transformBatchedRendererBranch(transformed, fileName, replaceOne)
85
+ }
86
+
87
+ return transformed === text ? null : transformed
88
+ }
89
+
90
+ return {
91
+ ...base,
92
+ hostShims: {
93
+ ...base.hostShims,
94
+ transformSource,
95
+ },
96
+ }
97
+ },
98
+ }
@@ -0,0 +1,160 @@
1
+ // Generic, opt-in render-list instancing. Only already-adjacent opaque draws
2
+ // with the same native geometry/material are candidates. No scene mutation.
3
+ export const nativeInstanceHelpers = `
4
+ /** @param {import('../core/Object3D.js').Object3D} object @return {boolean} */
5
+ function nativeInstanceTransformSupported( object ) {
6
+ const e = object.matrixWorld.elements;
7
+ const x = e[0]*e[0] + e[1]*e[1] + e[2]*e[2];
8
+ const y = e[4]*e[4] + e[5]*e[5] + e[6]*e[6];
9
+ const z = e[8]*e[8] + e[9]*e[9] + e[10]*e[10];
10
+ // Three's instanced normal transform supports positive TRS, not shear.
11
+ return e[3] === 0 && e[7] === 0 && e[11] === 0 && e[15] === 1 &&
12
+ x > 0 && y > 0 && z > 0 && object.matrixWorld.determinant() > 0 &&
13
+ Math.abs(e[0]*e[4] + e[1]*e[5] + e[2]*e[6]) <= 1e-10 * Math.sqrt(x*y) &&
14
+ Math.abs(e[0]*e[8] + e[1]*e[9] + e[2]*e[10]) <= 1e-10 * Math.sqrt(x*z) &&
15
+ Math.abs(e[4]*e[8] + e[5]*e[9] + e[6]*e[10]) <= 1e-10 * Math.sqrt(y*z);
16
+ }
17
+ `;
18
+
19
+ export function transformAutomaticInstancing(text, fileName, replaceOne, renderItemType) {
20
+ text = `import { nativeInstanceBeforeRender, nativeInstanceAfterRender } from '../core/Object3D.js';\nimport { nativeInstanceMaterialBeforeRender, nativeInstanceBeforeCompile, nativeInstanceProgramCacheKey } from '../materials/Material.js';\nimport { InstancedBufferAttribute } from '../core/InstancedBufferAttribute.js';\nimport { InterleavedBufferAttribute } from '../core/InterleavedBufferAttribute.js';\nimport { InstancedInterleavedBuffer } from '../core/InstancedInterleavedBuffer.js';\nimport { InstancedMesh } from '../objects/InstancedMesh.js';\nimport { Mesh } from '../objects/Mesh.js';\n${nativeInstanceHelpers}\n` + text;
21
+ const before = `\t\tfunction renderObjects( renderList, scene, camera ) {`;
22
+ const start = text.indexOf(before);
23
+ const end = text.indexOf('\n\t\tfunction renderObject(', start);
24
+ if (start < 0 || end < 0) throw new Error(`Three renderObjects source at ${fileName} no longer matches`);
25
+ const replacement = `
26
+ const nativeInstanceMatrix = new Matrix4();
27
+ let nativeInstanceUsed = 0;
28
+ /** @type {InstancedMesh[]} */
29
+ const nativeInstancePool = [];
30
+ /** @param {${renderItemType}} item */
31
+ function nativeCanInstanceMaterial( item ) {
32
+ const material = item.material;
33
+ // The pooled mesh must not inherit callbacks installed specifically
34
+ // on InstancedMesh, even when the source meshes keep canonical hooks.
35
+ if (InstancedMesh.prototype.onBeforeRender !== nativeInstanceBeforeRender ||
36
+ InstancedMesh.prototype.onAfterRender !== nativeInstanceAfterRender) return false;
37
+ if (material.isShaderMaterial || material.isRawShaderMaterial || material.isNodeMaterial ||
38
+ // Object-space normal maps use only the uniform normalMatrix in
39
+ // Three's fragment shader, bypassing per-instance normal transforms.
40
+ (material.normalMap && material.normalMapType === 1 /* ObjectSpaceNormalMap */) ||
41
+ material.transparent || !material.depthWrite || !material.depthTest ||
42
+ material.stencilWrite || material.wireframe || material.blending !== 1 ||
43
+ material.onBeforeRender !== nativeInstanceMaterialBeforeRender ||
44
+ material.onBeforeCompile !== nativeInstanceBeforeCompile ||
45
+ material.customProgramCacheKey !== nativeInstanceProgramCacheKey) return false;
46
+ if (!(material.type === 'MeshBasicMaterial' || material.type === 'MeshLambertMaterial' ||
47
+ material.type === 'MeshPhongMaterial' || material.type === 'MeshStandardMaterial')) return false;
48
+ if (item.geometry.isInstancedBufferGeometry || Object.keys(item.geometry.morphAttributes).length !== 0) return false;
49
+ const names = Object.keys(item.geometry.attributes);
50
+ if (names.length > 8) return false;
51
+ for (let i = 0; i < names.length; i++) {
52
+ const attribute = item.geometry.getAttribute(names[i]);
53
+ if (attribute instanceof InstancedBufferAttribute ||
54
+ (attribute instanceof InterleavedBufferAttribute && attribute.data instanceof InstancedInterleavedBuffer)) return false;
55
+ }
56
+ return true;
57
+ }
58
+ /** @param {${renderItemType}} item @param {import('../cameras/Camera.js').Camera} camera */
59
+ function nativeCanInstance( item, camera ) {
60
+ const object = item.object;
61
+ return object instanceof Mesh && !(object instanceof InstancedMesh) && !object.isSkinnedMesh && !object.isBatchedMesh && object.type === 'Mesh' &&
62
+ item.group === null && object.layers.test(camera.layers) &&
63
+ object.onBeforeRender === nativeInstanceBeforeRender &&
64
+ object.onAfterRender === nativeInstanceAfterRender &&
65
+ object.morphTargetInfluences === undefined && nativeInstanceTransformSupported(object);
66
+ }
67
+
68
+ /** @param {${renderItemType}[]} renderList */
69
+ function renderObjects( renderList, scene, camera ) {
70
+ const overrideMaterial = scene.isScene === true ? scene.overrideMaterial : null;
71
+ let batchIndex = 0;
72
+ for (let i = 0, l = renderList.length; i < l; i++) {
73
+ const item = renderList[i];
74
+ const object = item.object;
75
+ const geometry = item.geometry;
76
+ const group = item.group;
77
+ let material = item.material;
78
+ if (material.allowOverride === true && overrideMaterial !== null) material = overrideMaterial;
79
+ if (!object.layers.test(camera.layers)) continue;
80
+ let end = i + 1;
81
+ // Reject singleton runs before inspecting attributes. Those checks
82
+ // enumerate keys and would otherwise allocate for every ordinary draw.
83
+ if (end < l && renderList[end].geometry === geometry && renderList[end].material === material &&
84
+ overrideMaterial === null && !_this.localClippingEnabled && currentRenderState.state.lightProbeGridArray.length === 0 && nativeCanInstanceMaterial(item) && nativeCanInstance(item, camera)) {
85
+ while (end < l) {
86
+ const next = renderList[end];
87
+ if (next.geometry !== geometry || next.material !== material ||
88
+ next.groupOrder !== item.groupOrder || next.renderOrder !== item.renderOrder ||
89
+ next.object.receiveShadow !== object.receiveShadow || !nativeCanInstance(next, camera)) break;
90
+ end++;
91
+ }
92
+ }
93
+ const count = end - i;
94
+ if (count < 2) {
95
+ renderObject(object, scene, camera, geometry, material, group);
96
+ continue;
97
+ }
98
+ if (batchIndex >= nativeInstancePool.length) {
99
+ nativeInstancePool.push(new InstancedMesh(geometry, material, Math.max(16, count)));
100
+ }
101
+ let batch = nativeInstancePool[batchIndex];
102
+ if (batch.instanceMatrix.count < count) {
103
+ batch.dispose();
104
+ batch = new InstancedMesh(geometry, material, Math.max(count, batch.instanceMatrix.count * 2));
105
+ nativeInstancePool[batchIndex] = batch;
106
+ }
107
+ batchIndex++;
108
+ nativeInstanceUsed = Math.max(nativeInstanceUsed, batchIndex);
109
+ batch.geometry = geometry;
110
+ batch.material = material;
111
+ batch.count = count;
112
+ batch.receiveShadow = object.receiveShadow;
113
+ // Keep visible coordinates near the camera before Float32 upload.
114
+ // A distant first instance must not quantize nearby later instances.
115
+ const origin = camera.matrixWorld.elements;
116
+ const originX = origin[12], originY = origin[13], originZ = origin[14];
117
+ batch.matrixWorld.makeTranslation(originX, originY, originZ);
118
+ for (let j = i; j < end; j++) {
119
+ const original = renderList[j].object;
120
+ nativeInstanceMatrix.copy(original.matrixWorld);
121
+ const relative = nativeInstanceMatrix.elements;
122
+ relative[12] -= originX;
123
+ relative[13] -= originY;
124
+ relative[14] -= originZ;
125
+ batch.setMatrixAt(j - i, nativeInstanceMatrix);
126
+ // Retain Three's observable per-object camera matrices.
127
+ original.modelViewMatrix.multiplyMatrices(camera.matrixWorldInverse, original.matrixWorld);
128
+ original.normalMatrix.getNormalMatrix(original.modelViewMatrix);
129
+ }
130
+ batch.instanceMatrix.needsUpdate = true;
131
+ objects.update(batch);
132
+ // A renderer can submit several views in one frame. WebGLObjects'
133
+ // once-per-frame guard must not hide updated instance matrices.
134
+ attributes.update(batch.instanceMatrix, _gl.ARRAY_BUFFER);
135
+ renderObject(batch, scene, camera, geometry, material, null);
136
+ i = end - 1;
137
+ }
138
+ }
139
+ `;
140
+ text = text.slice(0, start) + replacement + text.slice(end);
141
+ text = replaceOne(text, '\t\tthis.render = /** @this {WebGLRenderer} */ function ( scene, camera ) {',
142
+ '\t\tthis.render = /** @this {WebGLRenderer} */ function ( scene, camera ) {\n\t\t\tif (renderListStack.length === 0) nativeInstanceUsed = 0;', fileName, 'instance pool per-render usage');
143
+ text = replaceOne(text, '\t\t\tbindingStates.resetDefaultState();',
144
+ '\t\t\tif (renderListStack.length === 1) {\n\t\t\t\twhile (nativeInstancePool.length > nativeInstanceUsed) {\n\t\t\t\t\tnativeInstancePool[nativeInstancePool.length - 1].dispose();\n\t\t\t\t\tnativeInstancePool.pop();\n\t\t\t\t}\n\t\t\t}\n\t\t\tbindingStates.resetDefaultState();', fileName, 'unused instance pool disposal');
145
+ return replaceOne(text, '\t\tthis.dispose = function () {',
146
+ '\t\tthis.dispose = function () {\n\t\t\tfor (let i = 0; i < nativeInstancePool.length; i++) nativeInstancePool[i].dispose();\n\t\t\tnativeInstancePool.length = 0;', fileName, 'renderer instance pool disposal');
147
+ }
148
+
149
+ export function transformCanonicalInstanceHooks(text, fileName) {
150
+ if (fileName.endsWith('/three/src/core/Object3D.js')) {
151
+ return text + `\nexport const nativeInstanceBeforeRender = Object3D.prototype.onBeforeRender;
152
+ export const nativeInstanceAfterRender = Object3D.prototype.onAfterRender;\n`;
153
+ }
154
+ if (fileName.endsWith('/three/src/materials/Material.js')) {
155
+ return text + `\nexport const nativeInstanceMaterialBeforeRender = Material.prototype.onBeforeRender;
156
+ export const nativeInstanceBeforeCompile = Material.prototype.onBeforeCompile;
157
+ export const nativeInstanceProgramCacheKey = Material.prototype.customProgramCacheKey;\n`;
158
+ }
159
+ return text;
160
+ }