@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
package/dist/decals.js ADDED
@@ -0,0 +1,326 @@
1
+ import {
2
+ affine,
3
+ defineResource,
4
+ defineSystem
5
+ } from "@aethervtt/shard-core";
6
+ import { FrameDemand, Time } from "@aethervtt/shard-runtime";
7
+ import { GlobalTransform } from "@aethervtt/shard-transform";
8
+ import { Materials, Meshes } from "./assets.js";
9
+ import { Mesh3d, MeshMaterial, MorphWeights, SkinnedMesh } from "./instances.js";
10
+ import { RenderLayers } from "./layers.js";
11
+ import { isTransparent } from "./materials.js";
12
+ import { Decal, DecalReceiver } from "./vfx.js";
13
+ import {
14
+ createRecord,
15
+ matrixChanged,
16
+ number,
17
+ point,
18
+ recordDescription,
19
+ refreshRecord,
20
+ VFX_STRIDE
21
+ } from "./vfx-state.js";
22
+ import { ComputedVisibility } from "./visibility.js";
23
+ const DECAL_TRIANGLE_BUDGET = 32768;
24
+ const Decals = defineResource("render/Decals", {
25
+ init: () => ({
26
+ records: [],
27
+ receivers: [],
28
+ receiverByEntity: /* @__PURE__ */ new Map(),
29
+ frame: 0,
30
+ revision: 0,
31
+ rejected: 0
32
+ })
33
+ });
34
+ function create(world, entity) {
35
+ const max = world.get(entity, Decal).maxVertices;
36
+ return {
37
+ ...createRecord(world, entity, Decal, "decal", max),
38
+ inverse: new Float64Array(12),
39
+ settings: new Float64Array(8).fill(NaN),
40
+ geometryCount: 0,
41
+ receiverRevision: -1,
42
+ rebuilds: 0,
43
+ ranges: [],
44
+ clipA: new Float64Array(48),
45
+ clipB: new Float64Array(48),
46
+ worldTriangle: new Float64Array(9)
47
+ };
48
+ }
49
+ function clipPlane(input, count, output, axis, sign, half) {
50
+ let n = 0;
51
+ for (let i = 0; i < count; i++) {
52
+ const a = (i + count - 1) % count * 3, b = i * 3;
53
+ const da = half - sign * input[a + axis], db = half - sign * input[b + axis];
54
+ if (da >= 0 !== db >= 0) {
55
+ const f = da / (da - db);
56
+ for (let k = 0; k < 3; k++)
57
+ output[n * 3 + k] = input[a + k] + (input[b + k] - input[a + k]) * f;
58
+ n++;
59
+ }
60
+ if (db >= 0) {
61
+ for (let k = 0; k < 3; k++) output[n * 3 + k] = input[b + k];
62
+ n++;
63
+ }
64
+ }
65
+ return n;
66
+ }
67
+ function intersects(r, receiver, sx, sy, sz) {
68
+ const bounds = receiver.mesh.bounds, scratch = r.worldTriangle;
69
+ let minX = Infinity, minY = Infinity, minZ = Infinity, maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
70
+ for (let i = 0; i < 8; i++) {
71
+ point(
72
+ scratch,
73
+ 0,
74
+ receiver.matrix,
75
+ bounds[i & 1 ? 3 : 0],
76
+ bounds[i & 2 ? 4 : 1],
77
+ bounds[i & 4 ? 5 : 2]
78
+ );
79
+ point(scratch, 3, r.inverse, scratch[0], scratch[1], scratch[2]);
80
+ minX = Math.min(minX, scratch[3]);
81
+ minY = Math.min(minY, scratch[4]);
82
+ minZ = Math.min(minZ, scratch[5]);
83
+ maxX = Math.max(maxX, scratch[3]);
84
+ maxY = Math.max(maxY, scratch[4]);
85
+ maxZ = Math.max(maxZ, scratch[5]);
86
+ }
87
+ return minX <= sx / 2 && maxX >= -sx / 2 && minY <= sy / 2 && maxY >= -sy / 2 && minZ <= sz / 2 && maxZ >= -sz / 2;
88
+ }
89
+ function projectDecal(r, receivers) {
90
+ r.count = 0;
91
+ r.geometryCount = 0;
92
+ r.ranges.length = 0;
93
+ r.error = null;
94
+ r.rebuilds++;
95
+ if (!affine.invert(r.inverse, r.matrix)) {
96
+ r.error = "render/decal-singular";
97
+ return;
98
+ }
99
+ const size = r.columns.size, so = r.row * 3;
100
+ const sx = size[so], sy = size[so + 1], sz = size[so + 2];
101
+ if (!(sx > 0 && sy > 0 && sz > 0)) {
102
+ r.error = "render/decal-size";
103
+ return;
104
+ }
105
+ const mask = number(r, "receiverMask"), offset = number(r, "offset"), cos = Math.cos(number(r, "maxAngle") * Math.PI / 180);
106
+ const zx = r.matrix[2], zy = r.matrix[6], zz = r.matrix[10], zl = Math.sqrt(zx * zx + zy * zy + zz * zz);
107
+ let tested = 0;
108
+ for (let ri = 0; ri < receivers.length; ri++) {
109
+ const receiver = receivers[ri], mesh = receiver.mesh;
110
+ if (!receiver.visible || !(receiver.mask & mask) || !mesh || !intersects(r, receiver, sx, sy, sz))
111
+ continue;
112
+ if (receiver.unsupported) {
113
+ r.error = "render/decal-unsupported-receiver";
114
+ break;
115
+ }
116
+ const start = r.count, positions = mesh.positions, indices = mesh.indices;
117
+ const triangles = Math.floor((indices ? indices.length : positions.length / 3) / 3);
118
+ if (tested + triangles > DECAL_TRIANGLE_BUDGET) {
119
+ r.error = "render/decal-input-budget";
120
+ break;
121
+ }
122
+ tested += triangles;
123
+ for (let t = 0; t < triangles; t++) {
124
+ for (let v = 0; v < 3; v++) {
125
+ const vi = (indices ? indices[t * 3 + v] : t * 3 + v) * 3;
126
+ point(
127
+ r.worldTriangle,
128
+ v * 3,
129
+ receiver.matrix,
130
+ positions[vi],
131
+ positions[vi + 1],
132
+ positions[vi + 2]
133
+ );
134
+ }
135
+ const w = r.worldTriangle;
136
+ const ax = w[3] - w[0], ay = w[4] - w[1], az = w[5] - w[2], bx = w[6] - w[0], by = w[7] - w[1], bz = w[8] - w[2];
137
+ let nx = ay * bz - az * by, ny = az * bx - ax * bz, nz = ax * by - ay * bx;
138
+ const length = Math.sqrt(nx * nx + ny * ny + nz * nz);
139
+ if (length < 1e-12) continue;
140
+ nx /= length;
141
+ ny /= length;
142
+ nz /= length;
143
+ if ((nx * zx + ny * zy + nz * zz) / zl < cos) continue;
144
+ for (let v = 0; v < 3; v++)
145
+ point(r.clipA, v * 3, r.inverse, w[v * 3], w[v * 3 + 1], w[v * 3 + 2]);
146
+ let a = r.clipA, b = r.clipB, count = 3;
147
+ for (let plane = 0; plane < 6 && count; plane++) {
148
+ const axis = plane >> 1, half = (axis === 0 ? sx : axis === 1 ? sy : sz) * 0.5;
149
+ count = clipPlane(a, count, b, axis, plane % 2 ? -1 : 1, half);
150
+ const swap = a;
151
+ a = b;
152
+ b = swap;
153
+ }
154
+ if (count < 3) continue;
155
+ if (r.count + (count - 2) * 3 > r.vertices.length / VFX_STRIDE) {
156
+ r.error = "render/decal-output-budget";
157
+ break;
158
+ }
159
+ for (let fan = 1; fan < count - 1; fan++)
160
+ for (let v = 0; v < 3; v++) {
161
+ const at = (v === 0 ? 0 : v === 1 ? fan : fan + 1) * 3, o = r.count++ * VFX_STRIDE;
162
+ point(w, 0, r.matrix, a[at], a[at + 1], a[at + 2]);
163
+ r.vertices[o] = w[0] + nx * offset;
164
+ r.vertices[o + 1] = w[1] + ny * offset;
165
+ r.vertices[o + 2] = w[2] + nz * offset;
166
+ r.vertices[o + 3] = 0;
167
+ r.vertices[o + 4] = 0;
168
+ r.vertices[o + 5] = 0;
169
+ r.vertices[o + 6] = a[at] / sx + 0.5;
170
+ r.vertices[o + 7] = 0.5 - a[at + 1] / sy;
171
+ for (let k = 8; k < 12; k++) r.vertices[o + k] = 1;
172
+ r.vertices[o + 12] = 0;
173
+ r.vertices[o + 13] = 0;
174
+ }
175
+ }
176
+ if (r.error) break;
177
+ if (r.count > start)
178
+ r.ranges.push({
179
+ start,
180
+ count: r.count - start,
181
+ entity: receiver.entity,
182
+ layers: receiver.layers
183
+ });
184
+ }
185
+ if (r.error) {
186
+ r.count = 0;
187
+ r.ranges.length = 0;
188
+ }
189
+ r.geometryCount = r.count;
190
+ r.version++;
191
+ }
192
+ const updateDecals = defineSystem({
193
+ name: "render/decals-update",
194
+ setup: (world) => ({
195
+ q: world.query({ with: [Decal, GlobalTransform] }),
196
+ receivers: world.query({ with: [DecalReceiver, Mesh3d, GlobalTransform] })
197
+ }),
198
+ run({ q, receivers }, world) {
199
+ const state = world.resource(Decals), meshes = world.resource(Meshes), delta = world.resource(Time).delta;
200
+ state.frame++;
201
+ state.rejected = 0;
202
+ for (let ti = 0; ti < receivers.tables.length; ti++) {
203
+ const table = receivers.tables[ti], mask = table.column(DecalReceiver, "mask"), refs = table.column(Mesh3d, "mesh");
204
+ for (let row = 0; row < table.count; row++) {
205
+ const entity = table.entities[row];
206
+ let receiver = state.receiverByEntity.get(entity);
207
+ if (!receiver) {
208
+ receiver = {
209
+ entity,
210
+ matrix: new Float64Array(12),
211
+ mesh: void 0,
212
+ version: -1,
213
+ mask: -1,
214
+ visible: false,
215
+ unsupported: false,
216
+ layers: 1,
217
+ seen: 0
218
+ };
219
+ state.receivers.push(receiver);
220
+ state.receiverByEntity.set(entity, receiver);
221
+ state.revision++;
222
+ }
223
+ const mesh = meshes.get(refs[row]), version = mesh?.version ?? -1;
224
+ const visible = !table.has(ComputedVisibility) || table.column(ComputedVisibility, "visible")[row] !== 0;
225
+ const layers = table.has(RenderLayers) ? table.column(RenderLayers, "mask")[row] : 1;
226
+ let unsupported = !!mesh && (!mesh.positions || !!mesh.joints || !!mesh.targets?.length || table.has(SkinnedMesh) || table.has(MorphWeights));
227
+ if (table.has(MeshMaterial)) {
228
+ const material = world.tryResource(Materials)?.get(table.column(MeshMaterial, "material")[row]);
229
+ if (material && isTransparent(material.type.blendOf(material.value))) unsupported = true;
230
+ }
231
+ if (matrixChanged(world, entity, receiver.matrix) || receiver.mesh !== mesh || receiver.version !== version || receiver.mask !== mask[row] || receiver.visible !== visible || receiver.layers !== layers || receiver.unsupported !== unsupported)
232
+ state.revision++;
233
+ receiver.mesh = mesh;
234
+ receiver.version = version;
235
+ receiver.mask = mask[row];
236
+ receiver.visible = visible;
237
+ receiver.layers = layers;
238
+ receiver.unsupported = unsupported;
239
+ receiver.seen = state.frame;
240
+ }
241
+ }
242
+ for (let i = state.receivers.length - 1; i >= 0; i--)
243
+ if (state.receivers[i].seen !== state.frame) {
244
+ state.receiverByEntity.delete(state.receivers[i].entity);
245
+ state.receivers.splice(i, 1);
246
+ state.revision++;
247
+ }
248
+ let demand = false;
249
+ for (let ti = 0; ti < q.tables.length; ti++) {
250
+ const table = q.tables[ti];
251
+ for (let row = 0; row < table.count; row++) {
252
+ const entity = table.entities[row];
253
+ const maxVertices = table.column(Decal, "maxVertices")[row];
254
+ if (maxVertices < 3 || maxVertices > 65535) {
255
+ state.rejected++;
256
+ continue;
257
+ }
258
+ let r;
259
+ for (let i = 0; i < state.records.length; i++)
260
+ if (state.records[i].entity === entity) {
261
+ r = state.records[i];
262
+ break;
263
+ }
264
+ if (!r) {
265
+ if (state.records.length >= 64) {
266
+ state.rejected++;
267
+ continue;
268
+ }
269
+ r = create(world, entity);
270
+ state.records.push(r);
271
+ }
272
+ refreshRecord(world, r);
273
+ r.seen = state.frame;
274
+ let changed = matrixChanged(world, entity, r.matrix);
275
+ const size = r.columns.size;
276
+ for (let i = 0; i < 8; i++) {
277
+ const value = i < 3 ? size[row * 3 + i] : i === 3 ? number(r, "receiverMask") : i === 4 ? number(r, "maxAngle") : i === 5 ? number(r, "offset") : i === 6 ? number(r, "maxVertices") : 0;
278
+ if (r.settings[i] !== value) changed = true;
279
+ r.settings[i] = value;
280
+ }
281
+ const capacity = number(r, "maxVertices") * VFX_STRIDE;
282
+ if (r.vertices.length !== capacity) {
283
+ r.vertices = new Float32Array(capacity);
284
+ changed = true;
285
+ }
286
+ const playing = !!number(r, "playing"), paused = !!number(r, "paused"), lifetime = number(r, "lifetime");
287
+ if (playing && !r.wasPlaying) r.age = 0;
288
+ r.wasPlaying = playing;
289
+ if (playing && !paused) r.age += delta;
290
+ if (changed || r.receiverRevision !== state.revision) {
291
+ projectDecal(r, state.receivers);
292
+ r.receiverRevision = state.revision;
293
+ }
294
+ r.count = playing && (!lifetime || r.age < lifetime) ? r.geometryCount : 0;
295
+ if (lifetime && number(r, "fadeOut") > 0)
296
+ r.opacity *= Math.min(1, Math.max(0, (lifetime - r.age) / number(r, "fadeOut")));
297
+ if (playing && !paused && lifetime > 0 && r.age < lifetime) demand = true;
298
+ }
299
+ }
300
+ for (let i = state.records.length - 1; i >= 0; i--)
301
+ if (state.records[i].seen !== state.frame) state.records.splice(i, 1);
302
+ world.tryResource(FrameDemand)?.set("render/decals", demand);
303
+ }
304
+ });
305
+ function describeDecals(world) {
306
+ const s = world.resource(Decals);
307
+ return {
308
+ rejected: s.rejected,
309
+ error: s.rejected ? "render/decal-limit" : null,
310
+ receivers: s.receivers.length,
311
+ records: s.records.map((r) => ({
312
+ ...recordDescription(r),
313
+ cpuBytes: r.vertices.byteLength + r.matrix.byteLength + r.inverse.byteLength + r.settings.byteLength + r.clipA.byteLength + r.clipB.byteLength + r.worldTriangle.byteLength,
314
+ rebuilds: r.rebuilds,
315
+ triangles: r.geometryCount / 3
316
+ }))
317
+ };
318
+ }
319
+ export {
320
+ DECAL_TRIANGLE_BUDGET,
321
+ Decals,
322
+ clipPlane,
323
+ describeDecals,
324
+ projectDecal,
325
+ updateDecals
326
+ };
@@ -66,6 +66,7 @@ import shard::mesh::mesh_vertex_at;
66
66
  import shard::pbr::material::pbr_input;
