@expofp/renderer 0.0.0-experimental.2d6c681
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 +145 -0
- package/dist/Renderer.d.ts +102 -0
- package/dist/Renderer.js +96 -0
- package/dist/controls/CameraControls.d.ts +108 -0
- package/dist/controls/CameraControls.js +203 -0
- package/dist/controls/Controls.d.ts +53 -0
- package/dist/controls/Controls.js +54 -0
- package/dist/controls/camera-api.d.ts +226 -0
- package/dist/controls/camera-api.js +179 -0
- package/dist/controls/gestures.d.ts +71 -0
- package/dist/controls/gestures.js +98 -0
- package/dist/controls/handlers/DoubleTapZoom.d.ts +23 -0
- package/dist/controls/handlers/DoubleTapZoom.js +82 -0
- package/dist/controls/handlers/Inertia.d.ts +60 -0
- package/dist/controls/handlers/Inertia.js +222 -0
- package/dist/controls/handlers/Pan.d.ts +23 -0
- package/dist/controls/handlers/Pan.js +212 -0
- package/dist/controls/handlers/Pitch.d.ts +19 -0
- package/dist/controls/handlers/Pitch.js +200 -0
- package/dist/controls/handlers/Roll.d.ts +30 -0
- package/dist/controls/handlers/Roll.js +196 -0
- package/dist/controls/handlers/Zoom.d.ts +28 -0
- package/dist/controls/handlers/Zoom.js +34 -0
- package/dist/controls/hooks.d.ts +136 -0
- package/dist/controls/hooks.js +200 -0
- package/dist/controls/index.d.ts +9 -0
- package/dist/controls/index.js +7 -0
- package/dist/controls/zoom-to.d.ts +89 -0
- package/dist/controls/zoom-to.js +91 -0
- package/dist/core/InstanceUniforms.d.ts +108 -0
- package/dist/core/InstanceUniforms.js +298 -0
- package/dist/core/SquareDataTexture.d.ts +277 -0
- package/dist/core/SquareDataTexture.js +476 -0
- package/dist/core/batch/BatchedMesh.d.ts +74 -0
- package/dist/core/batch/BatchedMesh.js +155 -0
- package/dist/core/instanced/FrustumCulling.d.ts +141 -0
- package/dist/core/instanced/FrustumCulling.js +246 -0
- package/dist/core/instanced/InstancedMesh.d.ts +302 -0
- package/dist/core/instanced/InstancedMesh.js +497 -0
- package/dist/core/instanced/Instances.d.ts +119 -0
- package/dist/core/instanced/Instances.js +230 -0
- package/dist/core/instanced/Raycasting.d.ts +118 -0
- package/dist/core/instanced/Raycasting.js +134 -0
- package/dist/core/instanced/instanced-material.d.ts +77 -0
- package/dist/core/instanced/instanced-material.js +228 -0
- package/dist/core/lines/BatchedLines.d.ts +137 -0
- package/dist/core/lines/BatchedLines.js +215 -0
- package/dist/core/lines/FrustumCullingLines.d.ts +44 -0
- package/dist/core/lines/FrustumCullingLines.js +85 -0
- package/dist/core/lines/RaycastingLines.d.ts +45 -0
- package/dist/core/lines/RaycastingLines.js +91 -0
- package/dist/core/lines/lines-material.d.ts +47 -0
- package/dist/core/lines/lines-material.js +124 -0
- package/dist/core/shader-source.d.ts +70 -0
- package/dist/core/shader-source.js +110 -0
- package/dist/core/sprites/BatchedSprites.d.ts +115 -0
- package/dist/core/sprites/BatchedSprites.js +173 -0
- package/dist/core/sprites/RaycastingSprites.d.ts +41 -0
- package/dist/core/sprites/RaycastingSprites.js +71 -0
- package/dist/core/sprites/sprite-material.d.ts +59 -0
- package/dist/core/sprites/sprite-material.js +192 -0
- package/dist/core/text/BatchedText.d.ts +233 -0
- package/dist/core/text/BatchedText.js +473 -0
- package/dist/core/text/MemberTexture.d.ts +90 -0
- package/dist/core/text/MemberTexture.js +289 -0
- package/dist/core/text/Text.d.ts +88 -0
- package/dist/core/text/Text.js +230 -0
- package/dist/core/text/batched-text-material.d.ts +56 -0
- package/dist/core/text/batched-text-material.js +123 -0
- package/dist/core/text/glyph-packing.d.ts +17 -0
- package/dist/core/text/glyph-packing.js +61 -0
- package/dist/core/typed-array.d.ts +16 -0
- package/dist/core/typed-array.js +20 -0
- package/dist/debug/components/DebugPanelProvider.d.ts +20 -0
- package/dist/debug/components/DebugPanelProvider.js +50 -0
- package/dist/debug/components/DebugTools.d.ts +22 -0
- package/dist/debug/components/DebugTools.js +28 -0
- package/dist/debug/components/Gui.d.ts +22 -0
- package/dist/debug/components/Gui.js +42 -0
- package/dist/debug/components/MemoryInfo.d.ts +22 -0
- package/dist/debug/components/MemoryInfo.js +42 -0
- package/dist/debug/components/Panel.d.ts +31 -0
- package/dist/debug/components/Panel.js +104 -0
- package/dist/debug/components/Stats.d.ts +9 -0
- package/dist/debug/components/Stats.js +24 -0
- package/dist/debug/debug-chunk.d.ts +26 -0
- package/dist/debug/debug-chunk.js +32 -0
- package/dist/debug/debug-context.d.ts +41 -0
- package/dist/debug/debug-context.js +59 -0
- package/dist/debug/index.d.ts +41 -0
- package/dist/debug/index.js +43 -0
- package/dist/events/LayoutDriver.d.ts +36 -0
- package/dist/events/LayoutDriver.js +192 -0
- package/dist/events/PointerMissed.d.ts +23 -0
- package/dist/events/PointerMissed.js +58 -0
- package/dist/events/def-culling.d.ts +46 -0
- package/dist/events/def-culling.js +69 -0
- package/dist/events/layout-targets.d.ts +151 -0
- package/dist/events/layout-targets.js +397 -0
- package/dist/events/placement/box.d.ts +59 -0
- package/dist/events/placement/box.js +96 -0
- package/dist/events/placement/grid.d.ts +161 -0
- package/dist/events/placement/grid.js +235 -0
- package/dist/events/placement/index.d.ts +2 -0
- package/dist/events/placement/index.js +1 -0
- package/dist/events/placement/pass.d.ts +70 -0
- package/dist/events/placement/pass.js +617 -0
- package/dist/events/pointer.d.ts +194 -0
- package/dist/events/pointer.js +356 -0
- package/dist/external/ExternalCanvas.d.ts +56 -0
- package/dist/external/ExternalCanvas.js +177 -0
- package/dist/external/external-camera.d.ts +18 -0
- package/dist/external/external-camera.js +103 -0
- package/dist/external/external-events.d.ts +14 -0
- package/dist/external/external-events.js +33 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +14 -0
- package/dist/objects/Background.d.ts +31 -0
- package/dist/objects/Background.js +37 -0
- package/dist/objects/containers/SlotContainer.d.ts +312 -0
- package/dist/objects/containers/SlotContainer.js +311 -0
- package/dist/objects/containers/hooks.d.ts +61 -0
- package/dist/objects/containers/hooks.js +196 -0
- package/dist/objects/containers/index.d.ts +6 -0
- package/dist/objects/containers/index.js +4 -0
- package/dist/objects/containers/keys.d.ts +10 -0
- package/dist/objects/containers/keys.js +18 -0
- package/dist/objects/containers/mesh-writes.d.ts +106 -0
- package/dist/objects/containers/mesh-writes.js +97 -0
- package/dist/objects/containers/props.d.ts +33 -0
- package/dist/objects/containers/props.js +1 -0
- package/dist/objects/images/ImageInstance.d.ts +29 -0
- package/dist/objects/images/ImageInstance.js +152 -0
- package/dist/objects/images/Images.d.ts +49 -0
- package/dist/objects/images/Images.js +394 -0
- package/dist/objects/images/atlas-packer.d.ts +129 -0
- package/dist/objects/images/atlas-packer.js +287 -0
- package/dist/objects/images/image-container.d.ts +74 -0
- package/dist/objects/images/image-container.js +100 -0
- package/dist/objects/images/index.d.ts +2 -0
- package/dist/objects/images/index.js +1 -0
- package/dist/objects/images/pack-sources.d.ts +190 -0
- package/dist/objects/images/pack-sources.js +272 -0
- package/dist/objects/images/texture-budget.d.ts +120 -0
- package/dist/objects/images/texture-budget.js +215 -0
- package/dist/objects/images/worker/atlas-worker.d.ts +56 -0
- package/dist/objects/images/worker/atlas-worker.js +123 -0
- package/dist/objects/images/worker/client.d.ts +62 -0
- package/dist/objects/images/worker/client.js +249 -0
- package/dist/objects/images/worker/index.d.ts +10 -0
- package/dist/objects/images/worker/index.js +1 -0
- package/dist/objects/layer/Layer.d.ts +28 -0
- package/dist/objects/layer/Layer.js +134 -0
- package/dist/objects/layer/index.d.ts +2 -0
- package/dist/objects/layer/index.js +1 -0
- package/dist/objects/layer/layer-context.d.ts +74 -0
- package/dist/objects/layer/layer-context.js +82 -0
- package/dist/objects/layer/mount-scheduler.d.ts +131 -0
- package/dist/objects/layer/mount-scheduler.js +289 -0
- package/dist/objects/layer/visibility-gui.d.ts +19 -0
- package/dist/objects/layer/visibility-gui.js +93 -0
- package/dist/objects/lighting/Lighting.d.ts +92 -0
- package/dist/objects/lighting/Lighting.js +384 -0
- package/dist/objects/lighting/OcclusionPasses.d.ts +23 -0
- package/dist/objects/lighting/OcclusionPasses.js +271 -0
- package/dist/objects/lighting/index.d.ts +11 -0
- package/dist/objects/lighting/index.js +10 -0
- package/dist/objects/lighting/lighting-gui.d.ts +24 -0
- package/dist/objects/lighting/lighting-gui.js +114 -0
- package/dist/objects/lighting/occluder.d.ts +34 -0
- package/dist/objects/lighting/occluder.js +62 -0
- package/dist/objects/lighting/occlusion-targets.d.ts +143 -0
- package/dist/objects/lighting/occlusion-targets.js +276 -0
- package/dist/objects/lines/LineInstance.d.ts +21 -0
- package/dist/objects/lines/LineInstance.js +73 -0
- package/dist/objects/lines/Lines.d.ts +17 -0
- package/dist/objects/lines/Lines.js +31 -0
- package/dist/objects/lines/index.d.ts +2 -0
- package/dist/objects/lines/index.js +1 -0
- package/dist/objects/lines/line-container.d.ts +35 -0
- package/dist/objects/lines/line-container.js +40 -0
- package/dist/objects/materials/ColorMaterial.d.ts +92 -0
- package/dist/objects/materials/ColorMaterial.js +124 -0
- package/dist/objects/materials/LineMaterial.d.ts +44 -0
- package/dist/objects/materials/LineMaterial.js +74 -0
- package/dist/objects/materials/ModelMaterial.d.ts +53 -0
- package/dist/objects/materials/ModelMaterial.js +111 -0
- package/dist/objects/materials/TextureMaterial.d.ts +33 -0
- package/dist/objects/materials/TextureMaterial.js +48 -0
- package/dist/objects/materials/effects/ao/ambient-occlusion-pass.d.ts +77 -0
- package/dist/objects/materials/effects/ao/ambient-occlusion-pass.js +296 -0
- package/dist/objects/materials/effects/ao/ambient-occlusion-patch.d.ts +29 -0
- package/dist/objects/materials/effects/ao/ambient-occlusion-patch.js +115 -0
- package/dist/objects/materials/effects/ao/blur-pass.d.ts +61 -0
- package/dist/objects/materials/effects/ao/blur-pass.js +101 -0
- package/dist/objects/materials/effects/ao/index.d.ts +129 -0
- package/dist/objects/materials/effects/ao/index.js +72 -0
- package/dist/objects/materials/effects/color-dim.d.ts +115 -0
- package/dist/objects/materials/effects/color-dim.js +206 -0
- package/dist/objects/materials/effects/contact-shadow.d.ts +100 -0
- package/dist/objects/materials/effects/contact-shadow.js +167 -0
- package/dist/objects/materials/effects/effects-context.d.ts +53 -0
- package/dist/objects/materials/effects/effects-context.js +24 -0
- package/dist/objects/materials/shared.d.ts +24 -0
- package/dist/objects/materials/shared.js +72 -0
- package/dist/objects/models/ModelInstance.d.ts +22 -0
- package/dist/objects/models/ModelInstance.js +33 -0
- package/dist/objects/models/Models.d.ts +22 -0
- package/dist/objects/models/Models.js +186 -0
- package/dist/objects/models/gltf-cache.d.ts +17 -0
- package/dist/objects/models/gltf-cache.js +50 -0
- package/dist/objects/models/index.d.ts +2 -0
- package/dist/objects/models/index.js +1 -0
- package/dist/objects/models/model-asset.d.ts +48 -0
- package/dist/objects/models/model-asset.js +77 -0
- package/dist/objects/models/model-container.d.ts +123 -0
- package/dist/objects/models/model-container.js +207 -0
- package/dist/objects/shapes/ShapeInstance.d.ts +26 -0
- package/dist/objects/shapes/ShapeInstance.js +39 -0
- package/dist/objects/shapes/Shapes.d.ts +21 -0
- package/dist/objects/shapes/Shapes.js +131 -0
- package/dist/objects/shapes/index.d.ts +2 -0
- package/dist/objects/shapes/index.js +1 -0
- package/dist/objects/shapes/shape-container.d.ts +141 -0
- package/dist/objects/shapes/shape-container.js +242 -0
- package/dist/objects/shapes/shape-geometry.d.ts +27 -0
- package/dist/objects/shapes/shape-geometry.js +94 -0
- package/dist/objects/text/TextInstance.d.ts +22 -0
- package/dist/objects/text/TextInstance.js +95 -0
- package/dist/objects/text/Texts.d.ts +30 -0
- package/dist/objects/text/Texts.js +43 -0
- package/dist/objects/text/index.d.ts +2 -0
- package/dist/objects/text/index.js +1 -0
- package/dist/objects/text/text-container.d.ts +121 -0
- package/dist/objects/text/text-container.js +229 -0
- package/dist/objects/text/text-layout.d.ts +92 -0
- package/dist/objects/text/text-layout.js +180 -0
- package/dist/space/Camera.d.ts +103 -0
- package/dist/space/Camera.js +321 -0
- package/dist/space/Scene.d.ts +47 -0
- package/dist/space/Scene.js +127 -0
- package/dist/space/Space.d.ts +325 -0
- package/dist/space/Space.js +359 -0
- package/dist/space/VisibleRect.d.ts +45 -0
- package/dist/space/VisibleRect.js +129 -0
- package/dist/space/camera-handle.d.ts +67 -0
- package/dist/space/camera-handle.js +63 -0
- package/dist/space/camera-kind.d.ts +26 -0
- package/dist/space/camera-kind.js +30 -0
- package/dist/space/canvas-size.d.ts +24 -0
- package/dist/space/canvas-size.js +20 -0
- package/dist/space/clipping.d.ts +69 -0
- package/dist/space/clipping.js +130 -0
- package/dist/space/layout-measure.d.ts +67 -0
- package/dist/space/layout-measure.js +175 -0
- package/dist/space/viewport.d.ts +112 -0
- package/dist/space/viewport.js +67 -0
- package/dist/types/defs.d.ts +234 -0
- package/dist/types/defs.js +1 -0
- package/dist/types/events.d.ts +198 -0
- package/dist/types/events.js +1 -0
- package/dist/types/guards.d.ts +21 -0
- package/dist/types/guards.js +52 -0
- package/dist/update/index.d.ts +3 -0
- package/dist/update/index.js +2 -0
- package/dist/update/slot-writers.d.ts +30 -0
- package/dist/update/slot-writers.js +76 -0
- package/dist/update/store.d.ts +41 -0
- package/dist/update/store.js +104 -0
- package/dist/utils/arrays.d.ts +13 -0
- package/dist/utils/arrays.js +20 -0
- package/dist/utils/colors.d.ts +19 -0
- package/dist/utils/colors.js +27 -0
- package/dist/utils/hot-state.d.ts +41 -0
- package/dist/utils/hot-state.js +37 -0
- package/dist/utils/logging.d.ts +29 -0
- package/dist/utils/logging.js +55 -0
- package/dist/utils/math.d.ts +52 -0
- package/dist/utils/math.js +76 -0
- package/package.json +79 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { openMain } from '../shader-source';
|
|
2
|
+
/**
|
|
3
|
+
* The material side of a text batch: what troika's derivation has to become before it can draw a
|
|
4
|
+
* batch's members, and where the batch's per-instance uniforms are declared.
|
|
5
|
+
*
|
|
6
|
+
* Troika builds that material in two moments with its own derivation between them, so the work
|
|
7
|
+
* splits the same way — {@link patchBaseMaterial} runs on the base material *before*
|
|
8
|
+
* `createDerivedMaterial`, {@link configureDerivedMaterial} on the result *after*. Each order is
|
|
9
|
+
* load-bearing; see the two.
|
|
10
|
+
*
|
|
11
|
+
* Free functions holding no state, as `instanced-material.ts` is. What is per-batch — which material
|
|
12
|
+
* has already been wrapped — stays on the batch.
|
|
13
|
+
*/
|
|
14
|
+
/** The per-member index attribute troika's batch material declares, and the uniforms texture indexes by. */
|
|
15
|
+
export const MEMBER_INDEX_ATTRIBUTE = 'aTroikaTextBatchMemberIndex';
|
|
16
|
+
/**
|
|
17
|
+
* Wraps a base material's `onBeforeCompile` so a batch's per-instance uniforms are declared ahead of
|
|
18
|
+
* anything the material itself patches in.
|
|
19
|
+
*
|
|
20
|
+
* The prologue goes on the **base**, ahead of troika's rewrite: troika calls the base material's
|
|
21
|
+
* handler first and then rebuilds `main` around it, renaming the original `troikaOrigMain`.
|
|
22
|
+
* Injecting here puts the prologue and whatever the material patches in on the same side of that
|
|
23
|
+
* rename, so a material patch reading a per-instance uniform finds it in scope; injecting from the
|
|
24
|
+
* derived handler instead would strand the two in different functions.
|
|
25
|
+
*
|
|
26
|
+
* Call once per material — a second wrap would nest, and the caller holds the memo that prevents it.
|
|
27
|
+
* @param material - The base material troika derives from.
|
|
28
|
+
* @param uniforms - The batch's per-instance uniforms.
|
|
29
|
+
*/
|
|
30
|
+
export function patchBaseMaterial(material, uniforms) {
|
|
31
|
+
const onBeforeCompileBase = material.onBeforeCompile.bind(material);
|
|
32
|
+
material.onBeforeCompile = (shader, renderer) => {
|
|
33
|
+
uniforms.patchShader(shader, MEMBER_INDEX_ATTRIBUTE);
|
|
34
|
+
onBeforeCompileBase(shader, renderer);
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Finishes troika's derivation: reverts its two impositions on the base, and installs the one shader
|
|
39
|
+
* hook a batch needs.
|
|
40
|
+
* @param derived - What `createDerivedMaterial` returned.
|
|
41
|
+
* @param base - The material it was derived from.
|
|
42
|
+
* @param uniforms - The batch's per-instance uniforms, whose presence the program is cached by.
|
|
43
|
+
* @returns `derived`.
|
|
44
|
+
*/
|
|
45
|
+
export function configureDerivedMaterial(derived, base, uniforms) {
|
|
46
|
+
// troika forces `transparent = true` on every derived text material, which moves the batch into
|
|
47
|
+
// three's *transparent* render list — drawn after every opaque object regardless of `renderOrder`,
|
|
48
|
+
// so text would always cover the layers above it. Our base materials get alpha blending from
|
|
49
|
+
// `CustomBlending` instead (objects/materials/shared.ts), so this restores what the base asked for.
|
|
50
|
+
// Everything else troika leaves alone already delegates to the base through the prototype chain.
|
|
51
|
+
derived.transparent = base.transparent;
|
|
52
|
+
// With `transparent` reverted the batch renders from three's *opaque* list, which
|
|
53
|
+
// `painterSortStable` orders by `material.id` — and troika builds its outline material with
|
|
54
|
+
// `id + 0.1`, so the preliminary outline pass would sort *after* the fill and paint over it. (Its
|
|
55
|
+
// own forced transparency hid this: the transparent list sorts by depth, with no `material.id`
|
|
56
|
+
// term.) Building it here, with a lower id, restores the intended order; troika's getter finds it
|
|
57
|
+
// already cached and uses it.
|
|
58
|
+
derived._outlineMtl = makeOutlineMaterial(derived);
|
|
59
|
+
// Recompile when the uniform schema changes, since that changes the generated GLSL. The whole
|
|
60
|
+
// schema, not just whether a texture exists: two batches declaring different uniforms compile
|
|
61
|
+
// different source, and three compares nothing but this key.
|
|
62
|
+
const cacheKeyBase = derived.customProgramCacheKey.bind(derived);
|
|
63
|
+
derived.customProgramCacheKey = () => `efpText_${uniforms.shaderKey}|${cacheKeyBase()}`;
|
|
64
|
+
// Assignment, *not* the read-bind-wrap pattern the sibling meshes use: troika defines
|
|
65
|
+
// `onBeforeCompile` as an accessor pair whose getter returns troika's own handler and whose setter
|
|
66
|
+
// stashes ours (troika-three-utils DerivedMaterial), so calling through the getter from inside our
|
|
67
|
+
// handler would re-enter troika's, which calls ours again. Troika invokes this one last, after its
|
|
68
|
+
// shader upgrade, which is the only thing that has to be true.
|
|
69
|
+
derived.onBeforeCompile = (shader) => {
|
|
70
|
+
// Late by necessity: troika's own `vertexTransform` *reads* this offset, and the transform runs
|
|
71
|
+
// from the main it builds, ahead of the base's. Initializing any earlier would leave the
|
|
72
|
+
// transform reading the garbage this exists to prevent.
|
|
73
|
+
shader.vertexShader = openMain(shader.vertexShader, POSITION_OFFSET_INIT);
|
|
74
|
+
};
|
|
75
|
+
return derived;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Whether a material draws through the batched text shader — the only thing a batch's per-draw
|
|
79
|
+
* texture fill asks of the material it is handed.
|
|
80
|
+
*
|
|
81
|
+
* `isTroikaTextMaterial`, which upstream tests instead, does not establish that. Troika sets it on
|
|
82
|
+
* the text derivation, which the batch's material only sits on top of — so a plain unbatched `Text`
|
|
83
|
+
* material carries the flag too, and filling one would write matrices its shader never reads. The
|
|
84
|
+
* uniforms are the honest test: a batch derives over a link that declares them, an unbatched `Text`
|
|
85
|
+
* never does, and every derivation merges its base's uniform map forward, so they survive into any
|
|
86
|
+
* sibling built by calling `material.constructor`.
|
|
87
|
+
* @param material - The material three is about to draw with.
|
|
88
|
+
* @returns Whether it reads its transforms from a matrix texture.
|
|
89
|
+
*/
|
|
90
|
+
export function readsMemberMatrices(material) {
|
|
91
|
+
return !!material.uniforms?.uTroikaMatricesTexture;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Builds the outline pass's material: troika's own recipe, with the id inverted.
|
|
95
|
+
*
|
|
96
|
+
* Prototype-chained to the fill material so it shares every uniform and shader, exactly as troika
|
|
97
|
+
* does — the two differ only in `isTextOutlineMaterial`, which is what the per-draw fill branches on
|
|
98
|
+
* to write the outline row of the data texture.
|
|
99
|
+
* @param derived - The fill material to derive the outline pass from.
|
|
100
|
+
* @returns The outline material.
|
|
101
|
+
*/
|
|
102
|
+
function makeOutlineMaterial(derived) {
|
|
103
|
+
const outline = Object.create(derived, {
|
|
104
|
+
// Below the fill, not above it: three's opaque sort is ascending by `material.id`, and the
|
|
105
|
+
// outline is a *preliminary* pass. A fraction keeps it clear of every integer material id.
|
|
106
|
+
id: { value: derived.id - 0.5 },
|
|
107
|
+
});
|
|
108
|
+
outline.isTextOutlineMaterial = true;
|
|
109
|
+
outline.depthWrite = false;
|
|
110
|
+
outline.map = null;
|
|
111
|
+
// troika disposes its own outline material with the fill; matched so replacing it leaks nothing.
|
|
112
|
+
derived.addEventListener('dispose', function onDispose() {
|
|
113
|
+
derived.removeEventListener('dispose', onDispose);
|
|
114
|
+
outline.dispose();
|
|
115
|
+
});
|
|
116
|
+
return outline;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Seeds the position-offset varying. An uninitialized varying reads as garbage on some Samsung/Mali
|
|
120
|
+
* drivers, and troika's `vertexTransform` only assigns this one in the outline branch — so the main
|
|
121
|
+
* pass would never write it at all.
|
|
122
|
+
*/
|
|
123
|
+
const POSITION_OFFSET_INIT = /* glsl */ `uTroikaPositionOffset = vec2(0.0);`;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type GlyphsGeometry, type TextRenderInfo } from 'troika-three-text';
|
|
2
|
+
import { type TextImpl } from './Text';
|
|
3
|
+
/**
|
|
4
|
+
* Rewrites a batch's glyph attributes from its members' render info.
|
|
5
|
+
*
|
|
6
|
+
* Two orders meet here, which is the whole point of the pass: glyph runs are appended in *paint*
|
|
7
|
+
* order, since draw order is simply the order runs appear in the batched attributes, while the
|
|
8
|
+
* member index each run carries is the member’s stable *id*, which is the row it reads its matrix
|
|
9
|
+
* and uniforms from.
|
|
10
|
+
* @param geometry - The batch's glyph geometry, whose attributes are replaced.
|
|
11
|
+
* @param ordered - The ids to draw, in paint order.
|
|
12
|
+
* @param textAt - The member holding an id, or `undefined` when it holds none.
|
|
13
|
+
* @returns The render info the batch should adopt as its own — any member's will do, since a batch
|
|
14
|
+
* shares one SDF atlas — or `undefined` when no member has synced.
|
|
15
|
+
*/
|
|
16
|
+
export declare function packGlyphs(geometry: GlyphsGeometry, ordered: readonly number[], textAt: (memberId: number) => TextImpl | undefined): TextRenderInfo | undefined;
|
|
17
|
+
//# sourceMappingURL=glyph-packing.d.ts.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { DynamicDrawUsage } from 'three';
|
|
2
|
+
import { resizeTypedArray } from '../typed-array';
|
|
3
|
+
import { MEMBER_INDEX_ATTRIBUTE } from './batched-text-material';
|
|
4
|
+
/**
|
|
5
|
+
* Rewrites a batch's glyph attributes from its members' render info.
|
|
6
|
+
*
|
|
7
|
+
* Two orders meet here, which is the whole point of the pass: glyph runs are appended in *paint*
|
|
8
|
+
* order, since draw order is simply the order runs appear in the batched attributes, while the
|
|
9
|
+
* member index each run carries is the member’s stable *id*, which is the row it reads its matrix
|
|
10
|
+
* and uniforms from.
|
|
11
|
+
* @param geometry - The batch's glyph geometry, whose attributes are replaced.
|
|
12
|
+
* @param ordered - The ids to draw, in paint order.
|
|
13
|
+
* @param textAt - The member holding an id, or `undefined` when it holds none.
|
|
14
|
+
* @returns The render info the batch should adopt as its own — any member's will do, since a batch
|
|
15
|
+
* shares one SDF atlas — or `undefined` when no member has synced.
|
|
16
|
+
*/
|
|
17
|
+
export function packGlyphs(geometry, ordered, textAt) {
|
|
18
|
+
const attributes = geometry.attributes;
|
|
19
|
+
let memberIndexes = attributes[MEMBER_INDEX_ATTRIBUTE]?.array ?? new Uint16Array(0);
|
|
20
|
+
let glyphIndexes = attributes[GLYPH_INDEX_ATTRIBUTE]?.array ?? new Float32Array(0);
|
|
21
|
+
let glyphBounds = attributes[GLYPH_BOUNDS_ATTRIBUTE]?.array ?? new Float32Array(0);
|
|
22
|
+
// Initial pass to collect total glyph count and resize the arrays if needed.
|
|
23
|
+
let totalGlyphCount = 0;
|
|
24
|
+
// The inherited `Text` code reads `textRenderInfo` for whole-batch state (the SDF atlas, its glyph
|
|
25
|
+
// size and exponent); any member's will do.
|
|
26
|
+
let batchRenderInfo;
|
|
27
|
+
for (const memberId of ordered) {
|
|
28
|
+
const renderInfo = textAt(memberId)?.textRenderInfo;
|
|
29
|
+
if (!renderInfo)
|
|
30
|
+
continue;
|
|
31
|
+
totalGlyphCount += renderInfo.glyphAtlasIndices.length;
|
|
32
|
+
batchRenderInfo = renderInfo;
|
|
33
|
+
}
|
|
34
|
+
if (totalGlyphCount !== memberIndexes.length) {
|
|
35
|
+
memberIndexes = resizeTypedArray(memberIndexes, totalGlyphCount);
|
|
36
|
+
glyphIndexes = resizeTypedArray(glyphIndexes, totalGlyphCount);
|
|
37
|
+
glyphBounds = resizeTypedArray(glyphBounds, totalGlyphCount * 4);
|
|
38
|
+
}
|
|
39
|
+
// Populate batch arrays.
|
|
40
|
+
let glyphIndex = 0;
|
|
41
|
+
for (const memberId of ordered) {
|
|
42
|
+
const renderInfo = textAt(memberId)?.textRenderInfo;
|
|
43
|
+
if (!renderInfo)
|
|
44
|
+
continue;
|
|
45
|
+
const glyphCount = renderInfo.glyphAtlasIndices.length;
|
|
46
|
+
memberIndexes.fill(memberId, glyphIndex, glyphIndex + glyphCount);
|
|
47
|
+
glyphIndexes.set(renderInfo.glyphAtlasIndices, glyphIndex);
|
|
48
|
+
glyphBounds.set(renderInfo.glyphBounds, glyphIndex * 4);
|
|
49
|
+
glyphIndex += glyphCount;
|
|
50
|
+
}
|
|
51
|
+
// Update the geometry attributes.
|
|
52
|
+
geometry.updateAttributeData(MEMBER_INDEX_ATTRIBUTE, memberIndexes, 1);
|
|
53
|
+
geometry.getAttribute(MEMBER_INDEX_ATTRIBUTE).setUsage(DynamicDrawUsage);
|
|
54
|
+
geometry.updateAttributeData(GLYPH_INDEX_ATTRIBUTE, glyphIndexes, 1);
|
|
55
|
+
geometry.updateAttributeData(GLYPH_BOUNDS_ATTRIBUTE, glyphBounds, 4);
|
|
56
|
+
return batchRenderInfo;
|
|
57
|
+
}
|
|
58
|
+
/** One SDF atlas index per glyph. */
|
|
59
|
+
const GLYPH_INDEX_ATTRIBUTE = 'aTroikaGlyphIndex';
|
|
60
|
+
/** Four floats per glyph, bounding the quad it is drawn on. */
|
|
61
|
+
const GLYPH_BOUNDS_ATTRIBUTE = 'aTroikaGlyphBounds';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type TypedArray } from 'three';
|
|
2
|
+
/**
|
|
3
|
+
* Copies a typed array into a fresh one of a different length, keeping as much of the contents as
|
|
4
|
+
* fits: growing zero-fills the tail, shrinking drops it.
|
|
5
|
+
*
|
|
6
|
+
* The element type is taken from the source's own constructor rather than a parameter, so a caller
|
|
7
|
+
* resizing a `Uint16Array` of member indexes and one resizing a `Float32Array` of glyph bounds share
|
|
8
|
+
* the same call. Always a new array — an in-place `ArrayBuffer.resize` would not do here, because
|
|
9
|
+
* both callers hand the result to something that captured the old buffer (three's `BufferAttribute`,
|
|
10
|
+
* or a `DataTexture`'s image) and must see a distinct one to notice the change.
|
|
11
|
+
* @param source - The array to copy.
|
|
12
|
+
* @param length - The new array's length, in elements.
|
|
13
|
+
* @returns The resized copy.
|
|
14
|
+
*/
|
|
15
|
+
export declare function resizeTypedArray<T extends TypedArray>(source: T, length: number): T;
|
|
16
|
+
//# sourceMappingURL=typed-array.d.ts.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copies a typed array into a fresh one of a different length, keeping as much of the contents as
|
|
3
|
+
* fits: growing zero-fills the tail, shrinking drops it.
|
|
4
|
+
*
|
|
5
|
+
* The element type is taken from the source's own constructor rather than a parameter, so a caller
|
|
6
|
+
* resizing a `Uint16Array` of member indexes and one resizing a `Float32Array` of glyph bounds share
|
|
7
|
+
* the same call. Always a new array — an in-place `ArrayBuffer.resize` would not do here, because
|
|
8
|
+
* both callers hand the result to something that captured the old buffer (three's `BufferAttribute`,
|
|
9
|
+
* or a `DataTexture`'s image) and must see a distinct one to notice the change.
|
|
10
|
+
* @param source - The array to copy.
|
|
11
|
+
* @param length - The new array's length, in elements.
|
|
12
|
+
* @returns The resized copy.
|
|
13
|
+
*/
|
|
14
|
+
export function resizeTypedArray(source, length) {
|
|
15
|
+
const constructor = source.constructor;
|
|
16
|
+
const resized = new constructor(length);
|
|
17
|
+
// `subarray` clamps to the source's own length, so this is also the growing case.
|
|
18
|
+
resized.set(source.subarray(0, length));
|
|
19
|
+
return resized;
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
/** Props for {@link DebugPanelProvider}. */
|
|
3
|
+
export interface DebugPanelProviderProps {
|
|
4
|
+
/** The scene to publish from. */
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Owns the debug panel's entries and the write API that fills them.
|
|
9
|
+
*
|
|
10
|
+
* Deliberately *inside* the `<Canvas>`. R3F re-runs its configuration on every render of the canvas
|
|
11
|
+
* element, and that path replaces the `size` object in its store even when the dimensions have not
|
|
12
|
+
* changed — so state held above the canvas and written once per frame, as a camera publishing its
|
|
13
|
+
* position is, hands every `useThree(state => state.size)` subscriber a new object every frame. Held
|
|
14
|
+
* here, a push re-renders this component and the panel below it and nothing else: `children` arrives
|
|
15
|
+
* as an unchanged element, so React skips the scene entirely.
|
|
16
|
+
* @param props - The scene to publish from.
|
|
17
|
+
* @returns The scene, under the panel's write API, with the panel beside it.
|
|
18
|
+
*/
|
|
19
|
+
export declare function DebugPanelProvider(props: DebugPanelProviderProps): ReactNode;
|
|
20
|
+
//# sourceMappingURL=DebugPanelProvider.d.ts.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { DebugPanelContext } from '../debug-context';
|
|
4
|
+
import { Panel } from './Panel';
|
|
5
|
+
/**
|
|
6
|
+
* Owns the debug panel's entries and the write API that fills them.
|
|
7
|
+
*
|
|
8
|
+
* Deliberately *inside* the `<Canvas>`. R3F re-runs its configuration on every render of the canvas
|
|
9
|
+
* element, and that path replaces the `size` object in its store even when the dimensions have not
|
|
10
|
+
* changed — so state held above the canvas and written once per frame, as a camera publishing its
|
|
11
|
+
* position is, hands every `useThree(state => state.size)` subscriber a new object every frame. Held
|
|
12
|
+
* here, a push re-renders this component and the panel below it and nothing else: `children` arrives
|
|
13
|
+
* as an unchanged element, so React skips the scene entirely.
|
|
14
|
+
* @param props - The scene to publish from.
|
|
15
|
+
* @returns The scene, under the panel's write API, with the panel beside it.
|
|
16
|
+
*/
|
|
17
|
+
export function DebugPanelProvider(props) {
|
|
18
|
+
const { children } = props;
|
|
19
|
+
const [entries, setEntries] = useState({});
|
|
20
|
+
// Not memoized by hand: the React Compiler keeps this object stable, so DebugPanelContext
|
|
21
|
+
// consumers don't re-render when `entries` changes. Both updaters take the functional form, so
|
|
22
|
+
// nothing here closes over the entries themselves.
|
|
23
|
+
const panel = {
|
|
24
|
+
set(key, value) {
|
|
25
|
+
setEntries((prev) => {
|
|
26
|
+
// An existing key keeps the slot it already holds, because the spread writes it in place.
|
|
27
|
+
if (key in prev)
|
|
28
|
+
return Object.is(prev[key], value) ? prev : { ...prev, [key]: value };
|
|
29
|
+
// A key arrives for the first time, so rebuild the object in name order. Insertion order is
|
|
30
|
+
// what the readout prints, and a key that a publisher removes on unmount would otherwise
|
|
31
|
+
// come back at the bottom. `Object.fromEntries` needs Chrome 73, so build the object here.
|
|
32
|
+
const next = {};
|
|
33
|
+
for (const name of [...Object.keys(prev), key].sort()) {
|
|
34
|
+
next[name] = name === key ? value : prev[name];
|
|
35
|
+
}
|
|
36
|
+
return next;
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
remove(key) {
|
|
40
|
+
setEntries((prev) => {
|
|
41
|
+
if (!(key in prev))
|
|
42
|
+
return prev;
|
|
43
|
+
const next = { ...prev };
|
|
44
|
+
delete next[key];
|
|
45
|
+
return next;
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
return (_jsxs(DebugPanelContext, { value: panel, children: [children, _jsx(Panel, { corner: "right", label: "Debug", value: entries })] }));
|
|
50
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import 'webgl-memory';
|
|
2
|
+
import { type ReactNode } from 'react';
|
|
3
|
+
/** Props for {@link DebugTools}. */
|
|
4
|
+
export interface DebugToolsProps {
|
|
5
|
+
/** The scene to instrument. */
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The debug overlay, canvas-free — the heavy, lazily-loaded chunk. Wraps the scene in the debug
|
|
10
|
+
* GUI (shared through context so scene components can add folders) and the debug-panel write API
|
|
11
|
+
* (so scene components can publish values via `useDebugPanel`), plus the frame-stats, GPU-memory,
|
|
12
|
+
* and combined debug-values panels. All of it attaches to the document imperatively, outside the
|
|
13
|
+
* R3F reconciler, which is what lets the same component serve both mounts: inside the renderer's
|
|
14
|
+
* own `<Canvas>` and inside an external host's root alike.
|
|
15
|
+
*
|
|
16
|
+
* lil-gui, stats-gl and webgl-memory are reachable only through here, so this module is loaded
|
|
17
|
+
* exclusively via the debug entry's dynamic import and never lands in the main bundle.
|
|
18
|
+
* @param props - The scene to instrument.
|
|
19
|
+
* @returns The scene wrapped in the debug overlay.
|
|
20
|
+
*/
|
|
21
|
+
export declare function DebugTools(props: DebugToolsProps): ReactNode;
|
|
22
|
+
//# sourceMappingURL=DebugTools.d.ts.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// Patches `HTMLCanvasElement.getContext` during chunk eval, so any context created after this
|
|
3
|
+
// chunk loads is born instrumented. The internal mount guarantees the ordering by suspending its
|
|
4
|
+
// Canvas behind this chunk (see `CanvasWithDebug`); an external host has to arrange it itself by
|
|
5
|
+
// awaiting `prepareDebug()` before creating its map — its context predates this code otherwise,
|
|
6
|
+
// and the memory panel stays empty.
|
|
7
|
+
import 'webgl-memory';
|
|
8
|
+
import { DebugPanelProvider } from './DebugPanelProvider';
|
|
9
|
+
import { Gui } from './Gui';
|
|
10
|
+
import { MemoryInfo } from './MemoryInfo';
|
|
11
|
+
import { Stats } from './Stats';
|
|
12
|
+
/**
|
|
13
|
+
* The debug overlay, canvas-free — the heavy, lazily-loaded chunk. Wraps the scene in the debug
|
|
14
|
+
* GUI (shared through context so scene components can add folders) and the debug-panel write API
|
|
15
|
+
* (so scene components can publish values via `useDebugPanel`), plus the frame-stats, GPU-memory,
|
|
16
|
+
* and combined debug-values panels. All of it attaches to the document imperatively, outside the
|
|
17
|
+
* R3F reconciler, which is what lets the same component serve both mounts: inside the renderer's
|
|
18
|
+
* own `<Canvas>` and inside an external host's root alike.
|
|
19
|
+
*
|
|
20
|
+
* lil-gui, stats-gl and webgl-memory are reachable only through here, so this module is loaded
|
|
21
|
+
* exclusively via the debug entry's dynamic import and never lands in the main bundle.
|
|
22
|
+
* @param props - The scene to instrument.
|
|
23
|
+
* @returns The scene wrapped in the debug overlay.
|
|
24
|
+
*/
|
|
25
|
+
export function DebugTools(props) {
|
|
26
|
+
const { children } = props;
|
|
27
|
+
return (_jsx(Gui, { children: _jsxs(DebugPanelProvider, { children: [children, _jsx(Stats, {}), _jsx(MemoryInfo, {})] }) }));
|
|
28
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import './Gui.module.css';
|
|
2
|
+
import { type PropsWithChildren } from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* Initializes a GUI panel and shares it with the scene through context.
|
|
5
|
+
*
|
|
6
|
+
* The panel is created during render (with `autoPlace: false`, so the
|
|
7
|
+
* constructor has no DOM side effect), which makes it available to the scene
|
|
8
|
+
* synchronously on first render — no `null` phase to guard. The effect only
|
|
9
|
+
* attaches its `domElement` to the document and, on cleanup, calls `destroy()`,
|
|
10
|
+
* which detaches the element and clears child folders without invalidating the
|
|
11
|
+
* instance or its element. That keeps it StrictMode-safe: after the dev-only
|
|
12
|
+
* unmount/remount, the same element is re-attached and the scene's own effects
|
|
13
|
+
* re-add their folders. Rendered as an ancestor of the scene, so on unmount
|
|
14
|
+
* React tears the scene's folders down before this component destroys the panel.
|
|
15
|
+
* It also registers the panel's single change handler, which asks for a redraw. Every folder in the
|
|
16
|
+
* panel inherits it, so no controller carries one of its own.
|
|
17
|
+
* @param props - Component props; `children` is the scene subtree that consumes
|
|
18
|
+
* the GUI via `useGui`.
|
|
19
|
+
* @returns The children wrapped with the GUI provided through context.
|
|
20
|
+
*/
|
|
21
|
+
export declare function Gui(props: PropsWithChildren): import("react").JSX.Element;
|
|
22
|
+
//# sourceMappingURL=Gui.d.ts.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import './Gui.module.css'; // Global overrides of lil-gui's stock styling (checkbox row layout).
|
|
3
|
+
import { useThree } from '@react-three/fiber';
|
|
4
|
+
import { GUI } from 'lil-gui';
|
|
5
|
+
import { useEffect, useState } from 'react';
|
|
6
|
+
import { GuiContext } from '../debug-context';
|
|
7
|
+
/**
|
|
8
|
+
* Initializes a GUI panel and shares it with the scene through context.
|
|
9
|
+
*
|
|
10
|
+
* The panel is created during render (with `autoPlace: false`, so the
|
|
11
|
+
* constructor has no DOM side effect), which makes it available to the scene
|
|
12
|
+
* synchronously on first render — no `null` phase to guard. The effect only
|
|
13
|
+
* attaches its `domElement` to the document and, on cleanup, calls `destroy()`,
|
|
14
|
+
* which detaches the element and clears child folders without invalidating the
|
|
15
|
+
* instance or its element. That keeps it StrictMode-safe: after the dev-only
|
|
16
|
+
* unmount/remount, the same element is re-attached and the scene's own effects
|
|
17
|
+
* re-add their folders. Rendered as an ancestor of the scene, so on unmount
|
|
18
|
+
* React tears the scene's folders down before this component destroys the panel.
|
|
19
|
+
* It also registers the panel's single change handler, which asks for a redraw. Every folder in the
|
|
20
|
+
* panel inherits it, so no controller carries one of its own.
|
|
21
|
+
* @param props - Component props; `children` is the scene subtree that consumes
|
|
22
|
+
* the GUI via `useGui`.
|
|
23
|
+
* @returns The children wrapped with the GUI provided through context.
|
|
24
|
+
*/
|
|
25
|
+
export function Gui(props) {
|
|
26
|
+
const { children } = props;
|
|
27
|
+
const [gui] = useState(() => new GUI({ autoPlace: false, closeFolders: true }));
|
|
28
|
+
const invalidate = useThree((state) => state.invalidate);
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
gui.domElement.classList.add('lil-auto-place', 'autoPlace');
|
|
31
|
+
document.body.appendChild(gui.domElement);
|
|
32
|
+
gui.close();
|
|
33
|
+
// One handler for the whole panel: lil-gui walks a changed controller up its parent chain, so
|
|
34
|
+
// this fires for every controller in every folder and no folder registers its own. It calls
|
|
35
|
+
// whichever `invalidate` the current mount installed — in the external embed `ExternalCanvas`
|
|
36
|
+
// replaces it with the host's `requestFrame`, which is how a GUI change reaches a host that owns
|
|
37
|
+
// the frame.
|
|
38
|
+
gui.onChange(() => invalidate());
|
|
39
|
+
return () => gui.destroy();
|
|
40
|
+
}, [gui, invalidate]);
|
|
41
|
+
return _jsx(GuiContext, { value: gui, children: children });
|
|
42
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GPU-memory debug panel backed by webgl-memory, shown bottom-left.
|
|
3
|
+
*
|
|
4
|
+
* The component lives inside the R3F `<Canvas>` tree so it can reach the
|
|
5
|
+
* renderer via `useThree`. The figures come from the `GMAN_webgl_memory` context
|
|
6
|
+
* extension that webgl-memory installs (see the debug entry, which loads it
|
|
7
|
+
* before the canvas context is created); this component only reads it, never
|
|
8
|
+
* imports the library.
|
|
9
|
+
*
|
|
10
|
+
* webgl-memory exposes no change event, so it samples the extension after every
|
|
11
|
+
* rendered frame and hands the snapshot to {@link Panel}. Without the
|
|
12
|
+
* extension — a context created before webgl-memory could instrument it —
|
|
13
|
+
* nothing ever fills the panel, and it stays visibly empty on purpose: a wrong
|
|
14
|
+
* readout is diagnosable where a missing one is not, and `prepareDebug` is what
|
|
15
|
+
* keeps that state out of practice. `getMemoryInfo` is
|
|
16
|
+
* cheap, and the panel skips its DOM update whenever the serialized readout is
|
|
17
|
+
* unchanged, so sampling unconditionally (rather than only while the panel is
|
|
18
|
+
* open) costs nothing visible.
|
|
19
|
+
* @returns The bottom-left GPU-memory panel.
|
|
20
|
+
*/
|
|
21
|
+
export declare function MemoryInfo(): import("react").JSX.Element;
|
|
22
|
+
//# sourceMappingURL=MemoryInfo.d.ts.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { addAfterEffect, useThree } from '@react-three/fiber';
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { Panel } from './Panel';
|
|
5
|
+
/**
|
|
6
|
+
* GPU-memory debug panel backed by webgl-memory, shown bottom-left.
|
|
7
|
+
*
|
|
8
|
+
* The component lives inside the R3F `<Canvas>` tree so it can reach the
|
|
9
|
+
* renderer via `useThree`. The figures come from the `GMAN_webgl_memory` context
|
|
10
|
+
* extension that webgl-memory installs (see the debug entry, which loads it
|
|
11
|
+
* before the canvas context is created); this component only reads it, never
|
|
12
|
+
* imports the library.
|
|
13
|
+
*
|
|
14
|
+
* webgl-memory exposes no change event, so it samples the extension after every
|
|
15
|
+
* rendered frame and hands the snapshot to {@link Panel}. Without the
|
|
16
|
+
* extension — a context created before webgl-memory could instrument it —
|
|
17
|
+
* nothing ever fills the panel, and it stays visibly empty on purpose: a wrong
|
|
18
|
+
* readout is diagnosable where a missing one is not, and `prepareDebug` is what
|
|
19
|
+
* keeps that state out of practice. `getMemoryInfo` is
|
|
20
|
+
* cheap, and the panel skips its DOM update whenever the serialized readout is
|
|
21
|
+
* unchanged, so sampling unconditionally (rather than only while the panel is
|
|
22
|
+
* open) costs nothing visible.
|
|
23
|
+
* @returns The bottom-left GPU-memory panel.
|
|
24
|
+
*/
|
|
25
|
+
export function MemoryInfo() {
|
|
26
|
+
const gl = useThree((state) => state.gl);
|
|
27
|
+
const [memoryInfo, setMemoryInfo] = useState(null);
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
const ext = gl.getContext().getExtension('GMAN_webgl_memory');
|
|
30
|
+
if (!ext)
|
|
31
|
+
return;
|
|
32
|
+
return addAfterEffect(() => {
|
|
33
|
+
setMemoryInfo({ ...ext.getMemoryInfo(), drawCalls: gl.info.render.calls });
|
|
34
|
+
});
|
|
35
|
+
}, [gl]);
|
|
36
|
+
return _jsx(Panel, { corner: "left", label: "Memory Info", value: memoryInfo, replacer: localizeNumbers });
|
|
37
|
+
}
|
|
38
|
+
// `JSON.stringify` replacer: render numbers with locale thousands separators
|
|
39
|
+
// (e.g. `1,048,576`).
|
|
40
|
+
function localizeNumbers(_key, value) {
|
|
41
|
+
return typeof value === 'number' ? value.toLocaleString() : value;
|
|
42
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Props for {@link Panel}. */
|
|
2
|
+
export interface PanelProps {
|
|
3
|
+
/** Which bottom corner the panel is pinned to. */
|
|
4
|
+
corner: 'left' | 'right';
|
|
5
|
+
/** Shown on the toggle button after "Show"/"Hide". */
|
|
6
|
+
label: string;
|
|
7
|
+
/** Value serialized to JSON and shown while the panel is open. */
|
|
8
|
+
value: unknown;
|
|
9
|
+
/** Optional `JSON.stringify` replacer, for custom formatting of the value. */
|
|
10
|
+
replacer?: (key: string, value: unknown) => unknown;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* A collapsible debug readout pinned to a bottom corner, rendered as regular DOM
|
|
14
|
+
* outside the R3F reconciler. The reusable chrome extracted from the GPU-memory
|
|
15
|
+
* panel: a caller only supplies a corner, a label, and the `value` to display.
|
|
16
|
+
*
|
|
17
|
+
* A component inside the `<Canvas>` tree cannot return DOM — R3F's reconciler
|
|
18
|
+
* only understands Three.js objects, so a returned `<div>` (even via
|
|
19
|
+
* `createPortal`) never becomes real DOM. So, like drei's `<Html>`, it renders
|
|
20
|
+
* into a plain element created with `document.createElement` using react-dom's
|
|
21
|
+
* own root, and returns `null` to R3F.
|
|
22
|
+
*
|
|
23
|
+
* The interactive open state and serialization live in {@link PanelView}, inside
|
|
24
|
+
* that react-dom root, so toggling, style HMR, and value changes reconcile there
|
|
25
|
+
* without depending on the parent (or R3F) re-running an effect. This component
|
|
26
|
+
* only pushes the latest props into the root whenever they change.
|
|
27
|
+
* @param props - Corner, toggle label, and the value to display.
|
|
28
|
+
* @returns `null`.
|
|
29
|
+
*/
|
|
30
|
+
export declare function Panel(props: PanelProps): null;
|
|
31
|
+
//# sourceMappingURL=Panel.d.ts.map
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { createRoot } from 'react-dom/client';
|
|
4
|
+
import styles from './Panel.module.css';
|
|
5
|
+
/**
|
|
6
|
+
* The panel's DOM, rendered *inside* the detached react-dom root. The open state
|
|
7
|
+
* and the serialization live here — not on the parent {@link Panel} — so the
|
|
8
|
+
* toggle click, HMR of these styles, and value changes all reconcile as an
|
|
9
|
+
* ordinary react-dom subtree. If the parent diffed the markup by hand instead
|
|
10
|
+
* (keyed on a fixed dep list), anything outside that list — opening the panel,
|
|
11
|
+
* editing this CSS module — wouldn't show until the next value push happened to
|
|
12
|
+
* re-run it.
|
|
13
|
+
* @param props - Corner, toggle label, value to display, and optional replacer.
|
|
14
|
+
* @returns The panel element.
|
|
15
|
+
*/
|
|
16
|
+
function PanelView(props) {
|
|
17
|
+
const { corner, label, value } = props;
|
|
18
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
19
|
+
function replacer(key, value) {
|
|
20
|
+
if (Array.isArray(value))
|
|
21
|
+
return `[${value.join(', ')}]`;
|
|
22
|
+
if (props.replacer)
|
|
23
|
+
return props.replacer(key, value);
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
function serialize() {
|
|
27
|
+
// Serialize only while open — the readout is hidden otherwise.
|
|
28
|
+
if (!isOpen)
|
|
29
|
+
return '';
|
|
30
|
+
// A single-key wrapper (e.g. `{ camera: {…} }`) is unwrapped to its value, so
|
|
31
|
+
// the readout shows the payload directly rather than one level of nesting.
|
|
32
|
+
let valueToSerialize = value;
|
|
33
|
+
if (typeof value === 'object' && value) {
|
|
34
|
+
const keys = Object.keys(value);
|
|
35
|
+
valueToSerialize = keys.length === 1 ? value[keys[0]] : value;
|
|
36
|
+
}
|
|
37
|
+
// `JSON.stringify` returns `undefined`, not a string, for a value that JSON cannot hold: the
|
|
38
|
+
// value `undefined` itself, or a function. The unwrap above exposes such a value, because a
|
|
39
|
+
// lone entry becomes the whole value. A wrapper of two entries hides it, because
|
|
40
|
+
// `JSON.stringify` drops the key. An empty string keeps the readout blank, so a publisher can
|
|
41
|
+
// push any value and the panel still renders.
|
|
42
|
+
const json = JSON.stringify(valueToSerialize, replacer, 2) ?? '';
|
|
43
|
+
return json.replaceAll('"', '');
|
|
44
|
+
}
|
|
45
|
+
return (_jsxs("div", { className: `${styles.container} ${styles[corner]}`, children: [_jsxs("button", { className: styles.toggle, onClick: () => setIsOpen((open) => !open), children: [isOpen ? 'Hide' : 'Show', " ", label] }), isOpen && _jsx("div", { className: styles.info, children: serialize() })] }));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A collapsible debug readout pinned to a bottom corner, rendered as regular DOM
|
|
49
|
+
* outside the R3F reconciler. The reusable chrome extracted from the GPU-memory
|
|
50
|
+
* panel: a caller only supplies a corner, a label, and the `value` to display.
|
|
51
|
+
*
|
|
52
|
+
* A component inside the `<Canvas>` tree cannot return DOM — R3F's reconciler
|
|
53
|
+
* only understands Three.js objects, so a returned `<div>` (even via
|
|
54
|
+
* `createPortal`) never becomes real DOM. So, like drei's `<Html>`, it renders
|
|
55
|
+
* into a plain element created with `document.createElement` using react-dom's
|
|
56
|
+
* own root, and returns `null` to R3F.
|
|
57
|
+
*
|
|
58
|
+
* The interactive open state and serialization live in {@link PanelView}, inside
|
|
59
|
+
* that react-dom root, so toggling, style HMR, and value changes reconcile there
|
|
60
|
+
* without depending on the parent (or R3F) re-running an effect. This component
|
|
61
|
+
* only pushes the latest props into the root whenever they change.
|
|
62
|
+
* @param props - Corner, toggle label, and the value to display.
|
|
63
|
+
* @returns `null`.
|
|
64
|
+
*/
|
|
65
|
+
export function Panel(props) {
|
|
66
|
+
const { corner, label, value, replacer } = props;
|
|
67
|
+
// Created imperatively, so R3F's reconciler never sees these DOM elements.
|
|
68
|
+
const [container] = useState(() => document.createElement('div'));
|
|
69
|
+
const rootRef = useRef(null);
|
|
70
|
+
const pendingUnmountRef = useRef(false);
|
|
71
|
+
// Mount the container and spin up a react-dom root once.
|
|
72
|
+
//
|
|
73
|
+
// Unmounting a nested root synchronously during the parent's commit warns
|
|
74
|
+
// "unmount while rendering", so the unmount is deferred to a microtask
|
|
75
|
+
// (Promise, not queueMicrotask — old WebViews lack it). That deferral has to
|
|
76
|
+
// be cancellable: under StrictMode the effect runs mount → unmount → mount,
|
|
77
|
+
// and the queued unmount can fire *after* the remount has already reused this
|
|
78
|
+
// root (react-dom flushes the microtask late, R3F's reconciler earlier). Left
|
|
79
|
+
// unguarded, the stale microtask would tear down the live panel. Every
|
|
80
|
+
// (re)mount clears `pendingUnmountRef`, so a superseded unmount skips itself;
|
|
81
|
+
// only a real unmount — with no remount to clear the flag — destroys the root.
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
pendingUnmountRef.current = false;
|
|
84
|
+
document.body.appendChild(container);
|
|
85
|
+
const root = rootRef.current ?? createRoot(container);
|
|
86
|
+
rootRef.current = root;
|
|
87
|
+
return () => {
|
|
88
|
+
container.remove();
|
|
89
|
+
pendingUnmountRef.current = true;
|
|
90
|
+
void Promise.resolve().then(() => {
|
|
91
|
+
if (!pendingUnmountRef.current)
|
|
92
|
+
return;
|
|
93
|
+
rootRef.current?.unmount();
|
|
94
|
+
rootRef.current = null;
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
}, [container]);
|
|
98
|
+
// Push the latest props into the detached root. PanelView reconciles as its own
|
|
99
|
+
// react-dom subtree, keeping its open state across these updates.
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
rootRef.current?.render(_jsx(PanelView, { corner: corner, label: label, value: value, replacer: replacer }));
|
|
102
|
+
}, [corner, label, value, replacer]);
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Performance-stats overlay (frame rate, GPU timing, refresh rate) attached to
|
|
3
|
+
* the document body and refreshed after every rendered frame.
|
|
4
|
+
*
|
|
5
|
+
* The overlay is mounted imperatively, so the component renders nothing.
|
|
6
|
+
* @returns `null`.
|
|
7
|
+
*/
|
|
8
|
+
export declare function Stats(): null;
|
|
9
|
+
//# sourceMappingURL=Stats.d.ts.map
|