@aethervtt/shard-render 0.0.1 → 0.1.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/assets.d.ts +3 -0
- package/dist/assets.js +43 -2
- package/dist/atmosphere-nodes.d.ts +9 -0
- package/dist/atmosphere-nodes.js +57 -18
- package/dist/atmosphere-plugin.js +8 -2
- 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 +120 -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/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/deferred-shaders.js +2 -0
- package/dist/deferred.js +24 -3
- 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 +15 -4
- 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/forward.d.ts +38 -2
- package/dist/forward.js +582 -184
- 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 +11 -0
- package/dist/graph.js +30 -5
- package/dist/health.d.ts +58 -0
- package/dist/health.js +112 -0
- package/dist/index.d.ts +30 -11
- package/dist/index.js +286 -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/overlays.js +1 -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 +58 -6
- package/dist/plugin.js +191 -24
- package/dist/post-common.d.ts +2 -0
- package/dist/post-common.js +3 -0
- package/dist/post-nodes.d.ts +2 -1
- package/dist/post-nodes.js +111 -19
- package/dist/post-plugin.js +5 -3
- package/dist/post-shaders.js +51 -6
- 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/screen-effects.d.ts +97 -0
- package/dist/screen-effects.js +192 -0
- package/dist/shaders.d.ts +13 -0
- package/dist/shaders.js +131 -29
- package/dist/shadow-catcher.d.ts +9 -0
- package/dist/shadow-catcher.js +133 -0
- package/dist/shadows.d.ts +25 -1
- package/dist/shadows.js +97 -19
- package/dist/standard-fields.d.ts +1 -0
- package/dist/standard-fields.js +5 -0
- package/dist/standard.js +12 -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 +10 -0
- package/dist/testing.js +33 -1
- package/dist/tier.d.ts +13 -0
- package/dist/tier.js +14 -0
- package/dist/timer.d.ts +2 -0
- package/dist/timer.js +7 -1
- package/dist/variants.d.ts +37 -0
- package/dist/variants.js +104 -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 +15 -11
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { type World } from '@aethervtt/shard-core';
|
|
2
|
+
import type { GpuContext } from '@aethervtt/shard-gpu';
|
|
3
|
+
import { Texture } from '@aethervtt/shard-texture';
|
|
4
|
+
import type { GpuTexture } from '../gpu-assets.js';
|
|
5
|
+
/** What a baseline texture asks of the asset layer that owns it. */
|
|
6
|
+
export interface TwinHost {
|
|
7
|
+
readonly gpu: GpuContext;
|
|
8
|
+
/** Reload the texture's artifact (its bytes come back, and its version bumps). */
|
|
9
|
+
reload(texture: Texture): void;
|
|
10
|
+
/** The loading fallback's view of this kind, shown while a twin waits. */
|
|
11
|
+
fallback(srgb: boolean, array: boolean): GPUTextureView;
|
|
12
|
+
/** A twin started waiting for its bytes (`ready` false), or was made (`ready` true). */
|
|
13
|
+
twin(texture: Texture, kind: string, ready: boolean): void;
|
|
14
|
+
/** Development builds: the texture's usage doesn't match the slot reading it. */
|
|
15
|
+
mismatch(texture: Texture, kind: string): void;
|
|
16
|
+
}
|
|
17
|
+
/** A twin that waited for its bytes: which texture and view, and how long it took. */
|
|
18
|
+
export interface TwinWait {
|
|
19
|
+
texture: string;
|
|
20
|
+
kind: string;
|
|
21
|
+
since: number;
|
|
22
|
+
ms: number | undefined;
|
|
23
|
+
}
|
|
24
|
+
/** What the asset layer lends its baseline textures. */
|
|
25
|
+
export interface TwinSource {
|
|
26
|
+
readonly gpu: GpuContext;
|
|
27
|
+
readonly world: World | undefined;
|
|
28
|
+
/** The asset guid of an imported texture. */
|
|
29
|
+
guidOf(texture: Texture): string | undefined;
|
|
30
|
+
/** Reloads the texture's artifact, once at a time. */
|
|
31
|
+
reload(texture: Texture): void;
|
|
32
|
+
/** The loading fallback: a solid white texture. */
|
|
33
|
+
white(): GpuTexture;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The twin bookkeeping of one asset layer: reloads, the loading fallback, the waits
|
|
37
|
+
* `render.describe` lists, and the dev-build `render/texture-color-space-mismatch` log.
|
|
38
|
+
*/
|
|
39
|
+
export declare class Twins implements TwinHost {
|
|
40
|
+
readonly gpu: GpuContext;
|
|
41
|
+
/** Pending twins, and the last 32 finished. */
|
|
42
|
+
readonly waits: TwinWait[];
|
|
43
|
+
private readonly source;
|
|
44
|
+
private readonly mismatched;
|
|
45
|
+
constructor(source: TwinSource);
|
|
46
|
+
reload(texture: Texture): void;
|
|
47
|
+
fallback(srgb: boolean, array: boolean): GPUTextureView;
|
|
48
|
+
twin(texture: Texture, kind: string, ready: boolean): void;
|
|
49
|
+
mismatch(texture: Texture, kind: string): void;
|
|
50
|
+
/** `render.describe → twins`: pending twins with how long they've waited, and recent ones. */
|
|
51
|
+
describe(): {
|
|
52
|
+
pending: {
|
|
53
|
+
texture: string;
|
|
54
|
+
kind: string;
|
|
55
|
+
waitedMs: number;
|
|
56
|
+
}[];
|
|
57
|
+
recent: {
|
|
58
|
+
texture: string;
|
|
59
|
+
kind: string;
|
|
60
|
+
waitedMs: number;
|
|
61
|
+
}[];
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* A texture on the baseline tier: the primary copy, and the twins slots asked for. The views are
|
|
66
|
+
* getters, so a twin is made (or requested) only when something binds it.
|
|
67
|
+
*/
|
|
68
|
+
export declare class BaselineTexture implements GpuTexture {
|
|
69
|
+
readonly texture: GPUTexture;
|
|
70
|
+
readonly version: number;
|
|
71
|
+
readonly generation: number;
|
|
72
|
+
bytes: number;
|
|
73
|
+
private readonly source;
|
|
74
|
+
private readonly host;
|
|
75
|
+
private readonly primarySrgb;
|
|
76
|
+
private readonly primaryDimension;
|
|
77
|
+
private readonly views;
|
|
78
|
+
private readonly pending;
|
|
79
|
+
/** Twins made, destroyed with the primary. */
|
|
80
|
+
readonly twins: GPUTexture[];
|
|
81
|
+
constructor(host: TwinHost, source: Texture);
|
|
82
|
+
get linear(): GPUTextureView;
|
|
83
|
+
get srgb(): GPUTextureView;
|
|
84
|
+
get arrayLinear(): GPUTextureView | undefined;
|
|
85
|
+
get arraySrgb(): GPUTextureView | undefined;
|
|
86
|
+
/** The primary and every twin. */
|
|
87
|
+
destroy(): void;
|
|
88
|
+
/**
|
|
89
|
+
* Whether a slot reading it this way waits for a twin's bytes (and shows the loading fallback).
|
|
90
|
+
* Asking requests the twin, as binding it would: material preparation asks for deferred draws.
|
|
91
|
+
*/
|
|
92
|
+
waiting(wantSrgb: boolean, array: boolean): boolean;
|
|
93
|
+
private keyFor;
|
|
94
|
+
private view;
|
|
95
|
+
}
|
|
96
|
+
/** Solid 1×1 textures for empty slots, as baseline textures that keep their bytes. */
|
|
97
|
+
export declare function solidTexture(host: TwinHost, rgba: readonly number[]): BaselineTexture;
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { ShardError } from "@aethervtt/shard-core";
|
|
2
|
+
import { DevMode, LogResource } from "@aethervtt/shard-runtime";
|
|
3
|
+
import { FORMAT_INFO, Texture } from "@aethervtt/shard-texture";
|
|
4
|
+
class Twins {
|
|
5
|
+
gpu;
|
|
6
|
+
/** Pending twins, and the last 32 finished. */
|
|
7
|
+
waits = [];
|
|
8
|
+
source;
|
|
9
|
+
mismatched = /* @__PURE__ */ new Set();
|
|
10
|
+
constructor(source) {
|
|
11
|
+
this.source = source;
|
|
12
|
+
this.gpu = source.gpu;
|
|
13
|
+
}
|
|
14
|
+
reload(texture) {
|
|
15
|
+
this.source.reload(texture);
|
|
16
|
+
}
|
|
17
|
+
fallback(srgb, array) {
|
|
18
|
+
const white = this.source.white();
|
|
19
|
+
return array ? srgb ? white.arraySrgb : white.arrayLinear : srgb ? white.srgb : white.linear;
|
|
20
|
+
}
|
|
21
|
+
twin(texture, kind, ready) {
|
|
22
|
+
const name = this.source.guidOf(texture) ?? texture.format;
|
|
23
|
+
const waits = this.waits;
|
|
24
|
+
const i = waits.findIndex((w) => w.texture === name && w.kind === kind && w.ms === void 0);
|
|
25
|
+
if (!ready) {
|
|
26
|
+
if (i === -1) waits.push({ texture: name, kind, since: performance.now(), ms: void 0 });
|
|
27
|
+
} else if (i !== -1) {
|
|
28
|
+
waits[i].ms = performance.now() - waits[i].since;
|
|
29
|
+
} else if (waits.length < 256) {
|
|
30
|
+
waits.push({ texture: name, kind, since: performance.now(), ms: 0 });
|
|
31
|
+
}
|
|
32
|
+
while (waits.filter((w) => w.ms !== void 0).length > 32) {
|
|
33
|
+
waits.splice(
|
|
34
|
+
waits.findIndex((w) => w.ms !== void 0),
|
|
35
|
+
1
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
mismatch(texture, kind) {
|
|
40
|
+
const world = this.source.world;
|
|
41
|
+
if (!world?.tryResource(DevMode)?.enabled) return;
|
|
42
|
+
const name = this.source.guidOf(texture);
|
|
43
|
+
if (name === void 0 || this.mismatched.has(`${name}|${kind}`)) return;
|
|
44
|
+
this.mismatched.add(`${name}|${kind}`);
|
|
45
|
+
world.tryResource(LogResource)?.error(
|
|
46
|
+
new ShardError(
|
|
47
|
+
"render/texture-color-space-mismatch",
|
|
48
|
+
`A ${texture.usage} texture is read as ${kind.split("/")[0]}: the baseline tier keeps a second copy of it`,
|
|
49
|
+
{ path: name, hint: "Set its usage to match the slot in its .meta, and the copy goes." }
|
|
50
|
+
)
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
/** `render.describe → twins`: pending twins with how long they've waited, and recent ones. */
|
|
54
|
+
describe() {
|
|
55
|
+
const now = performance.now();
|
|
56
|
+
const pending = [];
|
|
57
|
+
const recent = [];
|
|
58
|
+
for (const w of this.waits) {
|
|
59
|
+
if (w.ms === void 0)
|
|
60
|
+
pending.push({ texture: w.texture, kind: w.kind, waitedMs: now - w.since });
|
|
61
|
+
else recent.push({ texture: w.texture, kind: w.kind, waitedMs: w.ms });
|
|
62
|
+
}
|
|
63
|
+
return { pending, recent };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const DIMENSION = { plain: "2d", array: "2d-array", cube: "cube" };
|
|
67
|
+
const DIMENSIONS = ["2d", "2d-array", "cube"];
|
|
68
|
+
function keyOf(srgb, dimension) {
|
|
69
|
+
return (srgb ? 1 : 0) + DIMENSIONS.indexOf(dimension) * 2;
|
|
70
|
+
}
|
|
71
|
+
function kindOf(key) {
|
|
72
|
+
return `${key & 1 ? "srgb" : "linear"}/${DIMENSIONS[key >> 1]}`;
|
|
73
|
+
}
|
|
74
|
+
function create(gpu, texture, format, dimension) {
|
|
75
|
+
const info = FORMAT_INFO[texture.format];
|
|
76
|
+
const layers = texture.faces * texture.layers;
|
|
77
|
+
const handle = gpu.device.createTexture({
|
|
78
|
+
label: `texture/${format}${dimension === "2d" ? "" : `/${dimension}`}`,
|
|
79
|
+
size: { width: texture.width, height: texture.height, depthOrArrayLayers: layers },
|
|
80
|
+
format,
|
|
81
|
+
mipLevelCount: texture.mipCount,
|
|
82
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
|
|
83
|
+
textureBindingViewDimension: dimension
|
|
84
|
+
});
|
|
85
|
+
for (let level = 0; texture.levels && level < texture.mipCount; level++) {
|
|
86
|
+
const w = Math.max(1, texture.width >> level);
|
|
87
|
+
const h = Math.max(1, texture.height >> level);
|
|
88
|
+
const blocksWide = Math.ceil(w / info.block);
|
|
89
|
+
const blocksHigh = Math.ceil(h / info.block);
|
|
90
|
+
gpu.device.queue.writeTexture(
|
|
91
|
+
{ texture: handle, mipLevel: level },
|
|
92
|
+
texture.levels[level],
|
|
93
|
+
{ bytesPerRow: blocksWide * info.bytes, rowsPerImage: blocksHigh },
|
|
94
|
+
{
|
|
95
|
+
width: blocksWide * info.block,
|
|
96
|
+
height: blocksHigh * info.block,
|
|
97
|
+
depthOrArrayLayers: layers
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
return handle;
|
|
102
|
+
}
|
|
103
|
+
class BaselineTexture {
|
|
104
|
+
texture;
|
|
105
|
+
version;
|
|
106
|
+
generation;
|
|
107
|
+
bytes;
|
|
108
|
+
source;
|
|
109
|
+
host;
|
|
110
|
+
primarySrgb;
|
|
111
|
+
primaryDimension;
|
|
112
|
+
views = /* @__PURE__ */ new Map();
|
|
113
|
+
pending = /* @__PURE__ */ new Set();
|
|
114
|
+
/** Twins made, destroyed with the primary. */
|
|
115
|
+
twins = [];
|
|
116
|
+
constructor(host, source) {
|
|
117
|
+
this.host = host;
|
|
118
|
+
this.source = source;
|
|
119
|
+
this.version = source.version;
|
|
120
|
+
this.generation = host.gpu.generation;
|
|
121
|
+
this.bytes = source.byteSize;
|
|
122
|
+
const info = FORMAT_INFO[source.format];
|
|
123
|
+
this.primarySrgb = source.usage === "color" && info.srgbView !== void 0;
|
|
124
|
+
this.primaryDimension = source.faces === 6 ? DIMENSION.cube : source.layers > 1 ? DIMENSION.array : DIMENSION.plain;
|
|
125
|
+
const format = this.primarySrgb ? info.srgbView : source.format;
|
|
126
|
+
this.texture = create(host.gpu, source, format, this.primaryDimension);
|
|
127
|
+
this.views.set(
|
|
128
|
+
keyOf(this.primarySrgb, this.primaryDimension),
|
|
129
|
+
this.texture.createView({ dimension: this.primaryDimension })
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
get linear() {
|
|
133
|
+
return this.view(false, false);
|
|
134
|
+
}
|
|
135
|
+
get srgb() {
|
|
136
|
+
return this.view(true, false);
|
|
137
|
+
}
|
|
138
|
+
get arrayLinear() {
|
|
139
|
+
return this.source.faces === 1 ? this.view(false, true) : void 0;
|
|
140
|
+
}
|
|
141
|
+
get arraySrgb() {
|
|
142
|
+
return this.source.faces === 1 ? this.view(true, true) : void 0;
|
|
143
|
+
}
|
|
144
|
+
/** The primary and every twin. */
|
|
145
|
+
destroy() {
|
|
146
|
+
this.texture.destroy();
|
|
147
|
+
for (const t of this.twins) t.destroy();
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Whether a slot reading it this way waits for a twin's bytes (and shows the loading fallback).
|
|
151
|
+
* Asking requests the twin, as binding it would: material preparation asks for deferred draws.
|
|
152
|
+
*/
|
|
153
|
+
waiting(wantSrgb, array) {
|
|
154
|
+
this.view(wantSrgb, array);
|
|
155
|
+
return this.pending.has(this.keyFor(wantSrgb, array));
|
|
156
|
+
}
|
|
157
|
+
keyFor(wantSrgb, array) {
|
|
158
|
+
const srgb = wantSrgb && FORMAT_INFO[this.source.format].srgbView !== void 0;
|
|
159
|
+
const dimension = array ? DIMENSION.array : this.primaryDimension === DIMENSION.array ? DIMENSION.array : this.primaryDimension;
|
|
160
|
+
return keyOf(srgb, dimension);
|
|
161
|
+
}
|
|
162
|
+
view(wantSrgb, array) {
|
|
163
|
+
const key = this.keyFor(wantSrgb, array);
|
|
164
|
+
const ready = this.views.get(key);
|
|
165
|
+
if (ready) return ready;
|
|
166
|
+
const srgb = (key & 1) === 1;
|
|
167
|
+
const dimension = DIMENSIONS[key >> 1];
|
|
168
|
+
const info = FORMAT_INFO[this.source.format];
|
|
169
|
+
if (srgb !== this.primarySrgb) this.host.mismatch(this.source, kindOf(key));
|
|
170
|
+
if (!this.source.levels) {
|
|
171
|
+
if (!this.pending.has(key)) {
|
|
172
|
+
this.pending.add(key);
|
|
173
|
+
this.host.twin(this.source, kindOf(key), false);
|
|
174
|
+
this.host.reload(this.source);
|
|
175
|
+
}
|
|
176
|
+
return this.host.fallback(srgb, dimension === DIMENSION.array);
|
|
177
|
+
}
|
|
178
|
+
const format = srgb ? info.srgbView : this.source.format;
|
|
179
|
+
const twin = create(this.host.gpu, this.source, format, dimension);
|
|
180
|
+
this.twins.push(twin);
|
|
181
|
+
this.bytes += this.source.byteSize;
|
|
182
|
+
const view = twin.createView({ dimension });
|
|
183
|
+
this.views.set(key, view);
|
|
184
|
+
this.pending.delete(key);
|
|
185
|
+
this.host.twin(this.source, kindOf(key), true);
|
|
186
|
+
return view;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function solidTexture(host, rgba) {
|
|
190
|
+
const texture = Texture.create({
|
|
191
|
+
width: 1,
|
|
192
|
+
height: 1,
|
|
193
|
+
mips: [new Uint8Array(rgba)],
|
|
194
|
+
cpu: true,
|
|
195
|
+
usage: "data"
|
|
196
|
+
});
|
|
197
|
+
return new BaselineTexture(host, texture);
|
|
198
|
+
}
|
|
199
|
+
export {
|
|
200
|
+
BaselineTexture,
|
|
201
|
+
Twins,
|
|
202
|
+
solidTexture
|
|
203
|
+
};
|
package/dist/camera.d.ts
CHANGED
|
@@ -41,6 +41,7 @@ export declare const Camera3d: import("@aethervtt/shard-core").ComponentDef<{
|
|
|
41
41
|
readonly clearColor: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Color, "f32">;
|
|
42
42
|
readonly target: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").AssetRef<"RenderTarget"> | null, "object">;
|
|
43
43
|
readonly active: import("@aethervtt/shard-core").FieldType<boolean, "u8">;
|
|
44
|
+
readonly layers: import("@aethervtt/shard-core").FieldType<number, "u16">;
|
|
44
45
|
}>;
|
|
45
46
|
export type Camera3dValue = Infer<typeof Camera3d>;
|
|
46
47
|
export declare const PhysicalCamera: import("@aethervtt/shard-core").ComponentDef<{
|
|
@@ -65,6 +66,7 @@ export declare function camera2d(options?: {
|
|
|
65
66
|
readonly clearColor: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Color, "f32">;
|
|
66
67
|
readonly target: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").AssetRef<"RenderTarget"> | null, "object">;
|
|
67
68
|
readonly active: import("@aethervtt/shard-core").FieldType<boolean, "u8">;
|
|
69
|
+
readonly layers: import("@aethervtt/shard-core").FieldType<number, "u16">;
|
|
68
70
|
}>, {
|
|
69
71
|
readonly projection: 'orthographic';
|
|
70
72
|
readonly orthoHeight: number;
|
package/dist/camera.js
CHANGED
|
@@ -77,6 +77,10 @@ const Camera3d = defineComponent(
|
|
|
77
77
|
active: t.bool({
|
|
78
78
|
default: true,
|
|
79
79
|
description: "Renders when true. An inactive camera keeps its settings and transform but draws nothing."
|
|
80
|
+
}),
|
|
81
|
+
layers: t.u16({
|
|
82
|
+
default: 65535,
|
|
83
|
+
description: "Render layers this camera draws (0057): a renderable draws if its RenderLayers mask shares a bit. Default: all."
|
|
80
84
|
})
|
|
81
85
|
},
|
|
82
86
|
{
|
package/dist/culling-shaders.js
CHANGED
|
@@ -37,6 +37,13 @@ struct CullView {
|
|
|
37
37
|
lod_camera: u32,
|
|
38
38
|
args_base: u32,
|
|
39
39
|
visible_base: u32,
|
|
40
|
+
/** Render layers the view draws (0057). */
|
|
41
|
+
layers: u32,
|
|
42
|
+
/** Its hidden set's first word in lod_state, past the LOD entries (0070), or NO_HIDDEN. */
|
|
43
|
+
@if(HIDDEN) hidden_base: u32,
|
|
44
|
+
@if(!HIDDEN) _pad0: u32,
|
|
45
|
+
_pad1: u32,
|
|
46
|
+
_pad2: u32,
|
|
40
47
|
}
|
|
41
48
|
|
|
42
49
|
struct Params {
|
|
@@ -67,12 +74,15 @@ struct Deform {
|
|
|
67
74
|
}
|
|
68
75
|
@group(0) @binding(8) var<storage, read> deforms: array<Deform>;
|
|
69
76
|
|
|
77
|
+
@if(HIDDEN) const NO_HIDDEN: u32 = 0xffffffffu;
|
|
78
|
+
|
|
70
79
|
const NO_BATCH: u32 = 0xffffffffu;
|
|
71
80
|
const LOD_BIT: u32 = 0x80000000u;
|
|
72
81
|
const LOD_UNSET: u32 = 0xffu;
|
|
73
82
|
const FLAG_VISIBLE: u32 = 1u;
|
|
74
83
|
const FLAG_CASTER: u32 = 2u;
|
|
75
84
|
const FLAG_RANGE: u32 = 8u;
|
|
85
|
+
const FLAG_SHADOW_ONLY: u32 = 128u;
|
|
76
86
|
const FLAG_SKINNED: u32 = 32u;
|
|
77
87
|
const VIEW_CASTERS: u32 = 1u;
|
|
78
88
|
const VIEW_UPDATE_LOD: u32 = 2u;
|
|
@@ -155,8 +165,17 @@ fn cull(@builtin(global_invocation_id) id: vec3u) {
|
|
|
155
165
|
let inst = instances[s];
|
|
156
166
|
if (inst.batch == NO_BATCH) { return; }
|
|
157
167
|
var mask = FLAG_VISIBLE;
|
|
158
|
-
|
|
159
|
-
if ((
|
|
168
|
+
var flags = inst.flags;
|
|
169
|
+
if ((view.flags & VIEW_CASTERS) != 0u) {
|
|
170
|
+
mask |= FLAG_CASTER;
|
|
171
|
+
// Hidden meshes that still cast (ShadowWhenHidden, 0067).
|
|
172
|
+
if ((flags & FLAG_SHADOW_ONLY) != 0u) { flags |= FLAG_VISIBLE; }
|
|
173
|
+
}
|
|
174
|
+
if ((flags & mask) != mask) { return; }
|
|
175
|
+
// Render layers (0057) ride in flags bits 8\u201323.
|
|
176
|
+
if ((((inst.flags >> 8u) & 0xffffu) & view.layers) == 0u) { return; }
|
|
177
|
+
// Hidden from this view (ViewVisibility, 0070): a bit per slot.
|
|
178
|
+
@if(HIDDEN) if (view.hidden_base != NO_HIDDEN && (lod_state[view.hidden_base + (s >> 5u)] & (1u << (s & 31u))) != 0u) { return; }
|
|
160
179
|
var b = inst.batch;
|
|
161
180
|
var lod = false;
|
|
162
181
|
var lod_set: LodSet;
|
package/dist/culling.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { GpuBuffer, type GpuContext } from '@aethervtt/shard-gpu';
|
|
2
2
|
import type { NodeContext } from './graph.js';
|
|
3
3
|
import { type CullParams, type DrawList, type InstanceStore } from './instances.js';
|
|
4
|
+
import type { HiddenSets } from './view-visibility-plugin.js';
|
|
4
5
|
/**
|
|
5
6
|
* GPU culling: one compute pass per frame culls every instance slot against every draw view
|
|
6
7
|
* (cameras first, which choose LOD levels, then shadow views, which reuse them), writing indirect
|
|
@@ -11,6 +12,8 @@ export declare class GpuCuller {
|
|
|
11
12
|
readonly supported: boolean;
|
|
12
13
|
/** Turn off to force CPU culling (tests, debugging). */
|
|
13
14
|
enabled: boolean;
|
|
15
|
+
/** Views this frame with a hidden set (0070): the cull links its HIDDEN variant. */
|
|
16
|
+
private hiddenViews;
|
|
14
17
|
private readonly views;
|
|
15
18
|
private viewCount;
|
|
16
19
|
private cameraViews;
|
|
@@ -58,8 +61,12 @@ export declare class GpuCuller {
|
|
|
58
61
|
* batches into `forwardOnly`.
|
|
59
62
|
*/
|
|
60
63
|
add(store: InstanceStore, list: DrawList, params: CullParams, lodCamera: number, forwardOnly?: DrawList): number;
|
|
61
|
-
/**
|
|
62
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Uploads the frame's view, batch, and LOD tables, and sizes the buffers. Hidden sets (0070)
|
|
66
|
+
* ride in the LOD state buffer after its entries: the cull's storage bindings are at WebGPU's
|
|
67
|
+
* guaranteed 8 per stage.
|
|
68
|
+
*/
|
|
69
|
+
prepare(store: InstanceStore, hidden?: HiddenSets): void;
|
|
63
70
|
private slotCount;
|
|
64
71
|
private slotCapacity;
|
|
65
72
|
/** Encodes the cull: reset arguments, cull camera views, then shadow views. */
|
|
@@ -75,6 +82,8 @@ export declare const Culler: import("@aethervtt/shard-core").ResourceDef<GpuCull
|
|
|
75
82
|
* members of blended batches, so it costs what's transparent, not what exists.
|
|
76
83
|
*/
|
|
77
84
|
export declare function cullTransparent(store: InstanceStore, list: DrawList, params: CullParams, forward: Float32Array): void;
|
|
85
|
+
/** The CPU ground list (0057) of a view the GPU culls. */
|
|
86
|
+
export declare function cullGround(store: InstanceStore, list: DrawList, params: CullParams, overlay?: DrawList): void;
|
|
78
87
|
/**
|
|
79
88
|
* The CPU-side visible set of a view as slots (tests compare it with the GPU's). Pass
|
|
80
89
|
* `mask = 0xffffffff` to keep the LOD level in the top bits.
|
package/dist/culling.js
CHANGED
|
@@ -7,8 +7,9 @@ import {
|
|
|
7
7
|
SLOT_MASK
|
|
8
8
|
} from "./instances.js";
|
|
9
9
|
import { Shaders } from "./plugin.js";
|
|
10
|
+
import { NO_HIDDEN } from "./view-visibility.js";
|
|
10
11
|
const ARGS_WORDS = 5;
|
|
11
|
-
const VIEW_FLOATS =
|
|
12
|
+
const VIEW_FLOATS = 36;
|
|
12
13
|
const BATCH_WORDS = 8;
|
|
13
14
|
const LOD_WORDS = 20;
|
|
14
15
|
const VIEW_CASTERS = 1;
|
|
@@ -21,6 +22,8 @@ class GpuCuller {
|
|
|
21
22
|
supported;
|
|
22
23
|
/** Turn off to force CPU culling (tests, debugging). */
|
|
23
24
|
enabled = true;
|
|
25
|
+
/** Views this frame with a hidden set (0070): the cull links its HIDDEN variant. */
|
|
26
|
+
hiddenViews = 0;
|
|
24
27
|
views = [];
|
|
25
28
|
viewCount = 0;
|
|
26
29
|
cameraViews = 0;
|
|
@@ -59,7 +62,7 @@ class GpuCuller {
|
|
|
59
62
|
pending = /* @__PURE__ */ new Set();
|
|
60
63
|
constructor(gpu) {
|
|
61
64
|
this.gpu = gpu;
|
|
62
|
-
this.supported = gpu.features.has("indirect-first-instance");
|
|
65
|
+
this.supported = gpu.tier === "full" && gpu.features.has("indirect-first-instance");
|
|
63
66
|
const storage = GPUBufferUsage.STORAGE;
|
|
64
67
|
this.viewBuffer = new GpuBuffer(gpu, { label: "cull/views", usage: storage, size: 4096 });
|
|
65
68
|
this.batchBuffer = new GpuBuffer(gpu, { label: "cull/batches", usage: storage, size: 2048 });
|
|
@@ -82,6 +85,7 @@ class GpuCuller {
|
|
|
82
85
|
beginFrame() {
|
|
83
86
|
this.viewCount = 0;
|
|
84
87
|
this.cameraViews = 0;
|
|
88
|
+
this.hiddenViews = 0;
|
|
85
89
|
}
|
|
86
90
|
/** The LOD state row of a camera (cameras choose; their shadow views reuse the choice). */
|
|
87
91
|
lodCamera(entity) {
|
|
@@ -110,7 +114,9 @@ class GpuCuller {
|
|
|
110
114
|
hasEye: false,
|
|
111
115
|
lodScale: 1,
|
|
112
116
|
flags: 0,
|
|
113
|
-
lodCamera: 0
|
|
117
|
+
lodCamera: 0,
|
|
118
|
+
layers: 4294967295,
|
|
119
|
+
hiddenBase: NO_HIDDEN
|
|
114
120
|
};
|
|
115
121
|
this.views.push(view);
|
|
116
122
|
}
|
|
@@ -122,6 +128,9 @@ class GpuCuller {
|
|
|
122
128
|
if (params.eye) view.eye.set(params.eye);
|
|
123
129
|
view.lodScale = params.lodScale;
|
|
124
130
|
view.lodCamera = lodCamera;
|
|
131
|
+
view.layers = params.layers ?? 4294967295;
|
|
132
|
+
view.hiddenBase = params.hiddenBase ?? NO_HIDDEN;
|
|
133
|
+
if (view.hiddenBase !== NO_HIDDEN) this.hiddenViews++;
|
|
125
134
|
view.flags = (params.require & InstanceFlags.Caster ? VIEW_CASTERS : 0) | (params.updateLod ? VIEW_UPDATE_LOD : 0) | (params.orthographic ? VIEW_ORTHO : 0) | (params.planes ? 0 : VIEW_NO_PLANES);
|
|
126
135
|
if (params.updateLod) this.cameraViews = v + 1;
|
|
127
136
|
list.cullView = v;
|
|
@@ -136,7 +145,8 @@ class GpuCuller {
|
|
|
136
145
|
let n = 0;
|
|
137
146
|
let m = 0;
|
|
138
147
|
for (const batch of store.sorted) {
|
|
139
|
-
if (batch.count === 0 || batch.transparent || !store.anyVisible(batch))
|
|
148
|
+
if (batch.count === 0 || batch.transparent || batch.ground || !store.anyVisible(batch))
|
|
149
|
+
continue;
|
|
140
150
|
if (!batch.ready) {
|
|
141
151
|
list.pending += batch.count;
|
|
142
152
|
continue;
|
|
@@ -157,9 +167,22 @@ class GpuCuller {
|
|
|
157
167
|
if (forwardOnly) forwardOnly.length = m;
|
|
158
168
|
return v;
|
|
159
169
|
}
|
|
160
|
-
/**
|
|
161
|
-
|
|
170
|
+
/**
|
|
171
|
+
* Uploads the frame's view, batch, and LOD tables, and sizes the buffers. Hidden sets (0070)
|
|
172
|
+
* ride in the LOD state buffer after its entries: the cull's storage bindings are at WebGPU's
|
|
173
|
+
* guaranteed 8 per stage.
|
|
174
|
+
*/
|
|
175
|
+
prepare(store, hidden) {
|
|
162
176
|
if (this.viewCount === 0) return;
|
|
177
|
+
const lodCams = Math.max(1, this.lodCameras.size);
|
|
178
|
+
const hiddenWords = hidden && hidden.sets.size > 0 ? hidden.data.length : 0;
|
|
179
|
+
if (this.lodStateCapacity !== store.capacity * lodCams || this.lodState.byteLength < (this.lodStateCapacity + hiddenWords) * 4) {
|
|
180
|
+
this.lodStateCapacity = store.capacity * lodCams;
|
|
181
|
+
this.lodState.ensureCapacity((this.lodStateCapacity + hiddenWords) * 4);
|
|
182
|
+
const fill = new Uint32Array(this.lodStateCapacity).fill(LOD_UNSET);
|
|
183
|
+
this.lodState.write(fill);
|
|
184
|
+
}
|
|
185
|
+
if (hiddenWords > 0) hidden.upload(this.lodState, this.lodStateCapacity * 4);
|
|
163
186
|
const batches = store.batches;
|
|
164
187
|
this.batchCount = batches.length;
|
|
165
188
|
if (this.batchData.length < batches.length * BATCH_WORDS) {
|
|
@@ -182,7 +205,7 @@ class GpuCuller {
|
|
|
182
205
|
this.batchData[o + 4] = batch.mesh.drawCount;
|
|
183
206
|
this.batchData[o + 5] = batch.mesh.indexed ? 1 + batch.mesh.baseVertex : 0;
|
|
184
207
|
this.batchData[o + 6] = batch.region;
|
|
185
|
-
this.batchData[o + 7] = (batch.transparent ? BATCH_TRANSPARENT : 0) | (batch.ready ? 0 : BATCH_NOT_READY);
|
|
208
|
+
this.batchData[o + 7] = (batch.transparent || batch.ground ? BATCH_TRANSPARENT : 0) | (batch.ready ? 0 : BATCH_NOT_READY);
|
|
186
209
|
}
|
|
187
210
|
this.regionSize = Math.max(1, region);
|
|
188
211
|
this.batchBuffer.write(this.batchData, 0, 0, Math.max(1, batches.length) * BATCH_WORDS);
|
|
@@ -220,6 +243,8 @@ class GpuCuller {
|
|
|
220
243
|
this.viewU32[o + 29] = view.lodCamera;
|
|
221
244
|
this.viewU32[o + 30] = v * batches.length;
|
|
222
245
|
this.viewU32[o + 31] = v * this.regionSize;
|
|
246
|
+
this.viewU32[o + 32] = view.layers;
|
|
247
|
+
this.viewU32[o + 33] = view.hiddenBase === NO_HIDDEN ? NO_HIDDEN : this.lodStateCapacity + view.hiddenBase;
|
|
223
248
|
for (const list of [view.list, view.forwardOnly]) {
|
|
224
249
|
if (!list) continue;
|
|
225
250
|
for (let d = 0; d < list.length; d++) {
|
|
@@ -232,12 +257,6 @@ class GpuCuller {
|
|
|
232
257
|
this.viewBuffer.write(this.viewData, 0, 0, this.viewCount * VIEW_FLOATS);
|
|
233
258
|
this.args.ensureCapacity(Math.max(1, this.viewCount * batches.length) * ARGS_WORDS * 4);
|
|
234
259
|
this.visible.ensureCapacity(this.viewCount * this.regionSize * 4);
|
|
235
|
-
const lodCams = Math.max(1, this.lodCameras.size);
|
|
236
|
-
if (this.lodStateCapacity !== store.capacity * lodCams) {
|
|
237
|
-
this.lodStateCapacity = store.capacity * lodCams;
|
|
238
|
-
const fill = new Uint32Array(this.lodStateCapacity).fill(LOD_UNSET);
|
|
239
|
-
this.lodState.write(fill);
|
|
240
|
-
}
|
|
241
260
|
this.frameViews = this.views.slice(0, this.viewCount);
|
|
242
261
|
this.slotCount = store.high;
|
|
243
262
|
this.slotCapacity = store.capacity;
|
|
@@ -249,7 +268,8 @@ class GpuCuller {
|
|
|
249
268
|
if (this.viewCount === 0) return;
|
|
250
269
|
const gpu = ctx.gpu;
|
|
251
270
|
const world = ctx.world;
|
|
252
|
-
const
|
|
271
|
+
const hidden = this.hiddenViews > 0;
|
|
272
|
+
const module = world.resource(Shaders).module(gpu, hidden ? HIDDEN_REQUEST : CULL_REQUEST);
|
|
253
273
|
if (!module) {
|
|
254
274
|
gpu.pipelines.skipped++;
|
|
255
275
|
return;
|
|
@@ -257,12 +277,12 @@ class GpuCuller {
|
|
|
257
277
|
const layout = cullLayout(gpu);
|
|
258
278
|
const pipelineLayout = gpu.layouts.pipelineLayout({ label: "cull", bindGroupLayouts: [layout] });
|
|
259
279
|
const reset = gpu.pipelines.compute({
|
|
260
|
-
label: "cull/reset",
|
|
280
|
+
label: hidden ? "cull/reset/hidden" : "cull/reset",
|
|
261
281
|
layout: pipelineLayout,
|
|
262
282
|
compute: { module, entryPoint: "reset" }
|
|
263
283
|
});
|
|
264
284
|
const cull = gpu.pipelines.compute({
|
|
265
|
-
label: "cull/instances",
|
|
285
|
+
label: hidden ? "cull/instances/hidden" : "cull/instances",
|
|
266
286
|
layout: pipelineLayout,
|
|
267
287
|
compute: { module, entryPoint: "cull" }
|
|
268
288
|
});
|
|
@@ -309,7 +329,7 @@ class GpuCuller {
|
|
|
309
329
|
label: "cull",
|
|
310
330
|
layout,
|
|
311
331
|
entries: [
|
|
312
|
-
{ binding: 0, resource: { buffer: store.instanceBuffer.
|
|
332
|
+
{ binding: 0, resource: { buffer: store.instanceBuffer.gpuBuffer } },
|
|
313
333
|
{ binding: 1, resource: { buffer: this.batchBuffer.buffer } },
|
|
314
334
|
{ binding: 2, resource: { buffer: this.lodBuffer.buffer } },
|
|
315
335
|
{ binding: 3, resource: { buffer: this.viewBuffer.buffer } },
|
|
@@ -317,7 +337,7 @@ class GpuCuller {
|
|
|
317
337
|
{ binding: 5, resource: { buffer: this.visible.buffer } },
|
|
318
338
|
{ binding: 6, resource: { buffer: this.lodState.buffer } },
|
|
319
339
|
{ binding: 7, resource: { buffer: params.buffer } },
|
|
320
|
-
{ binding: 8, resource: { buffer: store.deform.recordBuffer.
|
|
340
|
+
{ binding: 8, resource: { buffer: store.deform.recordBuffer.gpuBuffer } }
|
|
321
341
|
]
|
|
322
342
|
})
|
|
323
343
|
)
|
|
@@ -434,6 +454,8 @@ class GpuCuller {
|
|
|
434
454
|
while (this.pending.size > 0) await Promise.all(this.pending);
|
|
435
455
|
}
|
|
436
456
|
}
|
|
457
|
+
const CULL_REQUEST = { root: "shard::cull" };
|
|
458
|
+
const HIDDEN_REQUEST = { root: "shard::cull", defines: { HIDDEN: true } };
|
|
437
459
|
function cullLayout(gpu) {
|
|
438
460
|
const C = GPUShaderStage.COMPUTE;
|
|
439
461
|
const read = (binding) => ({
|
|
@@ -467,6 +489,9 @@ const Culler = defineResource("render/Culler", {
|
|
|
467
489
|
function cullTransparent(store, list, params, forward) {
|
|
468
490
|
store.cullTransparentMembers(list, params, forward);
|
|
469
491
|
}
|
|
492
|
+
function cullGround(store, list, params, overlay) {
|
|
493
|
+
store.cullGroundMembers(list, params, overlay);
|
|
494
|
+
}
|
|
470
495
|
function visibleSlots(store, list, mask = SLOT_MASK) {
|
|
471
496
|
const out = /* @__PURE__ */ new Set();
|
|
472
497
|
for (let d = 0; d < list.length; d++) {
|
|
@@ -492,6 +517,7 @@ async function readVisibleSlots(gpu, culler, list, mask = SLOT_MASK) {
|
|
|
492
517
|
export {
|
|
493
518
|
Culler,
|
|
494
519
|
GpuCuller,
|
|
520
|
+
cullGround,
|
|
495
521
|
cullTransparent,
|
|
496
522
|
readVisibleSlots,
|
|
497
523
|
visibleSlots
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type Entity, type World } from '@aethervtt/shard-core';
|
|
2
|
+
import { GpuBuffer } from '@aethervtt/shard-gpu';
|
|
3
|
+
export declare const CUTAWAY_SHADERS: Record<string, string>;
|
|
4
|
+
/** A camera's reveal points as uploaded, and the bind group over them. */
|
|
5
|
+
interface CutawayCamera {
|
|
6
|
+
data: Float32Array;
|
|
7
|
+
buffer: GpuBuffer | undefined;
|
|
8
|
+
bindGroup: GPUBindGroup | undefined;
|
|
9
|
+
/** The device generation and buffer version the bind group was made for. */
|
|
10
|
+
boundGeneration: number;
|
|
11
|
+
boundVersion: number;
|
|
12
|
+
/** Points in use (at most MAX_REVEAL_POINTS). */
|
|
13
|
+
count: number;
|
|
14
|
+
/** Points the component listed. */
|
|
15
|
+
listed: number;
|
|
16
|
+
frame: number;
|
|
17
|
+
/** Too many points was reported for this camera. */
|
|
18
|
+
warned: boolean;
|
|
19
|
+
/** The device generation its buffer was last written on (a new device loses the contents). */
|
|
20
|
+
written: number;
|
|
21
|
+
/** Per batch index: 1 when a point can cut one of its Cutaway instances this frame. */
|
|
22
|
+
cuts: Uint8Array;
|
|
23
|
+
}
|
|
24
|
+
export interface CutawayState {
|
|
25
|
+
cameras: Map<Entity, CutawayCamera>;
|
|
26
|
+
frame: number;
|
|
27
|
+
layout: {
|
|
28
|
+
layout: GPUBindGroupLayout;
|
|
29
|
+
generation: number;
|
|
30
|
+
} | undefined;
|
|
31
|
+
}
|
|
32
|
+
export declare const CutawayCameras: import("@aethervtt/shard-core").ResourceDef<CutawayState>;
|
|
33
|
+
/**
|
|
34
|
+
* Packs each camera's CutawayView into its uniform, with each point's line of sight from the
|
|
35
|
+
* camera. Only a camera whose values changed uploads (a camera moving changes the lines); a still
|
|
36
|
+
* frame writes nothing and allocates nothing.
|
|
37
|
+
*/
|
|
38
|
+
export declare const extractCutaways: import("@aethervtt/shard-core").SystemDef<{
|
|
39
|
+
q: import("@aethervtt/shard-core").Query;
|
|
40
|
+
}>;
|
|
41
|
+
/** render.describe's cutaways section: per camera, its reveal points, radius, margin and edge. */
|
|
42
|
+
export declare function describeCutaways(world: World): Record<string, unknown>;
|
|
43
|
+
/**
|
|
44
|
+
* Cutaways (0070): Cutaway renderables open around a camera's CutawayView points, in every camera
|
|
45
|
+
* pass and no shadow pass. Cameras without reveal points draw the plain variants.
|
|
46
|
+
*/
|
|
47
|
+
export declare const cutawayPlugin: import("@aethervtt/shard-runtime").Plugin;
|
|
48
|
+
export {};
|