@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/forward.js CHANGED
@@ -1,4 +1,11 @@
1
- import { defineResource, defineSystem, Last, PostUpdate } from "@aethervtt/shard-core";
1
+ import { assetServer } from "@aethervtt/shard-assets";
2
+ import {
3
+ defineResource,
4
+ defineSystem,
5
+ Last,
6
+ PostUpdate,
7
+ PreUpdate
8
+ } from "@aethervtt/shard-core";
2
9
  import { GpuBuffer } from "@aethervtt/shard-gpu";
3
10
  import { definePlugin, LogResource, Time } from "@aethervtt/shard-runtime";
4
11
  import { setTextureCapabilities, Textures } from "@aethervtt/shard-texture";
@@ -23,11 +30,16 @@ import {
23
30
  MAX_LIGHTS_PER_CLUSTER,
24
31
  ViewLightList
25
32
  } from "./clusters.js";
26
- import { Culler, cullTransparent, GpuCuller } from "./culling.js";
33
+ import { Culler, cullGround, cullTransparent, GpuCuller } from "./culling.js";
34
+ import { Cutaway, CutawayPath, CutawayView } from "./cutaway.js";
35
+ import { DataStore, dataEntry } from "./data-store.js";
27
36
  import { addDisplayNodes } from "./display-nodes.js";
28
37
  import { Environments, environmentParams } from "./environment-state.js";
38
+ import { addRenderFeatures } from "./features.js";
39
+ import { FoliagePath } from "./foliage-path.js";
29
40
  import { GpuAssets, GpuAssetsResource } from "./gpu-assets.js";
30
41
  import { RenderPhase } from "./graph.js";
