@aethervtt/shard-render 0.0.1

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 (139) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -0
  3. package/dist/assets.d.ts +104 -0
  4. package/dist/assets.js +213 -0
  5. package/dist/atmosphere-model.d.ts +96 -0
  6. package/dist/atmosphere-model.js +370 -0
  7. package/dist/atmosphere-nodes.d.ts +55 -0
  8. package/dist/atmosphere-nodes.js +780 -0
  9. package/dist/atmosphere-plugin.d.ts +5 -0
  10. package/dist/atmosphere-plugin.js +42 -0
  11. package/dist/atmosphere-shaders.d.ts +7 -0
  12. package/dist/atmosphere-shaders.js +595 -0
  13. package/dist/atmosphere-state.d.ts +72 -0
  14. package/dist/atmosphere-state.js +18 -0
  15. package/dist/atmosphere.d.ts +191 -0
  16. package/dist/atmosphere.js +950 -0
  17. package/dist/camera.d.ts +83 -0
  18. package/dist/camera.js +163 -0
  19. package/dist/clusters.d.ts +68 -0
  20. package/dist/clusters.js +288 -0
  21. package/dist/culling-shaders.d.ts +2 -0
  22. package/dist/culling-shaders.js +203 -0
  23. package/dist/culling.d.ts +84 -0
  24. package/dist/culling.js +498 -0
  25. package/dist/debug-shaders.d.ts +3 -0
  26. package/dist/debug-shaders.js +207 -0
  27. package/dist/debug-views.d.ts +60 -0
  28. package/dist/debug-views.js +197 -0
  29. package/dist/deferred-plugin.d.ts +5 -0
  30. package/dist/deferred-plugin.js +20 -0
  31. package/dist/deferred-shaders.d.ts +2 -0
  32. package/dist/deferred-shaders.js +151 -0
  33. package/dist/deferred.d.ts +22 -0
  34. package/dist/deferred.js +296 -0
  35. package/dist/deform.d.ts +80 -0
  36. package/dist/deform.js +213 -0
  37. package/dist/describe-plugin.d.ts +5 -0
  38. package/dist/describe-plugin.js +19 -0
  39. package/dist/display-nodes.d.ts +5 -0
  40. package/dist/display-nodes.js +189 -0
  41. package/dist/dynamic-resolution.d.ts +47 -0
  42. package/dist/dynamic-resolution.js +191 -0
  43. package/dist/environment-plugin.d.ts +6 -0
  44. package/dist/environment-plugin.js +44 -0
  45. package/dist/environment-shaders.d.ts +2 -0
  46. package/dist/environment-shaders.js +260 -0
  47. package/dist/environment-state.d.ts +71 -0
  48. package/dist/environment-state.js +144 -0
  49. package/dist/environment.d.ts +70 -0
  50. package/dist/environment.js +529 -0
  51. package/dist/forward.d.ts +123 -0
  52. package/dist/forward.js +1216 -0
  53. package/dist/fxaa.d.ts +2 -0
  54. package/dist/fxaa.js +65 -0
  55. package/dist/gizmo-font.d.ts +21 -0
  56. package/dist/gizmo-font.js +132 -0
  57. package/dist/gizmos-plugin.d.ts +5 -0
  58. package/dist/gizmos-plugin.js +34 -0
  59. package/dist/gizmos.d.ts +156 -0
  60. package/dist/gizmos.js +772 -0
  61. package/dist/gpu-assets.d.ts +114 -0
  62. package/dist/gpu-assets.js +494 -0
  63. package/dist/graph.d.ts +204 -0
  64. package/dist/graph.js +417 -0
  65. package/dist/index.d.ts +53 -0
  66. package/dist/index.js +540 -0
  67. package/dist/instances.d.ts +349 -0
  68. package/dist/instances.js +1335 -0
  69. package/dist/large-world.d.ts +13 -0
  70. package/dist/large-world.js +20 -0
  71. package/dist/lights.d.ts +160 -0
  72. package/dist/lights.js +468 -0
  73. package/dist/material-noise.d.ts +19 -0
  74. package/dist/material-noise.js +7 -0
  75. package/dist/material-pipelines.d.ts +46 -0
  76. package/dist/material-pipelines.js +166 -0
  77. package/dist/materials.d.ts +108 -0
  78. package/dist/materials.js +219 -0
  79. package/dist/noise.d.ts +76 -0
  80. package/dist/noise.js +286 -0
  81. package/dist/overlay-registry.d.ts +18 -0
  82. package/dist/overlay-registry.js +30 -0
  83. package/dist/overlays.d.ts +35 -0
  84. package/dist/overlays.js +344 -0
  85. package/dist/paths.d.ts +20 -0
  86. package/dist/paths.js +46 -0
  87. package/dist/picking-plugin.d.ts +2 -0
  88. package/dist/picking-plugin.js +20 -0
  89. package/dist/picking.d.ts +94 -0
  90. package/dist/picking.js +456 -0
  91. package/dist/pixel-perfect-plugin.d.ts +9 -0
  92. package/dist/pixel-perfect-plugin.js +135 -0
  93. package/dist/pixel-perfect.d.ts +27 -0
  94. package/dist/pixel-perfect.js +70 -0
  95. package/dist/plugin.d.ts +111 -0
  96. package/dist/plugin.js +182 -0
  97. package/dist/pool.d.ts +19 -0
  98. package/dist/pool.js +43 -0
  99. package/dist/post-common.d.ts +51 -0
  100. package/dist/post-common.js +148 -0
  101. package/dist/post-nodes.d.ts +39 -0
  102. package/dist/post-nodes.js +849 -0
  103. package/dist/post-plugin.d.ts +5 -0
  104. package/dist/post-plugin.js +26 -0
  105. package/dist/post-shaders.d.ts +8 -0
  106. package/dist/post-shaders.js +698 -0
  107. package/dist/post.d.ts +175 -0
  108. package/dist/post.js +453 -0
  109. package/dist/readback.d.ts +4 -0
  110. package/dist/readback.js +95 -0
  111. package/dist/render-scale.d.ts +53 -0
  112. package/dist/render-scale.js +63 -0
  113. package/dist/shaders.d.ts +122 -0
  114. package/dist/shaders.js +1168 -0
  115. package/dist/shadows.d.ts +119 -0
  116. package/dist/shadows.js +562 -0
  117. package/dist/skin-asset.d.ts +26 -0
  118. package/dist/skin-asset.js +25 -0
  119. package/dist/skinning-plugin.d.ts +5 -0
  120. package/dist/skinning-plugin.js +16 -0
  121. package/dist/skinning.d.ts +19 -0
  122. package/dist/skinning.js +343 -0
  123. package/dist/standard-fields.d.ts +67 -0
  124. package/dist/standard-fields.js +73 -0
  125. package/dist/standard.d.ts +7 -0
  126. package/dist/standard.js +37 -0
  127. package/dist/stats.d.ts +25 -0
  128. package/dist/stats.js +18 -0
  129. package/dist/target.d.ts +55 -0
  130. package/dist/target.js +70 -0
  131. package/dist/testing.d.ts +34 -0
  132. package/dist/testing.js +126 -0
  133. package/dist/timer.d.ts +28 -0
  134. package/dist/timer.js +136 -0
  135. package/dist/view.d.ts +117 -0
  136. package/dist/view.js +389 -0
  137. package/dist/visibility.d.ts +10 -0
  138. package/dist/visibility.js +84 -0
  139. package/package.json +49 -0