67
67
  import shard::pbr::standard::material;
68
68
  import shard::pbr::gbuffer::{ GBufferOutput, pack_gbuffer };
69
+ @if(CUTAWAY) import shard::cutaway::cutaway_clip;
69
70
 
70
71
  const FLAG_RECEIVER: u32 = 4u;
71
72
 
@@ -85,6 +86,7 @@ const FLAG_RECEIVER: u32 = 4u;
85
86
 
86
87
  /** The surface stage only: what the material makes of the surface, packed for lighting. */
87
88
  @fragment fn fs(in: VertexOutput) -> GBufferOutput {
89
+ @if(CUTAWAY) cutaway_clip(in.world_position, in.clip.xy, in.flags);
88
90
  let p = pbr_input(in);
89
91
  @if(MASK) if (p.alpha < material.alphaCutoff) { discard; }
90
92
  return pack_gbuffer(p, (in.flags & FLAG_RECEIVER) != 0u, view.exposure);
package/dist/deferred.js CHANGED
@@ -1,10 +1,13 @@
1
1
  import { ShardError } from "@aethervtt/shard-core";
2
2
  import { GpuBuffer } from "@aethervtt/shard-gpu";
3
3
  import { Culler } from "./culling.js";
4
+ import { addRenderFeatures } from "./features.js";
5
+ import { FoliagePath } from "./foliage-path.js";
4
6
  import {
5
7
  drawMaterials,
6
8
  ForwardStateResource,
7
9
  gbufferEmissiveFormat,
10
+ INTERIOR_DEFINES,
8
11
  PASS_GBUFFER,
9
12
  PASS_OPAQUE,
10
13
  sceneColor,
@@ -12,6 +15,7 @@ import {
12
15
  } from "./forward.js";
13
16
  import { RenderPhase } from "./graph.js";
14
17
  import { Gpu, Graph, RenderDescribers, Shaders, Views } from "./plugin.js";
18
+ import { depthReadEntry } from "./tier.js";
15
19
  import { Cameras, cameraOf } from "./view.js";
16
20
  const GBUFFER_CHANNELS = ["albedo", "normal", "roughness", "metallic", "emissive"];
17
21
  const deferredView = (view) => cameraOf(view)?.deferred === true;
@@ -28,7 +32,9 @@ function fillGBuffer(ctx) {
28
32
  const state = ctx.world.resource(ForwardStateResource);
29
33
  const cam = cameraOf(ctx.view);
30
34
  const pv = state.views.get(ctx.view.name);
31
- if (pv) drawMaterials(ctx, state, pv, cam, cam.draws, PASS_GBUFFER);
35
+ if (!pv) return;
36
+ drawMaterials(ctx, state, pv, cam, cam.draws, PASS_GBUFFER);
37
+ ctx.world.tryResource(FoliagePath)?.draw(ctx, cam, PASS_GBUFFER, ctx.renderPass);
32
38
  }
33
39
  const gbufferNode = {
34
40
  kind: "render",
@@ -80,7 +86,11 @@ function lightingNode() {
80
86
  const cam = cameraOf(ctx.view);
81
87
  const pv = state.views.get(ctx.view.name);
82
88
  if (!pv) return;
83
- const module = ctx.world.resource(Shaders).module(gpu, { root: "shard::pbr::deferred_lighting" });
89
+ const mode = state.interiorMode;
90
+ const module = ctx.world.resource(Shaders).module(
91
+ gpu,
92
+ mode !== 0 ? { root: "shard::pbr::deferred_lighting", defines: INTERIOR_DEFINES[mode] } : { root: "shard::pbr::deferred_lighting" }
93
+ );
84
94
  if (!module) {
85
95
  gpu.pipelines.skipped++;
86
96
  return;
@@ -93,7 +103,7 @@ function lightingNode() {
93
103
  { binding: 0, visibility: F, texture: { sampleType: "float" } },
94
104
  { binding: 1, visibility: F, texture: { sampleType: "unfilterable-float" } },
95
105
  { binding: 2, visibility: F, texture: { sampleType: "float" } },
96
- { binding: 3, visibility: F, texture: { sampleType: "depth" } }
106
+ depthReadEntry(gpu, 3, F)
97
107
  ]
98
108
  });
99
109
  layoutGeneration = gpu.generation;
@@ -271,7 +281,7 @@ function describeDeferred(world) {
271
281
  deferredMeshes: cam.draws.visible,
272
282
  forwardMeshes: cam.forwardOnly.visible + cam.transparent.visible,
273
283
  forwardReasons: forwardReason(cam, world)
274
- } : { path: "forward", msaa: cam.msaa };
284
+ } : cam.msaaCapped ? { path: "forward", msaa: cam.msaa, msaaLimit: "the device can't multisample rgba16float" } : { path: "forward", msaa: cam.msaa };
275
285
  }
276
286
  return { views };
277
287
  }
