@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,267 @@
1
+ import {
2
+ ChildOf,
3
+ Children,
4
+ defineSystem,
5
+ Last
6
+ } from "@aethervtt/shard-core";
7
+ import { definePlugin } from "@aethervtt/shard-runtime";
8
+ import { Camera3d } from "./camera.js";
9
+ import { addRenderFeatures } from "./features.js";
10
+ import { InstanceSlot, Instances, prepareInstances } from "./instances.js";
11
+ import { RenderDescribers, RenderSet } from "./plugin.js";
12
+ import { Cameras } from "./view.js";
13
+ import { HiddenSetsResource, ViewVisibility } from "./view-visibility.js";
14
+ class HiddenSets {
15
+ /** Words per set: one bit per instance slot of the store's capacity. */
16
+ words = 0;
17
+ data = new Uint32Array(0);
18
+ sets = /* @__PURE__ */ new Map();
19
+ /** Free set indices, reused before growing. */
20
+ free = [];
21
+ count = 0;
22
+ dirty = new Uint8Array(0);
23
+ dirtyLo = Number.POSITIVE_INFINITY;
24
+ dirtyHi = -1;
25
+ /** Every word uploads next time (a new layout, buffer, region or device). */
26
+ all = true;
27
+ /** Where the last upload went: a buffer version (it bumps when replaced) and byte offset. */
28
+ target;
29
+ targetVersion = -1;
30
+ targetOffset = -1;
31
+ /** Bytes uploaded by the last `upload`. */
32
+ uploadedBytes = 0;
33
+ /** Resolves since start (tests: a still frame resolves nothing). */
34
+ resolves = 0;
35
+ /** The set of `camera`, made on first use. */
36
+ setOf(camera) {
37
+ let set = this.sets.get(camera);
38
+ if (set) return set;
39
+ const index = this.free.pop() ?? this.count++;
40
+ if (index * this.words + this.words > this.data.length) this.layout(this.words, index + 1);
41
+ const base = index * this.words;
42
+ set = {
43
+ camera,
44
+ base,
45
+ bits: this.data.subarray(base, base + this.words),
46
+ slots: 0,
47
+ hide: [],
48
+ shadows: false,
49
+ frame: -1
50
+ };
51
+ this.sets.set(camera, set);
52
+ return set;
53
+ }
54
+ /** Frees a set: its words clear (and upload, so a reused index starts empty). */
55
+ drop(set) {
56
+ for (let w = 0; w < set.bits.length; w++) if (set.bits[w] !== 0) this.write(set.base + w, 0);
57
+ this.sets.delete(set.camera);
58
+ this.free.push(set.base / Math.max(1, this.words));
59
+ }
60
+ /** Makes room for `capacity` slots per set; a new width re-lays every set and uploads all. */
61
+ ensureSlots(capacity) {
62
+ const words = Math.ceil(capacity / 32);
63
+ if (words > this.words) this.layout(words, this.count);
64
+ }
65
+ layout(words, sets) {
66
+ const data = new Uint32Array(words * Math.max(1, sets * 2));
67
+ for (const set of this.sets.values()) {
68
+ const index = set.base / Math.max(1, this.words);
69
+ data.set(set.bits, index * words);
70
+ set.base = index * words;
71
+ }
72
+ this.data = data;
73
+ for (const set of this.sets.values()) set.bits = data.subarray(set.base, set.base + words);
74
+ this.words = words;
75
+ this.dirty = new Uint8Array(data.length);
76
+ this.all = true;
77
+ }
78
+ write(word, value) {
79
+ if (this.data[word] === value) return;
80
+ this.data[word] = value;
81
+ if (this.dirty[word]) return;
82
+ this.dirty[word] = 1;
83
+ if (word < this.dirtyLo) this.dirtyLo = word;
84
+ if (word > this.dirtyHi) this.dirtyHi = word;
85
+ }
86
+ /**
87
+ * Writes a set's new bits (from `next`, `words` long), marking only words that differ. Returns
88
+ * whether any did.
89
+ */
90
+ assign(set, next2) {
91
+ let changed = false;
92
+ for (let w = 0; w < set.bits.length; w++) {
93
+ const value = next2[w];
94
+ if (set.bits[w] !== value) {
95
+ this.write(set.base + w, value);
96
+ changed = true;
97
+ }
98
+ }
99
+ return changed;
100
+ }
101
+ /**
102
+ * Uploads changed words in runs to `buffer` at `byteOffset`; everything when that's not where
103
+ * the last upload went, or the buffer was replaced (grown, or a new device).
104
+ */
105
+ upload(buffer, byteOffset) {
106
+ buffer.ensureCapacity(byteOffset + this.data.byteLength);
107
+ void buffer.buffer;
108
+ if (buffer !== this.target || buffer.version !== this.targetVersion || byteOffset !== this.targetOffset) {
109
+ this.target = buffer;
110
+ this.targetVersion = buffer.version;
111
+ this.targetOffset = byteOffset;
112
+ this.all = true;
113
+ }
114
+ let bytes = 0;
115
+ if (this.all) {
116
+ this.all = false;
117
+ this.dirty.fill(0);
118
+ this.dirtyLo = Number.POSITIVE_INFINITY;
119
+ this.dirtyHi = -1;
120
+ if (this.data.length === 0) return 0;
121
+ buffer.write(this.data, byteOffset);
122
+ this.uploadedBytes = this.data.byteLength;
123
+ return this.uploadedBytes;
124
+ }
125
+ let start = -1;
126
+ for (let w = this.dirtyLo; w <= this.dirtyHi + 1; w++) {
127
+ if (w <= this.dirtyHi && this.dirty[w] === 1) {
128
+ this.dirty[w] = 0;
129
+ if (start < 0) start = w;
130
+ } else if (start >= 0) {
131
+ buffer.write(this.data, byteOffset + start * 4, start, w - start);
132
+ bytes += (w - start) * 4;
133
+ start = -1;
134
+ }
135
+ }
136
+ this.dirtyLo = Number.POSITIVE_INFINITY;
137
+ this.dirtyHi = -1;
138
+ this.uploadedBytes = bytes;
139
+ return bytes;
140
+ }
141
+ }
142
+ let next = new Uint32Array(0);
143
+ const stack = [];
144
+ function resolve(world, roots, out) {
145
+ out.fill(0);
146
+ let slots = 0;
147
+ stack.length = 0;
148
+ for (const e of roots) if (e !== null && e !== void 0) stack.push(e);
149
+ while (stack.length > 0) {
150
+ const e = stack.pop();
151
+ if (!world.isAlive(e)) continue;
152
+ const table = world.entityTable(e);
153
+ const row = world.entityRow(e);
154
+ if (table.has(InstanceSlot)) {
155
+ const slot = table.column(InstanceSlot, "slot")[row] - 1;
156
+ if (slot >= 0 && (out[slot >>> 5] & 1 << (slot & 31)) === 0) {
157
+ out[slot >>> 5] = out[slot >>> 5] | 1 << (slot & 31);
158
+ slots++;
159
+ }
160
+ }
161
+ if (table.has(Children)) {
162
+ const children = table.column(Children, "entities")[row];
163
+ if (children) {
164
+ for (const c of children) if (c !== null) stack.push(c);
165
+ }
166
+ }
167
+ }
168
+ return slots;
169
+ }
170
+ const resolveViewVisibility = defineSystem({
171
+ name: "render/resolve-view-visibility",
172
+ description: "Resolves cameras' ViewVisibility lists to hidden slot bitsets (0070).",
173
+ setup: (world) => ({
174
+ q: world.query({ with: [Camera3d, ViewVisibility] }),
175
+ hierarchy: world.query({ with: [ChildOf] }),
176
+ epoch: -1,
177
+ frame: 0
178
+ }),
179
+ run: (state, world, ctx) => {
180
+ const sets = world.tryResource(HiddenSetsResource);
181
+ if (!sets) return;
182
+ const cameras = world.resource(Cameras);
183
+ const store = world.resource(Instances);
184
+ const frame = ++state.frame;
185
+ let listed = 0;
186
+ for (const table of state.q.tables) listed += table.count;
187
+ if (listed === 0 && sets.sets.size === 0) return;
188
+ sets.ensureSlots(store.capacity);
189
+ if (next.length < sets.words) next = new Uint32Array(sets.words);
190
+ const since = ctx.lastRunTick;
191
+ let moved = store.slotEpoch !== state.epoch;
192
+ state.epoch = store.slotEpoch;
193
+ if (!moved) {
194
+ for (const table of state.hierarchy.tables) {
195
+ if (table.lastStructural > since || table.lastChanged(ChildOf) > since) {
196
+ moved = true;
197
+ break;
198
+ }
199
+ }
200
+ }
201
+ for (const table of state.q.tables) {
202
+ const hide = table.column(ViewVisibility, "hide");
203
+ const shadows = table.column(ViewVisibility, "shadows");
204
+ const changed = table.changedTicks(ViewVisibility);
205
+ for (let i = 0; i < table.count; i++) {
206
+ const camera = table.entities[i];
207
+ const fresh = !sets.sets.has(camera);
208
+ const set = sets.setOf(camera);
209
+ set.frame = frame;
210
+ const cam = cameras.get(camera);
211
+ if (cam) cam.hidden = set;
212
+ const hideShadows = shadows[i] === 1;
213
+ if (!fresh && !moved && changed[i] <= since && set.shadows === hideShadows) continue;
214
+ const list = hide[i] ?? [];
215
+ const words = next.subarray(0, sets.words);
216
+ set.slots = resolve(world, list, words);
217
+ set.hide = list.filter((e) => e !== null);
218
+ sets.resolves++;
219
+ if (sets.assign(set, words) && hideShadows || set.shadows !== hideShadows)
220
+ store.shadowEpoch++;
221
+ set.shadows = hideShadows;
222
+ }
223
+ }
224
+ if (sets.sets.size > listed) {
225
+ for (const set of sets.sets.values()) {
226
+ if (set.frame === frame) continue;
227
+ const cam = cameras.get(set.camera);
228
+ if (cam?.hidden === set) cam.hidden = void 0;
229
+ if (set.shadows) store.shadowEpoch++;
230
+ sets.drop(set);
231
+ }
232
+ }
233
+ }
234
+ });
235
+ function describeViewVisibility(world) {
236
+ const out = {};
237
+ for (const set of world.tryResource(HiddenSetsResource)?.sets.values() ?? [])
238
+ out[`camera:${set.camera}`] = {
239
+ hide: [...set.hide],
240
+ slots: set.slots,
241
+ shadows: set.shadows ? "hide" : "keep"
242
+ };
243
+ return out;
244
+ }
245
+ const viewVisibilityPlugin = definePlugin({
246
+ name: "render/view-visibility",
247
+ dependencies: ["render/forward"],
248
+ build(app) {
249
+ app.insertResource(HiddenSetsResource, new HiddenSets());
250
+ app.addSystems(Last, resolveViewVisibility.inSet(RenderSet.Prepare).after(prepareInstances));
251
+ },
252
+ ready(app) {
253
+ app.world.initResource(RenderDescribers).set("viewVisibility", describeViewVisibility);
254
+ addRenderFeatures(app.world, {
255
+ name: "render/view-visibility",
256
+ description: "Cameras' hidden lists (ViewVisibility).",
257
+ nodes: [],
258
+ baseline: { strategy: "CPU culls read the same bits" }
259
+ });
260
+ }
261
+ });
262
+ export {
263
+ HiddenSets,
264
+ describeViewVisibility,
265
+ resolveViewVisibility,
266
+ viewVisibilityPlugin
267
+ };
@@ -0,0 +1,28 @@
1
+ import { type Entity, type World } from '@aethervtt/shard-core';
2
+ import type { HiddenSets } from './view-visibility-plugin.js';
3
+ export declare const VIEW_SHADOWS: readonly ['keep', 'hide'];
4
+ export declare const ViewVisibility: import("@aethervtt/shard-core").ComponentDef<{
5
+ readonly hide: import("@aethervtt/shard-core").FieldType<(number | null)[], "object">;
6
+ readonly shadows: import("@aethervtt/shard-core").FieldType<"hide" | "keep", "u8">;
7
+ }>;
8
+ /** `hiddenBase` of a view with no hidden set. */
9
+ export declare const NO_HIDDEN = 4294967295;
10
+ /** One camera's hidden set: a bit per instance slot, in its range of `HiddenSets.data`. */
11
+ export interface HiddenSet {
12
+ camera: Entity;
13
+ /** Its first word in `HiddenSets.data` (and the GPU buffer). */
14
+ base: number;
15
+ /** Its words: bit `s & 31` of word `s >> 5` is slot `s`. */
16
+ bits: Uint32Array;
17
+ /** Slots it resolves to. */
18
+ slots: number;
19
+ /** The entities listed, as last resolved. */
20
+ hide: Entity[];
21
+ /** Its camera's cascades leave the slots out too. */
22
+ shadows: boolean;
23
+ /** The frame it was last seen on (sets of cameras that lose the component are freed). */
24
+ frame: number;
25
+ }
26
+ export declare const HiddenSetsResource: import("@aethervtt/shard-core").ResourceDef<HiddenSets>;
27
+ /** Warns once per world about a ViewVisibility without its plugin (render/feature-missing). */
28
+ export declare function observeViewVisibilityWithoutPlugin(world: World): void;
@@ -0,0 +1,46 @@
1
+ import {
2
+ defineComponent,
3
+ defineResource,
4
+ onAdd,
5
+ t
6
+ } from "@aethervtt/shard-core";
7
+ import { Camera3d } from "./camera.js";
8
+ import { warnFeatureMissing } from "./features.js";
9
+ const VIEW_SHADOWS = ["keep", "hide"];
10
+ const ViewVisibility = defineComponent(
11
+ "render/ViewVisibility",
12
+ {
13
+ hide: t.list(t.entity, {
14
+ description: "Entities this camera doesn't draw, with their descendants."
15
+ }),
16
+ shadows: t.enum(VIEW_SHADOWS, {
17
+ description: "keep: they still cast into this camera's shadows (a body its own camera doesn't draw). hide: its cascades leave them out too. Spot and point shadow maps are shared by every camera and always keep them."
18
+ })
19
+ },
20
+ {
21
+ description: "Hides entities from one camera (0070): other cameras still draw them. For a first-person body, split-screen markers, or a game master previewing a player view.",
22
+ requires: [Camera3d]
23
+ }
24
+ );
25
+ const NO_HIDDEN = 4294967295;
26
+ const HiddenSetsResource = defineResource("render/HiddenSets", {
27
+ description: "Each camera's hidden slots (ViewVisibility, 0070), as the culls read them."
28
+ });
29
+ function observeViewVisibilityWithoutPlugin(world) {
30
+ world.observe(onAdd(ViewVisibility), ({ world: w }) => {
31
+ if (!w.hasResource(HiddenSetsResource))
32
+ warnFeatureMissing(
33
+ w,
34
+ "A camera has ViewVisibility",
35
+ "viewVisibilityPlugin",
36
+ "it hides nothing"
37
+ );
38
+ });
39
+ }
40
+ export {
41
+ HiddenSetsResource,
42
+ NO_HIDDEN,
43
+ VIEW_SHADOWS,
44
+ ViewVisibility,
45
+ observeViewVisibilityWithoutPlugin
46
+ };
package/dist/view.d.ts CHANGED
@@ -3,6 +3,7 @@ import type { RenderView } from './graph.js';
3
3
  import { type DrawList } from './instances.js';
