@aethervtt/shard-render 0.0.1 → 0.2.0
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/dist/ablation.d.ts +74 -0
- package/dist/ablation.js +117 -0
- package/dist/assets.d.ts +3 -0
- package/dist/assets.js +43 -2
- package/dist/atmosphere-nodes.d.ts +9 -0
- package/dist/atmosphere-nodes.js +157 -30
- package/dist/atmosphere-plugin.js +8 -2
- package/dist/atmosphere-shaders.js +17 -5
- package/dist/baseline/atmosphere.d.ts +16 -0
- package/dist/baseline/atmosphere.js +301 -0
- package/dist/baseline/data-texture.d.ts +36 -0
- package/dist/baseline/data-texture.js +181 -0
- package/dist/baseline/environment.d.ts +14 -0
- package/dist/baseline/environment.js +368 -0
- package/dist/baseline/exposure.d.ts +25 -0
- package/dist/baseline/exposure.js +133 -0
- package/dist/baseline/lights.d.ts +33 -0
- package/dist/baseline/lights.js +133 -0
- package/dist/baseline/textures.d.ts +97 -0
- package/dist/baseline/textures.js +203 -0
- package/dist/camera.d.ts +2 -0
- package/dist/camera.js +4 -0
- package/dist/crowd.d.ts +21 -0
- package/dist/crowd.js +121 -0
- package/dist/culling-shaders.js +21 -2
- package/dist/culling.d.ts +11 -2
- package/dist/culling.js +44 -18
- package/dist/cutaway-plugin.d.ts +48 -0
- package/dist/cutaway-plugin.js +364 -0
- package/dist/cutaway.d.ts +27 -0
- package/dist/cutaway.js +49 -0
- package/dist/data-store.d.ts +69 -0
- package/dist/data-store.js +105 -0
- package/dist/debug-shaders.js +17 -3
- package/dist/debug-views.js +4 -2
- package/dist/decals-plugin.d.ts +5 -0
- package/dist/decals-plugin.js +54 -0
- package/dist/decals.d.ts +69 -0
- package/dist/decals.js +326 -0
- package/dist/deferred-shaders.js +2 -0
- package/dist/deferred.js +28 -4
- package/dist/deform.d.ts +5 -4
- package/dist/deform.js +4 -9
- package/dist/describe-plugin.d.ts +4 -2
- package/dist/describe-plugin.js +75 -3
- package/dist/display-nodes.js +37 -13
- package/dist/dynamic-resolution.d.ts +2 -0
- package/dist/dynamic-resolution.js +6 -1
- package/dist/environment-plugin.js +17 -2
- package/dist/environment-state.d.ts +7 -3
- package/dist/environment-state.js +20 -9
- package/dist/environment.js +7 -1
- package/dist/features.d.ts +49 -0
- package/dist/features.js +106 -0
- package/dist/foliage-budget.d.ts +64 -0
- package/dist/foliage-budget.js +108 -0
- package/dist/foliage-path.d.ts +19 -0
- package/dist/foliage-path.js +7 -0
- package/dist/foliage-shaders.d.ts +11 -0
- package/dist/foliage-shaders.js +504 -0
- package/dist/foliage.d.ts +194 -0
- package/dist/foliage.js +1027 -0
- package/dist/forward.d.ts +38 -2
- package/dist/forward.js +608 -186
- package/dist/fxaa.js +12 -2
- package/dist/gizmos-plugin.js +7 -0
- package/dist/gizmos.d.ts +3 -3
- package/dist/gizmos.js +11 -13
- package/dist/gpu-assets.d.ts +58 -4
- package/dist/gpu-assets.js +191 -8
- package/dist/graph.d.ts +45 -0
- package/dist/graph.js +169 -19
- package/dist/health.d.ts +58 -0
- package/dist/health.js +112 -0
- package/dist/index.d.ts +40 -11
- package/dist/index.js +343 -9
- package/dist/instances.d.ts +119 -8
- package/dist/instances.js +387 -78
- package/dist/interior-plugin.d.ts +121 -0
- package/dist/interior-plugin.js +465 -0
- package/dist/interior.d.ts +24 -0
- package/dist/interior.js +13 -0
- package/dist/layers.d.ts +35 -0
- package/dist/layers.js +49 -0
- package/dist/lens-plugin.d.ts +43 -0
- package/dist/lens-plugin.js +304 -0
- package/dist/lens.d.ts +41 -0
- package/dist/lens.js +99 -0
- package/dist/lights.d.ts +50 -5
- package/dist/lights.js +101 -22
- package/dist/material-pipelines.d.ts +37 -4
- package/dist/material-pipelines.js +102 -5
- package/dist/materials.d.ts +23 -0
- package/dist/materials.js +44 -1
- package/dist/outline-plugin.d.ts +48 -0
- package/dist/outline-plugin.js +529 -0
- package/dist/outline.d.ts +15 -0
- package/dist/outline.js +42 -0
- package/dist/overlay-registry.d.ts +3 -1
- package/dist/overlay-registry.js +3 -1
- package/dist/overlays.js +6 -2
- package/dist/perf-overlay.d.ts +9 -0
- package/dist/perf-overlay.js +125 -0
- package/dist/picking.d.ts +17 -0
- package/dist/picking.js +40 -11
- package/dist/pixel-perfect-plugin.js +10 -2
- package/dist/plugin.d.ts +69 -7
- package/dist/plugin.js +223 -25
- package/dist/post-common.d.ts +22 -2
- package/dist/post-common.js +82 -2
- package/dist/post-nodes.d.ts +7 -1
- package/dist/post-nodes.js +235 -111
- package/dist/post-plugin.js +5 -3
- package/dist/post-scene.d.ts +130 -0
- package/dist/post-scene.js +164 -0
- package/dist/post-shaders.js +85 -62
- package/dist/post.js +11 -1
- package/dist/projection.d.ts +24 -0
- package/dist/projection.js +87 -0
- package/dist/readback.d.ts +1 -0
- package/dist/readback.js +1 -0
- package/dist/render-scale.js +1 -0
- package/dist/ribbons-plugin.d.ts +5 -0
- package/dist/ribbons-plugin.js +59 -0
- package/dist/ribbons.d.ts +44 -0
- package/dist/ribbons.js +288 -0
- package/dist/scenario.d.ts +70 -0
- package/dist/scenario.js +125 -0
- package/dist/screen-effects.d.ts +97 -0
- package/dist/screen-effects.js +192 -0
- package/dist/shaders.d.ts +13 -0
- package/dist/shaders.js +189 -34
- package/dist/shadow-catcher.d.ts +9 -0
- package/dist/shadow-catcher.js +133 -0
- package/dist/shadows.d.ts +27 -2
- package/dist/shadows.js +100 -20
- package/dist/standard-fields.d.ts +1 -0
- package/dist/standard-fields.js +5 -0
- package/dist/standard.js +18 -0
- package/dist/stats.d.ts +45 -1
- package/dist/stats.js +91 -3
- package/dist/surface-model.d.ts +57 -0
- package/dist/surface-model.js +289 -0
- package/dist/surface-shaders.d.ts +3 -0
- package/dist/surface-shaders.js +160 -0
- package/dist/surface.d.ts +40 -0
- package/dist/surface.js +86 -0
- package/dist/target.d.ts +1 -13
- package/dist/target.js +1 -26
- package/dist/testing.d.ts +11 -0
- package/dist/testing.js +39 -1
- package/dist/tier.d.ts +13 -0
- package/dist/tier.js +14 -0
- package/dist/timer.d.ts +18 -1
- package/dist/timer.js +73 -20
- package/dist/variants.d.ts +37 -0
- package/dist/variants.js +104 -0
- package/dist/vfx-draw.d.ts +37 -0
- package/dist/vfx-draw.js +291 -0
- package/dist/vfx-state.d.ts +42 -0
- package/dist/vfx-state.js +81 -0
- package/dist/vfx.d.ts +69 -0
- package/dist/vfx.js +103 -0
- package/dist/view-visibility-plugin.d.ts +61 -0
- package/dist/view-visibility-plugin.js +267 -0
- package/dist/view-visibility.d.ts +28 -0
- package/dist/view-visibility.js +46 -0
- package/dist/view.d.ts +29 -1
- package/dist/view.js +57 -34
- package/package.json +23 -11
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { type World } from '@aethervtt/shard-core';
|
|
2
|
+
/**
|
|
3
|
+
* Per-pass GPU cost by ablation (0075). On tile-based GPUs passes overlap and a pass's timestamps
|
|
4
|
+
* include waiting for earlier work, so per-pass times add up to more than the frame. `gpu:frame`
|
|
5
|
+
* (first pass start to last pass end) stays true, so a pass's cost is how much `gpu:frame` drops
|
|
6
|
+
* with the pass disabled.
|
|
7
|
+
*/
|
|
8
|
+
export interface AblationOptions {
|
|
9
|
+
/** Render-graph nodes to measure, each on its own. */
|
|
10
|
+
passes: readonly string[];
|
|
11
|
+
/** Frames measured per pass and round (the median is kept). Default 120. */
|
|
12
|
+
frames?: number;
|
|
13
|
+
/** Rounds, each measuring every pass and the baseline; the median over rounds is kept. Default 5. */
|
|
14
|
+
rounds?: number;
|
|
15
|
+
/** Frames dropped after each change, while timings of the previous state land. Default 4. */
|
|
16
|
+
settle?: number;
|
|
17
|
+
/** Also measure every pass disabled at once. */
|
|
18
|
+
together?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Names in `passes` that stand for several nodes, disabled together (0075's scenario tests ablate
|
|
21
|
+
* a slice: every node whose `gpu:<node>` its key covers).
|
|
22
|
+
*/
|
|
23
|
+
groups?: Readonly<Record<string, readonly string[]>>;
|
|
24
|
+
}
|
|
25
|
+
export interface PassCost {
|
|
26
|
+
/** The node, or every node joined with '+' for `together`. */
|
|
27
|
+
pass: string;
|
|
28
|
+
/** Median over rounds of baseline `gpu:frame` minus `gpu:frame` with the pass disabled, in ms. */
|
|
29
|
+
ms: number;
|
|
30
|
+
/** Each round's difference. */
|
|
31
|
+
rounds: number[];
|
|
32
|
+
}
|
|
33
|
+
export interface AblationResult {
|
|
34
|
+
/** Median `gpu:frame` with every pass enabled, in ms. */
|
|
35
|
+
frameMs: number;
|
|
36
|
+
passes: PassCost[];
|
|
37
|
+
/** Every pass disabled at once, with `together`. */
|
|
38
|
+
together?: PassCost;
|
|
39
|
+
frames: number;
|
|
40
|
+
rounds: number;
|
|
41
|
+
/** GPU frame timings read, all states together. */
|
|
42
|
+
samples: number;
|
|
43
|
+
}
|
|
44
|
+
export interface PassCostsData {
|
|
45
|
+
/**
|
|
46
|
+
* The latest ablation in this app, and when it finished (`Time.elapsed`, s). The `perf` overlay
|
|
47
|
+
* and the playground HUD rank passes by it where their timestamps overlap (0075).
|
|
48
|
+
*/
|
|
49
|
+
latest: (AblationResult & {
|
|
50
|
+
at: number;
|
|
51
|
+
}) | undefined;
|
|
52
|
+
}
|
|
53
|
+
/** The latest `perf.ablate` result, where the overlay, the HUD and `perf.describe` read it. */
|
|
54
|
+
export declare const PassCosts: import("@aethervtt/shard-core").ResourceDef<PassCostsData>;
|
|
55
|
+
/** How a measurement drives the app: what to disable, and one frame's `gpu:frame`. */
|
|
56
|
+
export interface AblationDriver {
|
|
57
|
+
disable(passes: readonly string[]): void;
|
|
58
|
+
/** Renders a frame and resolves its `gpu:frame` in ms, or undefined when none landed. */
|
|
59
|
+
frame(): Promise<number | undefined>;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The measurement, apart from any app: each round measures the baseline, every pass disabled in
|
|
63
|
+
* turn (in reverse order on odd rounds), and the baseline again; a pass's difference in a round is
|
|
64
|
+
* the mean of the two baselines minus its median. Interleaving rounds cancels thermal drift, as
|
|
65
|
+
* `structure/src/cutaway.test.ts` does. Restores every pass before it returns or throws.
|
|
66
|
+
*/
|
|
67
|
+
export declare function runAblation(driver: AblationDriver, options: AblationOptions): Promise<AblationResult>;
|
|
68
|
+
/**
|
|
69
|
+
* Ablates render-graph passes in a running app: `frame` produces one frame (rendering without
|
|
70
|
+
* advancing time, or waiting for the loop). Reads `gpu:frame` from the graph's `GpuTimer`, so the
|
|
71
|
+
* device needs `timestamp-query` (`render/gpu-timing-unavailable` otherwise). Unknown passes throw
|
|
72
|
+
* `render/unknown-node`.
|
|
73
|
+
*/
|
|
74
|
+
export declare function ablatePasses(world: World, options: AblationOptions, frame: () => Promise<void> | void): Promise<AblationResult>;
|
package/dist/ablation.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { defineResource, ShardError } from "@aethervtt/shard-core";
|
|
2
|
+
import { PerfProviders, Time } from "@aethervtt/shard-runtime";
|
|
3
|
+
import { Gpu, Graph } from "./plugin.js";
|
|
4
|
+
const PassCosts = defineResource("render/PassCosts", {
|
|
5
|
+
description: "The latest ablation's per-pass GPU costs (0075): what ranks passes where their timestamps overlap.",
|
|
6
|
+
init: () => ({ latest: void 0 })
|
|
7
|
+
});
|
|
8
|
+
function keep(world, result) {
|
|
9
|
+
const costs = world.initResource(PassCosts);
|
|
10
|
+
costs.latest = { ...result, at: world.tryResource(Time)?.elapsed ?? 0 };
|
|
11
|
+
const sections = world.initResource(PerfProviders).sections;
|
|
12
|
+
if (!sections.has("ablation")) {
|
|
13
|
+
sections.set("ablation", (w) => {
|
|
14
|
+
const latest = w.tryResource(PassCosts)?.latest;
|
|
15
|
+
if (!latest) return void 0;
|
|
16
|
+
return {
|
|
17
|
+
at: latest.at,
|
|
18
|
+
frameMs: latest.frameMs,
|
|
19
|
+
passes: [...latest.passes].sort((a, b) => b.ms - a.ms).map((p) => ({ pass: p.pass, ms: p.ms })),
|
|
20
|
+
...latest.together ? { together: latest.together.ms } : {}
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const median = (values) => {
|
|
26
|
+
if (values.length === 0) return Number.NaN;
|
|
27
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
28
|
+
const mid = sorted.length >> 1;
|
|
29
|
+
return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
|
|
30
|
+
};
|
|
31
|
+
async function runAblation(driver, options) {
|
|
32
|
+
const frames = options.frames ?? 120;
|
|
33
|
+
const rounds = options.rounds ?? 5;
|
|
34
|
+
const settle = options.settle ?? 4;
|
|
35
|
+
const sets = options.passes.map((p) => [p]);
|
|
36
|
+
if (options.together && options.passes.length > 1) sets.push(options.passes);
|
|
37
|
+
let samples = 0;
|
|
38
|
+
const measure = async (disabled) => {
|
|
39
|
+
driver.disable(disabled);
|
|
40
|
+
for (let i = 0; i < settle; i++) await driver.frame();
|
|
41
|
+
const times = [];
|
|
42
|
+
for (let i = 0; i < frames; i++) {
|
|
43
|
+
const ms = await driver.frame();
|
|
44
|
+
if (ms !== void 0) times.push(ms);
|
|
45
|
+
}
|
|
46
|
+
samples += times.length;
|
|
47
|
+
return median(times);
|
|
48
|
+
};
|
|
49
|
+
const diffs = sets.map(() => []);
|
|
50
|
+
const baselines = [];
|
|
51
|
+
try {
|
|
52
|
+
for (let round = 0; round < rounds; round++) {
|
|
53
|
+
const before = await measure([]);
|
|
54
|
+
const order = sets.map((_, i) => i);
|
|
55
|
+
if (round % 2 === 1) order.reverse();
|
|
56
|
+
const off = /* @__PURE__ */ new Map();
|
|
57
|
+
for (const i of order) off.set(i, await measure(sets[i]));
|
|
58
|
+
const after = await measure([]);
|
|
59
|
+
const base = (before + after) / 2;
|
|
60
|
+
baselines.push(base);
|
|
61
|
+
for (let i = 0; i < sets.length; i++) diffs[i].push(base - off.get(i));
|
|
62
|
+
}
|
|
63
|
+
} finally {
|
|
64
|
+
driver.disable([]);
|
|
65
|
+
}
|
|
66
|
+
const costs = sets.map(
|
|
67
|
+
(set, i) => ({ pass: set.join("+"), ms: median(diffs[i]), rounds: diffs[i] })
|
|
68
|
+
);
|
|
69
|
+
const together = options.together && options.passes.length > 1 ? costs.pop() : void 0;
|
|
70
|
+
return {
|
|
71
|
+
frameMs: median(baselines),
|
|
72
|
+
passes: costs,
|
|
73
|
+
...together ? { together } : {},
|
|
74
|
+
frames,
|
|
75
|
+
rounds,
|
|
76
|
+
samples
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
async function ablatePasses(world, options, frame) {
|
|
80
|
+
const graph = world.resource(Graph);
|
|
81
|
+
const gpu = world.resource(Gpu);
|
|
82
|
+
if (!graph.timer.enabled) {
|
|
83
|
+
throw new ShardError("render/gpu-timing-unavailable", "This device can't time GPU passes", {
|
|
84
|
+
hint: "Ablation reads gpu:frame, which needs the 'timestamp-query' feature."
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
if (options.passes.length === 0) {
|
|
88
|
+
throw new ShardError("render/unknown-node", "No passes to ablate", {
|
|
89
|
+
hint: `Name render graph nodes: ${graph.nodeNames().join(", ")}.`
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
const groups = options.groups;
|
|
93
|
+
const nodesOf = (passes) => groups ? passes.flatMap((p) => groups[p] ?? [p]) : passes;
|
|
94
|
+
graph.ablate(nodesOf(options.passes));
|
|
95
|
+
graph.ablate([]);
|
|
96
|
+
const timer = graph.timer;
|
|
97
|
+
const driver = {
|
|
98
|
+
disable: (passes) => graph.ablate(nodesOf(passes)),
|
|
99
|
+
async frame() {
|
|
100
|
+
const seen = timer.frameSamples;
|
|
101
|
+
await frame();
|
|
102
|
+
for (let i = 0; i < 8 && timer.frameSamples === seen; i++) {
|
|
103
|
+
await gpu.device.queue.onSubmittedWorkDone();
|
|
104
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
105
|
+
}
|
|
106
|
+
return timer.frameSamples === seen ? void 0 : timer.frameMs;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
const result = await runAblation(driver, options);
|
|
110
|
+
keep(world, result);
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
export {
|
|
114
|
+
PassCosts,
|
|
115
|
+
ablatePasses,
|
|
116
|
+
runAblation
|
|
117
|
+
};
|
package/dist/assets.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export declare const StandardMaterial: import("@aethervtt/shard-core").Component
|
|
|
12
12
|
emissive: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Color, "f32">;
|
|
13
13
|
emissiveLuminance: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
14
14
|
doubleSided: import("@aethervtt/shard-core").FieldType<boolean, "u8">;
|
|
15
|
+
deferUntilReady: import("@aethervtt/shard-core").FieldType<boolean, "u8">;
|
|
15
16
|
alphaMode: import("@aethervtt/shard-core").FieldType<"additive" | "alpha" | "mask" | "opaque" | "premultiplied", "u8">;
|
|
16
17
|
alphaCutoff: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
17
18
|
normalScale: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
@@ -94,6 +95,8 @@ export declare const Materials: import("@aethervtt/shard-core").ResourceDef<Asse
|
|
|
94
95
|
export declare const RenderTargets: import("@aethervtt/shard-core").ResourceDef<AssetStore<RenderTarget, "RenderTarget">>;
|
|
95
96
|
/** Meshes load from binary artifacts (`encodeMesh`); a reload updates the mesh in place. */
|
|
96
97
|
export declare const MeshAsset: import("@aethervtt/shard-assets").AssetTypeDef<Mesh>;
|
|
98
|
+
/** What a mesh that failed to load shows (0061): a 1 m beveled box, drawn with the missing material. */
|
|
99
|
+
export declare function missingMesh(): Mesh;
|
|
97
100
|
/** Materials load from material JSON (any type); a reload bumps the material's version. */
|
|
98
101
|
export declare const MaterialAssetType: import("@aethervtt/shard-assets").AssetTypeDef<MaterialAsset>;
|
|
99
102
|
/**
|
package/dist/assets.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
defineSchema,
|
|
6
6
|
ShardError
|
|
7
7
|
} from "@aethervtt/shard-core";
|
|
8
|
-
import { decodeMesh } from "@aethervtt/shard-mesh";
|
|
8
|
+
import { bevelBox, decodeMesh } from "@aethervtt/shard-mesh";
|
|
9
9
|
import {
|
|
10
10
|
allMaterialTypes,
|
|
11
11
|
findMaterialType,
|
|
@@ -103,8 +103,20 @@ const RenderTargets = defineResource(
|
|
|
103
103
|
const MeshAsset = defineAssetType("Mesh", {
|
|
104
104
|
store: Meshes,
|
|
105
105
|
load: (artifact) => decodeMesh(artifact.bytes),
|
|
106
|
-
update: (existing, next) => existing.update(next.data())
|
|
106
|
+
update: (existing, next) => existing.update(next.data()),
|
|
107
|
+
fallback: () => missingMesh(),
|
|
108
|
+
cost: (mesh) => ({ triangles: Math.floor(mesh.drawCount / 3), bytes: meshBytes(mesh) })
|
|
107
109
|
});
|
|
110
|
+
function missingMesh() {
|
|
111
|
+
const mesh = bevelBox({ x: 1, y: 1, z: 1 });
|
|
112
|
+
mesh.missing = true;
|
|
113
|
+
return mesh;
|
|
114
|
+
}
|
|
115
|
+
function meshBytes(mesh) {
|
|
116
|
+
let bytes = mesh.positions.byteLength + (mesh.indices?.byteLength ?? 0);
|
|
117
|
+
for (const a of [mesh.normals, mesh.uvs, mesh.uvs1, mesh.tangents]) bytes += a?.byteLength ?? 0;
|
|
118
|
+
return bytes;
|
|
119
|
+
}
|
|
108
120
|
const MaterialAssetType = defineAssetType("Material", {
|
|
109
121
|
store: Materials,
|
|
110
122
|
// Texture paths resolve to guids here, so the renderer can look them up directly.
|
|
@@ -115,6 +127,8 @@ const MaterialAssetType = defineAssetType("Material", {
|
|
|
115
127
|
};
|
|
116
128
|
const errors = validateMaterial(artifact.json, { resolveAsset });
|
|
117
129
|
if (errors.length > 0) {
|
|
130
|
+
const color = readableBaseColor(artifact.json);
|
|
131
|
+
if (color) readableColors.set(ctx.guid, color);
|
|
118
132
|
const first = errors[0];
|
|
119
133
|
throw new ShardError("assets/load-failed", `${ctx.path}: ${first.message}`, {
|
|
120
134
|
path: first.path,
|
|
@@ -127,8 +141,34 @@ const MaterialAssetType = defineAssetType("Material", {
|
|
|
127
141
|
update: (existing, next) => {
|
|
128
142
|
existing.type = next.type;
|
|
129
143
|
existing.set(next.value);
|
|
144
|
+
},
|
|
145
|
+
references: (material) => handlesIn(material.value),
|
|
146
|
+
// A material that failed to load draws as the standard one, in its own base color if readable.
|
|
147
|
+
fallback: ({ guid }) => {
|
|
148
|
+
const baseColor = readableColors.get(guid);
|
|
149
|
+
readableColors.delete(guid);
|
|
150
|
+
return new MaterialAsset(baseColor ? { baseColor } : {});
|
|
130
151
|
}
|
|
131
152
|
});
|
|
153
|
+
function* handlesIn(value) {
|
|
154
|
+
if (value === null || typeof value !== "object" || ArrayBuffer.isView(value)) return;
|
|
155
|
+
if (Array.isArray(value)) {
|
|
156
|
+
for (const v of value) yield* handlesIn(v);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (typeof value.guid === "string") yield value;
|
|
160
|
+
for (const v of Object.values(value)) yield* handlesIn(v);
|
|
161
|
+
}
|
|
162
|
+
const readableColors = /* @__PURE__ */ new Map();
|
|
163
|
+
function readableBaseColor(json) {
|
|
164
|
+
const raw = json?.baseColor;
|
|
165
|
+
if (raw === void 0) return void 0;
|
|
166
|
+
try {
|
|
167
|
+
return [...StandardMaterial.deserialize({ baseColor: raw }).baseColor];
|
|
168
|
+
} catch {
|
|
169
|
+
return void 0;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
132
172
|
const MaterialImporter = defineImporter({
|
|
133
173
|
name: "data/material",
|
|
134
174
|
version: 2,
|
|
@@ -209,5 +249,6 @@ export {
|
|
|
209
249
|
materialFields,
|
|
210
250
|
materialFromJson,
|
|
211
251
|
materialTypeOf,
|
|
252
|
+
missingMesh,
|
|
212
253
|
validateMaterial
|
|
213
254
|
};
|
|
@@ -12,6 +12,11 @@ interface CameraGpu {
|
|
|
12
12
|
transmittance: GPUTexture | undefined;
|
|
13
13
|
/** The frame the sky-view and froxels were last computed. */
|
|
14
14
|
computed: number;
|
|
15
|
+
/**
|
|
16
|
+
* Whether this frame's uniform holds the atmospheres (it's zeros until their LUTs exist): a bake
|
|
17
|
+
* waits for it, or the LUTs computed this frame would bake a black sky.
|
|
18
|
+
*/
|
|
19
|
+
packed: boolean;
|
|
15
20
|
generation: number;
|
|
16
21
|
seen: number;
|
|
17
22
|
}
|
|
@@ -34,6 +39,8 @@ export declare class AtmosphereGpu {
|
|
|
34
39
|
frame: number;
|
|
35
40
|
readonly pipelines: Map<string, GPUComputePipeline | GPURenderPipeline>;
|
|
36
41
|
layouts: Record<string, GPUBindGroupLayout> | undefined;
|
|
42
|
+
/** The baseline tier's passes (0064): fragment passes, loaded only on a baseline device. */
|
|
43
|
+
baseline: typeof import('./baseline/atmosphere.js') | undefined;
|
|
37
44
|
private readonly groups;
|
|
38
45
|
ensure(gpu: GpuContext): void;
|
|
39
46
|
/** A layer for a record: its own, a free one, or the least recently used. */
|
|
@@ -42,6 +49,8 @@ export declare class AtmosphereGpu {
|
|
|
42
49
|
group(gpu: GpuContext, slot: string, key: string, layout: GPUBindGroupLayout, entries: () => GPUBindGroupEntry[]): GPUBindGroup;
|
|
43
50
|
}
|
|
44
51
|
export declare const AtmosphereGpuResource: import("@aethervtt/shard-core").ResourceDef<AtmosphereGpu>;
|
|
52
|
+
/** Writes a record's parameters (and the camera's origin in it) at `o`. */
|
|
53
|
+
export declare function packParams(rec: AtmosphereRecord, out: Float32Array, o: number, origin: Float64Array | undefined, oi: number): void;
|
|
45
54
|
/**
|
|
46
55
|
* Gives every listed atmosphere a LUT layer and uploads each camera's AtmosphereView uniform, and
|
|
47
56
|
* its sky-view and froxel textures at the right sizes.
|