material-designer-runtime 0.1.4 → 0.1.5
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 +167 -1
- package/dist/cache/bake-cache.d.ts +45 -0
- package/dist/cache/cache.worker.d.ts +1 -0
- package/dist/cache/codec.d.ts +13 -0
- package/dist/cache/idb-core.d.ts +27 -0
- package/dist/cache/index.d.ts +7 -0
- package/dist/cache/indexeddb-store.d.ts +13 -0
- package/dist/cache/key.d.ts +21 -0
- package/dist/cache/memory-store.d.ts +2 -0
- package/dist/cache/types.d.ts +85 -0
- package/dist/cache/worker-client.d.ts +8 -0
- package/dist/graph/bake-service.d.ts +21 -1
- package/dist/graph/channel-baker.d.ts +3 -0
- package/dist/graph/texture-transfer.d.ts +13 -0
- package/dist/graph/textured-surface.d.ts +2 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.js +1861 -904
- package/dist/runtime.d.ts +23 -1
- package/dist/topology.d.ts +2 -0
- package/dist/version.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -103,4 +103,170 @@ for PNG export, use `bakeService.readImage(session, channel, 1024)` → `ImageDa
|
|
|
103
103
|
multiple of 64). If you keep a live `MaterialGraphRuntime`, the same baked textures are also
|
|
104
104
|
reachable via `runtime.surface.getChannelTexture(channel)` and `runtime.surface.getHeightTexture()`.
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
## Persistent texture cache (opt-in)
|
|
107
|
+
|
|
108
|
+
Baking is dominated by **shader compilation**, not rendering — on a heavy graph the pipeline compile runs
|
|
109
|
+
into seconds while the render is tens of milliseconds. Without a cache that cost is paid again on every page
|
|
110
|
+
load, even when the document hasn't changed by a single texel.
|
|
111
|
+
|
|
112
|
+
The cache stores the baked channel texels and, on a hit, restores them with a GPU-to-GPU copy that
|
|
113
|
+
short-circuits **before** the compile. It is **off by default** — enabling it is the only way anything is
|
|
114
|
+
persisted:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
const runtime = new MaterialGraphRuntime({ document, cache: {} }); // {} = defaults, enabled
|
|
118
|
+
// …or turn it on later:
|
|
119
|
+
runtime.setCacheEnabled(true);
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Everything you need:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
runtime.cacheEnabled; // is it on?
|
|
126
|
+
runtime.setCacheEnabled(true | false); // toggle (installs the default cache on first enable)
|
|
127
|
+
await runtime.clearCache(); // drop every cached bake
|
|
128
|
+
await runtime.rebuildCache(); // re-bake for real and replace this document's entry,
|
|
129
|
+
// resolving only once the new entry is durably written
|
|
130
|
+
await runtime.getCachedTextures(); // this document's entry metadata, or null
|
|
131
|
+
await runtime.listCachedTextures(); // every entry's metadata, newest use first
|
|
132
|
+
await runtime.loadCachedChannelTextures(); // …or the actual textures (see below)
|
|
133
|
+
await runtime.cacheMetrics(); // size taken + hit/miss/evict counters
|
|
134
|
+
await runtime.flushCache(); // force the deferred write now (e.g. on unload)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`cacheMetrics()` answers "how much space is this taking":
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
{ enabled, available, store: "worker-indexeddb", encoding: "png",
|
|
141
|
+
entries, bytes, // bytes AS STORED (post-compression) — the real footprint
|
|
142
|
+
budgetBytes, // LRU-evicted down to this
|
|
143
|
+
hits, misses, writes, evictions, collisions,
|
|
144
|
+
quota: { usage, quota }, // navigator.storage.estimate(), where the browser reports it
|
|
145
|
+
lastError }
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Configuration
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
new MaterialGraphRuntime({ document, cache: {
|
|
152
|
+
enabled: true,
|
|
153
|
+
store: myStore, // bring your own (see below); default is worker+IndexedDB
|
|
154
|
+
encoding: "deflate", // or "rgba8" (cheaper saves) / "png" (smallest, slower restores)
|
|
155
|
+
budgetBytes: 256 * 2 ** 20, // on-disk ceiling, in STORED (compressed) bytes
|
|
156
|
+
maxEntryBytes: 256 * 2 ** 20, // per-capture ceiling, in RAW texel bytes — see note below
|
|
157
|
+
minBakeMs: 250, // don't spend disk to save a bake cheaper than this
|
|
158
|
+
writeDelayMs: 750, // quiet period before the capture runs
|
|
159
|
+
worker: true, // false = main thread; { url } = self-hosted worker (strict CSP)
|
|
160
|
+
namespace: "my-app", // your own invalidation lever
|
|
161
|
+
onEvent: (e) => {}, // hit | miss | write | evict | collision | disabled | error
|
|
162
|
+
}});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The codec and all storage IO run **in a worker**, so neither encoding nor IO touches the main thread. Only the
|
|
166
|
+
GPU readback is main-thread (it has to be), and it is deferred past `writeDelayMs`, skipped when an entry
|
|
167
|
+
already exists, and collapsed to one capture per burst of re-bakes.
|
|
168
|
+
|
|
169
|
+
**On `encoding`.** All three options are lossless; they trade save cost against footprint.
|
|
170
|
+
|
|
171
|
+
- `"deflate"` (default) — byte compression, no image codec. Restores as fast as raw texels, and stores about
|
|
172
|
+
6× smaller. Saving costs real CPU, but it runs in the worker and does not hold the bake queue, so nothing
|
|
173
|
+
waits on it.
|
|
174
|
+
- `"rgba8"` — raw texels. Cheapest to save, largest on disk.
|
|
175
|
+
- `"png"` — smallest footprint, but measurably slower to restore: unpacking an image is real work, and a
|
|
176
|
+
cache exists to make the second load fast.
|
|
177
|
+
|
|
178
|
+
Footprint matters more than it first looks. Raw texels run ~80 MB per material at 2048², so the default
|
|
179
|
+
256 MB budget would hold roughly three materials before evicting; compressed it holds around eighteen. That is
|
|
180
|
+
the difference between a cache that accumulates and one that thrashes.
|
|
181
|
+
|
|
182
|
+
Lossy GPU formats (KTX2/Basis) are deliberately not offered. This cache must reproduce a bake *exactly* —
|
|
183
|
+
otherwise you would author a material against one image and get a different one back on reload — and block
|
|
184
|
+
compression visibly damages the data channels, normals worst of all. It would also buy nothing here: a restore
|
|
185
|
+
writes into the same render targets the baker draws into, and those are uncompressed by construction.
|
|
186
|
+
|
|
187
|
+
**The two size knobs are in different currencies.** `budgetBytes` is compressed bytes on disk.
|
|
188
|
+
`maxEntryBytes` is *raw* texel bytes, because it gates the capture before the readback — at which point the
|
|
189
|
+
stored size can't be known. Size it against raw totals:
|
|
190
|
+
|
|
191
|
+
| bake size | 1 channel | 7 channels (raw) |
|
|
192
|
+
| --- | --- | --- |
|
|
193
|
+
| 512² | 1 MB | 7 MB |
|
|
194
|
+
| 1024² | 4 MB | 28 MB |
|
|
195
|
+
| 2048² | 16 MB | 112 MB |
|
|
196
|
+
| 4096² | 64 MB | 448 MB |
|
|
197
|
+
|
|
198
|
+
PNG typically shrinks that by 10× or more, so if you set `maxEntryBytes` to the on-disk figure you have in
|
|
199
|
+
mind, larger materials will silently stop being cached.
|
|
200
|
+
|
|
201
|
+
### What a capture costs
|
|
202
|
+
|
|
203
|
+
The readback is the one part that runs on the main thread. Measured on an M-series Mac in Chrome, per
|
|
204
|
+
channel, median of 12 runs:
|
|
205
|
+
|
|
206
|
+
| bake size | GPU→CPU read | row flip (JS) | total | ×7 channels |
|
|
207
|
+
| --- | --- | --- | --- | --- |
|
|
208
|
+
| 512² | 1.0 ms | 0.2 ms | 1.2 ms | ~8 ms |
|
|
209
|
+
| 1024² | 2.4 ms | 0.6 ms | 3.0 ms | ~21 ms |
|
|
210
|
+
| 2048² | 6.3 ms | 2.4 ms | 8.7 ms | ~61 ms |
|
|
211
|
+
| 4096² | 22.3 ms | 9.9 ms | 32.2 ms | ~225 ms |
|
|
212
|
+
|
|
213
|
+
Roughly linear in bytes (~0.35 ms/MB transfer, ~0.15 ms/MB flip). This is why the capture is deferred rather
|
|
214
|
+
than run inline at the end of a bake: it is work that only pays off on the *next* load, so making the current
|
|
215
|
+
one wait for it would be paying now for a benefit later. De-padding is free at bake sizes that are multiples
|
|
216
|
+
of 64 (the rows arrive tightly packed), which every surface bake is.
|
|
217
|
+
|
|
218
|
+
### Bring your own storage
|
|
219
|
+
|
|
220
|
+
Implement `BakeCacheStore` and the runtime uses it instead of IndexedDB — for a server, a CDN, your own
|
|
221
|
+
database, or node's filesystem:
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
import { BakeTextureCache, type BakeCacheStore } from "material-designer-runtime";
|
|
225
|
+
|
|
226
|
+
const store: BakeCacheStore = {
|
|
227
|
+
name: "my-backend",
|
|
228
|
+
async get(id) { … }, async put(entry) { … },
|
|
229
|
+
async touch(id, lastUsedAt) { … }, // must NOT rewrite the texels — runs on every hit
|
|
230
|
+
async delete(id) { … },
|
|
231
|
+
async list() { … }, // metadata ONLY, never texels
|
|
232
|
+
async clear() { … },
|
|
233
|
+
};
|
|
234
|
+
runtime.service.setCache(new BakeTextureCache({ enabled: true, store }));
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Texels cross this interface as tightly-packed, top-down RGBA8; compress internally if you like, but decode
|
|
238
|
+
before returning, and overwrite `meta.bytes` with the size you actually stored so the metrics stay truthful.
|
|
239
|
+
`createBakeCacheKey(...)` (also `runtime.cacheKey()`) is exported, so you can key your own cache identically.
|
|
240
|
+
|
|
241
|
+
### Using cached textures directly
|
|
242
|
+
|
|
243
|
+
`loadCachedChannelTextures()` needs **no renderer** — a page shipping a fixed material can dress a plain
|
|
244
|
+
THREE material from cache without ever compiling a shader:
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
const cached = await runtime.loadCachedChannelTextures();
|
|
248
|
+
if (cached) {
|
|
249
|
+
const material = buildMeshMaterial(runtime.getDocument(), cached);
|
|
250
|
+
// …you own these textures: cached.dispose() when the material is done with them.
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Notes
|
|
255
|
+
|
|
256
|
+
- **Invalidation is automatic.** The key covers the graph topology, every effective param value (registry
|
|
257
|
+
defaults merged in, so a sparse document keys the same as an explicit one), bake size, channel set, document
|
|
258
|
+
schema version, three's revision, and this package's version — the last because node maths changes texel
|
|
259
|
+
output without a schema bump. An upgrade therefore starts cold rather than restoring stale texels.
|
|
260
|
+
Cosmetics (node labels, positions, pan/zoom, metadata) never invalidate.
|
|
261
|
+
- **A cache failure can never break a bake.** Every storage call is contained; a miss, a corrupt record, a
|
|
262
|
+
dead worker, or an exhausted quota all fall through to baking normally.
|
|
263
|
+
- **The first edit after a hit costs one full rebuild.** A restore deliberately skips compilation, so there are
|
|
264
|
+
no compiled materials to re-render with until the next rebuild repopulates them.
|
|
265
|
+
- **It is a binary store, so `localStorage.clear()` will not touch it.** If your app has a "reset everything"
|
|
266
|
+
path, call `clearCache()` there too; `BAKE_CACHE_DB_NAME` is exported if you'd rather delete the database.
|
|
267
|
+
|
|
268
|
+
The editor owns UI state, selection, presets, and undo history. This package owns graph document loading,
|
|
269
|
+
compilation, baking, direct parameter updates, the material surface, and — opt-in — a cache of its own bake
|
|
270
|
+
artifacts. That last one is the single exception to "the editor owns storage": it caches only what this package
|
|
271
|
+
produces, it is off unless you enable it, and `cache.store` hands storage ownership back to you whenever you
|
|
272
|
+
want it.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type BakeCacheKey } from "./key";
|
|
2
|
+
import type { BakeCacheEntry, BakeCacheEntryMeta, BakeCacheMetrics, BakeCacheOptions, BakeCachePayload } from "./types";
|
|
3
|
+
export declare class BakeTextureCache {
|
|
4
|
+
private store;
|
|
5
|
+
private ownsStore;
|
|
6
|
+
private enabled_;
|
|
7
|
+
private options;
|
|
8
|
+
private onEvent;
|
|
9
|
+
private workerOption;
|
|
10
|
+
private hits;
|
|
11
|
+
private misses;
|
|
12
|
+
private writes;
|
|
13
|
+
private evictions;
|
|
14
|
+
private collisions;
|
|
15
|
+
private lastError_;
|
|
16
|
+
private quotaFailures;
|
|
17
|
+
private gcDone;
|
|
18
|
+
constructor(options?: BakeCacheOptions);
|
|
19
|
+
get enabled(): boolean;
|
|
20
|
+
get available(): boolean;
|
|
21
|
+
get storeName(): string;
|
|
22
|
+
get encoding(): BakeCacheOptions["encoding"];
|
|
23
|
+
get namespace(): string;
|
|
24
|
+
get writeDelayMs(): number;
|
|
25
|
+
get lastError(): string | null;
|
|
26
|
+
setEnabled(on: boolean): void;
|
|
27
|
+
configure(patch: BakeCacheOptions): void;
|
|
28
|
+
read(key: BakeCacheKey): Promise<BakeCacheEntry | null>;
|
|
29
|
+
peek(key: BakeCacheKey): Promise<BakeCacheEntryMeta | null>;
|
|
30
|
+
entries(): Promise<BakeCacheEntryMeta[]>;
|
|
31
|
+
canWrite(bakeMs: number, bytes: number): boolean;
|
|
32
|
+
write(key: BakeCacheKey, payload: BakeCachePayload): Promise<void>;
|
|
33
|
+
delete(key: BakeCacheKey | string): Promise<void>;
|
|
34
|
+
clear(): Promise<void>;
|
|
35
|
+
gc(): Promise<number>;
|
|
36
|
+
evict(): Promise<number>;
|
|
37
|
+
metrics(): Promise<BakeCacheMetrics>;
|
|
38
|
+
info(): Record<string, unknown>;
|
|
39
|
+
private put;
|
|
40
|
+
private evictTo;
|
|
41
|
+
private ensureGc;
|
|
42
|
+
private guard;
|
|
43
|
+
private fail;
|
|
44
|
+
private emit;
|
|
45
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { BakeCacheEncoding } from "./types";
|
|
2
|
+
export declare function canEncodePng(): boolean;
|
|
3
|
+
export declare function encodePng(texels: Uint8Array, size: number): Promise<Uint8Array>;
|
|
4
|
+
export declare function decodePng(bytes: Uint8Array, size: number): Promise<Uint8Array>;
|
|
5
|
+
export declare function canDeflate(): boolean;
|
|
6
|
+
export declare function deflateBytes(bytes: Uint8Array): Promise<Uint8Array>;
|
|
7
|
+
export declare function inflateBytes(bytes: Uint8Array): Promise<Uint8Array>;
|
|
8
|
+
export declare function encodeTexels(texels: Uint8Array, size: number, encoding: BakeCacheEncoding): Promise<{
|
|
9
|
+
bytes: Uint8Array;
|
|
10
|
+
encoding: BakeCacheEncoding;
|
|
11
|
+
}>;
|
|
12
|
+
export declare function decodeTexels(bytes: Uint8Array, size: number, encoding: BakeCacheEncoding): Promise<Uint8Array>;
|
|
13
|
+
export declare function pngRoundTripIsLossless(size?: number): Promise<boolean>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { BakeCacheChannel, BakeCacheEncoding, BakeCacheEntryMeta } from "./types";
|
|
2
|
+
export declare const BAKE_CACHE_DB_NAME = "material-designer-textures";
|
|
3
|
+
export declare const BAKE_CACHE_DB_VERSION = 1;
|
|
4
|
+
export declare const META_STORE = "meta";
|
|
5
|
+
export declare const DATA_STORE = "data";
|
|
6
|
+
export interface StoredTexels {
|
|
7
|
+
channel: BakeCacheChannel;
|
|
8
|
+
encoding: BakeCacheEncoding;
|
|
9
|
+
bytes: ArrayBuffer;
|
|
10
|
+
}
|
|
11
|
+
export interface StoredData {
|
|
12
|
+
id: string;
|
|
13
|
+
textures: StoredTexels[];
|
|
14
|
+
}
|
|
15
|
+
export declare function isIndexedDbAvailable(): boolean;
|
|
16
|
+
export declare function dbNameFor(namespace: string): string;
|
|
17
|
+
export declare function openBakeCacheDb(dbName: string): Promise<IDBDatabase>;
|
|
18
|
+
export declare function idbGet(db: IDBDatabase, id: string): Promise<{
|
|
19
|
+
meta: BakeCacheEntryMeta;
|
|
20
|
+
data: StoredData;
|
|
21
|
+
} | null>;
|
|
22
|
+
export declare function idbPut(db: IDBDatabase, meta: BakeCacheEntryMeta, data: StoredData): Promise<void>;
|
|
23
|
+
export declare function idbTouch(db: IDBDatabase, id: string, lastUsedAt: number): Promise<void>;
|
|
24
|
+
export declare function idbDelete(db: IDBDatabase, id: string): Promise<void>;
|
|
25
|
+
export declare function idbList(db: IDBDatabase): Promise<BakeCacheEntryMeta[]>;
|
|
26
|
+
export declare function idbClear(db: IDBDatabase): Promise<void>;
|
|
27
|
+
export declare function toStandaloneBuffer(bytes: Uint8Array): ArrayBuffer;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { BakeTextureCache } from "./bake-cache";
|
|
2
|
+
export { createMemoryCacheStore } from "./memory-store";
|
|
3
|
+
export { BAKE_CACHE_DB_NAME, BAKE_CACHE_DB_VERSION, createDefaultCacheStore, createIndexedDbCacheStore, isIndexedDbAvailable, type DefaultCacheStoreOptions, type IndexedDbCacheStoreOptions, } from "./indexeddb-store";
|
|
4
|
+
export { createWorkerCacheStore, isWorkerCacheAvailable, type WorkerCacheStoreOptions, } from "./worker-client";
|
|
5
|
+
export { canDeflate, canEncodePng, decodeTexels, deflateBytes, encodeTexels, inflateBytes, pngRoundTripIsLossless, } from "./codec";
|
|
6
|
+
export { BAKE_CACHE_ID_PREFIX, BAKE_CACHE_SCHEMA, BAKE_CACHE_VERSION, BAKE_ENCODER_VERSION, createBakeCacheKey, fnv1a64, type BakeCacheKey, type BakeCacheKeyInput, } from "./key";
|
|
7
|
+
export type { BakeCacheChannel, BakeCacheEncoding, BakeCacheEntry, BakeCacheEntryMeta, BakeCacheEvent, BakeCacheEventKind, BakeCacheMetrics, BakeCacheOptions, BakeCachePayload, BakeCacheStore, BakeCacheStoreEstimate, BakeCacheTexels, } from "./types";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { BakeCacheEncoding, BakeCacheStore } from "./types";
|
|
2
|
+
export { BAKE_CACHE_DB_NAME, BAKE_CACHE_DB_VERSION, isIndexedDbAvailable, } from "./idb-core";
|
|
3
|
+
export interface IndexedDbCacheStoreOptions {
|
|
4
|
+
namespace?: string;
|
|
5
|
+
encoding?: BakeCacheEncoding;
|
|
6
|
+
}
|
|
7
|
+
export declare function createIndexedDbCacheStore(options?: IndexedDbCacheStoreOptions): BakeCacheStore;
|
|
8
|
+
export interface DefaultCacheStoreOptions extends IndexedDbCacheStoreOptions {
|
|
9
|
+
worker?: boolean | {
|
|
10
|
+
url: string | URL;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export declare function createDefaultCacheStore(options?: DefaultCacheStoreOptions): BakeCacheStore;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { NodeRegistry } from "../graph/registry";
|
|
2
|
+
import type { MaterialGraphDocument } from "../graph/types";
|
|
3
|
+
import type { BakeCacheChannel } from "./types";
|
|
4
|
+
export declare const BAKE_CACHE_VERSION = 1;
|
|
5
|
+
export declare const BAKE_ENCODER_VERSION = 1;
|
|
6
|
+
export declare const BAKE_CACHE_ID_PREFIX: string;
|
|
7
|
+
export declare const BAKE_CACHE_SCHEMA: string;
|
|
8
|
+
export interface BakeCacheKeyInput {
|
|
9
|
+
document: MaterialGraphDocument;
|
|
10
|
+
registry: NodeRegistry;
|
|
11
|
+
size: number;
|
|
12
|
+
channels: readonly BakeCacheChannel[];
|
|
13
|
+
backend?: string;
|
|
14
|
+
namespace?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface BakeCacheKey {
|
|
17
|
+
id: string;
|
|
18
|
+
key: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function fnv1a64(text: string): string;
|
|
21
|
+
export declare function createBakeCacheKey(input: BakeCacheKeyInput): BakeCacheKey;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { PbrSocket } from "../graph/types";
|
|
2
|
+
export type BakeCacheChannel = PbrSocket | "height";
|
|
3
|
+
export type BakeCacheEncoding = "png" | "rgba8" | "deflate";
|
|
4
|
+
export interface BakeCacheTexels {
|
|
5
|
+
channel: BakeCacheChannel;
|
|
6
|
+
encoding: BakeCacheEncoding;
|
|
7
|
+
bytes: Uint8Array;
|
|
8
|
+
}
|
|
9
|
+
export interface BakeCacheEntryMeta {
|
|
10
|
+
id: string;
|
|
11
|
+
key: string;
|
|
12
|
+
schema: string;
|
|
13
|
+
size: number;
|
|
14
|
+
encoding: BakeCacheEncoding;
|
|
15
|
+
channels: BakeCacheChannel[];
|
|
16
|
+
hasHeight: boolean;
|
|
17
|
+
bytes: number;
|
|
18
|
+
bakeMs: number;
|
|
19
|
+
createdAt: number;
|
|
20
|
+
lastUsedAt: number;
|
|
21
|
+
}
|
|
22
|
+
export interface BakeCacheEntry extends BakeCacheEntryMeta {
|
|
23
|
+
textures: BakeCacheTexels[];
|
|
24
|
+
}
|
|
25
|
+
export interface BakeCachePayload {
|
|
26
|
+
size: number;
|
|
27
|
+
channels: BakeCacheChannel[];
|
|
28
|
+
hasHeight: boolean;
|
|
29
|
+
bakeMs: number;
|
|
30
|
+
textures: BakeCacheTexels[];
|
|
31
|
+
}
|
|
32
|
+
export interface BakeCacheStoreEstimate {
|
|
33
|
+
entries: number;
|
|
34
|
+
bytes: number;
|
|
35
|
+
}
|
|
36
|
+
export interface BakeCacheStore {
|
|
37
|
+
readonly name: string;
|
|
38
|
+
get(id: string): Promise<BakeCacheEntry | null>;
|
|
39
|
+
put(entry: BakeCacheEntry): Promise<void>;
|
|
40
|
+
touch(id: string, lastUsedAt: number): Promise<void>;
|
|
41
|
+
delete(id: string): Promise<void>;
|
|
42
|
+
list(): Promise<BakeCacheEntryMeta[]>;
|
|
43
|
+
clear(): Promise<void>;
|
|
44
|
+
estimate?(): Promise<BakeCacheStoreEstimate>;
|
|
45
|
+
}
|
|
46
|
+
export type BakeCacheEventKind = "hit" | "miss" | "write" | "evict" | "collision" | "disabled" | "error";
|
|
47
|
+
export interface BakeCacheEvent {
|
|
48
|
+
kind: BakeCacheEventKind;
|
|
49
|
+
id?: string;
|
|
50
|
+
bytes?: number;
|
|
51
|
+
message?: string;
|
|
52
|
+
}
|
|
53
|
+
export interface BakeCacheOptions {
|
|
54
|
+
enabled?: boolean;
|
|
55
|
+
store?: BakeCacheStore;
|
|
56
|
+
encoding?: BakeCacheEncoding;
|
|
57
|
+
budgetBytes?: number;
|
|
58
|
+
maxEntryBytes?: number;
|
|
59
|
+
minBakeMs?: number;
|
|
60
|
+
writeDelayMs?: number;
|
|
61
|
+
worker?: boolean | {
|
|
62
|
+
url: string | URL;
|
|
63
|
+
};
|
|
64
|
+
namespace?: string;
|
|
65
|
+
onEvent?: (event: BakeCacheEvent) => void;
|
|
66
|
+
}
|
|
67
|
+
export interface BakeCacheMetrics {
|
|
68
|
+
enabled: boolean;
|
|
69
|
+
available: boolean;
|
|
70
|
+
store: string;
|
|
71
|
+
encoding: BakeCacheEncoding;
|
|
72
|
+
entries: number;
|
|
73
|
+
bytes: number;
|
|
74
|
+
budgetBytes: number;
|
|
75
|
+
hits: number;
|
|
76
|
+
misses: number;
|
|
77
|
+
writes: number;
|
|
78
|
+
evictions: number;
|
|
79
|
+
collisions: number;
|
|
80
|
+
quota: {
|
|
81
|
+
usage: number;
|
|
82
|
+
quota: number;
|
|
83
|
+
} | null;
|
|
84
|
+
lastError: string | null;
|
|
85
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { BakeCacheEncoding, BakeCacheStore } from "./types";
|
|
2
|
+
export interface WorkerCacheStoreOptions {
|
|
3
|
+
namespace?: string;
|
|
4
|
+
encoding?: BakeCacheEncoding;
|
|
5
|
+
url?: string | URL;
|
|
6
|
+
}
|
|
7
|
+
export declare function isWorkerCacheAvailable(): boolean;
|
|
8
|
+
export declare function createWorkerCacheStore(options?: WorkerCacheStoreOptions): BakeCacheStore | null;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import * as THREE from "three";
|
|
2
2
|
import { RenderTarget, type WebGPURenderer, type MeshBasicNodeMaterial } from "three/webgpu";
|
|
3
3
|
import { type CacheEntry, type ParamUsage } from "./compiler";
|
|
4
|
+
import type { BakeTextureCache } from "../cache/bake-cache";
|
|
5
|
+
import { type BakeCacheKey } from "../cache/key";
|
|
6
|
+
import type { BakeCacheChannel, BakeCacheMetrics } from "../cache/types";
|
|
4
7
|
import { type NodeProfileOptions, type NodeProfileReport } from "./node-profiler";
|
|
5
8
|
import type { MaterialGraphSource, MaterialValue, PbrSocket } from "./types";
|
|
6
9
|
export declare const SURFACE_CHANNELS: PbrSocket[];
|
|
@@ -10,11 +13,12 @@ export interface BakeOptions {
|
|
|
10
13
|
soloNodeId?: string;
|
|
11
14
|
label?: string;
|
|
12
15
|
source?: string;
|
|
16
|
+
bypassCache?: boolean;
|
|
13
17
|
}
|
|
14
18
|
export interface BakeReport {
|
|
15
19
|
runId: number;
|
|
16
20
|
source: string | undefined;
|
|
17
|
-
phase: "nodes" | "shaders" | "render" | "done";
|
|
21
|
+
phase: "nodes" | "shaders" | "render" | "restore" | "done";
|
|
18
22
|
nodeCount: number;
|
|
19
23
|
compileMs: number;
|
|
20
24
|
texturesTotal: number;
|
|
@@ -34,6 +38,10 @@ export declare class BakedTextureSet {
|
|
|
34
38
|
hasHeight: boolean;
|
|
35
39
|
present: Set<"roughness" | "normal" | "baseColor" | "metallic" | "ambientOcclusion" | "emission">;
|
|
36
40
|
signature: string;
|
|
41
|
+
restored: boolean;
|
|
42
|
+
contentStamp: number;
|
|
43
|
+
lastRebuildMs: number;
|
|
44
|
+
disposed: boolean;
|
|
37
45
|
size: number;
|
|
38
46
|
constructor(size: number, channels: PbrSocket[]);
|
|
39
47
|
resize(size: number): void;
|
|
@@ -46,6 +54,7 @@ export declare class BakedTextureSet {
|
|
|
46
54
|
firstTarget(): RenderTarget | null;
|
|
47
55
|
ensureHeightTarget(): RenderTarget;
|
|
48
56
|
texture(ch: PbrSocket): THREE.Texture | null;
|
|
57
|
+
setPresence(present: Set<PbrSocket>, hasHeight: boolean): boolean;
|
|
49
58
|
allocConstantArray(nodeId: string, key: string, data: ArrayLike<number>, elementType: "float" | "vec3"): MaterialValue;
|
|
50
59
|
debugCounts(): Record<string, number>;
|
|
51
60
|
flushCaches(): void;
|
|
@@ -71,6 +80,9 @@ export declare class MaterialBakeService {
|
|
|
71
80
|
private scratchSize;
|
|
72
81
|
private readonly scratchCaches;
|
|
73
82
|
private scratchCacheSize;
|
|
83
|
+
private cache_;
|
|
84
|
+
private readonly pendingWrites;
|
|
85
|
+
private readonly pendingStores;
|
|
74
86
|
attachRenderer(renderer: WebGPURenderer): void;
|
|
75
87
|
get hasRenderer(): boolean;
|
|
76
88
|
onProgress(cb: (p: BakeProgress) => void): () => void;
|
|
@@ -85,6 +97,14 @@ export declare class MaterialBakeService {
|
|
|
85
97
|
bake(graph: MaterialGraphSource, opts?: BakeOptions): Promise<BakedTextureSet>;
|
|
86
98
|
readImage(graph: MaterialGraphSource, channel: PbrSocket, size?: number): Promise<ImageData | null>;
|
|
87
99
|
gpuInfo(): Record<string, unknown>;
|
|
100
|
+
setCache(cache: BakeTextureCache | null): void;
|
|
101
|
+
get cache(): BakeTextureCache | null;
|
|
102
|
+
cacheMetrics(): Promise<BakeCacheMetrics | null>;
|
|
103
|
+
cacheKeyFor(graph: MaterialGraphSource, size: number, channels?: readonly BakeCacheChannel[]): BakeCacheKey;
|
|
104
|
+
private tryRestoreFromCache;
|
|
105
|
+
scheduleCacheWrite(set: BakedTextureSet, graph: MaterialGraphSource, opts?: BakeOptions): void;
|
|
106
|
+
flushCacheWrites(): Promise<void>;
|
|
107
|
+
private runCacheWrite;
|
|
88
108
|
private scratchTarget;
|
|
89
109
|
profileNodes(graph: MaterialGraphSource, opts?: NodeProfileOptions): Promise<NodeProfileReport>;
|
|
90
110
|
private scratchCache;
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { RenderTarget, MeshBasicNodeMaterial, type WebGPURenderer } from "three/webgpu";
|
|
2
|
+
import { type Texture } from "three";
|
|
2
3
|
import type { MaterialValue, PbrSocket } from "./types";
|
|
3
4
|
export declare const FIELD_CHANNELS: PbrSocket[];
|
|
4
5
|
export declare const COLOR_CHANNELS: PbrSocket[];
|
|
5
6
|
export declare function encodeChannel(node: MaterialValue, channel: PbrSocket): MaterialValue;
|
|
6
7
|
export declare function ssPoolInfo(): string[];
|
|
7
8
|
export declare function makeChannelMaterial(): MeshBasicNodeMaterial;
|
|
9
|
+
export declare function configureChannelTexture(t: Texture, ch: PbrSocket | "height"): void;
|
|
10
|
+
export declare const MAX_ANISOTROPY = 8;
|
|
8
11
|
export declare function compileMaterialsAsync(renderer: WebGPURenderer, materials: MeshBasicNodeMaterial[], destSize: number): Promise<void>;
|
|
9
12
|
export declare function renderCacheToTarget(renderer: WebGPURenderer, material: MeshBasicNodeMaterial, rt: RenderTarget): void;
|
|
10
13
|
export declare function renderMaterialToTarget(renderer: WebGPURenderer, material: MeshBasicNodeMaterial, rt: RenderTarget, isNormal?: boolean): void;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
import type { RenderTarget, WebGPURenderer } from "three/webgpu";
|
|
3
|
+
import type { PbrSocket } from "./types";
|
|
4
|
+
export declare function channelByteLength(size: number): number;
|
|
5
|
+
export declare function paddedBytesPerRow(width: number): number;
|
|
6
|
+
export declare function depadRows(buffer: Uint8Array, width: number, height: number): Uint8Array;
|
|
7
|
+
export declare function flipRows(buffer: Uint8Array, width: number, height: number): Uint8Array;
|
|
8
|
+
export declare function flipRowsInto(buffer: Uint8Array, out: Uint8Array, width: number, height: number): void;
|
|
9
|
+
export declare function readTargetTexels(renderer: WebGPURenderer, rt: RenderTarget): Promise<Uint8Array>;
|
|
10
|
+
export declare function transferPoolInfo(): string[];
|
|
11
|
+
export declare function disposeTransferPool(): void;
|
|
12
|
+
export declare function channelDataTexture(texels: Uint8Array, size: number, channel: PbrSocket | "height"): THREE.DataTexture;
|
|
13
|
+
export declare function writeTargetTexels(renderer: WebGPURenderer, rt: RenderTarget, texels: Uint8Array): void;
|
|
@@ -24,6 +24,7 @@ export declare class TexturedSurface {
|
|
|
24
24
|
private updateInFlight;
|
|
25
25
|
private pendingKind;
|
|
26
26
|
private flushRequested;
|
|
27
|
+
private bypassCacheOnce;
|
|
27
28
|
private readonly listeners;
|
|
28
29
|
private readonly textureListeners;
|
|
29
30
|
private lastError_;
|
|
@@ -63,6 +64,7 @@ export declare class TexturedSurface {
|
|
|
63
64
|
private pump;
|
|
64
65
|
private rerenderOffline;
|
|
65
66
|
private surfaceBakeSize;
|
|
67
|
+
get bakeSize(): number;
|
|
66
68
|
private rebuild;
|
|
67
69
|
private buildLive;
|
|
68
70
|
private wire;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
export { MaterialGraphRuntime, type MaterialGraphRuntimeOptions } from "./runtime";
|
|
1
|
+
export { MaterialGraphRuntime, type CachedChannelTextures, type MaterialGraphRuntimeOptions, } from "./runtime";
|
|
2
2
|
export { MATERIAL_DOCUMENT_VERSION, MaterialGraphSession, cloneMaterialDocument, createDefaultMaterialDocument, migrateMaterialDocument, } from "./document";
|
|
3
|
-
export {
|
|
3
|
+
export { MATERIAL_RUNTIME_VERSION } from "./version";
|
|
4
|
+
export { canonicalStringify, createMaterialParamKey, createMaterialTopologyKey } from "./topology";
|
|
5
|
+
export * from "./cache";
|
|
4
6
|
export { MaterialBakeService, BakedTextureSet, SURFACE_CHANNELS, bakeService, type BakeOptions, type BakeReport, } from "./graph/bake-service";
|
|
5
7
|
export { type NodeProfileOptions, type NodeProfileReport, type NodeProfileRow, } from "./graph/node-profiler";
|
|
6
8
|
export { compileGraph, compileSockets, countGraphNodes, newSurfaceMaterial, readMaterialSurface, readMaterialConfig, readOutputResolution, type CompileOptions, type CompiledSockets, type MaterialConfig, } from "./graph/compiler";
|
|
7
9
|
export { buildMeshMaterial, type ChannelTextures } from "./graph/mesh-material";
|
|
10
|
+
export { channelByteLength, channelDataTexture, depadRows, disposeTransferPool, flipRows, paddedBytesPerRow, readTargetTexels, transferPoolInfo, writeTargetTexels, } from "./graph/texture-transfer";
|
|
8
11
|
export { NodeRegistry, createDefaultRegistry, defaultRegistry, nodeParamDefs, nodePorts, } from "./graph/registry";
|
|
9
12
|
export { TexturedSurface } from "./graph/textured-surface";
|
|
10
13
|
export { runTilingTest } from "./graph/tiling-test";
|