@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 +202 -0
- package/README.md +184 -0
- package/geatsc-plugin-batched-probe.mjs +98 -0
- package/geatsc-plugin-instancing.mjs +160 -0
- package/geatsc-plugin-uniforms.mjs +1955 -0
- package/geatsc-plugin-uploads.mjs +29 -0
- package/geatsc-plugin.mjs +10275 -0
- package/native/angle_webgl_host.mm +2687 -0
- package/native/audio_host.mm +857 -0
- package/native/audio_uwp.h +116 -0
- package/package.json +70 -0
- package/src/gea-native-types.d.ts +4 -0
- package/src/nativeAudioHost.ts +296 -0
- package/src/nativeThreeUtils.ts +11 -0
- package/src/nativeWebGL.ts +1211 -0
- package/src/nativeWebGLAnimation.ts +13 -0
- package/src/nativeWebGLHost.ts +333 -0
- package/src/nativeWebXRManager.ts +48 -0
- package/src/text/atlas-sans.ts +195 -0
- package/src/text/atlas-serif-italic.ts +195 -0
- package/src/text/atlas-serif.ts +195 -0
- package/src/text/native-text-types.ts +17 -0
- package/src/text/native-text.ts +203 -0
- package/src/troika-three-text.ts +248 -0
- package/test/auto-instanced-render-scene.ts +66 -0
- package/test/batched-render-scene.ts +58 -0
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
|
+
}
|