@expofp/renderer 3.2.5 → 3.27.1
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 +111 -0
- package/dist/Renderer.d.ts +88 -0
- package/dist/Renderer.js +53 -0
- package/dist/controls/CameraControls.d.ts +102 -0
- package/dist/controls/CameraControls.js +239 -0
- package/dist/controls/camera-api.d.ts +206 -0
- package/dist/controls/camera-api.js +181 -0
- package/dist/controls/camera-move.d.ts +13 -0
- package/dist/controls/camera-move.js +18 -0
- package/dist/controls/camera-state.d.ts +40 -0
- package/dist/controls/camera-state.js +65 -0
- package/dist/controls/gestures.d.ts +70 -0
- package/dist/controls/gestures.js +84 -0
- package/dist/controls/handlers/Inertia.d.ts +111 -0
- package/dist/controls/handlers/Inertia.js +215 -0
- package/dist/controls/handlers/Pan.d.ts +23 -0
- package/dist/controls/handlers/Pan.js +32 -0
- package/dist/controls/handlers/Pitch.d.ts +94 -0
- package/dist/controls/handlers/Pitch.js +191 -0
- package/dist/controls/handlers/Roll.d.ts +103 -0
- package/dist/controls/handlers/Roll.js +182 -0
- package/dist/controls/handlers/Zoom.d.ts +15 -0
- package/dist/controls/handlers/Zoom.js +26 -0
- package/dist/controls/index.d.ts +56 -0
- package/dist/controls/index.js +58 -0
- package/dist/controls/zoom-to.d.ts +89 -0
- package/dist/controls/zoom-to.js +91 -0
- package/dist/core/InstanceUniforms.d.ts +93 -0
- package/dist/core/InstanceUniforms.js +266 -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 +149 -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 +224 -0
- package/dist/core/lines/BatchedLines.d.ts +135 -0
- package/dist/core/lines/BatchedLines.js +213 -0
- package/dist/core/lines/FrustumCulling.d.ts +43 -0
- package/dist/core/lines/FrustumCulling.js +85 -0
- package/dist/core/lines/Raycasting.d.ts +44 -0
- package/dist/core/lines/Raycasting.js +91 -0
- package/dist/core/lines/lines-material.d.ts +47 -0
- package/dist/core/lines/lines-material.js +115 -0
- package/dist/core/shader-source.d.ts +50 -0
- package/dist/core/shader-source.js +84 -0
- package/dist/core/text/BatchedText.d.ts +227 -0
- package/dist/core/text/BatchedText.js +467 -0
- package/dist/core/text/MemberTexture.d.ts +90 -0
- package/dist/core/text/MemberTexture.js +194 -0
- package/dist/core/text/Text.d.ts +73 -0
- package/dist/core/text/Text.js +215 -0
- package/dist/core/text/batched-text-material.d.ts +56 -0
- package/dist/core/text/batched-text-material.js +121 -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 +38 -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 +20 -0
- package/dist/debug/components/Gui.js +32 -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 +98 -0
- package/dist/debug/components/Stats.d.ts +9 -0
- package/dist/debug/components/Stats.js +24 -0
- package/dist/debug/debug-context.d.ts +41 -0
- package/dist/debug/debug-context.js +59 -0
- package/dist/debug/index.d.ts +51 -0
- package/dist/debug/index.js +64 -0
- package/dist/events/LayoutDriver.d.ts +31 -0
- package/dist/events/LayoutDriver.js +88 -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 +104 -0
- package/dist/events/layout-targets.js +245 -0
- package/dist/events/pointer.d.ts +118 -0
- package/dist/events/pointer.js +168 -0
- package/dist/external/ExternalCanvas.d.ts +56 -0
- package/dist/external/ExternalCanvas.js +178 -0
- package/dist/external/external-camera.d.ts +17 -0
- package/dist/external/external-camera.js +82 -0
- package/dist/external/external-events.d.ts +14 -0
- package/dist/external/external-events.js +33 -0
- package/dist/index.d.ts +11 -783
- package/dist/index.js +12 -8031
- package/dist/objects/Background.d.ts +31 -0
- package/dist/objects/Background.js +37 -0
- package/dist/objects/Lighting.d.ts +20 -0
- package/dist/objects/Lighting.js +85 -0
- package/dist/objects/containers/SlotContainer.d.ts +266 -0
- package/dist/objects/containers/SlotContainer.js +251 -0
- package/dist/objects/containers/hooks.d.ts +46 -0
- package/dist/objects/containers/hooks.js +118 -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 +23 -0
- package/dist/objects/containers/props.js +1 -0
- package/dist/objects/images/ImageInstance.d.ts +29 -0
- package/dist/objects/images/ImageInstance.js +83 -0
- package/dist/objects/images/Images.d.ts +37 -0
- package/dist/objects/images/Images.js +204 -0
- package/dist/objects/images/atlas-packer.d.ts +128 -0
- package/dist/objects/images/atlas-packer.js +315 -0
- package/dist/objects/images/image-container.d.ts +51 -0
- package/dist/objects/images/image-container.js +70 -0
- package/dist/objects/images/index.d.ts +2 -0
- package/dist/objects/images/index.js +1 -0
- package/dist/objects/images/texture-budget.d.ts +120 -0
- package/dist/objects/images/texture-budget.js +215 -0
- package/dist/objects/layer/Layer.d.ts +27 -0
- package/dist/objects/layer/Layer.js +114 -0
- package/dist/objects/layer/index.d.ts +2 -0
- package/dist/objects/layer/index.js +1 -0
- package/dist/objects/layer/mount-scheduler.d.ts +102 -0
- package/dist/objects/layer/mount-scheduler.js +145 -0
- package/dist/objects/layer/visibility-gui.d.ts +19 -0
- package/dist/objects/layer/visibility-gui.js +93 -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 +29 -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 +85 -0
- package/dist/objects/materials/ColorMaterial.js +89 -0
- package/dist/objects/materials/LineMaterial.d.ts +44 -0
- package/dist/objects/materials/LineMaterial.js +72 -0
- package/dist/objects/materials/ModelMaterial.d.ts +41 -0
- package/dist/objects/materials/ModelMaterial.js +84 -0
- package/dist/objects/materials/TextureMaterial.d.ts +30 -0
- package/dist/objects/materials/TextureMaterial.js +29 -0
- package/dist/objects/materials/dim.d.ts +123 -0
- package/dist/objects/materials/dim.js +213 -0
- package/dist/objects/materials/shared.d.ts +6 -0
- package/dist/objects/materials/shared.js +51 -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 +177 -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 +139 -0
- package/dist/objects/models/model-container.js +227 -0
- package/dist/objects/shapes/ShapeInstance.d.ts +26 -0
- package/dist/objects/shapes/ShapeInstance.js +40 -0
- package/dist/objects/shapes/Shapes.d.ts +21 -0
- package/dist/objects/shapes/Shapes.js +120 -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 +165 -0
- package/dist/objects/shapes/shape-container.js +272 -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 +41 -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 +105 -0
- package/dist/objects/text/text-container.js +206 -0
- package/dist/objects/text/text-layout.d.ts +83 -0
- package/dist/objects/text/text-layout.js +154 -0
- package/dist/space/Camera.d.ts +103 -0
- package/dist/space/Camera.js +321 -0
- package/dist/space/Scene.d.ts +44 -0
- package/dist/space/Scene.js +112 -0
- package/dist/space/Space.d.ts +288 -0
- package/dist/space/Space.js +329 -0
- package/dist/space/VisibleRect.d.ts +72 -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 +55 -0
- package/dist/space/clipping.js +108 -0
- package/dist/space/layout-measure.d.ts +65 -0
- package/dist/space/layout-measure.js +173 -0
- package/dist/space/viewport.d.ts +112 -0
- package/dist/space/viewport.js +67 -0
- package/dist/types/defs.d.ts +169 -0
- package/dist/types/defs.js +1 -0
- package/dist/types/events.d.ts +183 -0
- package/dist/types/events.js +1 -0
- package/dist/types/guards.d.ts +14 -0
- package/dist/types/guards.js +42 -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 +72 -0
- package/dist/update/store.d.ts +41 -0
- package/dist/update/store.js +98 -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/logging.d.ts +26 -0
- package/dist/utils/logging.js +47 -0
- package/dist/utils/math.d.ts +24 -0
- package/dist/utils/math.js +27 -0
- package/package.json +67 -19
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import { MaxRectsPacker } from 'maxrects-packer';
|
|
2
|
+
import { createLogger } from '../../utils/logging';
|
|
3
|
+
const logger = createLogger('atlas-packer');
|
|
4
|
+
// Need to reduce blocking time on the main thread
|
|
5
|
+
// Naive approach of offloading to web worker is not feasible:
|
|
6
|
+
// HTMLImageElement can't be easily transferred, and createImageBitmap() on sources takes more time
|
|
7
|
+
// than actual atlas packing. Needs to be investigated further
|
|
8
|
+
/**
|
|
9
|
+
* Gap kept between neighboring images in an atlas, so linear filtering at one image's edge can
|
|
10
|
+
* never reach into the next one's pixels. The packer applies it *between* rects only — atlas edges
|
|
11
|
+
* stay flush against the pixels — which is also what lets a tile exactly `maxDimension` across
|
|
12
|
+
* still fit a bin.
|
|
13
|
+
*/
|
|
14
|
+
const PADDING = 1;
|
|
15
|
+
/**
|
|
16
|
+
* How many source pixels per square unit of `bounds` a def may carry before it is called
|
|
17
|
+
* over-sampled. Past this the extra resolution cannot reach the screen at any sane zoom, so the
|
|
18
|
+
* atlas space and texture memory are simply wasted — usually a full-size logo standing in for a
|
|
19
|
+
* marker icon.
|
|
20
|
+
*/
|
|
21
|
+
const MAX_AREA_RATIO = 1000;
|
|
22
|
+
/**
|
|
23
|
+
* Whether a source's pixels may be downscaled to fit a memory budget. `true` for decoded images and
|
|
24
|
+
* bitmaps, `false` for canvases — a canvas's pixels are the caller's, counted at full size but never
|
|
25
|
+
* compressed (see the scene's `memoryLimit`).
|
|
26
|
+
*
|
|
27
|
+
* Detected by the absence of `getContext`: `HTMLCanvasElement` and `OffscreenCanvas` both have it,
|
|
28
|
+
* `HTMLImageElement` and `ImageBitmap` do not. A property test rather than `instanceof`, because the
|
|
29
|
+
* SDK runs in WebViews where `OffscreenCanvas`/`ImageBitmap` may be missing as globals and an
|
|
30
|
+
* `instanceof` against an absent one throws.
|
|
31
|
+
* @param source - The source to classify.
|
|
32
|
+
* @returns Whether the source can be downscaled.
|
|
33
|
+
*/
|
|
34
|
+
export function isResizableSource(source) {
|
|
35
|
+
return !('getContext' in source);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Packs image defs into atlas canvases and reports where every def's pixels landed.
|
|
39
|
+
*
|
|
40
|
+
* Sources are split by kind first — resizable (image/bitmap) from fixed (canvas) — and each kind is
|
|
41
|
+
* packed on its own, so no atlas ever mixes the two and a memory budget can scale a resizable atlas
|
|
42
|
+
* whole without touching a canvas's pixels. Within a kind, sources are deduped — two defs pointing
|
|
43
|
+
* at the same URL, or holding the same canvas object, are drawn once and share one region — and then
|
|
44
|
+
* offered to the packer whole. Anything too big for an atlas comes back parked in a natural-size bin
|
|
45
|
+
* of its own, which the packer flags; those are the only sources cut into tiles, and the tiles go
|
|
46
|
+
* back through the same packer. Letting the packer decide is what keeps the guarantee that no atlas
|
|
47
|
+
* exceeds `maxDimension` without measuring every image up front.
|
|
48
|
+
*
|
|
49
|
+
* Packing reads only the sources' pixel dimensions, never `bounds`, so a def that later moves or
|
|
50
|
+
* resizes keeps its region: the quad's matrix scales it, and nothing needs repacking. Membership
|
|
51
|
+
* changes do, though — this is a batch call with no incremental path.
|
|
52
|
+
* @param defs - The images to pack.
|
|
53
|
+
* @param maxDimension - Largest atlas edge in pixels, floored to a positive integer.
|
|
54
|
+
* @param options - Overrides for how atlas canvases are made.
|
|
55
|
+
* @returns One entry per atlas, in packing order — resizable atlases first, then fixed.
|
|
56
|
+
*/
|
|
57
|
+
export function packAtlases(defs, maxDimension, options = {}) {
|
|
58
|
+
const startedAt = performance.now();
|
|
59
|
+
const { createCanvas = createDomCanvas } = options;
|
|
60
|
+
const limit = Math.max(1, Math.floor(maxDimension));
|
|
61
|
+
const resizable = [];
|
|
62
|
+
const fixed = [];
|
|
63
|
+
for (const def of defs)
|
|
64
|
+
(isResizableSource(def.source) ? resizable : fixed).push(def);
|
|
65
|
+
// One packer for both groups, `reset()` between them — the config (size, padding, options) is the
|
|
66
|
+
// same for each, so reusing it spares an allocation per pack. It cannot be shared across pack calls
|
|
67
|
+
// though: `limit` is baked in at construction and varies call to call.
|
|
68
|
+
// `pot: false` because WebGL 2 samples non-power-of-two textures without restriction, so rounding
|
|
69
|
+
// each atlas up would only waste memory. `smart` stays on (the default), shrinking every bin to
|
|
70
|
+
// its content — hence reading the size back off the bin rather than assuming `limit`.
|
|
71
|
+
const packer = new MaxRectsPacker(limit, limit, PADDING, { pot: false });
|
|
72
|
+
const packedResizable = packGroup(packer, resizable, limit, createCanvas, true);
|
|
73
|
+
const packedFixed = packGroup(packer, fixed, limit, createCanvas, false);
|
|
74
|
+
const atlases = [...packedResizable.atlases, ...packedFixed.atlases];
|
|
75
|
+
const elapsed = (performance.now() - startedAt).toFixed(1);
|
|
76
|
+
logger.debug('packed defs: %d (%d resizable), sources: %d, tiles: %d, atlases: %d (%sms)', packedResizable.defs + packedFixed.defs, packedResizable.defs, packedResizable.sources + packedFixed.sources, packedResizable.tiles + packedFixed.tiles, atlases.length, elapsed);
|
|
77
|
+
return atlases;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Packs one kind of source — all resizable, or all fixed — into atlases, tagging each with the
|
|
81
|
+
* group's kind. Same pipeline for both: dedup, offer whole, cut whatever the packer parks oversized.
|
|
82
|
+
* @param packer - The shared packer, reset to empty before this group is added.
|
|
83
|
+
* @param defs - The defs of this kind; may be empty, which packs to nothing.
|
|
84
|
+
* @param limit - Largest atlas edge in pixels.
|
|
85
|
+
* @param createCanvas - Makes each atlas canvas.
|
|
86
|
+
* @param resizable - The kind, stamped onto every atlas produced.
|
|
87
|
+
* @returns The atlases and their counts.
|
|
88
|
+
*/
|
|
89
|
+
function packGroup(packer, defs, limit, createCanvas, resizable) {
|
|
90
|
+
const sources = collectSources(defs);
|
|
91
|
+
const { rects, skipped } = sourceRects(sources.values());
|
|
92
|
+
// Clear whatever the previous group left in the packer, keeping its configuration.
|
|
93
|
+
packer.reset();
|
|
94
|
+
// `addArray` sorts by longest edge before placing, which packs far tighter than insertion order.
|
|
95
|
+
packer.addArray(rects);
|
|
96
|
+
// The packer tags whatever it had to park in an oversized bin, so it — not a measurement of our
|
|
97
|
+
// own — decides what needs cutting. The tiles are ordinary rects, and pack into the bins the
|
|
98
|
+
// first pass already opened.
|
|
99
|
+
const oversized = rects.filter((rect) => rect.oversized);
|
|
100
|
+
if (oversized.length > 0)
|
|
101
|
+
packer.addArray(oversized.flatMap((rect) => cutTiles(rect, limit)));
|
|
102
|
+
// Every oversized bin is now represented by its tiles, so it is dropped rather than returned:
|
|
103
|
+
// handing back a canvas larger than the caller's limit is the thing this all exists to prevent.
|
|
104
|
+
const bins = packer.bins.filter((bin) => !bin.rects.some((rect) => rect.oversized));
|
|
105
|
+
const atlases = bins.map((bin) => ({ ...renderAtlas(bin, createCanvas), resizable }));
|
|
106
|
+
return {
|
|
107
|
+
atlases,
|
|
108
|
+
defs: defs.length - skipped.defs,
|
|
109
|
+
sources: sources.size - skipped.sources,
|
|
110
|
+
tiles: bins.reduce((count, bin) => count + bin.rects.length, 0),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The pixel size of an image source.
|
|
115
|
+
*
|
|
116
|
+
* Duck-typed rather than `instanceof`: the SDK runs in WebViews where `OffscreenCanvas` and
|
|
117
|
+
* `ImageBitmap` may be missing as globals, and an `instanceof` against an absent global throws.
|
|
118
|
+
* @param source - The source to measure.
|
|
119
|
+
* @returns Its intrinsic size, `0 × 0` for an image element that has not decoded yet.
|
|
120
|
+
*/
|
|
121
|
+
export function imageSourceSize(source) {
|
|
122
|
+
// `naturalWidth` is the decoded size; an `<img>`'s `width` is its layout size, which is 0 for an
|
|
123
|
+
// element that was never in the document — the usual case for images loaded purely as textures.
|
|
124
|
+
const image = source;
|
|
125
|
+
return typeof image.naturalWidth === 'number'
|
|
126
|
+
? { width: image.naturalWidth, height: image.naturalHeight }
|
|
127
|
+
: { width: source.width, height: source.height };
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Groups defs by the source they draw, so identical images are packed once.
|
|
131
|
+
*
|
|
132
|
+
* Identity is the URL for an image element and the object itself for a canvas or bitmap — a `Map`
|
|
133
|
+
* never confuses the two, since a string key cannot collide with an object one. An `<img>` with no
|
|
134
|
+
* `src` keys on itself instead, so a set of blank images is not collapsed into a single region.
|
|
135
|
+
* @param defs - The defs to group.
|
|
136
|
+
* @returns One entry per unique source, in first-seen order.
|
|
137
|
+
*/
|
|
138
|
+
function collectSources(defs) {
|
|
139
|
+
const sources = new Map();
|
|
140
|
+
for (const def of defs) {
|
|
141
|
+
const src = def.source.src;
|
|
142
|
+
const key = typeof src === 'string' && src !== '' ? src : def.source;
|
|
143
|
+
let entry = sources.get(key);
|
|
144
|
+
if (!entry) {
|
|
145
|
+
entry = {
|
|
146
|
+
source: def.source,
|
|
147
|
+
...imageSourceSize(def.source),
|
|
148
|
+
defs: [],
|
|
149
|
+
bounds: { width: 0, height: 0 },
|
|
150
|
+
};
|
|
151
|
+
sources.set(key, entry);
|
|
152
|
+
}
|
|
153
|
+
entry.defs.push(def);
|
|
154
|
+
// Copied rather than referenced: `Rect` exposes an in-place `set`, so holding onto its size
|
|
155
|
+
// vector would let a later bounds change rewrite what was measured here.
|
|
156
|
+
const { width, height } = def.bounds.size;
|
|
157
|
+
if (width * height > entry.bounds.width * entry.bounds.height) {
|
|
158
|
+
entry.bounds = { width, height };
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return sources;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Turns each unique source into the one rect that offers it to the packer whole, dropping the ones
|
|
165
|
+
* with no pixels to draw and flagging the ones carrying more resolution than they can show.
|
|
166
|
+
* @param sources - The unique sources to offer.
|
|
167
|
+
* @returns The rects to pack, and what was dropped for having no pixels.
|
|
168
|
+
*/
|
|
169
|
+
function sourceRects(sources) {
|
|
170
|
+
const rects = [];
|
|
171
|
+
const skipped = { defs: 0, sources: 0 };
|
|
172
|
+
for (const entry of sources) {
|
|
173
|
+
// A `drawImage` with a zero-width or zero-height source region throws in some browsers, so an
|
|
174
|
+
// image that has not decoded (or a canvas sized to nothing) is dropped rather than drawn.
|
|
175
|
+
if (entry.width <= 0 || entry.height <= 0) {
|
|
176
|
+
skipped.sources++;
|
|
177
|
+
skipped.defs += entry.defs.length;
|
|
178
|
+
logger.warn('skipping source with no pixels (%dx%d) %O', entry.width, entry.height, entry.source);
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
warnIfOverSampled(entry);
|
|
182
|
+
rects.push({
|
|
183
|
+
width: entry.width,
|
|
184
|
+
height: entry.height,
|
|
185
|
+
x: 0,
|
|
186
|
+
y: 0,
|
|
187
|
+
hash: `${rects.length}`,
|
|
188
|
+
data: { entry },
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
return { rects, skipped };
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Reports a source carrying far more pixels than the bounds it is drawn into can ever show. Once
|
|
195
|
+
* per source rather than once per def, since deduped defs all share the one texture region.
|
|
196
|
+
* @param entry - The source to check.
|
|
197
|
+
*/
|
|
198
|
+
function warnIfOverSampled(entry) {
|
|
199
|
+
const boundsArea = entry.bounds.width * entry.bounds.height;
|
|
200
|
+
// Degenerate bounds would divide out to Infinity and warn about every such def, which says
|
|
201
|
+
// nothing about resolution — that is a bounds bug, and not this function's to report.
|
|
202
|
+
if (boundsArea <= 0)
|
|
203
|
+
return;
|
|
204
|
+
const ratio = (entry.width * entry.height) / boundsArea;
|
|
205
|
+
if (ratio <= MAX_AREA_RATIO)
|
|
206
|
+
return;
|
|
207
|
+
logger.warn('over-sampled source: %dx%d px drawn into %sx%s units — %d px per unit², limit %d', entry.width, entry.height,
|
|
208
|
+
// At most two decimals, trailing zeros dropped: 4 → 4, 4.1 → 4.1, 4.2999 → 4.3.
|
|
209
|
+
Number(entry.bounds.width.toFixed(2)), Number(entry.bounds.height.toFixed(2)), Math.round(ratio), MAX_AREA_RATIO);
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Cuts a source the packer could not fit into a grid of tiles no larger than one atlas. Tiles are
|
|
213
|
+
* exact and non-overlapping — each ends up alone (or beside its own siblings) in an atlas, so
|
|
214
|
+
* nothing can bleed across the cut and no overlap column is needed to hide it.
|
|
215
|
+
* @param rect - The oversized rect, holding the source to cut.
|
|
216
|
+
* @param limit - Largest tile edge in pixels.
|
|
217
|
+
* @returns The tiles to pack in its place.
|
|
218
|
+
*/
|
|
219
|
+
function cutTiles(rect, limit) {
|
|
220
|
+
const { entry } = rect.data;
|
|
221
|
+
const columns = Math.ceil(entry.width / limit);
|
|
222
|
+
const rows = Math.ceil(entry.height / limit);
|
|
223
|
+
logger.warn('source %dx%d exceeds the %dpx atlas limit, splitting into %d tiles', entry.width, entry.height, limit, columns * rows);
|
|
224
|
+
const tiles = [];
|
|
225
|
+
for (let row = 0; row < rows; row++) {
|
|
226
|
+
const y = row * limit;
|
|
227
|
+
const height = Math.min(limit, entry.height - y);
|
|
228
|
+
for (let column = 0; column < columns; column++) {
|
|
229
|
+
const x = column * limit;
|
|
230
|
+
const width = Math.min(limit, entry.width - x);
|
|
231
|
+
tiles.push({
|
|
232
|
+
width,
|
|
233
|
+
height,
|
|
234
|
+
x: 0,
|
|
235
|
+
y: 0,
|
|
236
|
+
hash: `${rect.hash}:${column},${row}`,
|
|
237
|
+
data: { entry, source: { x, y, width, height } },
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return tiles;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Draws one packed bin onto a canvas and maps its rects back onto the defs that wanted them. The
|
|
245
|
+
* `resizable` flag is not this function's to set — {@link packGroup} stamps it from the group's kind.
|
|
246
|
+
* @param bin - The packed bin, already sized to its content.
|
|
247
|
+
* @param createCanvas - Makes the canvas to draw on.
|
|
248
|
+
* @returns The atlas, less its `resizable` flag.
|
|
249
|
+
*/
|
|
250
|
+
function renderAtlas(bin, createCanvas) {
|
|
251
|
+
const canvas = createCanvas(bin.width, bin.height);
|
|
252
|
+
const context = contextOf(canvas);
|
|
253
|
+
// Nothing downstream can recover from this: every atlas would upload blank, so the images would
|
|
254
|
+
// silently draw as nothing at all. Better to fail where the cause is still visible.
|
|
255
|
+
if (!context)
|
|
256
|
+
throw new Error(`atlas-packer: no 2d context for a ${bin.width}x${bin.height} atlas`);
|
|
257
|
+
const images = [];
|
|
258
|
+
for (const rect of bin.rects) {
|
|
259
|
+
const { entry, source } = rect.data;
|
|
260
|
+
// The copied region is the tile when there is one and the whole image otherwise — both read
|
|
261
|
+
// from the source's own axes. It happens to equal the packed rect's size today (tiles are
|
|
262
|
+
// placed 1:1, unrotated), but taking it from the destination would only be right by accident.
|
|
263
|
+
// Kept apart from `source` itself, which stays undefined for an unsplit image so the placement
|
|
264
|
+
// below does not report a region the caller has no use for.
|
|
265
|
+
const region = source ?? { x: 0, y: 0, width: entry.width, height: entry.height };
|
|
266
|
+
context.drawImage(entry.source, region.x, region.y, region.width, region.height, rect.x, rect.y, rect.width, rect.height);
|
|
267
|
+
// The rect is in canvas pixels and uv keeps the canvas's axes, so this is a plain normalization
|
|
268
|
+
// with no vertical inversion (see {@link UvRect}). Shared by every def on this rect — dedup is
|
|
269
|
+
// the whole point.
|
|
270
|
+
const uv = {
|
|
271
|
+
u0: rect.x / bin.width,
|
|
272
|
+
v0: rect.y / bin.height,
|
|
273
|
+
u1: (rect.x + rect.width) / bin.width,
|
|
274
|
+
v1: (rect.y + rect.height) / bin.height,
|
|
275
|
+
};
|
|
276
|
+
for (const def of entry.defs)
|
|
277
|
+
images.push(source ? { def, uv, source } : { def, uv });
|
|
278
|
+
}
|
|
279
|
+
return { canvas, images };
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Makes an atlas canvas in the document, the default when the caller supplies no factory.
|
|
283
|
+
* @param width - The atlas width in pixels.
|
|
284
|
+
* @param height - The atlas height in pixels.
|
|
285
|
+
* @returns The canvas.
|
|
286
|
+
*/
|
|
287
|
+
function createDomCanvas(width, height) {
|
|
288
|
+
const canvas = document.createElement('canvas');
|
|
289
|
+
canvas.width = width;
|
|
290
|
+
canvas.height = height;
|
|
291
|
+
return canvas;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Resolves an atlas canvas's 2D context, asking one canvas kind at a time.
|
|
295
|
+
*
|
|
296
|
+
* Deliberately not `canvas.getContext('2d')` on the union. The two kinds' overload lists disagree
|
|
297
|
+
* at the end — `HTMLCanvasElement` closes with `getContext(id: string): RenderingContext | null`,
|
|
298
|
+
* `OffscreenCanvas` with an `OffscreenRenderingContextId` one — and resolving across the union can
|
|
299
|
+
* combine one kind's `"2d"` overload with the other's catch-all, yielding
|
|
300
|
+
* `OffscreenCanvasRenderingContext2D | RenderingContext`: a type covering bitmap and WebGL contexts
|
|
301
|
+
* that have no `drawImage`. It surfaces only on incremental rebuilds (any compile error in this
|
|
302
|
+
* file leaves the bad resolution cached in `.tsbuildinfo`, where it outlives the error that caused
|
|
303
|
+
* it), so a clean build hides it and a working tree quietly breaks. Narrowed first, each branch has
|
|
304
|
+
* one concrete type whose `"2d"` overload is the only candidate, and the result is stable either
|
|
305
|
+
* way.
|
|
306
|
+
* @param canvas - The atlas canvas.
|
|
307
|
+
* @returns Its 2D context, or `null` when the canvas cannot provide one.
|
|
308
|
+
*/
|
|
309
|
+
function contextOf(canvas) {
|
|
310
|
+
// `in` rather than `instanceof`: in a worker there is no `HTMLCanvasElement` global to test
|
|
311
|
+
// against, and an `instanceof` against a missing global throws.
|
|
312
|
+
if ('convertToBlob' in canvas)
|
|
313
|
+
return canvas.getContext('2d');
|
|
314
|
+
return canvas.getContext('2d');
|
|
315
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type InstancedMeshExt, type InstancedMeshExtParams } from '../../core/instanced/InstancedMesh';
|
|
2
|
+
import { type ImageDef } from '../../types/defs';
|
|
3
|
+
import { SlotContainer } from '../containers';
|
|
4
|
+
import { type Atlas, type PackedImage } from './atlas-packer';
|
|
5
|
+
/**
|
|
6
|
+
* The per-instance uniform each atlas mesh reads its texture region from. The container owns the
|
|
7
|
+
* name: it writes each slot's region here (`setUniformAt`) and hands it to the mesh's
|
|
8
|
+
* `TextureMaterial` as the `uvOffset` prop, so the material patches its shader to sample the region
|
|
9
|
+
* without knowing the name itself. `xy` is the region's origin in uv space and `zw` its size, so
|
|
10
|
+
* `uv * zw + xy` maps the quad's unit uv onto the region.
|
|
11
|
+
*/
|
|
12
|
+
export declare const UV_OFFSET_UNIFORM = "uvOffset";
|
|
13
|
+
/**
|
|
14
|
+
* The constructor tuple R3F builds one atlas's mesh from, passed as `<instancedMesh args={…}>`.
|
|
15
|
+
* R3F diffs `args` element-wise and rebuilds the object when any element changes, so this is
|
|
16
|
+
* snapshotted once and never mutated.
|
|
17
|
+
*
|
|
18
|
+
* Only the options: `InstancedMeshExt` takes no geometry or material, seeding throwaways that the
|
|
19
|
+
* element's `<planeGeometry>` and `<TextureMaterial>` children replace (R3F creates, attaches, and
|
|
20
|
+
* disposes those).
|
|
21
|
+
*/
|
|
22
|
+
export type ImageMeshArgs = [params: InstancedMeshExtParams];
|
|
23
|
+
/**
|
|
24
|
+
* One atlas's mesh bookkeeping.
|
|
25
|
+
*
|
|
26
|
+
* Simpler than a shape container, and deliberately so: an atlas draws one shared quad, so there is
|
|
27
|
+
* no geometry cache and no geometry-id juggling, and `InstancedMeshExt` grows its own buffers, so
|
|
28
|
+
* there is no capacity pre-scan — the mesh is merely sized to the atlas's packed-image count and
|
|
29
|
+
* left to resize itself if a claim ever overflows it. What remains is a slot per packed image and
|
|
30
|
+
* the uv region each one samples, written once at claim since a def's source never changes.
|
|
31
|
+
*
|
|
32
|
+
* Paint order is incremental, like the shape container's, because packing is order-independent: a
|
|
33
|
+
* reorder of the same images keeps this container and only re-labels its slots, never repacking. A
|
|
34
|
+
* change to the *set* of images is the harder case and still repacks into a fresh container —
|
|
35
|
+
* packing has no incremental path for that — but a pure reorder does not.
|
|
36
|
+
*/
|
|
37
|
+
export declare class ImageContainer extends SlotContainer<PackedImage, InstancedMeshExt, ImageDef> {
|
|
38
|
+
/** The constructor args R3F builds the mesh from; a frozen snapshot, never mutated. */
|
|
39
|
+
readonly args: ImageMeshArgs;
|
|
40
|
+
/**
|
|
41
|
+
* Builds one atlas's bookkeeping. The mesh, its quad, and its material are all R3F's — the atlas
|
|
42
|
+
* component renders `<instancedMesh args={container.args}>` with the geometry and material as
|
|
43
|
+
* children, and hands the mesh back through {@link SlotContainer.bind} — so R3F owns their
|
|
44
|
+
* lifetimes, including disposal on unmount and survival across StrictMode's simulated remount.
|
|
45
|
+
* @param atlas - The packed atlas this container draws; only its image count sizes the mesh.
|
|
46
|
+
*/
|
|
47
|
+
constructor(atlas: Atlas);
|
|
48
|
+
protected addInstance(packed: PackedImage): number;
|
|
49
|
+
protected removeInstance(slot: number): void;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=image-container.d.ts.map
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Vector4 } from 'three';
|
|
2
|
+
import { SlotContainer } from '../containers';
|
|
3
|
+
import { DIM_SCHEMA } from '../materials/dim';
|
|
4
|
+
/**
|
|
5
|
+
* The per-instance uniform each atlas mesh reads its texture region from. The container owns the
|
|
6
|
+
* name: it writes each slot's region here (`setUniformAt`) and hands it to the mesh's
|
|
7
|
+
* `TextureMaterial` as the `uvOffset` prop, so the material patches its shader to sample the region
|
|
8
|
+
* without knowing the name itself. `xy` is the region's origin in uv space and `zw` its size, so
|
|
9
|
+
* `uv * zw + xy` maps the quad's unit uv onto the region.
|
|
10
|
+
*/
|
|
11
|
+
export const UV_OFFSET_UNIFORM = 'uvOffset';
|
|
12
|
+
/**
|
|
13
|
+
* One atlas's mesh bookkeeping.
|
|
14
|
+
*
|
|
15
|
+
* Simpler than a shape container, and deliberately so: an atlas draws one shared quad, so there is
|
|
16
|
+
* no geometry cache and no geometry-id juggling, and `InstancedMeshExt` grows its own buffers, so
|
|
17
|
+
* there is no capacity pre-scan — the mesh is merely sized to the atlas's packed-image count and
|
|
18
|
+
* left to resize itself if a claim ever overflows it. What remains is a slot per packed image and
|
|
19
|
+
* the uv region each one samples, written once at claim since a def's source never changes.
|
|
20
|
+
*
|
|
21
|
+
* Paint order is incremental, like the shape container's, because packing is order-independent: a
|
|
22
|
+
* reorder of the same images keeps this container and only re-labels its slots, never repacking. A
|
|
23
|
+
* change to the *set* of images is the harder case and still repacks into a fresh container —
|
|
24
|
+
* packing has no incremental path for that — but a pure reorder does not.
|
|
25
|
+
*/
|
|
26
|
+
export class ImageContainer extends SlotContainer {
|
|
27
|
+
/** The constructor args R3F builds the mesh from; a frozen snapshot, never mutated. */
|
|
28
|
+
args;
|
|
29
|
+
/**
|
|
30
|
+
* Builds one atlas's bookkeeping. The mesh, its quad, and its material are all R3F's — the atlas
|
|
31
|
+
* component renders `<instancedMesh args={container.args}>` with the geometry and material as
|
|
32
|
+
* children, and hands the mesh back through {@link SlotContainer.bind} — so R3F owns their
|
|
33
|
+
* lifetimes, including disposal on unmount and survival across StrictMode's simulated remount.
|
|
34
|
+
* @param atlas - The packed atlas this container draws; only its image count sizes the mesh.
|
|
35
|
+
*/
|
|
36
|
+
constructor(atlas) {
|
|
37
|
+
super({ schema: IMAGE_SCHEMA });
|
|
38
|
+
this.args = [{ capacity: Math.max(1, atlas.images.length) }];
|
|
39
|
+
}
|
|
40
|
+
addInstance(packed) {
|
|
41
|
+
// `addInstances` grows the buffers itself if the atlas ever overflows its sized capacity, so
|
|
42
|
+
// there is nothing to pre-scan; the id is captured from the creation callback.
|
|
43
|
+
let slot = -1;
|
|
44
|
+
this.mesh.addInstances(1, (id) => {
|
|
45
|
+
slot = id;
|
|
46
|
+
});
|
|
47
|
+
// The uv region is fixed for the instance's life — a def's source never changes, and a set
|
|
48
|
+
// change repacks into a fresh container — so it is written once, here, rather than on every
|
|
49
|
+
// sync.
|
|
50
|
+
const { u0, v0, u1, v1 } = packed.uv;
|
|
51
|
+
this.mesh.setUniformAt(slot, UV_OFFSET_UNIFORM, _uvOffset.set(u0, v0, u1 - u0, v1 - v0));
|
|
52
|
+
return slot;
|
|
53
|
+
}
|
|
54
|
+
removeInstance(slot) {
|
|
55
|
+
this.mesh.removeInstances(slot);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The schema the mesh declares (via `addUniforms`) so it can hold a {@link UV_OFFSET_UNIFORM} and a
|
|
60
|
+
* dim per slot.
|
|
61
|
+
*
|
|
62
|
+
* Both in one call although the two come from different places: `InstanceUniforms.add` merges and
|
|
63
|
+
* rebuilds the backing texture, so declaring them separately would build it twice per bind.
|
|
64
|
+
*/
|
|
65
|
+
const IMAGE_SCHEMA = {
|
|
66
|
+
vertex: { [UV_OFFSET_UNIFORM]: 'vec4', ...DIM_SCHEMA.vertex },
|
|
67
|
+
};
|
|
68
|
+
// Scratch for the one uv-region upload a claim performs, filled and read within a single
|
|
69
|
+
// synchronous statement.
|
|
70
|
+
const _uvOffset = new Vector4();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Images } from './Images';
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { type Atlas } from './atlas-packer';
|
|
2
|
+
/**
|
|
3
|
+
* The GPU footprint of an atlas of the given pixel size: RGBA, with the mipmap chain counted. Reads
|
|
4
|
+
* the atlas's full (packed) dimensions — the resize factor scales this down, so budgeting against
|
|
5
|
+
* the full size is what the factor is solved from.
|
|
6
|
+
* @param width - Atlas width in pixels.
|
|
7
|
+
* @param height - Atlas height in pixels.
|
|
8
|
+
* @returns The size in bytes.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getAtlasSizeBytes(width: number, height: number): number;
|
|
11
|
+
/**
|
|
12
|
+
* One atlas's contribution to the budget: its full size, and whether it may be scaled to fit. The
|
|
13
|
+
* object doubles as its own registration handle — {@link TextureBudget.unregister} takes the very
|
|
14
|
+
* object {@link TextureBudget.register} was given, so identity alone distinguishes two atlases of
|
|
15
|
+
* equal size and no separate key is needed.
|
|
16
|
+
*/
|
|
17
|
+
interface AtlasBudgetEntry {
|
|
18
|
+
/** Full atlas width in pixels. */
|
|
19
|
+
width: number;
|
|
20
|
+
/** Full atlas height in pixels. */
|
|
21
|
+
height: number;
|
|
22
|
+
/** Whether this atlas's bytes shrink with the resize factor, or count at full size always. */
|
|
23
|
+
resizable: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The scene's shared texture-memory budget. Every image atlas registers its full size here, and the
|
|
27
|
+
* store solves for one linear resize factor that brings the resizable atlases within `memoryLimit` —
|
|
28
|
+
* shared by all of them, so their relative sizes are preserved with no priority.
|
|
29
|
+
*
|
|
30
|
+
* The factor is exposed through `useSyncExternalStore`: an atlas that mounts, unmounts, or a limit
|
|
31
|
+
* that changes recomputes it, and a material move re-derives only the resizable atlases' textures —
|
|
32
|
+
* never a repack, since uv regions are normalized and so invariant to the canvas's pixel size.
|
|
33
|
+
* Recompute runs synchronously per registration; the render churn that would cause is left to
|
|
34
|
+
* React's automatic batching to coalesce, and the `FACTOR_EPSILON` gate keeps sub-pixel nudges from
|
|
35
|
+
* notifying at all. When no cap is set the whole apparatus stands down: {@link TextureBudget.isActive}
|
|
36
|
+
* is false, atlases skip registration entirely (see {@link useResizeFactor}), and the factor is 1.
|
|
37
|
+
*/
|
|
38
|
+
export declare class TextureBudget {
|
|
39
|
+
/** The cap in megabytes, or undefined/`0` for no cap — a falsy value scales nothing. */
|
|
40
|
+
private memoryLimitMb;
|
|
41
|
+
/** Every registered atlas; the entry object is its own handle. */
|
|
42
|
+
private readonly atlases;
|
|
43
|
+
/** Subscribers to the resize factor — the mounted resizable atlases. */
|
|
44
|
+
private readonly listeners;
|
|
45
|
+
/** The current shared linear resize factor; `1` means no scaling. */
|
|
46
|
+
private factor;
|
|
47
|
+
/**
|
|
48
|
+
* @param memoryLimitMb - The cap in megabytes, or undefined/`0` for no cap.
|
|
49
|
+
*/
|
|
50
|
+
constructor(memoryLimitMb?: number);
|
|
51
|
+
/**
|
|
52
|
+
* Whether a cap is in force. False for an absent or zero limit — nothing can be compressed, so
|
|
53
|
+
* consumers skip the budget entirely rather than do work that resolves to "no scaling".
|
|
54
|
+
* @returns Whether a positive limit is set.
|
|
55
|
+
*/
|
|
56
|
+
isActive(): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Changes the cap. Called when the renderer's `memoryLimit` prop changes, so a caller can raise,
|
|
59
|
+
* lower, or clear it at runtime and the resizable atlases re-derive their textures live.
|
|
60
|
+
* @param memoryLimitMb - The new cap, or undefined/`0` for no cap.
|
|
61
|
+
*/
|
|
62
|
+
setMemoryLimit(memoryLimitMb: number | undefined): void;
|
|
63
|
+
/**
|
|
64
|
+
* Registers one atlas's full size, recomputing the factor. Only called while {@link isActive}, so
|
|
65
|
+
* an uncapped scene never populates this set.
|
|
66
|
+
* @param entry - The atlas's dimensions and kind; pass the same object back to unregister.
|
|
67
|
+
*/
|
|
68
|
+
register(entry: AtlasBudgetEntry): void;
|
|
69
|
+
/**
|
|
70
|
+
* Drops an atlas's contribution, recomputing the factor.
|
|
71
|
+
* @param entry - The object {@link register} was given.
|
|
72
|
+
*/
|
|
73
|
+
unregister(entry: AtlasBudgetEntry): void;
|
|
74
|
+
/**
|
|
75
|
+
* The current shared resize factor.
|
|
76
|
+
* @returns The factor in `(0, 1]`.
|
|
77
|
+
*/
|
|
78
|
+
getResizeFactor(): number;
|
|
79
|
+
/**
|
|
80
|
+
* Subscribes to factor and active-state changes.
|
|
81
|
+
* @param listener - Called when the factor moves past {@link FACTOR_EPSILON} or the cap toggles.
|
|
82
|
+
* @returns The unsubscribe function.
|
|
83
|
+
*/
|
|
84
|
+
subscribe(listener: () => void): () => void;
|
|
85
|
+
/** Recomputes the factor and, if it moved materially, notifies subscribers. */
|
|
86
|
+
private recompute;
|
|
87
|
+
/** Notifies every subscriber. */
|
|
88
|
+
private notify;
|
|
89
|
+
/**
|
|
90
|
+
* Solves for the linear factor that fits the resizable atlases in the budget the fixed ones leave.
|
|
91
|
+
*
|
|
92
|
+
* Every degenerate case falls back to `1` — no scaling — rather than forcing a fit: no cap, nothing
|
|
93
|
+
* resizable to scale, or a cap the fixed atlases alone already exceed (canvases cannot be
|
|
94
|
+
* compressed, so there is nothing to give). The factor is only ever clamped up to `1`, never down
|
|
95
|
+
* to a floor: an image is scaled by however much the budget demands.
|
|
96
|
+
* @returns The factor in `(0, 1]`.
|
|
97
|
+
*/
|
|
98
|
+
private computeResizeFactor;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The scene's texture budget, provided by `Renderer`. The default is an uncapped budget, so an image
|
|
102
|
+
* layer rendered outside a `Renderer` (a test, a story) behaves exactly as before this existed —
|
|
103
|
+
* everything uploads at full size.
|
|
104
|
+
*/
|
|
105
|
+
export declare const TextureBudgetContext: import("react").Context<TextureBudget>;
|
|
106
|
+
/**
|
|
107
|
+
* Registers an atlas with the scene budget for its mounted lifetime and returns the resize factor it
|
|
108
|
+
* should upload at: the shared factor for a resizable atlas, always `1` for a fixed one (its pixels
|
|
109
|
+
* still count toward the budget, they just never scale).
|
|
110
|
+
*
|
|
111
|
+
* When the budget is inactive — no `memoryLimit` — the atlas is not registered at all: there is
|
|
112
|
+
* nothing to solve, so an uncapped scene does no per-atlas budget work. Turning a cap on at runtime
|
|
113
|
+
* flips `isActive`, which re-runs the registration effect.
|
|
114
|
+
* @param budget - The scene budget from context.
|
|
115
|
+
* @param atlas - The atlas to register and read a factor for.
|
|
116
|
+
* @returns The linear factor this atlas's canvas should be scaled by before upload, in `(0, 1]`.
|
|
117
|
+
*/
|
|
118
|
+
export declare function useResizeFactor(budget: TextureBudget, atlas: Atlas): number;
|
|
119
|
+
export {};
|
|
120
|
+
//# sourceMappingURL=texture-budget.d.ts.map
|