@@ -281,6 +291,20 @@ function addDeferredNodes(app) {
281
291
  graph.declare({ name: "gbuffer0", format: "rgba8unorm-srgb" });
282
292
  graph.declare({ name: "gbuffer1", format: "rgba16float" });
283
293
  graph.declare({ name: "gbuffer2", format: gbufferEmissiveFormat(gpu) });
294
+ addRenderFeatures(app.world, {
295
+ name: "render/deferred",
296
+ description: "The deferred path: G-buffer, lighting, and forward-only materials.",
297
+ nodes: [
298
+ "deferred-gbuffer",
299
+ "gbuffer-fill",
300
+ "deferred-lighting",
301
+ "deferred-forward",
302
+ "gbuffer-debug"
303
+ ],
304
+ baseline: {
305
+ strategy: "The same passes, reading lights and clusters through the baseline accessors"
306
+ }
307
+ });
284
308
  graph.addNode("deferred-gbuffer", gbufferNode);
285
309
  graph.addNode("gbuffer-fill", gbufferFillNode);
286
310
  graph.addNode("deferred-lighting", lightingNode());
package/dist/deform.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { GpuBuffer, type GpuContext } from '@aethervtt/shard-gpu';
1
+ import type { GpuContext } from '@aethervtt/shard-gpu';
2
2
  import type { Mesh } from '@aethervtt/shard-mesh';
3
+ import { DataStore } from './data-store.js';
3
4
  /**
4
5
  * Per-slot deform record, 12 words (mirrors `Deform` in `shard::mesh` and `shard::cull`):
5
6
  *
@@ -47,9 +48,9 @@ export declare class DeformStore {
47
48
  private vertexWords;
48
49
  private wasted;
49
50
  readonly meshes: Map<Mesh, MeshDeform>;
50
- readonly recordBuffer: GpuBuffer;
51
- readonly poseBuffer: GpuBuffer;
52
- readonly vertexBuffer: GpuBuffer;
51
+ readonly recordBuffer: DataStore;
52
+ readonly poseBuffer: DataStore;
53
+ readonly vertexBuffer: DataStore;
53
54
  private recordLo;
54
55
  private recordHi;
55
56
  private vertexDirty;
package/dist/deform.js CHANGED
@@ -1,4 +1,4 @@
1
- import { GpuBuffer } from "@aethervtt/shard-gpu";
1
+ import { DataStore } from "./data-store.js";
2
2
  const DEFORM_WORDS = 12;
3
3
  const DEFORM_BYTES = DEFORM_WORDS * 4;
4
4
  const SKIN_WORDS = 6;
@@ -26,14 +26,9 @@ class DeformStore {
26
26
  constructor(gpu) {
27
27
  this.gpu = gpu;
28
28
  this.generation = gpu.generation;
29
- const usage = GPUBufferUsage.STORAGE;
30
- this.recordBuffer = new GpuBuffer(gpu, {
31
- label: "deform/records",
32
- usage,
33
- size: DEFORM_BYTES * 256
34
- });
35
- this.poseBuffer = new GpuBuffer(gpu, { label: "deform/poses", usage, size: 16 * 1024 });
36
- this.vertexBuffer = new GpuBuffer(gpu, { label: "deform/vertices", usage, size: 4096 });
29
+ this.recordBuffer = new DataStore(gpu, { label: "deform/records", size: DEFORM_BYTES * 256 });
30
+ this.poseBuffer = new DataStore(gpu, { label: "deform/poses", size: 16 * 1024 });
31
+ this.vertexBuffer = new DataStore(gpu, { label: "deform/vertices", size: 4096 });
37
32
  }
38
33
  /** Slots the record array holds. */
39
34
  get capacity() {
@@ -1,5 +1,7 @@
1
1
  /**
2
- * The lighting, culling, and renderScale sections of `render.describe`, for agents and the editor.
3
- * Only introspection: an app that ships without it renders the same.
2
+ * The lighting, culling, renderScale, surfaces, uploads, and frames sections of `render.describe`,
3
+ * and the
4
+ * `gpu.stats` method, for agents and the editor. Only introspection: an app that ships without it
5
+ * renders the same.
4
6
  */
5
7
  export declare const renderDescribePlugin: import("@aethervtt/shard-runtime").Plugin;
@@ -1,17 +1,89 @@
1
- import { definePlugin } from "@aethervtt/shard-runtime";
1
+ import { defineSchema, t } from "@aethervtt/shard-core";
2
+ import { definePlugin, FrameDemand } from "@aethervtt/shard-runtime";
2
3
  import { describeCulling, describeLighting } from "./debug-views.js";
3
- import { RenderDescribers } from "./plugin.js";
4
+ import { GroundLayer } from "./layers.js";
5
+ import { Gpu, RenderDescribers, RenderOptions, Window } from "./plugin.js";
4
6
  import { describeRenderScale } from "./render-scale.js";
7
+ import { RenderStats } from "./stats.js";
8
+ import { Cameras } from "./view.js";
9
+ function describeSurfaces(world) {
10
+ const window = world.tryResource(Window);
11
+ return world.resource(Gpu).surfaces.map((s) => ({
12
+ label: s.label,
13
+ size: [s.width, s.height],
14
+ alpha: s.alpha,
15
+ pixelRatio: s.pixelRatio,
16
+ thisApp: s === window
17
+ }));
18
+ }
19
+ function describeUploads(world) {
20
+ const stats = world.resource(RenderStats);
21
+ const bytes = (r) => ({
22
+ bytes: { ...r.bytes },
23
+ sceneBytes: r.sceneBytes,
24
+ created: r.created
25
+ });
26
+ const rebuilds = (r) => ({
27
+ chunksRebuilt: r.chunksRebuilt,
28
+ meshesRebuilt: r.meshesRebuilt,
29
+ shadowMapsRendered: r.shadowMapsRendered
30
+ });
31
+ return {
32
+ frames: stats.frames,
33
+ lastFrame: bytes(stats.lastFrame),
34
+ recent: bytes(stats.recent),
35
+ rebuilds: { lastFrame: rebuilds(stats.lastFrame), recent: rebuilds(stats.recent) }
36
+ };
37
+ }
38
+ function describeGround(world) {
39
+ const bands = {};
40
+ world.query({ with: [GroundLayer] }).each((_e, row, table) => {
41
+ const band = String(table.column(GroundLayer, "band")[row]);
42
+ bands[band] = (bands[band] ?? 0) + 1;
43
+ });
44
+ const views = {};
45
+ for (const cam of world.resource(Cameras).values())
46
+ views[`camera:${cam.entity}`] = {
47
+ instances: cam.ground.visible + cam.overlay.visible,
48
+ draws: cam.ground.length + cam.overlay.length
49
+ };
50
+ return { bands, views };
51
+ }
52
+ const gpuStats = {
53
+ name: "gpu.stats",
54
+ description: "Live GPU buffers and textures, by owner: each app's render plugin is one, and 'gpu' holds what apps on the device share.",
55
+ params: defineSchema("render/GpuStatsParams", {
56
+ owner: t.string({ description: "One owner. Empty: this app, every owner, and the total." })
57
+ }),
58
+ handler: ({ world }, p) => {
59
+ const gpu = world.resource(Gpu);
60
+ if (p.owner) return { owner: p.owner, ...gpu.stats(p.owner) };
61
+ return {
62
+ owner: world.resource(RenderOptions).owner,
63
+ total: gpu.stats(),
64
+ owners: Object.fromEntries(gpu.owners().map((o) => [o, gpu.stats(o)]))
65
+ };
66
+ }
67
+ };
5
68
  const renderDescribePlugin = definePlugin({
6
69
  name: "render/describe",
7
70
  dependencies: ["render/forward"],
8
- build() {
71
+ build(app) {
72
+ app.addMethod(gpuStats);
9
73
  },
10
74
  ready(app) {
11
75
  const describers = app.world.initResource(RenderDescribers);
12
76
  describers.set("lighting", (world) => describeLighting(world));
13
77
  describers.set("culling", (world) => describeCulling(world));
14
78
  describers.set("renderScale", (world) => describeRenderScale(world));
79
+ describers.set("surfaces", describeSurfaces);
80
+ describers.set("uploads", describeUploads);
81
+ describers.set("ground", describeGround);
82
+ describers.set("frames", (world) => world.resource(FrameDemand).describe());
83
+ describers.set(
84
+ "gpuObjects",
85
+ (world) => world.resource(Gpu).stats(world.resource(RenderOptions).owner)
86
+ );
15
87
  }
16
88
  });
17
89
  export {