@@ -0,0 +1,1216 @@
1
+ import { defineResource, defineSystem, Last, PostUpdate } from "@aethervtt/shard-core";
2
+ import { GpuBuffer } from "@aethervtt/shard-gpu";
3
+ import { definePlugin, LogResource, Time } from "@aethervtt/shard-runtime";
4
+ import { setTextureCapabilities, Textures } from "@aethervtt/shard-texture";
5
+ import { TransformSystems } from "@aethervtt/shard-transform";
6
+ import {
7
+ MaterialAssetType,
8
+ MaterialImporter,
9
+ Materials,
10
+ MeshAsset,
11
+ Meshes,
12
+ RenderTargets,
13
+ STANDARD_TYPE,
14
+ StandardMaterial
15
+ } from "./assets.js";
16
+ import { Atmospheres } from "./atmosphere-state.js";
17
+ import { applyPhysicalCameras, Camera3d, Exposure, PhysicalCamera } from "./camera.js";
18
+ import {
19
+ CLUSTER_COUNT,
20
+ CLUSTER_Z,
21
+ ClusterBuffers,
22
+ clusterRange,
23
+ MAX_LIGHTS_PER_CLUSTER,
24
+ ViewLightList
25
+ } from "./clusters.js";
26
+ import { Culler, cullTransparent, GpuCuller } from "./culling.js";
27
+ import { addDisplayNodes } from "./display-nodes.js";
28
+ import { Environments, environmentParams } from "./environment-state.js";
29
+ import { GpuAssets, GpuAssetsResource } from "./gpu-assets.js";
30
+ import { RenderPhase } from "./graph.js";
31
+ import {
32
+ DeformPath,
33
+ InstanceData,
34
+ InstanceFlags,
35
+ InstanceSlot,
36
+ InstanceStore,
37
+ Instances,
38
+ LOD_UNSET,
39
+ Lod,
40
+ Mesh3d,
41
+ MeshMaterial,
42
+ MorphWeights,
43
+ NotShadowCaster,
44
+ NotShadowReceiver,
45
+ observeInstanceRemovals,
46
+ prepareInstances,
47
+ SkinnedMesh,
48
+ VisibilityRange
49
+ } from "./instances.js";
50
+ import { observeOriginShifts } from "./large-world.js";
51
+ import {
52
+ AmbientLight,
53
+ DirectionalLight,
54
+ extractLights,
55
+ LightingSettings,
56
+ LightStore,
57
+ Lights,
58
+ observeLightRemovals,
59
+ PointLight,
60
+ SpotLight
61
+ } from "./lights.js";
62
+ import { MaterialNoise } from "./material-noise.js";
63
+ import {
64
+ blendState,
65
+ MaterialPipelines,
66
+ materialVariant,
67
+ typeOrdinal,
68
+ variantBlend,
69
+ variantCull
70
+ } from "./material-pipelines.js";
71
+ import { isTransparent } from "./materials.js";
72
+ import { PixelPerfect, PixelPerfectPath, PixelTargets } from "./pixel-perfect.js";
73
+ import { Gpu, Graph, RenderSet, Shaders, Views } from "./plugin.js";
74
+ import {
75
+ Antialiasing,
76
+ AutoExposure,
77
+ Bloom,
78
+ ColorGrading,
79
+ DepthOfField,
80
+ Fog,
81
+ MotionBlur,
82
+ PostFeatures,
83
+ Ssao,
84
+ Vignette
85
+ } from "./post.js";
86
+ import { RenderScale } from "./render-scale.js";
87
+ import { ViewUniform, viewLayout } from "./shaders.js";
88
+ import {
89
+ assignLocalShadows,
90
+ Cascades,
91
+ drawShadowCasters,
92
+ fitCascades,
93
+ fitLocalShadows,
94
+ LocalShadows,
95
+ packShadowData,
96
+ SHADOW_DATA_FLOATS,
97
+ ShadowPassUniforms,
98
+ ShadowsResource,
99
+ shadowViewLayout
100
+ } from "./shadows.js";
101
+ import { SkinAssetType, Skins } from "./skin-asset.js";
102
+ import { GpuMemory, RenderCounters, RenderStats } from "./stats.js";
103
+ import {
104
+ Cameras,
105
+ cameraOf,
106
+ DeferredPath,
107
+ extractCameras,
108
+ RenderPath,
109
+ Tonemapping,
110
+ ViewSettings
111
+ } from "./view.js";
112
+ import { ComputedVisibility, computeVisibility, Visibility } from "./visibility.js";
113
+ const ForwardStateResource = defineResource("render/ForwardState");
114
+ const State = ForwardStateResource;
115
+ const prepareLights = defineSystem({
116
+ name: "render/prepare-lights",
117
+ description: "Assigns shadow maps within the budgets and uploads changed lights.",
118
+ run: (_, world) => {
119
+ const lights = world.resource(Lights);
120
+ const shadows = world.resource(ShadowsResource);
121
+ const settings = world.resource(LightingSettings);
122
+ let main;
123
+ let order = Number.POSITIVE_INFINITY;
124
+ for (const view of world.resource(Views).list) {
125
+ const cam = cameraOf(view);
126
+ if (cam && view.order < order) {
127
+ main = cam;
128
+ order = view.order;
129
+ }
130
+ }
131
+ assignLocalShadows(
132
+ shadows.local,
133
+ lights,
134
+ main,
135
+ settings.maxShadowedSpots,
136
+ settings.maxShadowedPoints
137
+ );
138
+ lights.upload();
139
+ const state = world.resource(State);
140
+ const over = shadows.local.overBudget.map((r) => r.entity).join(",");
141
+ if (over !== state.warned.budget) {
142
+ state.warned.budget = over;
143
+ if (over) {
144
+ world.tryResource(LogResource)?.log(
145
+ "warn",
146
+ `${shadows.local.overBudget.length} shadowed light(s) over the shadow budget render without shadows`,
147
+ {
148
+ code: "render/shadow-budget",
149
+ hint: "Raise LightingSettings.maxShadowedSpots/maxShadowedPoints, or turn off shadows on minor lights.",
150
+ data: { lights: shadows.local.overBudget.map((r) => r.entity) }
151
+ }
152
+ );
153
+ }
154
+ }
155
+ }
156
+ });
157
+ const cameraCull = {
158
+ planes: null,
159
+ require: 0,
160
+ eye: void 0,
161
+ lodScale: 1,
162
+ orthographic: false,
163
+ lodState: void 0,
164
+ updateLod: true
165
+ };
166
+ const shadowCull = {
167
+ planes: null,
168
+ require: InstanceFlags.Caster,
169
+ eye: void 0,
170
+ lodScale: 1,
171
+ orthographic: false,
172
+ lodState: void 0,
173
+ updateLod: false
174
+ };
175
+ function cameraParams(cam, out) {
176
+ out.planes = cam.frozenFrustum ?? cam.frustum;
177
+ out.eye = cam.frozenPosition ?? cam.position;
178
+ out.orthographic = cam.orthographic;
179
+ out.lodScale = cam.orthographic ? 2 / cam.orthoHeight : 1 / Math.tan(cam.fovY / 2);
180
+ if (cam.lodState.length < cam.lodCapacity)
181
+ cam.lodState = new Uint8Array(cam.lodCapacity).fill(LOD_UNSET);
182
+ out.lodState = cam.lodState;
183
+ return out;
184
+ }
185
+ const queue = defineSystem({
186
+ name: "render/forward-queue",
187
+ description: "Culls instances per camera and shadow view (on the GPU when it can), clusters lights, and writes view uniforms.",
188
+ run: (_, world) => {
189
+ const state = world.resource(State);
190
+ const gpu = world.resource(Gpu);
191
+ const store = world.resource(Instances);
192
+ const culler = world.resource(Culler);
193
+ const lights = world.resource(Lights);
194
+ const shadows = world.resource(ShadowsResource);
195
+ const settings = world.resource(LightingSettings);
196
+ const stats = world.resource(RenderStats);
197
+ stats.clear();
198
+ state.frame++;
199
+ ensureLayouts(gpu, state, world.resource(GpuAssetsResource), store);
200
+ state.pipelines.beginFrame(state.frame);
201
+ shadows.uniforms.reset();
202
+ culler.beginFrame();
203
+ const gpuCull = culler.active;
204
+ state.cullerActive = gpuCull;
205
+ const time = world.resource(Time);
206
+ const g = state.globalsData;
207
+ g[0] = time.elapsed;
208
+ g[1] = time.delta;
209
+ new Uint32Array(g.buffer)[2] = time.frame;
210
+ state.globals.write(g);
211
+ let main;
212
+ let mainOrder = Number.POSITIVE_INFINITY;
213
+ for (const view of world.resource(Views).list) {
214
+ const cam = cameraOf(view);
215
+ if (!cam) continue;
216
+ cam.lodCapacity = store.capacity;
217
+ const params = cameraParams(cam, cameraCull);
218
+ const forwardOnly = cam.deferred ? cam.forwardOnly : void 0;
219
+ if (gpuCull) {
220
+ culler.add(store, cam.draws, params, culler.lodCamera(cam.entity), forwardOnly);
221
+ cullTransparent(store, cam.transparent, params, cam.forward);
222
+ } else {
223
+ store.cullCpu(cam.draws, params, void 0, cam.transparent, cam.forward, forwardOnly);
224
+ }
225
+ store.orderNearFirst(cam.draws, cam.position);
226
+ if (view.order < mainOrder) {
227
+ main = cam;
228
+ mainOrder = view.order;
229
+ }
230
+ }
231
+ const shadowParams = (cam, planes) => {
232
+ cameraParams(cam, shadowCull);
233
+ shadowCull.planes = planes;
234
+ shadowCull.updateLod = false;
235
+ return shadowCull;
236
+ };
237
+ const cullShadow = (cam, draws, planes, box) => {
238
+ const params = shadowParams(cam, planes);
239
+ if (gpuCull) {
240
+ culler.add(store, draws, params, culler.lodCamera(cam.entity));
241
+ if (box) store.casterBounds(box);
242
+ return true;
243
+ }
244
+ store.cullCpu(draws, params, box);
245
+ return draws.visible > 0;
246
+ };
247
+ const local = shadows.local;
248
+ fitLocalShadows(local, settings.shadowMapSize);
249
+ if (main) {
250
+ for (let i = 0; i < local.spots.length; i++) {
251
+ const view = local.spotViews[i];
252
+ cullShadow(main, view.draws, view.frustum);
253
+ view.offset = shadows.uniforms.push(view.viewProj);
254
+ }
255
+ for (let i = 0; i < local.points.length * 6; i++) {
256
+ const view = local.pointViews[i];
257
+ cullShadow(main, view.draws, view.frustum);
258
+ view.offset = shadows.uniforms.push(view.viewProj);
259
+ }
260
+ }
261
+ for (const view of world.resource(Views).list) {
262
+ const cam = cameraOf(view);
263
+ if (!cam) continue;
264
+ const pv = viewGpu(gpu, state, view.name);
265
+ pv.ao = void 0;
266
+ pv.lightList.build(cam, lights.records, lights.high);
267
+ pv.clusters.upload(pv.lightList, cam, settings.clusterFar);
268
+ const sun = lights.shadowSun;
269
+ if (sun) {
270
+ fitCascades(
271
+ pv.cascades,
272
+ cam,
273
+ sun,
274
+ settings.cascadeMapSize,
275
+ (v, planes, box) => cullShadow(cam, v.draws, planes, box)
276
+ );
277
+ for (let i = 0; i < pv.cascades.count; i++) {
278
+ const v = pv.cascades.views[i];
279
+ v.offset = shadows.uniforms.push(v.viewProj);
280
+ }
281
+ } else {
282
+ pv.cascades.count = 0;
283
+ }
284
+ packShadowData(
285
+ state.shadowFloats,
286
+ sun ? pv.cascades : void 0,
287
+ sun,
288
+ local,
289
+ settings.cascadeMapSize,
290
+ settings.shadowMapSize
291
+ );
292
+ pv.shadowData.write(state.shadowFloats);
293
+ writeViewUniform(world, state, pv, cam, world.resource(AmbientLight), settings.clusterFar);
294
+ const st = drawStats(cam.draws);
295
+ if (cam.draws.cullView >= 0) {
296
+ st.drawCalls = culler.drawCounts.get(cam.draws) ?? 0;
297
+ st.hidden = store.hiddenCount;
298
+ const others = cam.deferred ? culler.counts.get(cam.forwardOnly) ?? 0 : 0;
299
+ st.culled = Math.max(0, store.drawableCount - store.hiddenCount - st.visible - others);
300
+ }
301
+ if (cam.deferred) {
302
+ st.visible += cam.forwardOnly.visible;
303
+ st.drawCalls += cam.forwardOnly.cullView >= 0 ? culler.drawCounts.get(cam.forwardOnly) ?? 0 : cam.forwardOnly.length;
304
+ }
305
+ st.visible += cam.transparent.visible;
306
+ st.drawCalls += cam.transparent.length;
307
+ stats.set(view.name, st);
308
+ }
309
+ }
310
+ });
311
+ function viewGpu(gpu, state, name) {
312
+ let pv = state.views.get(name);
313
+ if (!pv) {
314
+ pv = {
315
+ uniform: new GpuBuffer(gpu, {
316
+ label: `${name}/view`,
317
+ usage: GPUBufferUsage.UNIFORM,
318
+ size: viewLayout.size
319
+ }),
320
+ lightList: new ViewLightList(),
321
+ clusters: new ClusterBuffers(gpu, name),
322
+ shadowData: new GpuBuffer(gpu, {
323
+ label: `${name}/shadows`,
324
+ usage: GPUBufferUsage.STORAGE,
325
+ size: SHADOW_DATA_FLOATS * 4
326
+ }),
327
+ cascades: new Cascades(),
328
+ ao: void 0,
329
+ bindGroup: void 0,
330
+ bound: "",
331
+ clusterBindGroup: void 0,
332
+ clusterBound: ""
333
+ };
334
+ state.views.set(name, pv);
335
+ }
336
+ return pv;
337
+ }
338
+ function drawStats(list) {
339
+ return {
340
+ visible: list.visible,
341
+ culled: list.culled,
342
+ hidden: list.hidden,
343
+ pending: list.pending,
344
+ drawCalls: list.length,
345
+ pipelineSwitches: 0
346
+ };
347
+ }
348
+ const viewport = new Float32Array(4);
349
+ const clusterParams = new Float32Array(4);
350
+ const ambientScratch = new Float32Array(3);
351
+ const envScratch = new Float32Array(4);
352
+ const jitterScratch = new Float32Array(4);
353
+ const noTransmittance = new Float32Array(16).fill(1);
354
+ function writeViewUniform(world, state, pv, cam, ambient, clusterFar) {
355
+ environmentParams(world.resource(Environments), cam, envScratch);
356
+ const [near, far] = clusterRange(cam, clusterFar);
357
+ viewport[0] = cam.width;
358
+ viewport[1] = cam.height;
359
+ viewport[2] = 1 / cam.width;
360
+ viewport[3] = 1 / cam.height;
361
+ clusterParams[0] = near;
362
+ clusterParams[1] = far;
363
+ clusterParams[2] = CLUSTER_Z / Math.log(far / near);
364
+ clusterParams[3] = cam.debug;
365
+ jitterScratch[0] = cam.post.jitter[0];
366
+ jitterScratch[1] = cam.post.jitter[1];
367
+ jitterScratch[2] = cam.frames;
368
+ ambientScratch[0] = ambient.color[0] * ambient.brightness;
369
+ ambientScratch[1] = ambient.color[1] * ambient.brightness;
370
+ ambientScratch[2] = ambient.color[2] * ambient.brightness;
371
+ viewLayout.write(state.viewBytes, 0, {
372
+ viewProj: cam.viewProj,
373
+ view: cam.view,
374
+ invViewProj: cam.invViewProj,
375
+ cameraPosition: cam.position,
376
+ exposure: cam.exposure,
377
+ viewport,
378
+ clusterParams,
379
+ ambient: ambientScratch,
380
+ envParams: envScratch,
381
+ viewProjNoJitter: cam.viewProjNoJitter,
382
+ prevViewProj: cam.prevViewProj,
383
+ jitter: jitterScratch,
384
+ sunTransmittance: world.tryResource(Atmospheres)?.cameras.get(cam.entity)?.sunTransmittance ?? noTransmittance
385
+ });
386
+ pv.uniform.write(new Float32Array(state.viewBytes.buffer, 0, viewLayout.size / 4));
387
+ }
388
+ const upload = defineSystem({
389
+ name: "render/upload-visible",
390
+ description: "Uploads every view's visible instance lists and shadow view matrices.",
391
+ run: (_, world) => {
392
+ const store = world.resource(Instances);
393
+ const culler = world.resource(Culler);
394
+ if (culler.active) {
395
+ culler.prepare(store);
396
+ store.gpuVisible = culler.visible;
397
+ }
398
+ store.finishFrame();
399
+ const state = world.resource(State);
400
+ world.resource(ShadowsResource).uniforms.upload(state.layouts.shadowView, state.globals);
401
+ }
402
+ });
403
+ const VERTEX_BUFFERS = [
404
+ { arrayStride: 12, attributes: [{ shaderLocation: 0, offset: 0, format: "float32x3" }] },
405
+ { arrayStride: 12, attributes: [{ shaderLocation: 1, offset: 0, format: "float32x3" }] },
406
+ { arrayStride: 8, attributes: [{ shaderLocation: 2, offset: 0, format: "float32x2" }] },
407
+ { arrayStride: 8, attributes: [{ shaderLocation: 3, offset: 0, format: "float32x2" }] },
408
+ { arrayStride: 16, attributes: [{ shaderLocation: 4, offset: 0, format: "float32x4" }] }
409
+ ];
410
+ const FORWARD_DEFINES = [
411
+ { PREMULTIPLY: false, MASK: false },
412
+ { PREMULTIPLY: true, MASK: false },
413
+ { PREMULTIPLY: false, MASK: true },
414
+ { PREMULTIPLY: true, MASK: true }
415
+ ];
416
+ function materialLayout(gpu, state, assets, type, store) {
417
+ return gpu.layouts.pipelineLayout({
418
+ label: `forward/${type.name}`,
419
+ bindGroupLayouts: [state.layouts.view, assets.layoutOf(type), store.layout]
420
+ });
421
+ }
422
+ const PASS_OPAQUE = 0;
423
+ const PASS_TRANSPARENT = 1;
424
+ const PASS_GBUFFER = 3;
425
+ const PASS_PREPASS = 5;
426
+ const PASS_PICK = 7;
427
+ const PICK_TARGETS = [
428
+ { format: "r32uint" },
429
+ { format: "rgba32float" }
430
+ ];
431
+ const PREPASS_TARGETS = [{ format: "rgba16float" }, { format: "rg16float" }];
432
+ function drawMaterials(ctx, state, pv, cam, draws, pass) {
433
+ const store = ctx.world.resource(Instances);
434
+ const assets = ctx.world.resource(GpuAssetsResource);
435
+ const gpu = ctx.gpu;
436
+ const renderPass = ctx.renderPass;
437
+ const instances = draws.cullView >= 0 ? store.gpuBindGroup : store.bindGroup;
438
+ if (!instances) return 0;
439
+ const args = ctx.world.resource(Culler).args.buffer;
440
+ renderPass.setBindGroup(0, viewBindGroup(gpu, ctx.world, pv, cam));
441
+ renderPass.setBindGroup(2, instances);
442
+ let current;
443
+ let boundMaterial;
444
+ let boundPositions;
445
+ let boundTangents;
446
+ let boundIndices;
447
+ let switches = 0;
448
+ for (let d = 0; d < draws.length; d++) {
449
+ 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);
461
+ 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;
496
+ }
497
+ const mat = assets.material(ctx.world, material);
498
+ if (!mat?.bindGroup) continue;
499
+ const gm = assets.mesh(item.batch.mesh);
500
+ if (pipeline !== current) {
501
+ renderPass.setPipeline(pipeline);
502
+ current = pipeline;
503
+ boundMaterial = void 0;
504
+ switches++;
505
+ }
506
+ if (mat.bindGroup !== boundMaterial) {
507
+ renderPass.setBindGroup(1, mat.bindGroup);
508
+ boundMaterial = mat.bindGroup;
509
+ }
510
+ if (gm.positions !== boundPositions || gm.tangents !== boundTangents) {
511
+ renderPass.setVertexBuffer(0, gm.positions);
512
+ renderPass.setVertexBuffer(1, gm.normals);
513
+ renderPass.setVertexBuffer(2, gm.uvs);
514
+ renderPass.setVertexBuffer(3, gm.uvs1);
515
+ renderPass.setVertexBuffer(4, gm.tangents);
516
+ boundPositions = gm.positions;
517
+ boundTangents = gm.tangents;
518
+ }
519
+ if (gm.indices) {
520
+ if (gm.indices !== boundIndices) {
521
+ renderPass.setIndexBuffer(gm.indices, gm.indexFormat);
522
+ boundIndices = gm.indices;
523
+ }
524
+ if (item.indirect >= 0) renderPass.drawIndexedIndirect(args, item.indirect);
525
+ else renderPass.drawIndexed(gm.count, item.count, 0, gm.baseVertex, item.first);
526
+ } else if (item.indirect >= 0) {
527
+ renderPass.drawIndirect(args, item.indirect);
528
+ } else {
529
+ renderPass.draw(gm.count, item.count, 0, item.first);
530
+ }
531
+ }
532
+ return switches;
533
+ }
534
+ function recordSwitches(ctx, switches) {
535
+ const stats = ctx.world.resource(RenderStats).get(ctx.view.name);
536
+ if (stats) stats.pipelineSwitches = Math.max(stats.pipelineSwitches ?? 0, switches);
537
+ }
538
+ const isCamera = (view) => cameraOf(view) !== void 0;
539
+ const msaaOf = (view) => cameraOf(view)?.msaa ?? 1;
540
+ function whiteTexture(gpu, state) {
541
+ if (!state.white || state.white.generation !== gpu.generation) {
542
+ const texture = gpu.device.createTexture({
543
+ label: "white",
544
+ size: [1, 1, 1],
545
+ format: "rgba8unorm",
546
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
547
+ });
548
+ gpu.device.queue.writeTexture({ texture }, new Uint8Array([255, 255, 255, 255]), {}, [1, 1]);
549
+ state.white = { texture, generation: gpu.generation };
550
+ }
551
+ return state.white.texture;
552
+ }
553
+ function emptyShadow(gpu, state) {
554
+ if (!state.emptyShadow || state.emptyShadow.generation !== gpu.generation) {
555
+ state.emptyShadow = {
556
+ texture: gpu.device.createTexture({
557
+ label: "shadows/empty",
558
+ size: [1, 1, 1],
559
+ format: "depth32float",
560
+ usage: GPUTextureUsage.TEXTURE_BINDING
561
+ }),
562
+ generation: gpu.generation
563
+ };
564
+ state.shadowSampler = void 0;
565
+ }
566
+ return state.emptyShadow.texture;
567
+ }
568
+ const textureIds = /* @__PURE__ */ new WeakMap();
569
+ let nextTextureId = 1;
570
+ function idOf(t) {
571
+ let id = textureIds.get(t);
572
+ if (id === void 0) {
573
+ id = nextTextureId++;
574
+ textureIds.set(t, id);
575
+ }
576
+ return id;
577
+ }
578
+ function viewBindGroup(gpu, world, pv, cam) {
579
+ const state = world.resource(State);
580
+ const lights = world.resource(Lights);
581
+ const local = world.resource(ShadowsResource).local;
582
+ const settings = world.resource(LightingSettings);
583
+ const empty = emptyShadow(gpu, state);
584
+ state.shadowSampler ??= gpu.device.createSampler({
585
+ label: "shadows/compare",
586
+ compare: "greater-equal",
587
+ magFilter: "linear",
588
+ minFilter: "linear",
589
+ addressModeU: "clamp-to-edge",
590
+ addressModeV: "clamp-to-edge"
591
+ });
592
+ const cascades = pv.cascades.count > 0 ? pv.cascades.ensureTexture(gpu, settings.cascadeMapSize, "view") : pv.cascades.texture && pv.cascades.generation === gpu.generation ? pv.cascades.texture : empty;
593
+ if (local.spots.length > 0 || local.points.length > 0) {
594
+ local.ensureTextures(gpu, settings.shadowMapSize);
595
+ }
596
+ const live = (t) => t && local.generation === gpu.generation ? t : empty;
597
+ const spots = live(local.spotTexture);
598
+ const points = live(local.pointTexture);
599
+ const envs = world.resource(Environments);
600
+ envs.ensure(gpu);
601
+ const env = envs.cameras.get(cam.entity)?.environment;
602
+ const baked = env && env.bakes > 0 ? env : void 0;
603
+ const specular = baked?.specularView ?? envs.emptyView;
604
+ const source = baked?.sourceView ?? envs.emptyView;
605
+ const sh = baked?.sh ?? envs.emptySh;
606
+ 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}`;
608
+ if (!pv.bindGroup || pv.bound !== key) {
609
+ pv.bindGroup = gpu.device.createBindGroup({
610
+ label: "forward/view",
611
+ 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
+ ]
630
+ });
631
+ pv.bound = key;
632
+ }
633
+ return pv.bindGroup;
634
+ }
635
+ function forwardNode(state) {
636
+ return {
637
+ kind: "render",
638
+ phase: RenderPhase.Opaque,
639
+ enabled: (view) => isCamera(view) && !cameraOf(view).deferred,
640
+ reads: ["clusters", "shadow-cascades", "shadow-local", "environment", "culled", "ssao"],
641
+ writes: ["scene-color", "scene-depth", "hdr"],
642
+ color: (view) => sceneColor(view, cameraOf(view)?.clear),
643
+ depth: { resource: "scene-depth", clear: 0 },
644
+ run: (ctx) => {
645
+ const cam = cameraOf(ctx.view);
646
+ const pv = state.views.get(ctx.view.name);
647
+ if (!pv) return;
648
+ recordSwitches(ctx, drawMaterials(ctx, state, pv, cam, cam.draws, PASS_OPAQUE));
649
+ }
650
+ };
651
+ }
652
+ function transparentNode(state) {
653
+ return {
654
+ kind: "render",
655
+ phase: RenderPhase.Transparent,
656
+ enabled: (view) => (cameraOf(view)?.transparent.length ?? 0) > 0,
657
+ reads: ["clusters", "shadow-cascades", "shadow-local", "environment", "culled", "ssao"],
658
+ writes: ["scene-color", "hdr"],
659
+ color: (view) => sceneColor(view),
660
+ depth: { resource: "scene-depth", readOnly: true },
661
+ run: (ctx) => {
662
+ const cam = cameraOf(ctx.view);
663
+ const pv = state.views.get(ctx.view.name);
664
+ if (!pv) return;
665
+ drawMaterials(ctx, state, pv, cam, cam.transparent, PASS_TRANSPARENT);
666
+ }
667
+ };
668
+ }
669
+ function clusterNode(state) {
670
+ return {
671
+ kind: "raw",
672
+ phase: RenderPhase.Setup,
673
+ enabled: isCamera,
674
+ writes: ["clusters"],
675
+ run: (ctx) => {
676
+ const pv = state.views.get(ctx.view.name);
677
+ if (!pv) return;
678
+ const gpu = ctx.gpu;
679
+ const module = ctx.world.resource(Shaders).module(gpu, { root: "shard::lighting::cluster" });
680
+ if (!module) {
681
+ gpu.pipelines.skipped++;
682
+ return;
683
+ }
684
+ const pipeline = gpu.pipelines.compute({
685
+ label: "light-clusters",
686
+ layout: state.layouts.clusterPipeline,
687
+ compute: { module, entryPoint: "main" }
688
+ });
689
+ if (!pipeline) return;
690
+ const c = pv.clusters;
691
+ const ao = pv.ao ?? whiteTexture(gpu, state);
692
+ const key = `${idOf(ao)}/${gpu.generation}/${pv.uniform.version}/${c.lightList.version}/${c.aabbs.version}/${c.clusters.version}/${c.stats.version}`;
693
+ if (!pv.clusterBindGroup || pv.clusterBound !== key) {
694
+ pv.clusterBindGroup = gpu.device.createBindGroup({
695
+ label: "light-clusters",
696
+ layout: state.layouts.cluster,
697
+ entries: [
698
+ { binding: 0, resource: { buffer: pv.uniform.buffer } },
699
+ { binding: 1, resource: { buffer: c.lightList.buffer } },
700
+ { binding: 2, resource: { buffer: c.aabbs.buffer } },
701
+ { binding: 3, resource: { buffer: c.clusters.buffer } },
702
+ { binding: 4, resource: { buffer: c.stats.buffer } }
703
+ ]
704
+ });
705
+ pv.clusterBound = key;
706
+ }
707
+ const pass = ctx.encoder.beginComputePass({
708
+ label: `${ctx.view.name}/light-clusters`,
709
+ timestampWrites: ctx.timestamps("light-clusters")
710
+ });
711
+ pass.setPipeline(pipeline);
712
+ pass.setBindGroup(0, pv.clusterBindGroup);
713
+ pass.dispatchWorkgroups(Math.ceil(CLUSTER_COUNT / 64));
714
+ pass.end();
715
+ const map = c.readback(ctx.encoder);
716
+ if (map) ctx.afterSubmit(map);
717
+ const overflows = c.latest.overflows;
718
+ const warnKey = overflows > 0 ? `${ctx.view.name}:${overflows}` : "";
719
+ if (warnKey !== state.warned.overflow) {
720
+ state.warned.overflow = warnKey;
721
+ if (overflows > 0) {
722
+ ctx.world.tryResource(LogResource)?.log(
723
+ "warn",
724
+ `${overflows} light cluster(s) in ${ctx.view.name} hit ${MAX_LIGHTS_PER_CLUSTER} lights; extra lights are dropped`,
725
+ {
726
+ code: "render/cluster-overflow",
727
+ hint: "Give point and spot lights smaller ranges so fewer overlap."
728
+ }
729
+ );
730
+ }
731
+ }
732
+ }
733
+ };
734
+ }
735
+ function cascadeNode(state) {
736
+ return {
737
+ kind: "raw",
738
+ phase: RenderPhase.Shadows,
739
+ enabled: (view) => isCamera(view) && (state.views.get(view.name)?.cascades.count ?? 0) > 0,
740
+ reads: ["culled"],
741
+ writes: ["shadow-cascades"],
742
+ run: (ctx) => {
743
+ const pv = state.views.get(ctx.view.name);
744
+ const shadows = ctx.world.resource(ShadowsResource);
745
+ const size = ctx.world.resource(LightingSettings).cascadeMapSize;
746
+ const texture = pv.cascades.ensureTexture(ctx.gpu, size, ctx.view.name);
747
+ for (let i = 0; i < pv.cascades.count; i++) {
748
+ drawShadowCasters(
749
+ ctx,
750
+ texture.createView({ dimension: "2d", baseArrayLayer: i, arrayLayerCount: 1 }),
751
+ pv.cascades.views[i],
752
+ pv.cascades.views[i].offset,
753
+ shadows.uniforms,
754
+ state.pipelines,
755
+ state.layouts.shadowView,
756
+ `shadows/cascade${i}`
757
+ );
758
+ }
759
+ }
760
+ };
761
+ }
762
+ function localShadowNode(state) {
763
+ return {
764
+ kind: "raw",
765
+ phase: RenderPhase.Shadows,
766
+ enabled: isCamera,
767
+ reads: ["culled"],
768
+ writes: ["shadow-local"],
769
+ run: (ctx) => {
770
+ const shadows = ctx.world.resource(ShadowsResource);
771
+ const local = shadows.local;
772
+ if (local.renderedFrame === state.frame) return;
773
+ local.renderedFrame = state.frame;
774
+ if (local.spots.length === 0 && local.points.length === 0) return;
775
+ local.ensureTextures(ctx.gpu, ctx.world.resource(LightingSettings).shadowMapSize);
776
+ const layer = (texture, i) => texture.createView({ dimension: "2d", baseArrayLayer: i, arrayLayerCount: 1 });
777
+ for (let i = 0; i < local.spots.length; i++) {
778
+ const view = local.spotViews[i];
779
+ drawShadowCasters(
780
+ ctx,
781
+ layer(local.spotTexture, i),
782
+ view,
783
+ view.offset,
784
+ shadows.uniforms,
785
+ state.pipelines,
786
+ state.layouts.shadowView,
787
+ `shadows/spot${i}`
788
+ );
789
+ }
790
+ for (let i = 0; i < local.points.length * 6; i++) {
791
+ const view = local.pointViews[i];
792
+ drawShadowCasters(
793
+ ctx,
794
+ layer(local.pointTexture, i),
795
+ view,
796
+ view.offset,
797
+ shadows.uniforms,
798
+ state.pipelines,
799
+ state.layouts.shadowView,
800
+ `shadows/point${i}`
801
+ );
802
+ }
803
+ }
804
+ };
805
+ }
806
+ function cullNode(state) {
807
+ let frame = -1;
808
+ return {
809
+ kind: "raw",
810
+ phase: RenderPhase.Setup,
811
+ enabled: (view) => isCamera(view) && state.cullerActive,
812
+ writes: ["culled"],
813
+ run: (ctx) => {
814
+ if (frame === state.frame) return;
815
+ frame = state.frame;
816
+ ctx.world.resource(Culler).encode(ctx, ctx.world.resource(Instances));
817
+ }
818
+ };
819
+ }
820
+ function skyNode(state) {
821
+ const data = new Float32Array(4);
822
+ const buffers = /* @__PURE__ */ new Map();
823
+ return {
824
+ kind: "render",
825
+ phase: RenderPhase.Sky,
826
+ enabled: (view) => {
827
+ const cam = cameraOf(view);
828
+ if (!cam) return false;
829
+ const entry = view.data.environment;
830
+ return (entry?.background ?? -1) >= 0 && !entry?.sky;
831
+ },
832
+ reads: ["environment"],
833
+ writes: ["scene-color", "hdr"],
834
+ color: (view) => sceneColor(view),
835
+ depth: { resource: "scene-depth", readOnly: true },
836
+ run: (ctx) => {
837
+ const cam = cameraOf(ctx.view);
838
+ const pv = state.views.get(ctx.view.name);
839
+ const envs = ctx.world.resource(Environments);
840
+ const entry = envs.cameras.get(cam.entity);
841
+ const env = entry?.environment;
842
+ if (!pv || !entry || !env || env.bakes === 0) return;
843
+ const gpu = ctx.gpu;
844
+ const module = ctx.world.resource(Shaders).module(gpu, { root: "shard::sky::background" });
845
+ if (!module) {
846
+ gpu.pipelines.skipped++;
847
+ return;
848
+ }
849
+ const bgLayout = gpu.layouts.bindGroupLayout({
850
+ label: "sky/background",
851
+ entries: [
852
+ {
853
+ binding: 0,
854
+ visibility: GPUShaderStage.FRAGMENT,
855
+ buffer: { type: "uniform" }
856
+ }
857
+ ]
858
+ });
859
+ const pipeline = gpu.pipelines.render({
860
+ label: `sky/x${cam.msaa}`,
861
+ layout: gpu.layouts.pipelineLayout({
862
+ label: "sky",
863
+ bindGroupLayouts: [state.layouts.view, bgLayout]
864
+ }),
865
+ vertex: { module, entryPoint: "vs" },
866
+ fragment: { module, entryPoint: "fs", targets: [{ format: "rgba16float" }] },
867
+ depthStencil: { format: "depth32float", depthWriteEnabled: false, depthCompare: "equal" },
868
+ multisample: { count: cam.msaa }
869
+ });
870
+ if (!pipeline) return;
871
+ let b = buffers.get(ctx.view.name);
872
+ if (!b) {
873
+ b = {
874
+ buffer: new GpuBuffer(gpu, {
875
+ label: `${ctx.view.name}/sky`,
876
+ usage: GPUBufferUsage.UNIFORM,
877
+ size: data.byteLength
878
+ }),
879
+ bindGroup: void 0,
880
+ bound: ""
881
+ };
882
+ buffers.set(ctx.view.name, b);
883
+ }
884
+ data[0] = entry.background;
885
+ b.buffer.write(data);
886
+ const key = `${gpu.generation}/${b.buffer.version}`;
887
+ if (!b.bindGroup || b.bound !== key) {
888
+ b.bindGroup = gpu.device.createBindGroup({
889
+ label: "sky/background",
890
+ layout: bgLayout,
891
+ entries: [{ binding: 0, resource: { buffer: b.buffer.buffer } }]
892
+ });
893
+ b.bound = key;
894
+ }
895
+ const pass = ctx.renderPass;
896
+ pass.setPipeline(pipeline);
897
+ pass.setBindGroup(0, viewBindGroup(gpu, ctx.world, pv, cam));
898
+ pass.setBindGroup(1, b.bindGroup);
899
+ pass.draw(3);
900
+ }
901
+ };
902
+ }
903
+ const MSAA_LOAD = [{ resource: "scene-color", resolve: "hdr" }];
904
+ const PLAIN_LOAD = [{ resource: "scene-color" }];
905
+ function sceneColor(view, clear) {
906
+ const msaa = msaaOf(view) > 1;
907
+ if (clear === void 0) return msaa ? MSAA_LOAD : PLAIN_LOAD;
908
+ return [{ resource: "scene-color", clear, resolve: msaa ? "hdr" : void 0 }];
909
+ }
910
+ function depthResolveNode() {
911
+ let bindGroups = /* @__PURE__ */ new WeakMap();
912
+ let generation = -1;
913
+ return {
914
+ kind: "render",
915
+ phase: RenderPhase.Resolve,
916
+ enabled: (view) => msaaOf(view) > 1,
917
+ reads: ["scene-depth"],
918
+ writes: ["depth"],
919
+ depth: { resource: "depth", clear: 0 },
920
+ run: (ctx) => {
921
+ const gpu = ctx.gpu;
922
+ if (generation !== gpu.generation) {
923
+ bindGroups = /* @__PURE__ */ new WeakMap();
924
+ generation = gpu.generation;
925
+ }
926
+ const module = ctx.world.resource(Shaders).module(gpu, { root: "shard::post::depth_resolve" });
927
+ const vs = ctx.world.resource(Shaders).module(gpu, { root: "shard::fullscreen" });
928
+ if (!module || !vs) return;
929
+ const layout = gpu.layouts.bindGroupLayout({
930
+ label: "depth-resolve",
931
+ entries: [
932
+ {
933
+ binding: 0,
934
+ visibility: GPUShaderStage.FRAGMENT,
935
+ texture: { sampleType: "depth", multisampled: true }
936
+ }
937
+ ]
938
+ });
939
+ const pipeline = gpu.pipelines.render({
940
+ label: "depth-resolve",
941
+ layout: gpu.layouts.pipelineLayout({ label: "depth-resolve", bindGroupLayouts: [layout] }),
942
+ vertex: { module: vs, entryPoint: "vs" },
943
+ fragment: { module, entryPoint: "fs", targets: [] },
944
+ depthStencil: { format: "depth32float", depthWriteEnabled: true, depthCompare: "always" }
945
+ });
946
+ if (!pipeline) return;
947
+ const source = ctx.texture("scene-depth");
948
+ let bg = bindGroups.get(source);
949
+ if (!bg) {
950
+ bg = gpu.device.createBindGroup({
951
+ label: "depth-resolve",
952
+ layout,
953
+ entries: [{ binding: 0, resource: source.createView() }]
954
+ });
955
+ bindGroups.set(source, bg);
956
+ }
957
+ const pass = ctx.renderPass;
958
+ pass.setPipeline(pipeline);
959
+ pass.setBindGroup(0, bg);
960
+ pass.draw(3);
961
+ }
962
+ };
963
+ }
964
+ function createLayouts(gpu, assets, store) {
965
+ const F = GPUShaderStage.FRAGMENT;
966
+ const storage = (binding) => ({
967
+ binding,
968
+ visibility: F,
969
+ buffer: { type: "read-only-storage" }
970
+ });
971
+ const depthArray = (binding) => ({
972
+ binding,
973
+ visibility: F,
974
+ texture: { sampleType: "depth", viewDimension: "2d-array" }
975
+ });
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
+ });
997
+ const C = GPUShaderStage.COMPUTE;
998
+ const cluster = gpu.layouts.bindGroupLayout({
999
+ label: "light-clusters",
1000
+ entries: [
1001
+ { binding: 0, visibility: C, buffer: { type: "uniform" } },
1002
+ { binding: 1, visibility: C, buffer: { type: "read-only-storage" } },
1003
+ { binding: 2, visibility: C, buffer: { type: "read-only-storage" } },
1004
+ { binding: 3, visibility: C, buffer: { type: "storage" } },
1005
+ { binding: 4, visibility: C, buffer: { type: "storage" } }
1006
+ ]
1007
+ });
1008
+ const shadowView = shadowViewLayout(gpu);
1009
+ return {
1010
+ view,
1011
+ pipeline: gpu.layouts.pipelineLayout({
1012
+ label: "forward",
1013
+ bindGroupLayouts: [view, assets.materialLayout, store.layout]
1014
+ }),
1015
+ cluster,
1016
+ clusterPipeline: gpu.layouts.pipelineLayout({
1017
+ label: "light-clusters",
1018
+ bindGroupLayouts: [cluster]
1019
+ }),
1020
+ shadowView,
1021
+ shadowPipeline: gpu.layouts.pipelineLayout({
1022
+ label: "shadows",
1023
+ bindGroupLayouts: [shadowView, assets.materialLayout, store.layout]
1024
+ })
1025
+ };
1026
+ }
1027
+ function ensureLayouts(gpu, state, assets, store) {
1028
+ if (state.layoutGeneration === gpu.generation) return;
1029
+ state.layoutGeneration = gpu.generation;
1030
+ state.layouts = createLayouts(gpu, assets, store);
1031
+ for (const pv of state.views.values()) {
1032
+ pv.bindGroup = void 0;
1033
+ pv.clusterBindGroup = void 0;
1034
+ }
1035
+ }
1036
+ function forwardCorePlugin(options = {}) {
1037
+ return definePlugin({
1038
+ name: "render/forward",
1039
+ provides: [
1040
+ // core
1041
+ Environments,
1042
+ DeferredPath,
1043
+ DeformPath,
1044
+ PostFeatures,
1045
+ MaterialNoise,
1046
+ MaterialAssetType,
1047
+ MaterialImporter,
1048
+ Materials,
1049
+ MeshAsset,
1050
+ Meshes,
1051
+ RenderTargets,
1052
+ STANDARD_TYPE,
1053
+ StandardMaterial,
1054
+ Camera3d,
1055
+ Exposure,
1056
+ PhysicalCamera,
1057
+ ForwardStateResource,
1058
+ Mesh3d,
1059
+ MeshMaterial,
1060
+ GpuAssetsResource,
1061
+ InstanceData,
1062
+ InstanceSlot,
1063
+ Instances,
1064
+ Lod,
1065
+ MorphWeights,
1066
+ NotShadowCaster,
1067
+ NotShadowReceiver,
1068
+ SkinnedMesh,
1069
+ VisibilityRange,
1070
+ AmbientLight,
1071
+ DirectionalLight,
1072
+ LightingSettings,
1073
+ Lights,
1074
+ PointLight,
1075
+ SpotLight,
1076
+ ComputedVisibility,
1077
+ Visibility,
1078
+ Cameras,
1079
+ RenderPath,
1080
+ Tonemapping,
1081
+ ViewSettings,
1082
+ Culler,
1083
+ ViewUniform,
1084
+ GpuMemory,
1085
+ RenderCounters,
1086
+ RenderStats,
1087
+ SkinAssetType,
1088
+ Skins,
1089
+ RenderScale,
1090
+ // shadows
1091
+ ShadowsResource,
1092
+ // post
1093
+ Antialiasing,
1094
+ AutoExposure,
1095
+ Bloom,
1096
+ ColorGrading,
1097
+ DepthOfField,
1098
+ Fog,
1099
+ MotionBlur,
1100
+ Ssao,
1101
+ Vignette,
1102
+ // pixel-perfect
1103
+ PixelPerfect,
1104
+ PixelPerfectPath,
1105
+ PixelTargets
1106
+ ],
1107
+ dependencies: ["render", "core/transform"],
1108
+ build(app) {
1109
+ const w = app.world;
1110
+ w.initResource(Meshes);
1111
+ w.initResource(Skins);
1112
+ w.initResource(Materials);
1113
+ w.initResource(Textures);
1114
+ w.initResource(RenderTargets);
1115
+ w.initResource(AmbientLight);
1116
+ w.initResource(RenderStats);
1117
+ w.initResource(Cameras);
1118
+ w.initResource(LightingSettings);
1119
+ w.initResource(Environments);
1120
+ w.initResource(ViewSettings).msaa = options.msaa ?? 4;
1121
+ Object.assign(w.initResource(RenderScale), options.renderScale);
1122
+ observeInstanceRemovals(w);
1123
+ observeLightRemovals(w);
1124
+ observeOriginShifts(w);
1125
+ app.addSystems(PostUpdate, computeVisibility.after(TransformSystems), applyPhysicalCameras).addSystems(
1126
+ Last,
1127
+ extractCameras.inSet(RenderSet.Extract),
1128
+ extractLights.inSet(RenderSet.Extract),
1129
+ prepareInstances.inSet(RenderSet.Prepare),
1130
+ prepareLights.inSet(RenderSet.Prepare),
1131
+ queue.inSet(RenderSet.Queue),
1132
+ upload.inSet(RenderSet.Upload)
1133
+ );
1134
+ },
1135
+ ready(app) {
1136
+ 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)
1154
+ });
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);
1192
+ }
1193
+ });
1194
+ }
1195
+ function gbufferEmissiveFormat(gpu) {
1196
+ return gpu.features.has("rg11b10ufloat-renderable") ? "rg11b10ufloat" : "rgba16float";
1197
+ }
1198
+ export {
1199
+ ForwardStateResource,
1200
+ Mesh3d,
1201
+ MeshMaterial,
1202
+ PASS_GBUFFER,
1203
+ PASS_OPAQUE,
1204
+ PASS_PICK,
1205
+ PASS_PREPASS,
1206
+ PASS_TRANSPARENT,
1207
+ PICK_TARGETS,
1208
+ drawMaterials,
1209
+ forwardCorePlugin,
1210
+ gbufferEmissiveFormat,
1211
+ isCamera,
1212
+ sceneColor,
1213
+ skyNode,
1214
+ upload,
1215
+ viewBindGroup
1216
+ };