42
+ import { clearHealthIssue, MaterialFallbacks, raiseHealthIssue } from "./health.js";
31
43
  import {
32
44
  DeformPath,
33
45
  InstanceData,
@@ -44,10 +56,14 @@ import {
44
56
  NotShadowReceiver,
45
57
  observeInstanceRemovals,
46
58
  prepareInstances,
59
+ ShadowWhenHidden,
47
60
  SkinnedMesh,
48
61
  VisibilityRange
49
62
  } from "./instances.js";
63
+ import { INTERIOR_BINDINGS, InteriorPath } from "./interior.js";
50
64
  import { observeOriginShifts } from "./large-world.js";
65
+ import { GroundLayer, RenderLayers } from "./layers.js";
66
+ import { expireLensFields, Lens, LensFields, LensPath } from "./lens.js";
51
67
  import {
52
68
  AmbientLight,
53
69
  DirectionalLight,
@@ -69,8 +85,9 @@ import {
69
85
  variantCull
70
86
  } from "./material-pipelines.js";
71
87
  import { isTransparent } from "./materials.js";
88
+ import { Outline, OutlinePath, observeOutlinesWithoutPass } from "./outline.js";
72
89
  import { PixelPerfect, PixelPerfectPath, PixelTargets } from "./pixel-perfect.js";
73
- import { Gpu, Graph, RenderSet, Shaders, Views } from "./plugin.js";
90
+ import { Gpu, Graph, RenderDescribers, RenderSet, Shaders, Views } from "./plugin.js";
74
91
  import {
75
92
  Antialiasing,
76
93
  AutoExposure,
@@ -84,6 +101,13 @@ import {
84
101
  Vignette
85
102
  } from "./post.js";
86
103
  import { RenderScale } from "./render-scale.js";
104
+ import {
105
+ describeScreenEffects,
106
+ expireScreenEffects,
107
+ runScreenEffects,
108
+ ScreenEffectHandlers,
109
+ ScreenEffects
110
+ } from "./screen-effects.js";
87
111
  import { ViewUniform, viewLayout } from "./shaders.js";
88
112
  import {
89
113
  assignLocalShadows,
@@ -92,15 +116,29 @@ import {
92
116
  fitCascades,
93
117
  fitLocalShadows,
94
118
  LocalShadows,
119
+ markShadowDrawn,
95
120
  packShadowData,
96
121
  SHADOW_DATA_FLOATS,
97
122
  ShadowPassUniforms,
98
123
  ShadowsResource,
124
+ shadowRedraw,
99
125
  shadowViewLayout
100
126
  } from "./shadows.js";
101
127
  import { SkinAssetType, Skins } from "./skin-asset.js";
102
128
  import { GpuMemory, RenderCounters, RenderStats } from "./stats.js";
129
+ import { bindingDimension } from "./tier.js";
130
+ import { addShaderVariantSource, materialVariants, ShaderVariantSources } from "./variants.js";
103
131
  import {
132
+ Beam,
133
+ Decal,
134
+ DecalReceiver,
135
+ DecalsPath,
136
+ observeVfxWithoutPlugin,
137
+ RibbonsPath,
138
+ Trail
139
+ } from "./vfx.js";
140
+ import {
141
+ CameraMoved,
104
142
  Cameras,
105
143
  cameraOf,
106
144
  DeferredPath,
@@ -109,6 +147,12 @@ import {
109
147
  Tonemapping,
110
148
  ViewSettings
111
149
  } from "./view.js";
150
+ import {
151
+ HiddenSetsResource,
152
+ NO_HIDDEN,
153
+ observeViewVisibilityWithoutPlugin,
154
+ ViewVisibility
155
+ } from "./view-visibility.js";
112
156
  import { ComputedVisibility, computeVisibility, Visibility } from "./visibility.js";
113
157
  const ForwardStateResource = defineResource("render/ForwardState");
114
158
  const State = ForwardStateResource;
@@ -161,7 +205,8 @@ const cameraCull = {
161
205
  lodScale: 1,
162
206
  orthographic: false,
163
207
  lodState: void 0,
164
- updateLod: true
208
+ updateLod: true,
209
+ layers: 4294967295
165
210
  };
166
211
  const shadowCull = {
167
212
  planes: null,
@@ -170,19 +215,23 @@ const shadowCull = {
170
215
  lodScale: 1,
171
216
  orthographic: false,
172
217
  lodState: void 0,
173
- updateLod: false
218
+ updateLod: false,
219
+ layers: 4294967295
174
220
  };
175
221
  function cameraParams(cam, out) {
176
222
  out.planes = cam.frozenFrustum ?? cam.frustum;
177
223
  out.eye = cam.frozenPosition ?? cam.position;
178
224
  out.orthographic = cam.orthographic;
225
+ out.layers = cam.layers;
226
+ out.hidden = cam.hidden?.bits;
227
+ out.hiddenBase = cam.hidden ? cam.hidden.base : NO_HIDDEN;
179
228
  out.lodScale = cam.orthographic ? 2 / cam.orthoHeight : 1 / Math.tan(cam.fovY / 2);
180
229
  if (cam.lodState.length < cam.lodCapacity)
181
230
  cam.lodState = new Uint8Array(cam.lodCapacity).fill(LOD_UNSET);
182
231
  out.lodState = cam.lodState;
183
232
  return out;
184
233
  }
185
- const queue = defineSystem({
234
+ const forwardQueue = defineSystem({
186
235
  name: "render/forward-queue",
187
236
  description: "Culls instances per camera and shadow view (on the GPU when it can), clusters lights, and writes view uniforms.",
188
237
  run: (_, world) => {
@@ -197,7 +246,8 @@ const queue = defineSystem({
197
246
  stats.clear();
198
247
  state.frame++;
199
248
  ensureLayouts(gpu, state, world.resource(GpuAssetsResource), store);
200
- state.pipelines.beginFrame(state.frame);
249
+ chooseInterior(gpu, world, state);
250
+ state.pipelines.beginFrame(state.frame, world.resource(Shaders).revision);
201
251
  shadows.uniforms.reset();
202
252
  culler.beginFrame();
203
253
  const gpuCull = culler.active;
@@ -219,8 +269,19 @@ const queue = defineSystem({
219
269
  if (gpuCull) {
220
270
  culler.add(store, cam.draws, params, culler.lodCamera(cam.entity), forwardOnly);
221
271
  cullTransparent(store, cam.transparent, params, cam.forward);
272
+ cullGround(store, cam.ground, params, cam.overlay);
222
273
  } else {
223
- store.cullCpu(cam.draws, params, void 0, cam.transparent, cam.forward, forwardOnly);
274
+ store.cullCpu(
275
+ cam.draws,
276
+ params,
277
+ void 0,
278
+ cam.transparent,
279
+ cam.forward,
280
+ forwardOnly,
281
+ false,
282
+ cam.ground,
283
+ cam.overlay
284
+ );
224
285
  }
225
286
  store.orderNearFirst(cam.draws, cam.position);
226
287
  if (view.order < mainOrder) {
@@ -228,14 +289,18 @@ const queue = defineSystem({
228
289
  mainOrder = view.order;
229
290
  }
230
291
  }
231
- const shadowParams = (cam, planes) => {
292
+ const shadowParams = (cam, planes, own) => {
232
293
  cameraParams(cam, shadowCull);
233
294
  shadowCull.planes = planes;
234
295
  shadowCull.updateLod = false;
296
+ if (!own || !cam.hidden?.shadows) {
297
+ shadowCull.hidden = void 0;
298
+ shadowCull.hiddenBase = NO_HIDDEN;
299
+ }
235
300
  return shadowCull;
236
301
  };
237
- const cullShadow = (cam, draws, planes, box) => {
238
- const params = shadowParams(cam, planes);
302
+ const cullShadow = (cam, draws, planes, box, own = false) => {
303
+ const params = shadowParams(cam, planes, own);
239
304
  if (gpuCull) {
240
305
  culler.add(store, draws, params, culler.lodCamera(cam.entity));
241
306
  if (box) store.casterBounds(box);
@@ -246,16 +311,19 @@ const queue = defineSystem({
246
311
  };
247
312
  const local = shadows.local;
248
313
  fitLocalShadows(local, settings.shadowMapSize);
314
+ store.checkMeshVersions();
249
315
  if (main) {
250
316
  for (let i = 0; i < local.spots.length; i++) {
251
317
  const view = local.spotViews[i];
252
318
  cullShadow(main, view.draws, view.frustum);
253
319
  view.offset = shadows.uniforms.push(view.viewProj);
320
+ view.redraw = shadowRedraw(view, local.spots[i]?.cachedShadow ?? false, store);
254
321
  }
255
322
  for (let i = 0; i < local.points.length * 6; i++) {
256
323
  const view = local.pointViews[i];
257
324
  cullShadow(main, view.draws, view.frustum);
258
325
  view.offset = shadows.uniforms.push(view.viewProj);
326
+ view.redraw = shadowRedraw(view, local.points[i / 6 | 0]?.cachedShadow ?? false, store);
259
327
  }
260
328
  }
261
329
  for (const view of world.resource(Views).list) {
@@ -264,7 +332,20 @@ const queue = defineSystem({
264
332
  const pv = viewGpu(gpu, state, view.name);
265
333
  pv.ao = void 0;
266
334
  pv.lightList.build(cam, lights.records, lights.high);
267
- pv.clusters.upload(pv.lightList, cam, settings.clusterFar);
335
+ if (pv.clusters) pv.clusters.upload(pv.lightList, cam, settings.clusterFar);
336
+ else {
337
+ const b = pv.baseline;
338
+ state.baseline.binBaselineLights(
339
+ b,
340
+ pv.lightList,
341
+ lights.data,
342
+ cam,
343
+ settings.clusterFar,
344
+ settings.baselineMaxLights
345
+ );
346
+ if (b.dropped !== b.reported)
347
+ reportLightBudget(world, view.name, b, settings.baselineMaxLights);
348
+ }
268
349
  const sun = lights.shadowSun;
269
350
  if (sun) {
270
351
  fitCascades(
@@ -272,11 +353,12 @@ const queue = defineSystem({
272
353
  cam,
273
354
  sun,
274
355
  settings.cascadeMapSize,
275
- (v, planes, box) => cullShadow(cam, v.draws, planes, box)
356
+ (v, planes, box) => cullShadow(cam, v.draws, planes, box, true)
276
357
  );
277
358
  for (let i = 0; i < pv.cascades.count; i++) {
278
359
  const v = pv.cascades.views[i];
279
360
  v.offset = shadows.uniforms.push(v.viewProj);
361
+ v.redraw = shadowRedraw(v, sun.cached, store);
280
362
  }
281
363
  } else {
282
364
  pv.cascades.count = 0;
@@ -302,8 +384,8 @@ const queue = defineSystem({
302
384
  st.visible += cam.forwardOnly.visible;
303
385
  st.drawCalls += cam.forwardOnly.cullView >= 0 ? culler.drawCounts.get(cam.forwardOnly) ?? 0 : cam.forwardOnly.length;
304
386
  }
305
- st.visible += cam.transparent.visible;
306
- st.drawCalls += cam.transparent.length;
387
+ st.visible += cam.transparent.visible + cam.ground.visible;
388
+ st.drawCalls += cam.transparent.length + cam.ground.length;
307
389
  stats.set(view.name, st);
308
390
  }
309
391
  }
@@ -318,10 +400,11 @@ function viewGpu(gpu, state, name) {
318
400
  size: viewLayout.size
319
401
  }),
320
402
  lightList: new ViewLightList(),
321
- clusters: new ClusterBuffers(gpu, name),
322
- shadowData: new GpuBuffer(gpu, {
403
+ clusters: state.baseline ? void 0 : new ClusterBuffers(gpu, name),
404
+ baseline: state.baseline?.baselineViewLights(gpu, name),
405
+ shadowData: new DataStore(gpu, {
323
406
  label: `${name}/shadows`,
324
- usage: GPUBufferUsage.STORAGE,
407
+ kind: "uniform",
325
408
  size: SHADOW_DATA_FLOATS * 4
326
409
  }),
327
410
  cascades: new Cascades(),
@@ -351,6 +434,14 @@ const ambientScratch = new Float32Array(3);
351
434
  const envScratch = new Float32Array(4);
352
435
  const jitterScratch = new Float32Array(4);
353
436
  const noTransmittance = new Float32Array(16).fill(1);
437
+ const pixelScaleScratch = new Float32Array(4);
438
+ function viewPixelScale(cam, out) {
439
+ out[0] = cam.pixelRatio * cam.width / Math.max(1, cam.displayWidth);
440
+ out[1] = cam.orthographic ? cam.orthoHeight / Math.max(1, cam.height) : 2 * Math.tan(cam.fovY / 2) / Math.max(1, cam.height);
441
+ out[2] = cam.orthographic ? 1 : 0;
442
+ out[3] = 0;
443
+ return out;
444
+ }
354
445
  function writeViewUniform(world, state, pv, cam, ambient, clusterFar) {
355
446
  environmentParams(world.resource(Environments), cam, envScratch);
356
447
  const [near, far] = clusterRange(cam, clusterFar);
@@ -381,7 +472,8 @@ function writeViewUniform(world, state, pv, cam, ambient, clusterFar) {
381
472
  viewProjNoJitter: cam.viewProjNoJitter,
382
473
  prevViewProj: cam.prevViewProj,
383
474
  jitter: jitterScratch,
384
- sunTransmittance: world.tryResource(Atmospheres)?.cameras.get(cam.entity)?.sunTransmittance ?? noTransmittance
475
+ sunTransmittance: world.tryResource(Atmospheres)?.cameras.get(cam.entity)?.sunTransmittance ?? noTransmittance,
476
+ pixelScale: viewPixelScale(cam, pixelScaleScratch)
385
477
  });
386
478
  pv.uniform.write(new Float32Array(state.viewBytes.buffer, 0, viewLayout.size / 4));
387
479
  }
@@ -392,7 +484,7 @@ const upload = defineSystem({
392
484
  const store = world.resource(Instances);
393
485
  const culler = world.resource(Culler);
394
486
  if (culler.active) {
395
- culler.prepare(store);
487
+ culler.prepare(store, world.tryResource(HiddenSetsResource));
396
488
  store.gpuVisible = culler.visible;
397
489
  }
398
490
  store.finishFrame();
@@ -408,15 +500,33 @@ const VERTEX_BUFFERS = [
408
500
  { arrayStride: 16, attributes: [{ shaderLocation: 4, offset: 0, format: "float32x4" }] }
409
501
  ];
410
502
  const FORWARD_DEFINES = [
411
- { PREMULTIPLY: false, MASK: false },
412
- { PREMULTIPLY: true, MASK: false },
413
- { PREMULTIPLY: false, MASK: true },
414
- { PREMULTIPLY: true, MASK: true }
503
+ { PREMULTIPLY: false, MASK: false, OPAQUE: false },
504
+ { PREMULTIPLY: true, MASK: false, OPAQUE: false },
505
+ { PREMULTIPLY: false, MASK: true, OPAQUE: false },
506
+ { PREMULTIPLY: true, MASK: true, OPAQUE: false },
507
+ { PREMULTIPLY: false, MASK: false, OPAQUE: true },
508
+ { PREMULTIPLY: true, MASK: false, OPAQUE: true },
509
+ { PREMULTIPLY: false, MASK: true, OPAQUE: true },
510
+ { PREMULTIPLY: true, MASK: true, OPAQUE: true }
511
+ ];
512
+ const CUT_KEY = 2 ** 40;
513
+ const INTERIOR_KEY = 2 ** 41;
514
+ const CUTAWAY_DEFINES = FORWARD_DEFINES.map((d) => ({ ...d, CUTAWAY: true }));
515
+ const INTERIOR_DEFINES = [
516
+ void 0,
517
+ { SKY_VISIBILITY: true },
518
+ { BLOCKED_LIGHTS: true },
519
+ { SKY_VISIBILITY: true, BLOCKED_LIGHTS: true }
415
520
  ];
416
- function materialLayout(gpu, state, assets, type, store) {
521
+ const LIT_DEFINES = INTERIOR_DEFINES.map(
522
+ (extra) => [FORWARD_DEFINES, CUTAWAY_DEFINES].map(
523
+ (set) => extra ? set.map((d) => ({ ...d, ...extra })) : set
524
+ )
525
+ );
526
+ function materialLayout(gpu, state, assets, type, store, cut) {
417
527
  return gpu.layouts.pipelineLayout({
418
- label: `forward/${type.name}`,
419
- bindGroupLayouts: [state.layouts.view, assets.layoutOf(type), store.layout]
528
+ label: cut ? `forward/${type.name}/cutaway` : `forward/${type.name}`,
529
+ bindGroupLayouts: cut ? [state.layouts.view, assets.layoutOf(type), store.layout, cut] : [state.layouts.view, assets.layoutOf(type), store.layout]
420
530
  });
421
531
  }
422
532
  const PASS_OPAQUE = 0;
@@ -424,11 +534,16 @@ const PASS_TRANSPARENT = 1;
424
534
  const PASS_GBUFFER = 3;
425
535
  const PASS_PREPASS = 5;
426
536
  const PASS_PICK = 7;
537
+ const PASS_GROUND = 9;
538
+ const PASS_PICK_GROUND = 11;
539
+ const PASS_DEPTH = 13;
540
+ const GROUND_DEPTH_BIAS = 8;
541
+ const GROUND_SLOPE_BIAS = 2;
427
542
  const PICK_TARGETS = [
428
543
  { format: "r32uint" },
429
544
  { format: "rgba32float" }
430
545
  ];
431
- const PREPASS_TARGETS = [{ format: "rgba16float" }, { format: "rg16float" }];
546
+ const PREPASS_TARGETS = [{ format: "rg16float" }, { format: "rg16float" }];
432
547
  function drawMaterials(ctx, state, pv, cam, draws, pass) {
433
548
  const store = ctx.world.resource(Instances);
434
549
  const assets = ctx.world.resource(GpuAssetsResource);
@@ -439,6 +554,9 @@ function drawMaterials(ctx, state, pv, cam, draws, pass) {
439
554
  const args = ctx.world.resource(Culler).args.buffer;
440
555
  renderPass.setBindGroup(0, viewBindGroup(gpu, ctx.world, pv, cam));
441
556
  renderPass.setBindGroup(2, instances);
557
+ const cutaway = ctx.world.tryResource(CutawayPath);
558
+ const cutGroup = cutaway?.bindGroup(gpu, cam.entity);
559
+ if (cutGroup) renderPass.setBindGroup(3, cutGroup);
442
560
  let current;
443
561
  let boundMaterial;
444
562
  let boundPositions;
@@ -447,52 +565,15 @@ function drawMaterials(ctx, state, pv, cam, draws, pass) {
447
565
  let switches = 0;
448
566
  for (let d = 0; d < draws.length; d++) {
449
567
  const item = draws.items[d];
450
- const material = item.batch.material;
451
- const type = material.type;
452
- const variant = materialVariant(material);
453
- const blend = variantBlend(variant);
454
- const gbuffer = pass === PASS_GBUFFER;
455
- const prepass = pass === PASS_PREPASS;
456
- const pick = pass === PASS_PICK;
457
- if (gbuffer && !type.standard) continue;
458
- const msaa = gbuffer || prepass || pick ? 1 : cam.msaa;
459
- const key = ((pass * 1024 + typeOrdinal(type)) * 16 + variant) * 8 + msaa;
460
- let pipeline = state.pipelines.cached(key);
568
+ const own = item.batch.material;
569
+ if (!inPass(own.type, pass)) continue;
570
+ const cut = cutGroup !== void 0 && item.batch.cutaway > 0 && cutaway.cuts(cam.entity, item.batch.index) ? cutaway : void 0;
571
+ const fallback = state.pipelines.failing(own.type);
572
+ const material = fallback ? state.pipelines.proxy(own) : own;
573
+ const pipeline = materialPipeline(ctx, state, cam, store, assets, material, pass, true, cut);
461
574
  if (!pipeline) {
462
- const premultiply = blend === "premultiplied";
463
- const mask = blend === "mask";
464
- const slot = pick ? 40 : prepass ? 32 + (type.standard ? 0 : 4) + (mask ? 2 : 0) : (gbuffer ? 16 : type.standard ? 0 : 4) + (premultiply ? 1 : 0) + (mask ? 2 : 0);
465
- const module = state.pipelines.module(
466
- ctx.world,
467
- gpu,
468
- type,
469
- slot,
470
- pick ? "shard::pick" : prepass ? type.standard ? "shard::prepass" : "shard::prepass::plain" : gbuffer ? "shard::pbr::gbuffer_pass" : type.standard ? "shard::pbr::forward" : "shard::unlit::forward",
471
- FORWARD_DEFINES[slot & 3]
472
- );
473
- if (!module) {
474
- gpu.pipelines.skipped++;
475
- continue;
476
- }
477
- const transparent = isTransparent(blend) && !pick;
478
- pipeline = state.pipelines.create(gpu, key, {
479
- label: `${pick ? "pick" : prepass ? "prepass" : gbuffer ? "gbuffer" : "forward"}/${type.name}/${blend}/x${msaa}/${variantCull(variant)}`,
480
- layout: materialLayout(gpu, state, assets, type, store),
481
- vertex: { module, entryPoint: "vs", buffers: VERTEX_BUFFERS },
482
- fragment: {
483
- module,
484
- entryPoint: "fs",
485
- targets: pick ? PICK_TARGETS : prepass ? PREPASS_TARGETS : gbuffer ? state.gbufferTargets : [{ format: "rgba16float", blend: blendState(blend) }]
486
- },
487
- primitive: { topology: "triangle-list", cullMode: variantCull(variant), frontFace: "ccw" },
488
- depthStencil: {
489
- format: "depth32float",
490
- depthWriteEnabled: !transparent,
491
- depthCompare: transparent ? "greater-equal" : "greater"
492
- },
493
- multisample: { count: msaa }
494
- });
495
- if (!pipeline) continue;
575
+ if (!fallback && state.pipelines.failing(own.type)) d--;
576
+ continue;
496
577
  }
497
578
  const mat = assets.material(ctx.world, material);
498
579
  if (!mat?.bindGroup) continue;
@@ -529,14 +610,127 @@ function drawMaterials(ctx, state, pv, cam, draws, pass) {
529
610
  renderPass.draw(gm.count, item.count, 0, item.first);
530
611
  }
531
612
  }
613
+ if (draws.cullView < 0) warmPipelines(ctx, state, cam, store, assets, pass, cutGroup && cutaway);
532
614
  return switches;
533
615
  }
616
+ function inPass(type, pass) {
617
+ if (pass === PASS_GBUFFER) return type.standard;
618
+ if (pass === PASS_PICK || pass === PASS_PICK_GROUND) return type.pickable;
619
+ return true;
620
+ }
621
+ function materialPipeline(ctx, state, cam, store, assets, material, pass, draw, cut) {
622
+ const gpu = ctx.gpu;
623
+ const type = material.type;
624
+ const variant = materialVariant(material);
625
+ const blend = variantBlend(variant);
626
+ const gbuffer = pass === PASS_GBUFFER;
627
+ const depthOnly = pass === PASS_DEPTH;
628
+ const prepass = pass === PASS_PREPASS || depthOnly;
629
+ const pick = pass === PASS_PICK || pass === PASS_PICK_GROUND;
630
+ const ground = pass === PASS_GROUND || pass === PASS_PICK_GROUND;
631
+ const msaa = gbuffer || prepass || pick ? 1 : cam.msaa;
632
+ const mode = gbuffer || prepass || pick ? 0 : state.interiorMode;
633
+ const key = ((pass * 1024 + typeOrdinal(type)) * 16 + variant) * 8 + msaa + (cut ? CUT_KEY : 0) + mode * INTERIOR_KEY;
634
+ let pipeline = state.pipelines.cached(key);
635
+ if (!pipeline) {
636
+ const premultiply = blend === "premultiplied";
637
+ const mask = blend === "mask";
638
+ const opaque = !gbuffer && !prepass && !pick && !isTransparent(blend);
639
+ const slot = (cut ? 64 : 0) + (pick ? 40 : prepass ? 32 + (type.standard ? 0 : 4) + (mask ? 2 : 0) : (gbuffer ? 16 : opaque ? 48 : 0) + (type.standard ? 0 : 4) + (premultiply ? 1 : 0) + (mask ? 2 : 0));
640
+ const module = state.pipelines.module(
641
+ ctx.world,
642
+ gpu,
643
+ type,
644
+ slot,
645
+ pick ? "shard::pick" : prepass ? type.standard ? "shard::prepass" : "shard::prepass::plain" : gbuffer ? "shard::pbr::gbuffer_pass" : type.standard ? "shard::pbr::forward" : "shard::unlit::forward",
646
+ // Only what the slot tells apart: the prepass and picking don't premultiply.
647
+ LIT_DEFINES[mode][cut ? 1 : 0][pick ? 0 : prepass ? mask ? 2 : 0 : (premultiply ? 1 : 0) | (mask ? 2 : 0) | (opaque ? 4 : 0)]
648
+ );
649
+ if (!module) {
650
+ if (draw && !state.pipelines.failing(type)) gpu.pipelines.skipped++;
651
+ return void 0;
652
+ }
653
+ const transparent = isTransparent(blend) && !pick;
654
+ pipeline = state.pipelines.create(
655
+ gpu,
656
+ key,
657
+ {
658
+ label: `${pick ? "pick" : prepass ? "prepass" : gbuffer ? "gbuffer" : ground ? "ground" : "forward"}/${type.name}/${blend}/x${msaa}/${variantCull(variant)}${cut ? "/cutaway" : ""}`,
659
+ layout: materialLayout(gpu, state, assets, type, store, cut?.layout(gpu)),
660
+ vertex: { module, entryPoint: "vs", buffers: VERTEX_BUFFERS },
661
+ fragment: {
662
+ module,
663
+ entryPoint: depthOnly ? "fs_depth" : "fs",
664
+ targets: pick ? PICK_TARGETS : depthOnly ? [] : prepass ? PREPASS_TARGETS : gbuffer ? state.gbufferTargets : [{ format: "rgba16float", blend: blendState(blend) }]
665
+ },
666
+ primitive: {
667
+ topology: "triangle-list",
668
+ cullMode: variantCull(variant),
669
+ frontFace: "ccw"
670
+ },
671
+ depthStencil: {
672
+ format: "depth32float",
673
+ // Ground bands never write depth (so they never fight); picking them writes it, so the
674
+ // nearest band wins at equal depth.
675
+ depthWriteEnabled: pick || !transparent && !ground,
676
+ depthCompare: transparent || ground ? "greater-equal" : "greater",
677
+ // Ground bands lie on their floor: a few ULPs and a slope term keep them in front of it
678
+ // at any angle (reversed Z: toward the camera). Bands don't write depth, so they never
679
+ // fight each other; the order does that.
680
+ depthBias: ground ? GROUND_DEPTH_BIAS : 0,
681
+ depthBiasSlopeScale: ground ? GROUND_SLOPE_BIAS : 0
682
+ },
683
+ multisample: { count: msaa }
684
+ },
685
+ type
686
+ );
687
+ if (!pipeline) return void 0;
688
+ }
689
+ return pipeline;
690
+ }
691
+ function warmPipelines(ctx, state, cam, store, assets, pass, cut) {
692
+ const slot = pass * 8 + cam.msaa + (cut ? CUT_KEY : 0) + state.interiorMode * INTERIOR_KEY;
693
+ if (state.pipelines.warmed(slot, store.structureVersion, ctx.gpu.generation)) return;
694
+ const ground = pass === PASS_GROUND || pass === PASS_PICK_GROUND;
695
+ const batches = store.batches;
696
+ for (let i = 0; i < batches.length; i++) {
697
+ const batch = batches[i];
698
+ if (batch.count === 0 || batch.ground !== ground) continue;
699
+ if (pass === PASS_TRANSPARENT ? !batch.transparent : batch.transparent && pass !== PASS_PICK) {
700
+ continue;
701
+ }
702
+ if (pass === PASS_OPAQUE && cam.deferred && batch.deferrable) continue;
703
+ const own = batch.material;
704
+ if (!inPass(own.type, pass)) continue;
705
+ const material = state.pipelines.failing(own.type) ? state.pipelines.proxy(own) : own;
706
+ materialPipeline(ctx, state, cam, store, assets, material, pass, false);
707
+ if (cut && batch.cutaway > 0)
708
+ materialPipeline(ctx, state, cam, store, assets, material, pass, false, cut);
709
+ assets.material(ctx.world, material);
710
+ }
711
+ if (ctx.gpu.pipelines.pending === 0) {
712
+ state.pipelines.markWarmed(slot, store.structureVersion, ctx.gpu.generation);
713
+ }
714
+ }
534
715
  function recordSwitches(ctx, switches) {
535
716
  const stats = ctx.world.resource(RenderStats).get(ctx.view.name);
536
717
  if (stats) stats.pipelineSwitches = Math.max(stats.pipelineSwitches ?? 0, switches);
537
718
  }
538
719
  const isCamera = (view) => cameraOf(view) !== void 0;
539
720
  const msaaOf = (view) => cameraOf(view)?.msaa ?? 1;
721
+ function reportLightBudget(world, view, b, max) {
722
+ b.reported = b.dropped;
723
+ if (b.dropped === 0) {
724
+ clearHealthIssue(world, "render/light-budget", view);
725
+ return;
726
+ }
727
+ raiseHealthIssue(world, {
728
+ code: "render/light-budget",
729
+ severity: "degraded",
730
+ ref: view,
731
+ message: `${b.inView} point and spot lights are in view, over the baseline tier's ${Math.min(max, 128)}: the ${b.dropped} farthest aren't shaded`
732
+ });
733
+ }
540
734
  function whiteTexture(gpu, state) {
541
735
  if (!state.white || state.white.generation !== gpu.generation) {
542
736
  const texture = gpu.device.createTexture({
@@ -557,7 +751,8 @@ function emptyShadow(gpu, state) {
557
751
  label: "shadows/empty",
558
752
  size: [1, 1, 1],
559
753
  format: "depth32float",
560
- usage: GPUTextureUsage.TEXTURE_BINDING
754
+ usage: GPUTextureUsage.TEXTURE_BINDING,
755
+ ...bindingDimension(gpu, "2d-array")
561
756
  }),
562
757
  generation: gpu.generation
563
758
  };
@@ -604,29 +799,35 @@ function viewBindGroup(gpu, world, pv, cam) {
604
799
  const source = baked?.sourceView ?? envs.emptyView;
605
800
  const sh = baked?.sh ?? envs.emptySh;
606
801
  const ao = pv.ao ?? whiteTexture(gpu, state);
607
- const key = `${idOf(ao)}/${gpu.generation}/${pv.uniform.version}/${lights.buffer.version}/${pv.clusters.clusters.version}/${lights.directional.version}/${pv.shadowData.version}/${idOf(cascades)}/${idOf(spots)}/${idOf(points)}/${baked ? idOf(baked.source) : 0}/${sh.version}/${state.globals.version}`;
802
+ const b = pv.baseline;
803
+ const lightsVersion = b ? b.lights.version : lights.buffer.version;
804
+ const clustersVersion = b ? b.bits.version : pv.clusters.clusters.version;
805
+ const interior = state.interiorMode !== 0 ? world.tryResource(InteriorPath) : void 0;
806
+ const key = `${idOf(ao)}/${gpu.generation}/${pv.uniform.version}/${lightsVersion}/${clustersVersion}/${lights.directional.version}/${pv.shadowData.version}/${idOf(cascades)}/${idOf(spots)}/${idOf(points)}/${baked ? idOf(baked.source) : 0}/${sh.version}/${state.globals.version}${interior ? `/${state.interiorMode}/${interior.version}` : ""}`;
608
807
  if (!pv.bindGroup || pv.bound !== key) {
808
+ const entries = [
809
+ { binding: 0, resource: { buffer: pv.uniform.buffer } },
810
+ { binding: 1, resource: b ? b.lights.resource() : { buffer: lights.buffer.buffer } },
811
+ { binding: 2, resource: b ? b.bits.resource() : { buffer: pv.clusters.clusters.buffer } },
812
+ { binding: 3, resource: lights.directional.resource() },
813
+ { binding: 4, resource: pv.shadowData.resource() },
814
+ { binding: 5, resource: cascades.createView({ dimension: "2d-array" }) },
815
+ { binding: 6, resource: spots.createView({ dimension: "2d-array" }) },
816
+ { binding: 7, resource: points.createView({ dimension: "2d-array" }) },
817
+ { binding: 8, resource: state.shadowSampler },
818
+ { binding: 9, resource: specular },
819
+ { binding: 10, resource: envs.lut.createView() },
820
+ { binding: 11, resource: envs.sampler },
821
+ { binding: 12, resource: sh.resource() },
822
+ { binding: 13, resource: source },
823
+ { binding: 14, resource: { buffer: state.globals.buffer } },
824
+ { binding: 15, resource: ao.createView() }
825
+ ];
826
+ if (interior) entries.push(...interior.entries(gpu));
609
827
  pv.bindGroup = gpu.device.createBindGroup({
610
828
  label: "forward/view",
611
829
  layout: state.layouts.view,
612
- entries: [
613
- { binding: 0, resource: { buffer: pv.uniform.buffer } },
614
- { binding: 1, resource: { buffer: lights.buffer.buffer } },
615
- { binding: 2, resource: { buffer: pv.clusters.clusters.buffer } },
616
- { binding: 3, resource: { buffer: lights.directional.buffer } },
617
- { binding: 4, resource: { buffer: pv.shadowData.buffer } },
618
- { binding: 5, resource: cascades.createView({ dimension: "2d-array" }) },
619
- { binding: 6, resource: spots.createView({ dimension: "2d-array" }) },
620
- { binding: 7, resource: points.createView({ dimension: "2d-array" }) },
621
- { binding: 8, resource: state.shadowSampler },
622
- { binding: 9, resource: specular },
623
- { binding: 10, resource: envs.lut.createView() },
624
- { binding: 11, resource: envs.sampler },
625
- { binding: 12, resource: { buffer: sh.buffer } },
626
- { binding: 13, resource: source },
627
- { binding: 14, resource: { buffer: state.globals.buffer } },
628
- { binding: 15, resource: ao.createView() }
629
- ]
830
+ entries
630
831
  });
631
832
  pv.bound = key;
632
833
  }
@@ -649,6 +850,40 @@ function forwardNode(state) {
649
850
  }
650
851
  };
651
852
  }
853
+ function groundNode(state) {
854
+ return {
855
+ kind: "render",
856
+ phase: RenderPhase.Ground,
857
+ enabled: (view) => (cameraOf(view)?.ground.length ?? 0) > 0,
858
+ reads: ["clusters", "shadow-cascades", "shadow-local", "environment", "culled", "ssao"],
859
+ writes: ["scene-color", "hdr"],
860
+ color: (view) => sceneColor(view),
861
+ depth: { resource: "scene-depth", readOnly: true },
862
+ run: (ctx) => {
863
+ const cam = cameraOf(ctx.view);
864
+ const pv = state.views.get(ctx.view.name);
865
+ if (!pv) return;
866
+ drawMaterials(ctx, state, pv, cam, cam.ground, PASS_GROUND);
867
+ }
868
+ };
869
+ }
870
+ function overlayNode(state) {
871
+ return {
872
+ kind: "render",
873
+ phase: RenderPhase.Overlay3d,
874
+ enabled: (view) => (cameraOf(view)?.overlay.length ?? 0) > 0,
875
+ reads: ["clusters", "shadow-cascades", "shadow-local", "environment", "culled", "ssao"],
876
+ writes: ["scene-color", "hdr"],
877
+ color: (view) => sceneColor(view),
878
+ depth: { resource: "scene-depth", readOnly: true },
879
+ run: (ctx) => {
880
+ const cam = cameraOf(ctx.view);
881
+ const pv = state.views.get(ctx.view.name);
882
+ if (!pv) return;
883
+ drawMaterials(ctx, state, pv, cam, cam.overlay, PASS_GROUND);
884
+ }
885
+ };
886
+ }
652
887
  function transparentNode(state) {
653
888
  return {
654
889
  kind: "render",
@@ -670,11 +905,12 @@ function clusterNode(state) {
670
905
  return {
671
906
  kind: "raw",
672
907
  phase: RenderPhase.Setup,
673
- enabled: isCamera,
908
+ // Baseline bins on the CPU (baseline/lights.ts): no compute.
909
+ enabled: (view) => isCamera(view) && !state.baseline,
674
910
  writes: ["clusters"],
675
911
  run: (ctx) => {
676
912
  const pv = state.views.get(ctx.view.name);
677
- if (!pv) return;
913
+ if (!pv?.clusters) return;
678
914
  const gpu = ctx.gpu;
679
915
  const module = ctx.world.resource(Shaders).module(gpu, { root: "shard::lighting::cluster" });
680
916
  if (!module) {
@@ -732,6 +968,18 @@ function clusterNode(state) {
732
968
  }
733
969
  };
734
970
  }
971
+ let skippedBefore = 0;
972
+ function beginShadowView(ctx, view) {
973
+ if (!view.redraw && view.drawn) return false;
974
+ skippedBefore = ctx.gpu.pipelines.skipped;
975
+ ctx.world.resource(RenderStats).current.shadowMapsRendered++;
976
+ return true;
977
+ }
978
+ function endShadowView(ctx, view) {
979
+ if (ctx.gpu.pipelines.skipped === skippedBefore)
980
+ markShadowDrawn(view, ctx.world.resource(Instances));
981
+ else view.drawn = false;
982
+ }
735
983
  function cascadeNode(state) {
736
984
  return {
737
985
  kind: "raw",
@@ -744,17 +992,27 @@ function cascadeNode(state) {
744
992
  const shadows = ctx.world.resource(ShadowsResource);
745
993
  const size = ctx.world.resource(LightingSettings).cascadeMapSize;
746
994
  const texture = pv.cascades.ensureTexture(ctx.gpu, size, ctx.view.name);
995
+ const foliage = ctx.world.tryResource(FoliagePath);
996
+ const cam = cameraOf(ctx.view);
997
+ const animated = foliage?.animatedShadows(cam) ?? false;
747
998
  for (let i = 0; i < pv.cascades.count; i++) {
999
+ const view = pv.cascades.views[i];
1000
+ if (animated && i === 0) view.redraw = true;
1001
+ if (!beginShadowView(ctx, view)) continue;
748
1002
  drawShadowCasters(
749
1003
  ctx,
750
1004
  texture.createView({ dimension: "2d", baseArrayLayer: i, arrayLayerCount: 1 }),
751
- pv.cascades.views[i],
752
- pv.cascades.views[i].offset,
1005
+ view,
1006
+ view.offset,
753
1007
  shadows.uniforms,
754
1008
  state.pipelines,
755
1009
  state.layouts.shadowView,
756
- `shadows/cascade${i}`
1010
+ `shadows/cascade${i}`,
1011
+ foliage,
1012
+ cam,
1013
+ i
757
1014
  );
1015
+ endShadowView(ctx, view);
758
1016
  }
759
1017
  }
760
1018
  };
@@ -776,6 +1034,7 @@ function localShadowNode(state) {
776
1034
  const layer = (texture, i) => texture.createView({ dimension: "2d", baseArrayLayer: i, arrayLayerCount: 1 });
777
1035
  for (let i = 0; i < local.spots.length; i++) {
778
1036
  const view = local.spotViews[i];
1037
+ if (!beginShadowView(ctx, view)) continue;
779
1038
  drawShadowCasters(
780
1039
  ctx,
781
1040
  layer(local.spotTexture, i),
@@ -786,9 +1045,11 @@ function localShadowNode(state) {
786
1045
  state.layouts.shadowView,
787
1046
  `shadows/spot${i}`
788
1047
  );
1048
+ endShadowView(ctx, view);
789
1049
  }
790
1050
  for (let i = 0; i < local.points.length * 6; i++) {
791
1051
  const view = local.pointViews[i];
1052
+ if (!beginShadowView(ctx, view)) continue;
792
1053
  drawShadowCasters(
793
1054
  ctx,
794
1055
  layer(local.pointTexture, i),
@@ -799,6 +1060,7 @@ function localShadowNode(state) {
799
1060
  state.layouts.shadowView,
800
1061
  `shadows/point${i}`
801
1062
  );
1063
+ endShadowView(ctx, view);
802
1064
  }
803
1065
  }
804
1066
  };
@@ -907,13 +1169,31 @@ function sceneColor(view, clear) {
907
1169
  if (clear === void 0) return msaa ? MSAA_LOAD : PLAIN_LOAD;
908
1170
  return [{ resource: "scene-color", clear, resolve: msaa ? "hdr" : void 0 }];
909
1171
  }
910
- function depthResolveNode() {
1172
+ function depthPrepassNode(state) {
1173
+ return {
1174
+ kind: "render",
1175
+ phase: RenderPhase.Prepass,
1176
+ enabled: (view) => state.baseline !== void 0 && msaaOf(view) > 1,
1177
+ reads: ["culled"],
1178
+ writes: ["depth"],
1179
+ depth: { resource: "depth", clear: 0 },
1180
+ run: (ctx) => {
1181
+ const pv = state.views.get(ctx.view.name);
1182
+ const cam = cameraOf(ctx.view);
1183
+ if (!pv || !cam) return;
1184
+ drawMaterials(ctx, state, pv, cam, cam.draws, PASS_DEPTH);
1185
+ if (cam.deferred) drawMaterials(ctx, state, pv, cam, cam.forwardOnly, PASS_DEPTH);
1186
+ }
1187
+ };
1188
+ }
1189
+ function depthResolveNode(state) {
911
1190
  let bindGroups = /* @__PURE__ */ new WeakMap();
912
1191
  let generation = -1;
913
1192
  return {
914
1193
  kind: "render",
915
1194
  phase: RenderPhase.Resolve,
916
- enabled: (view) => msaaOf(view) > 1,
1195
+ // Baseline draws `depth` in its own prepass instead (depthPrepassNode).
1196
+ enabled: (view) => state.baseline === void 0 && msaaOf(view) > 1,
917
1197
  reads: ["scene-depth"],
918
1198
  writes: ["depth"],
919
1199
  depth: { resource: "depth", clear: 0 },
@@ -961,39 +1241,36 @@ function depthResolveNode() {
961
1241
  }
962
1242
  };
963
1243
  }
964
- function createLayouts(gpu, assets, store) {
1244
+ function viewEntries(gpu) {
965
1245
  const F = GPUShaderStage.FRAGMENT;
966
- const storage = (binding) => ({
967
- binding,
968
- visibility: F,
969
- buffer: { type: "read-only-storage" }
970
- });
971
1246
  const depthArray = (binding) => ({
972
1247
  binding,
973
1248
  visibility: F,
974
1249
  texture: { sampleType: "depth", viewDimension: "2d-array" }
975
1250
  });
976
- const view = gpu.layouts.bindGroupLayout({
977
- label: "forward/view",
978
- entries: [
979
- { binding: 0, visibility: GPUShaderStage.VERTEX | F, buffer: { type: "uniform" } },
980
- storage(1),
981
- storage(2),
982
- storage(3),
983
- storage(4),
984
- depthArray(5),
985
- depthArray(6),
986
- depthArray(7),
987
- { binding: 8, visibility: F, sampler: { type: "comparison" } },
988
- { binding: 9, visibility: F, texture: { sampleType: "float", viewDimension: "cube" } },
989
- { binding: 10, visibility: F, texture: { sampleType: "float" } },
990
- { binding: 11, visibility: F, sampler: { type: "filtering" } },
991
- storage(12),
992
- { binding: 13, visibility: F, texture: { sampleType: "float", viewDimension: "cube" } },
993
- { binding: 14, visibility: GPUShaderStage.VERTEX | F, buffer: { type: "uniform" } },
994
- { binding: 15, visibility: F, texture: { sampleType: "float" } }
995
- ]
996
- });
1251
+ return [
1252
+ { binding: 0, visibility: GPUShaderStage.VERTEX | F, buffer: { type: "uniform" } },
1253
+ // Storage on the full tier; on baseline (0064) lights, directional lights, shadow data and SH
1254
+ // are uniform blocks and the cluster bits a data texture.
1255
+ dataEntry(gpu, 1, F, "uniform"),
1256
+ dataEntry(gpu, 2, F, "texture"),
1257
+ dataEntry(gpu, 3, F, "uniform"),
1258
+ dataEntry(gpu, 4, F, "uniform"),
1259
+ depthArray(5),
1260
+ depthArray(6),
1261
+ depthArray(7),
1262
+ { binding: 8, visibility: F, sampler: { type: "comparison" } },
1263
+ { binding: 9, visibility: F, texture: { sampleType: "float", viewDimension: "cube" } },
1264
+ { binding: 10, visibility: F, texture: { sampleType: "float" } },
1265
+ { binding: 11, visibility: F, sampler: { type: "filtering" } },
1266
+ dataEntry(gpu, 12, F, "uniform"),
1267
+ { binding: 13, visibility: F, texture: { sampleType: "float", viewDimension: "cube" } },
1268
+ { binding: 14, visibility: GPUShaderStage.VERTEX | F, buffer: { type: "uniform" } },
1269
+ { binding: 15, visibility: F, texture: { sampleType: "float" } }
1270
+ ];
1271
+ }
1272
+ function createLayouts(gpu, assets, store) {
1273
+ const view = gpu.layouts.bindGroupLayout({ label: "forward/view", entries: viewEntries(gpu) });
997
1274
  const C = GPUShaderStage.COMPUTE;
998
1275
  const cluster = gpu.layouts.bindGroupLayout({
999
1276
  label: "light-clusters",
@@ -1008,6 +1285,8 @@ function createLayouts(gpu, assets, store) {
1008
1285
  const shadowView = shadowViewLayout(gpu);
1009
1286
  return {
1010
1287
  view,
1288
+ viewBase: view,
1289
+ viewInterior: void 0,
1011
1290
  pipeline: gpu.layouts.pipelineLayout({
1012
1291
  label: "forward",
1013
1292
  bindGroupLayouts: [view, assets.materialLayout, store.layout]
@@ -1024,6 +1303,29 @@ function createLayouts(gpu, assets, store) {
1024
1303
  })
1025
1304
  };
1026
1305
  }
1306
+ function interiorViewLayout(gpu, state) {
1307
+ const layouts = state.layouts;
1308
+ if (layouts.viewInterior) return layouts.viewInterior;
1309
+ const F = GPUShaderStage.FRAGMENT;
1310
+ layouts.viewInterior = gpu.layouts.bindGroupLayout({
1311
+ label: "forward/view/interior",
1312
+ entries: [
1313
+ ...viewEntries(gpu),
1314
+ {
1315
+ binding: INTERIOR_BINDINGS.data,
1316
+ visibility: F,
1317
+ texture: { sampleType: "uint", viewDimension: "2d-array" }
1318
+ },
1319
+ { binding: INTERIOR_BINDINGS.table, visibility: F, buffer: { type: "uniform" } }
1320
+ ]
1321
+ });
1322
+ return layouts.viewInterior;
1323
+ }
1324
+ function chooseInterior(gpu, world, state) {
1325
+ const mode = world.tryResource(InteriorPath)?.mode ?? 0;
1326
+ state.interiorMode = mode;
1327
+ state.layouts.view = mode !== 0 ? interiorViewLayout(gpu, state) : state.layouts.viewBase;
1328
+ }
1027
1329
  function ensureLayouts(gpu, state, assets, store) {
1028
1330
  if (state.layoutGeneration === gpu.generation) return;
1029
1331
  state.layoutGeneration = gpu.generation;
@@ -1039,6 +1341,7 @@ function forwardCorePlugin(options = {}) {
1039
1341
  provides: [
1040
1342
  // core
1041
1343
  Environments,
1344
+ MaterialFallbacks,
1042
1345
  DeferredPath,
1043
1346
  DeformPath,
1044
1347
  PostFeatures,
@@ -1065,6 +1368,7 @@ function forwardCorePlugin(options = {}) {
1065
1368
  MorphWeights,
1066
1369
  NotShadowCaster,
1067
1370
  NotShadowReceiver,
1371
+ ShadowWhenHidden,
1068
1372
  SkinnedMesh,
1069
1373
  VisibilityRange,
1070
1374
  AmbientLight,
@@ -1075,6 +1379,14 @@ function forwardCorePlugin(options = {}) {
1075
1379
  SpotLight,
1076
1380
  ComputedVisibility,
1077
1381
  Visibility,
1382
+ // per-view visibility and cutaways (0070)
1383
+ ViewVisibility,
1384
+ HiddenSetsResource,
1385
+ Cutaway,
1386
+ CutawayView,
1387
+ CutawayPath,
1388
+ // interior lighting (0069)
1389
+ InteriorPath,
1078
1390
  Cameras,
1079
1391
  RenderPath,
1080
1392
  Tonemapping,
@@ -1087,6 +1399,18 @@ function forwardCorePlugin(options = {}) {
1087
1399
  SkinAssetType,
1088
1400
  Skins,
1089
1401
  RenderScale,
1402
+ // tabletop (0057)
1403
+ GroundLayer,
1404
+ RenderLayers,
1405
+ Outline,
1406
+ OutlinePath,
1407
+ Trail,
1408
+ Beam,
1409
+ Decal,
1410
+ DecalReceiver,
1411
+ RibbonsPath,
1412
+ DecalsPath,
1413
+ CameraMoved,
1090
1414
  // shadows
1091
1415
  ShadowsResource,
1092
1416
  // post
@@ -1102,11 +1426,21 @@ function forwardCorePlugin(options = {}) {
1102
1426
  // pixel-perfect
1103
1427
  PixelPerfect,
1104
1428
  PixelPerfectPath,
1105
- PixelTargets
1429
+ PixelTargets,
1430
+ // lens fields
1431
+ Lens,
1432
+ LensFields,
1433
+ LensPath,
1434
+ // screen effects
1435
+ ScreenEffects,
1436
+ ScreenEffectHandlers,
1437
+ // shard shaders bake (0064)
1438
+ ShaderVariantSources
1106
1439
  ],
1107
1440
  dependencies: ["render", "core/transform"],
1108
1441
  build(app) {
1109
1442
  const w = app.world;
1443
+ addShaderVariantSource(w, materialVariants);
1110
1444
  w.initResource(Meshes);
1111
1445
  w.initResource(Skins);
1112
1446
  w.initResource(Materials);
@@ -1119,98 +1453,186 @@ function forwardCorePlugin(options = {}) {
1119
1453
  w.initResource(Environments);
1120
1454
  w.initResource(ViewSettings).msaa = options.msaa ?? 4;
1121
1455
  Object.assign(w.initResource(RenderScale), options.renderScale);
1456
+ w.initResource(LensFields);
1457
+ w.initResource(ScreenEffects);
1458
+ w.initResource(ScreenEffectHandlers);
1459
+ w.initResource(RenderDescribers).set("screenEffects", describeScreenEffects);
1122
1460
  observeInstanceRemovals(w);
1461
+ observeOutlinesWithoutPass(w);
1462
+ observeVfxWithoutPlugin(w);
1463
+ observeViewVisibilityWithoutPlugin(w);
1123
1464
  observeLightRemovals(w);
1124
1465
  observeOriginShifts(w);
1125
- app.addSystems(PostUpdate, computeVisibility.after(TransformSystems), applyPhysicalCameras).addSystems(
1466
+ app.addSystems(PreUpdate, runScreenEffects).addSystems(PostUpdate, computeVisibility.after(TransformSystems), applyPhysicalCameras).addSystems(
1126
1467
  Last,
1468
+ expireLensFields.inSet(RenderSet.Begin),
1469
+ expireScreenEffects.inSet(RenderSet.Begin),
1127
1470
  extractCameras.inSet(RenderSet.Extract),
1128
1471
  extractLights.inSet(RenderSet.Extract),
1129
1472
  prepareInstances.inSet(RenderSet.Prepare),
1130
1473
  prepareLights.inSet(RenderSet.Prepare),
1131
- queue.inSet(RenderSet.Queue),
1474
+ forwardQueue.inSet(RenderSet.Queue),
1132
1475
  upload.inSet(RenderSet.Upload)
1133
1476
  );
1134
1477
  },
1135
- ready(app) {
1478
+ async ready(app) {
1136
1479
  const gpu = app.world.resource(Gpu);
1137
- setTextureCapabilities({
1138
- bc: gpu.features.has("texture-compression-bc"),
1139
- astc: gpu.features.has("texture-compression-astc"),
1140
- etc2: gpu.features.has("texture-compression-etc2")
1141
- });
1142
- const assets = new GpuAssets(gpu, app.world.initResource(GpuMemory));
1143
- const store = new InstanceStore(gpu);
1144
- app.insertResource(GpuAssetsResource, assets);
1145
- app.insertResource(Instances, store);
1146
- app.insertResource(Culler, new GpuCuller(gpu));
1147
- app.insertResource(
1148
- Lights,
1149
- new LightStore(gpu, app.world.resource(LightingSettings).maxLights)
1150
- );
1151
- app.insertResource(ShadowsResource, {
1152
- local: new LocalShadows(),
1153
- uniforms: new ShadowPassUniforms(gpu)
1480
+ const [baseline, baselineTextures] = gpu.tier === "baseline" ? await Promise.all([import("./baseline/lights.js"), import("./baseline/textures.js")]) : [void 0, void 0];
1481
+ app.scoped(() => {
1482
+ setTextureCapabilities({
1483
+ bc: gpu.features.has("texture-compression-bc"),
1484
+ astc: gpu.features.has("texture-compression-astc"),
1485
+ etc2: gpu.features.has("texture-compression-etc2")
1486
+ });
1487
+ const assets = new GpuAssets(gpu, app.world.initResource(GpuMemory));
1488
+ if (baselineTextures) {
1489
+ assets.baseline = baselineTextures;
1490
+ app.world.initResource(RenderDescribers).set("twins", () => assets.describeTwins());
1491
+ }
1492
+ assets.counts = app.world.initResource(RenderStats).current;
1493
+ const store = new InstanceStore(gpu);
1494
+ app.insertResource(GpuAssetsResource, assets);
1495
+ unwatch.set(app, assets.watch(assetServer(app.world)));
1496
+ app.world.owners.addDescriber("gpu", (owner) => {
1497
+ const server = assetServer(app.world);
1498
+ const total = { buffers: 0, textures: 0, bytes: 0 };
1499
+ for (const path of server.leasesOf(owner)) {
1500
+ const item = server.item(path);
1501
+ const o = assets.objectsOf(item);
1502
+ total.buffers += o.buffers;
1503
+ total.textures += o.textures;
1504
+ total.bytes += o.bytes;
1505
+ }
1506
+ return total;
1507
+ });
1508
+ app.insertResource(Instances, store);
1509
+ app.insertResource(Culler, new GpuCuller(gpu));
1510
+ app.insertResource(
1511
+ Lights,
1512
+ new LightStore(gpu, app.world.resource(LightingSettings).maxLights)
1513
+ );
1514
+ app.insertResource(ShadowsResource, {
1515
+ local: new LocalShadows(),
1516
+ uniforms: new ShadowPassUniforms(gpu)
1517
+ });
1518
+ const state = {
1519
+ views: /* @__PURE__ */ new Map(),
1520
+ viewBytes: new DataView(new ArrayBuffer(viewLayout.size)),
1521
+ shadowFloats: new Float32Array(SHADOW_DATA_FLOATS),
1522
+ layouts: createLayouts(gpu, assets, store),
1523
+ layoutGeneration: gpu.generation,
1524
+ emptyShadow: void 0,
1525
+ white: void 0,
1526
+ shadowSampler: void 0,
1527
+ frame: 0,
1528
+ warned: { budget: "", overflow: "" },
1529
+ globals: new GpuBuffer(gpu, {
1530
+ label: "globals",
1531
+ usage: GPUBufferUsage.UNIFORM,
1532
+ size: 16
1533
+ }),
1534
+ globalsData: new Float32Array(4),
1535
+ pipelines: new MaterialPipelines(),
1536
+ baseline,
1537
+ cullerActive: false,
1538
+ interiorMode: 0,
1539
+ gbufferTargets: [
1540
+ { format: "rgba8unorm-srgb" },
1541
+ { format: "rgba16float" },
1542
+ { format: gbufferEmissiveFormat(gpu) }
1543
+ ]
1544
+ };
1545
+ app.insertResource(MaterialFallbacks, {
1546
+ count: () => state.pipelines.failureCount,
1547
+ list: () => state.pipelines.failures()
1548
+ });
1549
+ store.batchKey = (b) => typeOrdinal(b.material.type) * 16 + materialVariant(b.material);
1550
+ app.insertResource(State, state);
1551
+ const graph = app.world.resource(Graph);
1552
+ graph.declare({ name: "scene-color", format: "rgba16float", sampleCount: msaaOf });
1553
+ graph.declare({ name: "scene-depth", format: "depth32float", sampleCount: msaaOf });
1554
+ graph.declare({ name: "hdr", format: "rgba16float" });
1555
+ graph.declare({ name: "depth", format: "depth32float" });
1556
+ graph.declare({ name: "ldr", format: "view" });
1557
+ addRenderFeatures(app.world, {
1558
+ name: "render/forward",
1559
+ description: "Opaque, ground and transparent mesh passes, and the MSAA depth resolve.",
1560
+ nodes: [
1561
+ "forward-opaque",
1562
+ "forward-ground",
1563
+ "forward-transparent",
1564
+ "forward-overlay",
1565
+ "depth-resolve",
1566
+ "depth-prepass"
1567
+ ],
1568
+ baseline: {
1569
+ strategy: "Instances, visibility and deform data in data textures; MSAA depth from a single-sample depth prepass"
1570
+ }
1571
+ });
1572
+ addRenderFeatures(app.world, {
1573
+ name: "render/culling",
1574
+ description: "GPU frustum, range and LOD culling with indirect draws.",
1575
+ nodes: ["instance-cull"],
1576
+ baseline: { strategy: "CPU culling and direct draws" }
1577
+ });
1578
+ addRenderFeatures(app.world, {
1579
+ name: "render/light-clusters",
1580
+ description: "Clustered light binning (Forward+).",
1581
+ nodes: ["light-clusters"],
1582
+ baseline: { strategy: "CPU binning into a 128-light bitmask texture" }
1583
+ });
1584
+ addRenderFeatures(app.world, {
1585
+ name: "render/shadows",
1586
+ description: "Cascaded directional shadows, and spot and point shadow layers.",
1587
+ nodes: ["shadows/cascades", "shadows/local"],
1588
+ baseline: { strategy: "The same render passes" }
1589
+ });
1590
+ graph.addNode("instance-cull", cullNode(state));
1591
+ graph.addNode("light-clusters", clusterNode(state));
1592
+ graph.addNode("shadows/cascades", cascadeNode(state));
1593
+ graph.addNode("shadows/local", localShadowNode(state));
1594
+ graph.addNode("forward-opaque", forwardNode(state));
1595
+ graph.addNode("forward-ground", groundNode(state));
1596
+ graph.addNode("forward-overlay", overlayNode(state));
1597
+ graph.addNode("forward-transparent", transparentNode(state));
1598
+ graph.addNode("depth-resolve", depthResolveNode(state));
1599
+ graph.addNode("depth-prepass", depthPrepassNode(state));
1600
+ addDisplayNodes(app.world);
1154
1601
  });
1155
- const state = {
1156
- views: /* @__PURE__ */ new Map(),
1157
- viewBytes: new DataView(new ArrayBuffer(viewLayout.size)),
1158
- shadowFloats: new Float32Array(SHADOW_DATA_FLOATS),
1159
- layouts: createLayouts(gpu, assets, store),
1160
- layoutGeneration: gpu.generation,
1161
- emptyShadow: void 0,
1162
- white: void 0,
1163
- shadowSampler: void 0,
1164
- frame: 0,
1165
- warned: { budget: "", overflow: "" },
1166
- globals: new GpuBuffer(gpu, { label: "globals", usage: GPUBufferUsage.UNIFORM, size: 16 }),
1167
- globalsData: new Float32Array(4),
1168
- pipelines: new MaterialPipelines(),
1169
- cullerActive: false,
1170
- gbufferTargets: [
1171
- { format: "rgba8unorm-srgb" },
1172
- { format: "rgba16float" },
1173
- { format: gbufferEmissiveFormat(gpu) }
1174
- ]
1175
- };
1176
- store.batchKey = (b) => typeOrdinal(b.material.type) * 16 + materialVariant(b.material);
1177
- app.insertResource(State, state);
1178
- const graph = app.world.resource(Graph);
1179
- graph.declare({ name: "scene-color", format: "rgba16float", sampleCount: msaaOf });
1180
- graph.declare({ name: "scene-depth", format: "depth32float", sampleCount: msaaOf });
1181
- graph.declare({ name: "hdr", format: "rgba16float" });
1182
- graph.declare({ name: "depth", format: "depth32float" });
1183
- graph.declare({ name: "ldr", format: "view" });
1184
- graph.addNode("instance-cull", cullNode(state));
1185
- graph.addNode("light-clusters", clusterNode(state));
1186
- graph.addNode("shadows/cascades", cascadeNode(state));
1187
- graph.addNode("shadows/local", localShadowNode(state));
1188
- graph.addNode("forward-opaque", forwardNode(state));
1189
- graph.addNode("forward-transparent", transparentNode(state));
1190
- graph.addNode("depth-resolve", depthResolveNode());
1191
- addDisplayNodes(app.world);
1602
+ },
1603
+ dispose(app) {
1604
+ unwatch.get(app)?.();
1605
+ unwatch.delete(app);
1192
1606
  }
1193
1607
  });
1194
1608
  }
1609
+ const unwatch = /* @__PURE__ */ new WeakMap();
1195
1610
  function gbufferEmissiveFormat(gpu) {
1196
1611
  return gpu.features.has("rg11b10ufloat-renderable") ? "rg11b10ufloat" : "rgba16float";
1197
1612
  }
1198
1613
  export {
1199
1614
  ForwardStateResource,
1615
+ INTERIOR_DEFINES,
1200
1616
  Mesh3d,
1201
1617
  MeshMaterial,
1618
+ PASS_DEPTH,
1202
1619
  PASS_GBUFFER,
1620
+ PASS_GROUND,
1203
1621
  PASS_OPAQUE,
1204
1622
  PASS_PICK,
1623
+ PASS_PICK_GROUND,
1205
1624
  PASS_PREPASS,
1206
1625
  PASS_TRANSPARENT,
1207
1626
  PICK_TARGETS,
1627
+ VERTEX_BUFFERS,
1208
1628
  drawMaterials,
1209
1629
  forwardCorePlugin,
1630
+ forwardQueue,
1210
1631
  gbufferEmissiveFormat,
1211
1632
  isCamera,
1212
1633
  sceneColor,
1213
1634
  skyNode,
1214
1635
  upload,
1215
- viewBindGroup
1636
+ viewBindGroup,
1637
+ viewPixelScale
1216
1638
  };