@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.
Files changed (170) hide show
  1. package/dist/ablation.d.ts +74 -0
  2. package/dist/ablation.js +117 -0
  3. package/dist/assets.d.ts +3 -0
  4. package/dist/assets.js +43 -2
  5. package/dist/atmosphere-nodes.d.ts +9 -0
  6. package/dist/atmosphere-nodes.js +157 -30
  7. package/dist/atmosphere-plugin.js +8 -2
  8. package/dist/atmosphere-shaders.js +17 -5
  9. package/dist/baseline/atmosphere.d.ts +16 -0
  10. package/dist/baseline/atmosphere.js +301 -0
  11. package/dist/baseline/data-texture.d.ts +36 -0
  12. package/dist/baseline/data-texture.js +181 -0
  13. package/dist/baseline/environment.d.ts +14 -0
  14. package/dist/baseline/environment.js +368 -0
  15. package/dist/baseline/exposure.d.ts +25 -0
  16. package/dist/baseline/exposure.js +133 -0
  17. package/dist/baseline/lights.d.ts +33 -0
  18. package/dist/baseline/lights.js +133 -0
  19. package/dist/baseline/textures.d.ts +97 -0
  20. package/dist/baseline/textures.js +203 -0
  21. package/dist/camera.d.ts +2 -0
  22. package/dist/camera.js +4 -0
  23. package/dist/crowd.d.ts +21 -0
  24. package/dist/crowd.js +121 -0
  25. package/dist/culling-shaders.js +21 -2
  26. package/dist/culling.d.ts +11 -2
  27. package/dist/culling.js +44 -18
  28. package/dist/cutaway-plugin.d.ts +48 -0
  29. package/dist/cutaway-plugin.js +364 -0
  30. package/dist/cutaway.d.ts +27 -0
  31. package/dist/cutaway.js +49 -0
  32. package/dist/data-store.d.ts +69 -0
  33. package/dist/data-store.js +105 -0
  34. package/dist/debug-shaders.js +17 -3
  35. package/dist/debug-views.js +4 -2
  36. package/dist/decals-plugin.d.ts +5 -0
  37. package/dist/decals-plugin.js +54 -0
  38. package/dist/decals.d.ts +69 -0
  39. package/dist/decals.js +326 -0
  40. package/dist/deferred-shaders.js +2 -0
  41. package/dist/deferred.js +28 -4
  42. package/dist/deform.d.ts +5 -4
  43. package/dist/deform.js +4 -9
  44. package/dist/describe-plugin.d.ts +4 -2
  45. package/dist/describe-plugin.js +75 -3
  46. package/dist/display-nodes.js +37 -13
  47. package/dist/dynamic-resolution.d.ts +2 -0
  48. package/dist/dynamic-resolution.js +6 -1
  49. package/dist/environment-plugin.js +17 -2
  50. package/dist/environment-state.d.ts +7 -3
  51. package/dist/environment-state.js +20 -9
  52. package/dist/environment.js +7 -1
  53. package/dist/features.d.ts +49 -0
  54. package/dist/features.js +106 -0
  55. package/dist/foliage-budget.d.ts +64 -0
  56. package/dist/foliage-budget.js +108 -0
  57. package/dist/foliage-path.d.ts +19 -0
  58. package/dist/foliage-path.js +7 -0
  59. package/dist/foliage-shaders.d.ts +11 -0
  60. package/dist/foliage-shaders.js +504 -0
  61. package/dist/foliage.d.ts +194 -0
  62. package/dist/foliage.js +1027 -0
  63. package/dist/forward.d.ts +38 -2
  64. package/dist/forward.js +608 -186
  65. package/dist/fxaa.js +12 -2
  66. package/dist/gizmos-plugin.js +7 -0
  67. package/dist/gizmos.d.ts +3 -3
  68. package/dist/gizmos.js +11 -13
  69. package/dist/gpu-assets.d.ts +58 -4
  70. package/dist/gpu-assets.js +191 -8
  71. package/dist/graph.d.ts +45 -0
  72. package/dist/graph.js +169 -19
  73. package/dist/health.d.ts +58 -0
  74. package/dist/health.js +112 -0
  75. package/dist/index.d.ts +40 -11
  76. package/dist/index.js +343 -9
  77. package/dist/instances.d.ts +119 -8
  78. package/dist/instances.js +387 -78
  79. package/dist/interior-plugin.d.ts +121 -0
  80. package/dist/interior-plugin.js +465 -0
  81. package/dist/interior.d.ts +24 -0
  82. package/dist/interior.js +13 -0
  83. package/dist/layers.d.ts +35 -0
  84. package/dist/layers.js +49 -0
  85. package/dist/lens-plugin.d.ts +43 -0
  86. package/dist/lens-plugin.js +304 -0
  87. package/dist/lens.d.ts +41 -0
  88. package/dist/lens.js +99 -0
  89. package/dist/lights.d.ts +50 -5
  90. package/dist/lights.js +101 -22
  91. package/dist/material-pipelines.d.ts +37 -4
  92. package/dist/material-pipelines.js +102 -5
  93. package/dist/materials.d.ts +23 -0
  94. package/dist/materials.js +44 -1
  95. package/dist/outline-plugin.d.ts +48 -0
  96. package/dist/outline-plugin.js +529 -0
  97. package/dist/outline.d.ts +15 -0
  98. package/dist/outline.js +42 -0
  99. package/dist/overlay-registry.d.ts +3 -1
  100. package/dist/overlay-registry.js +3 -1
  101. package/dist/overlays.js +6 -2
  102. package/dist/perf-overlay.d.ts +9 -0
  103. package/dist/perf-overlay.js +125 -0
  104. package/dist/picking.d.ts +17 -0
  105. package/dist/picking.js +40 -11
  106. package/dist/pixel-perfect-plugin.js +10 -2
  107. package/dist/plugin.d.ts +69 -7
  108. package/dist/plugin.js +223 -25
  109. package/dist/post-common.d.ts +22 -2
  110. package/dist/post-common.js +82 -2
  111. package/dist/post-nodes.d.ts +7 -1
  112. package/dist/post-nodes.js +235 -111
  113. package/dist/post-plugin.js +5 -3
  114. package/dist/post-scene.d.ts +130 -0
  115. package/dist/post-scene.js +164 -0
  116. package/dist/post-shaders.js +85 -62
  117. package/dist/post.js +11 -1
  118. package/dist/projection.d.ts +24 -0
  119. package/dist/projection.js +87 -0
  120. package/dist/readback.d.ts +1 -0
  121. package/dist/readback.js +1 -0
  122. package/dist/render-scale.js +1 -0
  123. package/dist/ribbons-plugin.d.ts +5 -0
  124. package/dist/ribbons-plugin.js +59 -0
  125. package/dist/ribbons.d.ts +44 -0
  126. package/dist/ribbons.js +288 -0
  127. package/dist/scenario.d.ts +70 -0
  128. package/dist/scenario.js +125 -0
  129. package/dist/screen-effects.d.ts +97 -0
  130. package/dist/screen-effects.js +192 -0
  131. package/dist/shaders.d.ts +13 -0
  132. package/dist/shaders.js +189 -34
  133. package/dist/shadow-catcher.d.ts +9 -0
  134. package/dist/shadow-catcher.js +133 -0
  135. package/dist/shadows.d.ts +27 -2
  136. package/dist/shadows.js +100 -20
  137. package/dist/standard-fields.d.ts +1 -0
  138. package/dist/standard-fields.js +5 -0
  139. package/dist/standard.js +18 -0
  140. package/dist/stats.d.ts +45 -1
  141. package/dist/stats.js +91 -3
  142. package/dist/surface-model.d.ts +57 -0
  143. package/dist/surface-model.js +289 -0
  144. package/dist/surface-shaders.d.ts +3 -0
  145. package/dist/surface-shaders.js +160 -0
  146. package/dist/surface.d.ts +40 -0
  147. package/dist/surface.js +86 -0
  148. package/dist/target.d.ts +1 -13
  149. package/dist/target.js +1 -26
  150. package/dist/testing.d.ts +11 -0
  151. package/dist/testing.js +39 -1
  152. package/dist/tier.d.ts +13 -0
  153. package/dist/tier.js +14 -0
  154. package/dist/timer.d.ts +18 -1
  155. package/dist/timer.js +73 -20
  156. package/dist/variants.d.ts +37 -0
  157. package/dist/variants.js +104 -0
  158. package/dist/vfx-draw.d.ts +37 -0
  159. package/dist/vfx-draw.js +291 -0
  160. package/dist/vfx-state.d.ts +42 -0
  161. package/dist/vfx-state.js +81 -0
  162. package/dist/vfx.d.ts +69 -0
  163. package/dist/vfx.js +103 -0
  164. package/dist/view-visibility-plugin.d.ts +61 -0
  165. package/dist/view-visibility-plugin.js +267 -0
  166. package/dist/view-visibility.d.ts +28 -0
  167. package/dist/view-visibility.js +46 -0
  168. package/dist/view.d.ts +29 -1
  169. package/dist/view.js +57 -34
  170. package/package.json +23 -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
  {
@@ -0,0 +1,21 @@
1
+ import { type World } from '@aethervtt/shard-core';
2
+ export interface CrowdOptions {
3
+ /** Instances (default 200 000). */
4
+ count?: number;
5
+ /** Every instance at full detail, no LOD (the playground's `?nolod`). */
6
+ flat?: boolean;
7
+ }
8
+ /**
9
+ * The crowd's turntable: 160 m out and 40 m up, looking at the field's centre, `angle` radians
10
+ * round (the playground turns it 2.86° a second).
11
+ */
12
+ export declare function crowdCamera(angle: number): {
13
+ eye: [number, number, number];
14
+ target: [number, number, number];
15
+ };
16
+ /**
17
+ * 200k static instances of 20 mesh types over a 600 m field, under a sun with 4 shadow cascades.
18
+ * Most have 3 LOD levels; every fifth is a small prop hidden past 120 m. The camera is the
19
+ * caller's (`crowdCamera` places the turntable).
20
+ */
21
+ export declare function spawnCrowd(world: World, options?: CrowdOptions): void;
package/dist/crowd.js ADDED
@@ -0,0 +1,121 @@
1
+ import { quat, Rng } from "@aethervtt/shard-core";
2
+ import {
3
+ box,
4
+ capsule,
5
+ cone,
6
+ cylinder,
7
+ plane,
8
+ sphere,
9
+ torus
10
+ } from "@aethervtt/shard-mesh";
11
+ import { Transform } from "@aethervtt/shard-transform";
12
+ import { MaterialAsset, Materials, Meshes } from "./assets.js";
13
+ import { Lod, Mesh3d, MeshMaterial, VisibilityRange } from "./instances.js";
14
+ import { DirectionalLight } from "./lights.js";
15
+ function kinds() {
16
+ const out = [];
17
+ for (const segments of [8, 12, 16, 24]) {
18
+ out.push([sphere({ radius: 0.5, segments }), sphere({ radius: 0.5, segments: 6 })]);
19
+ }
20
+ for (const y of [0.5, 0.8, 1.2]) {
21
+ const m = box({ x: 0.8, y, z: 0.8 });
22
+ out.push([m, m]);
23
+ }
24
+ for (const segments of [6, 12, 24]) {
25
+ out.push([
26
+ cylinder({ radius: 0.4, height: 1, segments }),
27
+ cylinder({ radius: 0.4, height: 1, segments: 5 })
28
+ ]);
29
+ }
30
+ for (const segments of [6, 16]) {
31
+ out.push([
32
+ cone({ radius: 0.5, height: 1.2, segments }),
33
+ cone({ radius: 0.5, height: 1.2, segments: 4 })
34
+ ]);
35
+ }
36
+ for (const segments of [8, 16]) {
37
+ out.push([
38
+ capsule({ radius: 0.3, height: 1.2, segments }),
39
+ capsule({ radius: 0.3, height: 1.2, segments: 4, rings: 2 })
40
+ ]);
41
+ }
42
+ for (const tube of [0.2, 0.1]) {
43
+ out.push([
44
+ torus({ radius: 0.4, tube }),
45
+ torus({ radius: 0.4, tube, radialSegments: 4, tubularSegments: 8 })
46
+ ]);
47
+ }
48
+ for (const y of [0.3, 0.6, 1]) {
49
+ const m = box({ x: 0.5, y, z: 1.2 });
50
+ out.push([m, m]);
51
+ }
52
+ const beam = box({ x: 1.5, y: 0.3, z: 0.3 });
53
+ out.push([beam, beam]);
54
+ return out;
55
+ }
56
+ function crowdCamera(angle) {
57
+ return { eye: [Math.sin(angle) * 160, 40, Math.cos(angle) * 160], target: [0, 0, 0] };
58
+ }
59
+ function spawnCrowd(world, options = {}) {
60
+ const count = options.count ?? 2e5;
61
+ const flat = options.flat ?? false;
62
+ world.spawn(
63
+ [DirectionalLight, { illuminance: 6e4, shadows: true }],
64
+ [Transform, { rotation: quat.fromEuler([0, 0, 0, 1], -0.8, 0.7, 0) }]
65
+ );
66
+ const meshes = world.resource(Meshes);
67
+ const materials = world.resource(Materials);
68
+ const rng = new Rng(11);
69
+ const stand = meshes.add(box({ x: 0.8, y: 0.8, z: 0.8 }));
70
+ const types = kinds().map(([hi, lo]) => {
71
+ const mesh = meshes.add(hi);
72
+ return {
73
+ mesh,
74
+ levels: [
75
+ { mesh, screenSize: 0.04 },
76
+ { mesh: meshes.add(lo), screenSize: 0.01 },
77
+ { mesh: stand, screenSize: 2e-3 }
78
+ ]
79
+ };
80
+ });
81
+ const looks = Array.from(
82
+ { length: 8 },
83
+ () => materials.add(
84
+ new MaterialAsset({
85
+ baseColor: [rng.range(0.2, 0.9), rng.range(0.2, 0.9), rng.range(0.2, 0.9), 1],
86
+ roughness: rng.range(0.2, 0.9)
87
+ })
88
+ )
89
+ );
90
+ world.spawn(
91
+ [Mesh3d, { mesh: meshes.add(plane({ size: 700 })) }],
92
+ [MeshMaterial, { material: looks[0] }],
93
+ Transform
94
+ );
95
+ for (let i = 0; i < count; i++) {
96
+ const s = rng.range(0.6, 1.8);
97
+ const kind = types[i % types.length];
98
+ const common = [
99
+ [MeshMaterial, { material: looks[i % looks.length] }],
100
+ [
101
+ Transform,
102
+ {
103
+ translation: [rng.range(-300, 300), s * 0.5, rng.range(-300, 300)],
104
+ rotation: quat.fromEuler([0, 0, 0, 1], 0, rng.float() * 6.28, 0),
105
+ scale: [s, s, s]
106
+ }
107
+ ]
108
+ ];
109
+ if (flat) {
110
+ world.spawn([Mesh3d, { mesh: kind.mesh }], ...common);
111
+ } else if (i % 5 === 0) {
112
+ world.spawn([Mesh3d, { mesh: kind.mesh }], [VisibilityRange, { end: 120 }], ...common);
113
+ } else {
114
+ world.spawn([Mesh3d, { mesh: kind.mesh }], [Lod, { levels: kind.levels }], ...common);
115
+ }
116
+ }
117
+ }
118
+ export {
119
+ crowdCamera,
120
+ spawnCrowd
121
+ };
@@ -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
- if ((view.flags & VIEW_CASTERS) != 0u) { mask |= FLAG_CASTER; }
159
- if ((inst.flags & mask) != mask) { return; }
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
- /** Uploads the frame's view, batch, and LOD tables, and sizes the buffers. */
62
- prepare(store: InstanceStore): void;
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.