4
4
  import { type PixelPerfectLayout } from './pixel-perfect.js';
5
5
  import { type PostSettings } from './post.js';
6
+ import type { HiddenSet } from './view-visibility.js';
6
7
  export declare const TONEMAP_CURVES: readonly ['aces', 'agx', 'pbr-neutral', 'reinhard', 'none'];
7
8
  export type TonemapCurve = (typeof TONEMAP_CURVES)[number];
8
9
  /** The curve for cameras without a Tonemapping component. */
@@ -56,9 +57,17 @@ export interface CameraData {
56
57
  /** Scene luminance → pre-exposed HDR. */
57
58
  exposure: number;
58
59
  ev100: number;
59
- /** Linear clear color, as displayed before the tonemap curve. */
60
+ /** Linear clear color, as displayed before the tonemap curve, premultiplied by its alpha. */
60
61
  clear: GPUColor;
62
+ /**
63
+ * Clears to an alpha below 1 (0052): the passes carry alpha to the target, the sky and the
64
+ * environment background aren't drawn, and light raises alpha, so the view composites over
65
+ * whatever is under its surface.
66
+ */
67
+ alphaOutput: boolean;
61
68
  msaa: number;
69
+ /** MSAA was asked for, but the device can't multisample HDR targets (0064): `msaa` is 1. */
70
+ msaaCapped: boolean;
62
71
  curve: number;
63
72
  dither: boolean;
64
73
  /** Culled draws of this camera, rebuilt each frame. */
@@ -67,6 +76,14 @@ export interface CameraData {
67
76
  transparent: DrawList;
68
77
  /** Deferred views: opaque draws the G-buffer can't take (custom lighting), drawn forward. */
69
78
  forwardOnly: DrawList;
79
+ /** Ground draws (0057): coplanar bands in (band, order) order, after opaque geometry. */
80
+ ground: DrawList;
81
+ /** Ground draws from the overlay band up: after transparent objects and projected fog (0058). */
82
+ overlay: DrawList;
83
+ /** Render layers this camera draws (Camera3d.layers). */
84
+ layers: number;
85
+ /** Slots it doesn't draw (ViewVisibility, 0070), while it has the component. */
86
+ hidden: HiddenSet | undefined;
70
87
  /** Renders through the G-buffer (RenderPath deferred). */
71
88
  deferred: boolean;
72
89
  /** G-buffer channel to show for a capture (-1: none), also forcing a G-buffer in forward views. */
@@ -85,7 +102,18 @@ export interface CameraData {
85
102
  post: PostSettings;
86
103
  /** Set for PixelPerfect cameras: the low-resolution target and how it scales up. */
87
104
  pixelPerfect: PixelPerfectLayout | undefined;
105
+ /** The display size and pixel ratio the last CameraMoved was sent for. */
106
+ movedWidth: number;
107
+ movedHeight: number;
108
+ movedRatio: number;
88
109
  }
110
+ /**
111
+ * Sent after extraction, once per camera whose projection moved this frame (its unjittered view-
112
+ * projection, display size or pixel ratio changed), and on its first frame (0057).
113
+ */
114
+ export declare const CameraMoved: import("@aethervtt/shard-core").EventDef<{
115
+ camera: Entity;
116
+ }>;
89
117
  export declare const Cameras: import("@aethervtt/shard-core").ResourceDef<Map<number, CameraData>>;
90
118
  /** Global render settings the camera extraction applies to every view. */
91
119
  export interface ViewSettingsValue {
package/dist/view.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  affine,
3
3
  defineComponent,
4
+ defineEvent,
4
5
  defineResource,
5
6
  defineSystem,
6
7
  frustum,
@@ -12,13 +13,15 @@ import { LogResource, Time } from "@aethervtt/shard-runtime";
12
13
  import { GlobalTransform } from "@aethervtt/shard-transform";
13
14
  import { RenderTargets } from "./assets.js";
14
15
  import { Camera3d, Exposure, exposureScale } from "./camera.js";
16
+ import { warnFeatureMissing } from "./features.js";
15
17
  import { createDrawList } from "./instances.js";
18
+ import { Lens, LensPath } from "./lens.js";
16
19
  import {
17
20
  PixelPerfect,
18
21
  PixelPerfectPath,
19
22
  pixelPerfectLayout
20
23
  } from "./pixel-perfect.js";
21
- import { Views, Window } from "./plugin.js";
24
+ import { Gpu, Views, Window } from "./plugin.js";
22
25
  import {
23
26
  antialiasingOf,
24
27
  CORE_EFFECTS,
@@ -58,13 +61,21 @@ const RenderPath = defineComponent(
58
61
  const DeferredPath = defineResource("render/DeferredPath", {
59
62
  description: "Present when the deferred path (deferredPlugin) is installed."
60
63
  });
64
+ const CameraMoved = defineEvent("render/CameraMoved", {
65
+ description: "A camera moved or its view resized: DOM overlays following the scene reposition."
66
+ });
67
+ function sameMatrix(a, b) {
68
+ for (let i = 0; i < 16; i++) if (a[i] !== b[i]) return false;
69
+ return true;
70
+ }
61
71
  const Cameras = defineResource("render/Cameras", {
62
72
  description: "Per-camera render data by entity, extracted each frame.",
63
73
  init: () => /* @__PURE__ */ new Map()
64
74
  });
65
75
  const ViewSettings = defineResource("render/ViewSettings", {
66
76
  description: "Render settings shared by all views: the default MSAA sample count, and the display density past which it is skipped.",
67
- init: () => ({ msaa: 4, msaaMaxPixelRatio: 1.5 })
77
+ init: () => ({ msaa: 4, msaaMaxPixelRatio: 1.5 }),
78
+ hostWritable: true
68
79
  });
69
80
  const scratchProj = mat4.create();
70
81
  const scratchSnap = affine.create();
@@ -90,32 +101,6 @@ function viewAliases(cam) {
90
101
  return cam.msaa > 1 ? MSAA_ALIASES : NO_MSAA_ALIASES;
91
102
  }
92
103
  const reportedEffects = /* @__PURE__ */ new WeakMap();
93
- const reportedDeferred = /* @__PURE__ */ new WeakSet();
94
- function warnMissingDeferred(world) {
95
- if (reportedDeferred.has(world)) return;
96
- reportedDeferred.add(world);
97
- world.tryResource(LogResource)?.log(
98
- "warn",
99
- "A camera asks for RenderPath deferred, but deferredPlugin isn't installed; it renders forward",
100
- {
101
- code: "render/feature-missing",
102
- hint: "Add deferredPlugin from '@aethervtt/shard-render' (forwardPlugin includes it)."
103
- }
104
- );
105
- }
106
- const reportedPixel = /* @__PURE__ */ new WeakSet();
107
- function warnMissingPixelPerfect(world) {
108
- if (reportedPixel.has(world)) return;
109
- reportedPixel.add(world);
110
- world.tryResource(LogResource)?.log(
111
- "warn",
112
- "A camera has PixelPerfect, but pixelPerfectPlugin isn't installed; it renders at full resolution",
113
- {
114
- code: "render/feature-missing",
115
- hint: "Add pixelPerfectPlugin from '@aethervtt/shard-render' (forwardPlugin includes it)."
116
- }
117
- );
118
- }
119
104
  function warnMissingEffects(world, missing) {
120
105
  const reported = reportedEffects.get(world) ?? 0;
121
106
  const fresh = missing & ~reported;
@@ -148,6 +133,8 @@ const extractCameras = defineSystem({
148
133
  const installed = world.tryResource(PostFeatures)?.effects ?? CORE_EFFECTS;
149
134
  const deferredInstalled = world.hasResource(DeferredPath);
150
135
  const pixelInstalled = world.hasResource(PixelPerfectPath);
136
+ const lensInstalled = world.hasResource(LensPath);
137
+ const maxSamples = world.resource(Gpu).capabilities.hdrSampleCount;
151
138
  for (const table of q.tables) {
152
139
  const projection = table.column(Camera3d, "projection");
153
140
  const fovY = table.column(Camera3d, "fovY");
@@ -158,13 +145,22 @@ const extractCameras = defineSystem({
158
145
  const clear = table.column(Camera3d, "clearColor");
159
146
  const target = table.column(Camera3d, "target");
160
147
  const active = table.column(Camera3d, "active");
148
+ const layers = table.column(Camera3d, "layers");
161
149
  const g = table.column(GlobalTransform, "matrix");
162
150
  const ev = table.column(Exposure, "ev100");
163
151
  const path = table.has(RenderPath) ? table.column(RenderPath, "mode") : void 0;
164
152
  const hasTonemap = table.has(Tonemapping);
165
153
  const curve = hasTonemap ? table.column(Tonemapping, "curve") : void 0;
166
154
  const dither = hasTonemap ? table.column(Tonemapping, "dither") : void 0;
167
- if (table.has(PixelPerfect) && !pixelInstalled) warnMissingPixelPerfect(world);
155
+ if (table.has(PixelPerfect) && !pixelInstalled)
156
+ warnFeatureMissing(
157
+ world,
158
+ "A camera has PixelPerfect",
159
+ "pixelPerfectPlugin",
160
+ "it renders at full resolution"
161
+ );
162
+ if (table.has(Lens) && !lensInstalled)
163
+ warnFeatureMissing(world, "A camera has Lens", "lensPlugin", "it renders unbent");
168
164
  const pixel = pixelInstalled && table.has(PixelPerfect);
169
165
  const ppu = pixel ? table.column(PixelPerfect, "pixelsPerUnit") : void 0;
170
166
  const snap = pixel ? table.column(PixelPerfect, "snap") : void 0;
@@ -205,12 +201,18 @@ const extractCameras = defineSystem({
205
201
  exposure: 1,
206
202
  ev100: 0,
207
203
  clear: { r: 0, g: 0, b: 0, a: 1 },
204
+ alphaOutput: false,
208
205
  msaa: 1,
206
+ msaaCapped: false,
209
207
  curve: 1,
210
208
  dither: true,
211
209
  draws: createDrawList(),
212
210
  transparent: createDrawList(),
213
211
  forwardOnly: createDrawList(),
212
+ ground: createDrawList(),
213
+ overlay: createDrawList(),
214
+ layers: 4294967295,
215
+ hidden: void 0,
214
216
  deferred: false,
215
217
  gbufferDebug: -1,
216
218
  lodState: new Uint8Array(0),
@@ -220,11 +222,15 @@ const extractCameras = defineSystem({
220
222
  frames: 0,
221
223
  debug: 0,
222
224
  post: createPostSettings(),
223
- pixelPerfect: void 0
225
+ pixelPerfect: void 0,
226
+ movedWidth: 0,
227
+ movedHeight: 0,
228
+ movedRatio: 0
224
229
  };
225
230
  cameras.set(entity, cam);
226
231
  }
227
232
  cam.orthographic = projection[i] !== 0;
233
+ cam.layers = layers[i];
228
234
  let height = orthoHeight[i];
229
235
  cam.pixelPerfect = void 0;
230
236
  if (ppu && cam.orthographic) {
@@ -258,7 +264,13 @@ const extractCameras = defineSystem({
258
264
  }
259
265
  const wantsDeferred = path ? path[i] === 1 : false;
260
266
  cam.deferred = wantsDeferred && deferredInstalled;
261
- if (wantsDeferred && !deferredInstalled) warnMissingDeferred(world);
267
+ if (wantsDeferred && !deferredInstalled)
268
+ warnFeatureMissing(
269
+ world,
270
+ "A camera asks for RenderPath deferred",
271
+ "deferredPlugin",
272
+ "it renders forward"
273
+ );
262
274
  const missing = extractPost(table, i, cam, delta, true, installed);
263
275
  if (missing !== 0) warnMissingEffects(world, missing);
264
276
  const world_ = g.subarray(i * 12, i * 12 + 12);
@@ -275,6 +287,12 @@ const extractCameras = defineSystem({
275
287
  if (cam.frames > 0) mat4.copy(cam.prevViewProj, cam.viewProjNoJitter);
276
288
  mat4.multiply(cam.viewProjNoJitter, scratchProj, cam.view);
277
289
  if (cam.frames === 0) mat4.copy(cam.prevViewProj, cam.viewProjNoJitter);
290
+ if (cam.frames === 0 || !sameMatrix(cam.viewProjNoJitter, cam.prevViewProj) || cam.displayWidth !== cam.movedWidth || cam.displayHeight !== cam.movedHeight || cam.pixelRatio !== cam.movedRatio) {
291
+ cam.movedWidth = cam.displayWidth;
292
+ cam.movedHeight = cam.displayHeight;
293
+ cam.movedRatio = cam.pixelRatio;
294
+ world.send(CameraMoved, { camera: entity });
295
+ }
278
296
  const jitter = cam.post.jitter;
279
297
  if (cam.post.effects & PostEffect.Taa) {
280
298
  const k = cam.frames % 8 + 1;
@@ -309,10 +327,14 @@ const extractCameras = defineSystem({
309
327
  cam.ev100 = ev[i];
310
328
  cam.exposure = exposureScale(ev[i]);
311
329
  const c = i * 4;
312
- cam.clear = { r: clear[c], g: clear[c + 1], b: clear[c + 2], a: clear[c + 3] };
330
+ const ca = Math.min(1, Math.max(0, clear[c + 3]));
331
+ cam.clear = { r: clear[c] * ca, g: clear[c + 1] * ca, b: clear[c + 2] * ca, a: ca };
332
+ cam.alphaOutput = ca < 1;
313
333
  const aa = antialiasingOf(table, i);
314
334
  const dense = settings.msaaMaxPixelRatio > 0 && cam.pixelRatio >= settings.msaaMaxPixelRatio;
315
- cam.msaa = cam.deferred ? 1 : aa < 0 ? dense ? 1 : settings.msaa : aa === 3 ? 4 : 1;
335
+ const msaa = cam.deferred ? 1 : aa < 0 ? dense ? 1 : settings.msaa : aa === 3 ? 4 : 1;
336
+ cam.msaa = Math.min(msaa, maxSamples);
337
+ cam.msaaCapped = cam.msaa < msaa;
316
338
  cam.curve = curve ? curve[i] : TONEMAP_CURVES.indexOf(DEFAULT_CURVE);
317
339
  cam.dither = dither ? dither[i] !== 0 : true;
318
340
  cam.frames++;
@@ -333,7 +355,7 @@ const extractCameras = defineSystem({
333
355
  name: `pixel-perfect:${entity}`,
334
356
  target: shown,
335
357
  order: order[i] + 0.5,
336
- data: { upscale: cam.pixelPerfect }
358
+ data: { upscale: cam.pixelPerfect, alphaOutput: cam.alphaOutput }
337
359
  });
338
360
  }
339
361
  }
@@ -374,6 +396,7 @@ function cameraOf(view) {
374
396
  return view.data.camera;
375
397
  }
376
398
  export {
399
+ CameraMoved,
377
400
  Cameras,
378
401
  DEFAULT_CURVE,
379
402
  DeferredPath,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aethervtt/shard-render",
3
- "version": "0.0.1",
3
+ "version": "0.2.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -9,6 +9,14 @@
9
9
  "types": "./dist/index.d.ts",
10
10
  "default": "./dist/index.js"
11
11
  },
12
+ "./crowd": {
13
+ "types": "./dist/crowd.d.ts",
14
+ "default": "./dist/crowd.js"
15
+ },
16
+ "./post-scene": {
17
+ "types": "./dist/post-scene.d.ts",
18
+ "default": "./dist/post-scene.js"
19
+ },
12
20
  "./testing": {
13
21
  "types": "./dist/testing.d.ts",
14
22
  "default": "./dist/testing.js"
@@ -16,6 +24,10 @@
16
24
  "./noise": {
17
25
  "types": "./dist/noise.d.ts",
18
26
  "default": "./dist/noise.js"
27
+ },
28
+ "./surface": {
29
+ "types": "./dist/surface.d.ts",
30
+ "default": "./dist/surface.js"
19
31
  }
20
32
  },
21
33
  "files": [
@@ -24,15 +36,15 @@
24
36
  "README.md"
25
37
  ],
26
38
  "dependencies": {
27
- "@aethervtt/shard-assets": "0.0.1",
28
- "@aethervtt/shard-core": "0.0.1",
29
- "@aethervtt/shard-gpu": "0.0.1",
30
- "@aethervtt/shard-mesh": "0.0.1",
31
- "@aethervtt/shard-noise": "0.0.1",
32
- "@aethervtt/shard-runtime": "0.0.1",
33
- "@aethervtt/shard-shader": "0.0.1",
34
- "@aethervtt/shard-texture": "0.0.1",
35
- "@aethervtt/shard-transform": "0.0.1"
39
+ "@aethervtt/shard-assets": "0.2.0",
40
+ "@aethervtt/shard-core": "0.2.0",
41
+ "@aethervtt/shard-gpu": "0.2.0",
42
+ "@aethervtt/shard-mesh": "0.2.0",
43
+ "@aethervtt/shard-noise": "0.2.0",
44
+ "@aethervtt/shard-runtime": "0.2.0",
45
+ "@aethervtt/shard-shader": "0.2.0",
46
+ "@aethervtt/shard-texture": "0.2.0",
47
+ "@aethervtt/shard-transform": "0.2.0"
36
48
  },
37
49
  "repository": {
38
50
  "type": "git",
@@ -44,6 +56,6 @@
44
56
  "provenance": true
45
57
  },
46
58
  "shard": {
47
- "buildSha": "d6c5c6aef4675575c878b02cf2c8f738b4190229"
59
+ "buildSha": "32c22a3944ed23f5d1baa90bf7cc9fae724f5fd8"
48
60
  }
49
61
  }