@driftengine/core 4.8.7 → 4.9.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 (163) hide show
  1. package/CHANGELOG.json +61 -0
  2. package/README.md +1 -1
  3. package/dist/index.d.ts +5 -0
  4. package/dist/index.js +7 -0
  5. package/dist/render/atmosphere.d.ts +13 -0
  6. package/dist/render/atmosphere.js +9 -1
  7. package/dist/render/backend/api.d.ts +9 -1
  8. package/dist/render/backend/createRenderer.d.ts +6 -0
  9. package/dist/render/backend/createRenderer.js +4 -1
  10. package/dist/render/backend/webgl2/renderer.d.ts +90 -2
  11. package/dist/render/backend/webgl2/renderer.js +297 -16
  12. package/dist/render/backend/webgpu/device.d.ts +7 -1
  13. package/dist/render/backend/webgpu/device.js +31 -3
  14. package/dist/render/backend/webgpu/flatPass.d.ts +16 -1
  15. package/dist/render/backend/webgpu/flatPass.js +58 -26
  16. package/dist/render/backend/webgpu/postPass.d.ts +51 -9
  17. package/dist/render/backend/webgpu/postPass.js +16 -2
  18. package/dist/render/backend/webgpu/reflectionSurfacePass.d.ts +30 -0
  19. package/dist/render/backend/webgpu/reflectionSurfacePass.js +100 -0
  20. package/dist/render/backend/webgpu/renderer.d.ts +131 -2
  21. package/dist/render/backend/webgpu/renderer.js +586 -70
  22. package/dist/render/displayRange.d.ts +31 -0
  23. package/dist/render/displayRange.js +47 -0
  24. package/dist/render/filmicCurve.d.ts +74 -0
  25. package/dist/render/filmicCurve.js +203 -0
  26. package/dist/render/fog.d.ts +4 -1
  27. package/dist/render/fog.js +12 -2
  28. package/dist/render/frame/resources.d.ts +2 -2
  29. package/dist/render/frame/resources.js +6 -0
  30. package/dist/render/frameReflections.d.ts +40 -0
  31. package/dist/render/frameReflections.js +48 -0
  32. package/dist/render/pass.d.ts +49 -0
  33. package/dist/render/reflectionSurfaceTargets.d.ts +28 -0
  34. package/dist/render/reflectionSurfaceTargets.js +99 -0
  35. package/dist/render/renderQuality.d.ts +39 -8
  36. package/dist/render/renderQuality.js +4 -0
  37. package/dist/render/sceneTarget.d.ts +5 -0
  38. package/dist/render/sceneTarget.js +24 -1
  39. package/dist/render/screenSpaceReflection.d.ts +5 -0
  40. package/dist/render/screenSpaceReflection.js +5 -0
  41. package/dist/render/shaders/arcane.d.ts +1 -1
  42. package/dist/render/shaders/bolt.d.ts +1 -1
  43. package/dist/render/shaders/caustics.d.ts +1 -1
  44. package/dist/render/shaders/film.d.ts +1 -1
  45. package/dist/render/shaders/filmLook.js +2 -1
  46. package/dist/render/shaders/fire.d.ts +1 -1
  47. package/dist/render/shaders/flat/glassTint.d.ts +1 -1
  48. package/dist/render/shaders/flat/glassTint.js +2 -0
  49. package/dist/render/shaders/flat/index.d.ts +10 -0
  50. package/dist/render/shaders/flat/index.js +6 -0
  51. package/dist/render/shaders/flat/main.d.ts +1 -1
  52. package/dist/render/shaders/flat/main.js +28 -0
  53. package/dist/render/shaders/flat/overlay.d.ts +16 -0
  54. package/dist/render/shaders/flat/overlay.js +117 -0
  55. package/dist/render/shaders/flat/pointShadow.js +1 -1
  56. package/dist/render/shaders/flat/reflectionSurface.d.ts +21 -0
  57. package/dist/render/shaders/flat/reflectionSurface.js +61 -0
  58. package/dist/render/shaders/fog.d.ts +1 -1
  59. package/dist/render/shaders/fog.js +9 -2
  60. package/dist/render/shaders/fringe.d.ts +29 -0
  61. package/dist/render/shaders/fringe.js +55 -0
  62. package/dist/render/shaders/generated/arcane.wgsl.d.ts +1 -1
  63. package/dist/render/shaders/generated/arcane.wgsl.js +1 -1
  64. package/dist/render/shaders/generated/bolt.wgsl.d.ts +1 -1
  65. package/dist/render/shaders/generated/bolt.wgsl.js +1 -1
  66. package/dist/render/shaders/generated/caustics.wgsl.d.ts +1 -1
  67. package/dist/render/shaders/generated/caustics.wgsl.js +1 -1
  68. package/dist/render/shaders/generated/film.wgsl.d.ts +1 -1
  69. package/dist/render/shaders/generated/film.wgsl.js +1 -1
  70. package/dist/render/shaders/generated/fire.wgsl.d.ts +1 -1
  71. package/dist/render/shaders/generated/fire.wgsl.js +1 -1
  72. package/dist/render/shaders/generated/flat.wgsl.d.ts +176 -16
  73. package/dist/render/shaders/generated/flat.wgsl.js +214 -54
  74. package/dist/render/shaders/generated/line.wgsl.d.ts +1 -1
  75. package/dist/render/shaders/generated/line.wgsl.js +1 -1
  76. package/dist/render/shaders/generated/particle.wgsl.d.ts +4 -4
  77. package/dist/render/shaders/generated/particle.wgsl.js +4 -4
  78. package/dist/render/shaders/generated/rush.wgsl.d.ts +56 -26
  79. package/dist/render/shaders/generated/rush.wgsl.js +56 -26
  80. package/dist/render/shaders/generated/scatter.wgsl.d.ts +1 -1
  81. package/dist/render/shaders/generated/scatter.wgsl.js +1 -1
  82. package/dist/render/shaders/generated/smoke.wgsl.d.ts +1 -1
  83. package/dist/render/shaders/generated/smoke.wgsl.js +1 -1
  84. package/dist/render/shaders/generated/ssrResolve.wgsl.d.ts +29 -0
  85. package/dist/render/shaders/generated/ssrResolve.wgsl.js +29 -0
  86. package/dist/render/shaders/generated/ssrTrace.wgsl.d.ts +22 -2
  87. package/dist/render/shaders/generated/ssrTrace.wgsl.js +22 -2
  88. package/dist/render/shaders/generated/water.wgsl.d.ts +1 -1
  89. package/dist/render/shaders/generated/water.wgsl.js +1 -1
  90. package/dist/render/shaders/gpudriven/fog.wgsl.d.ts +2 -2
  91. package/dist/render/shaders/gpudriven/fog.wgsl.js +11 -3
  92. package/dist/render/shaders/line.d.ts +1 -1
  93. package/dist/render/shaders/particle.d.ts +3 -3
  94. package/dist/render/shaders/rush.js +54 -11
  95. package/dist/render/shaders/scatter.d.ts +1 -1
  96. package/dist/render/shaders/smoke.d.ts +1 -1
  97. package/dist/render/shaders/ssrResolve.d.ts +17 -0
  98. package/dist/render/shaders/ssrResolve.js +51 -0
  99. package/dist/render/shaders/ssrTrace.js +26 -0
  100. package/dist/render/shaders/water.d.ts +1 -1
  101. package/dist/render/ssrPass.d.ts +16 -0
  102. package/dist/render/ssrPass.js +106 -1
  103. package/dist/render/surfaceOverlay.d.ts +132 -0
  104. package/dist/render/surfaceOverlay.js +85 -0
  105. package/dist/render/surfaceTexture.d.ts +19 -0
  106. package/dist/render/surfaceTexture.js +69 -0
  107. package/dist/render/temporalAa.d.ts +14 -0
  108. package/dist/render/temporalAa.js +20 -0
  109. package/dist/render/vertexDefaults.d.ts +17 -0
  110. package/dist/render/vertexDefaults.js +24 -0
  111. package/package.json +6 -6
  112. package/src/index.ts +16 -0
  113. package/src/render/atmosphere.ts +22 -1
  114. package/src/render/backend/api.ts +9 -0
  115. package/src/render/backend/createRenderer.ts +18 -1
  116. package/src/render/backend/webgl2/renderer.ts +357 -13
  117. package/src/render/backend/webgpu/device.ts +42 -3
  118. package/src/render/backend/webgpu/flatPass.ts +63 -26
  119. package/src/render/backend/webgpu/postPass.ts +18 -0
  120. package/src/render/backend/webgpu/reflectionSurfacePass.ts +114 -0
  121. package/src/render/backend/webgpu/renderer.ts +735 -83
  122. package/src/render/displayRange.ts +61 -0
  123. package/src/render/filmicCurve.ts +229 -0
  124. package/src/render/fog.ts +19 -3
  125. package/src/render/frame/resources.ts +6 -0
  126. package/src/render/frameReflections.ts +72 -0
  127. package/src/render/pass.ts +53 -1
  128. package/src/render/reflectionSurfaceTargets.ts +105 -0
  129. package/src/render/renderQuality.ts +44 -8
  130. package/src/render/sceneTarget.ts +30 -2
  131. package/src/render/screenSpaceReflection.ts +5 -0
  132. package/src/render/shaders/filmLook.ts +2 -1
  133. package/src/render/shaders/flat/glassTint.ts +5 -1
  134. package/src/render/shaders/flat/index.ts +16 -0
  135. package/src/render/shaders/flat/main.ts +28 -0
  136. package/src/render/shaders/flat/overlay.ts +117 -0
  137. package/src/render/shaders/flat/pointShadow.ts +1 -1
  138. package/src/render/shaders/flat/reflectionSurface.ts +61 -0
  139. package/src/render/shaders/fog.ts +9 -2
  140. package/src/render/shaders/fringe.ts +59 -0
  141. package/src/render/shaders/generated/arcane.wgsl.ts +1 -1
  142. package/src/render/shaders/generated/bolt.wgsl.ts +1 -1
  143. package/src/render/shaders/generated/caustics.wgsl.ts +1 -1
  144. package/src/render/shaders/generated/film.wgsl.ts +1 -1
  145. package/src/render/shaders/generated/fire.wgsl.ts +1 -1
  146. package/src/render/shaders/generated/flat.wgsl.ts +214 -54
  147. package/src/render/shaders/generated/line.wgsl.ts +1 -1
  148. package/src/render/shaders/generated/particle.wgsl.ts +4 -4
  149. package/src/render/shaders/generated/rush.wgsl.ts +56 -26
  150. package/src/render/shaders/generated/scatter.wgsl.ts +1 -1
  151. package/src/render/shaders/generated/smoke.wgsl.ts +1 -1
  152. package/src/render/shaders/generated/ssrResolve.wgsl.ts +30 -0
  153. package/src/render/shaders/generated/ssrTrace.wgsl.ts +22 -2
  154. package/src/render/shaders/generated/water.wgsl.ts +1 -1
  155. package/src/render/shaders/gpudriven/fog.wgsl.ts +11 -3
  156. package/src/render/shaders/rush.ts +54 -11
  157. package/src/render/shaders/ssrResolve.ts +52 -0
  158. package/src/render/shaders/ssrTrace.ts +26 -0
  159. package/src/render/ssrPass.ts +138 -1
  160. package/src/render/surfaceOverlay.ts +195 -0
  161. package/src/render/surfaceTexture.ts +67 -0
  162. package/src/render/temporalAa.ts +20 -0
  163. package/src/render/vertexDefaults.ts +28 -0
@@ -6,6 +6,11 @@
6
6
  * split matters because these two fail for unrelated reasons: a device is lost by a driver
7
7
  * reset the page did not cause, and a frame is wrong because of something the frame did.
8
8
  */
9
+ /** A canvas nobody asked to extend. */
10
+ const STANDARD_RANGE = {
11
+ range: 'standard',
12
+ reason: 'highDynamicRange was not asked for',
13
+ };
9
14
  /**
10
15
  * Take a canvas and a device, and hand back the surface the renderer draws into.
11
16
  *
@@ -25,12 +30,23 @@
25
30
  * consumer its GPU had failed every time it unmounted a canvas on purpose, so a disposed
26
31
  * surface stays silent.
27
32
  */
28
- export function createGpuSurface(canvas, device) {
33
+ export function createGpuSurface(canvas, device,
34
+ /** The range the profile and the display allow (`chooseDisplayRange`); standard unless given. */
35
+ wanted = STANDARD_RANGE) {
29
36
  const context = canvas.getContext('webgpu');
30
37
  if (context === null) {
31
38
  throw new Error('createGpuSurface: the canvas would not give a webgpu context');
32
39
  }
33
- const format = navigator.gpu.getPreferredCanvasFormat();
40
+ /*
41
+ * **A high range is half floats with extended tone mapping**: a value of 1 is the display's
42
+ * paper white, and what lies above it is shown up to the display's peak instead of clipped. The
43
+ * colour space stays sRGB, so the composite's encoding is the one it always wrote, carried past 1.
44
+ */
45
+ const extended = wanted.range === 'high';
46
+ const format = extended
47
+ ? 'rgba16float'
48
+ : navigator.gpu.getPreferredCanvasFormat();
49
+ let range = wanted;
34
50
  const listeners = [];
35
51
  let lost = false;
36
52
  let disposed = false;
@@ -89,14 +105,26 @@ export function createGpuSurface(canvas, device) {
89
105
  * has no use for a transparent canvas, and the opaque path is the one a compositor can
90
106
  * take without a blend.
91
107
  */
92
- context.configure({ device, format, alphaMode: 'opaque' });
108
+ context.configure(extended
109
+ ? { device, format, alphaMode: 'opaque', toneMapping: { mode: 'extended' } }
110
+ : { device, format, alphaMode: 'opaque' });
93
111
  };
94
112
  configure(canvas.width, canvas.height);
113
+ /*
114
+ * **Read back, because a browser that does not know the option ignores it** and draws the
115
+ * standard range from an extended-range frame, clipped at paper white, which is the frame a game
116
+ * would grade for the standard range if it knew. So it is told.
117
+ */
118
+ if (extended && context.getConfiguration?.()?.toneMapping?.mode !== 'extended') {
119
+ range = { range: 'standard', reason: 'this browser has no extended-range canvas' };
120
+ }
95
121
  return {
96
122
  device,
97
123
  context,
98
124
  canvas,
99
125
  format,
126
+ displayRange: range.range,
127
+ displayRangeReason: range.reason,
100
128
  get lost() {
101
129
  return lost;
102
130
  },
@@ -147,6 +147,16 @@ export declare const FLAT_VERT_FIELDS: {
147
147
  * 144 bytes a slot and is what a single ring costs.
148
148
  */
149
149
  export declare const FLAT_VERT_SIZE: number;
150
+ /**
151
+ * The most sampled textures and distinct samplers any lit variant carrying the environment probe
152
+ * binds in its fragment stage, counted from the generated bindings: what a device must allow per
153
+ * stage before the probe's permutation can be built. A sampler several textures share counts once,
154
+ * which is what `// wgsl:share` buys.
155
+ */
156
+ export declare function probeStageCeilings(): {
157
+ readonly textures: number;
158
+ readonly samplers: number;
159
+ };
150
160
  /** Everything the generator recorded about one permutation of the fragment stage. */
151
161
  export declare function flatFragmentBindings(variant: FlatVariant): StageBindings;
152
162
  /** The depth format both backends compare with. */
@@ -236,7 +246,12 @@ cloth?: boolean,
236
246
  /** Which shading model its fragment stage shades by, null the standard. The key must carry it. */
237
247
  model?: SurfaceModelKind | null,
238
248
  /** Which half of a skin it draws. The key must carry it: `|ss`, `|sd` or `|sa`. See `SkinHalf`. */
239
- skinPass?: SkinHalf): GPURenderPipeline;
249
+ skinPass?: SkinHalf,
250
+ /**
251
+ * The reflection pass's surface half: the draw again, against its own depth, into the two
252
+ * targets `reflectionSurfacePass.ts` holds. The key must carry it: `|rs`.
253
+ */
254
+ reflectionSurface?: boolean): GPURenderPipeline;
240
255
  /**
241
256
  * The same pipeline, compiled off the main thread.
242
257
  *
@@ -5,6 +5,7 @@ import { FLAT_BINDINGS, FLAT_FRAG_WGSL, FLAT_VERT_WGSL, } from '../../shaders/ge
5
5
  import { vertexBufferLayouts } from './buffers.js';
6
6
  import { shaderModule } from './shaderModules.js';
7
7
  import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.js';
8
+ import { REFLECTION_SURFACE_FORMAT } from './reflectionSurfacePass.js';
8
9
  /** The material maps the flat pass reads: `2d-array` colour, filtered, not shadow maps. */
9
10
  const SURFACE_MAPS = new Set([
10
11
  'uAlbedo',
@@ -144,6 +145,24 @@ export const FLAT_VERT_FIELDS = FLAT_BINDINGS.flatVert.none.fields;
144
145
  */
145
146
  export const FLAT_VERT_SIZE = Math.max(...Object.values(VERTEX_BINDINGS).map((bindings) => bindings.uniformSize));
146
147
  const FLAT_VERT_BINDING = PLAIN_VERTEX.uniforms;
148
+ /**
149
+ * The most sampled textures and distinct samplers any lit variant carrying the environment probe
150
+ * binds in its fragment stage, counted from the generated bindings: what a device must allow per
151
+ * stage before the probe's permutation can be built. A sampler several textures share counts once,
152
+ * which is what `// wgsl:share` buys.
153
+ */
154
+ export function probeStageCeilings() {
155
+ let textures = 0;
156
+ let samplers = 0;
157
+ for (const [variant, bindings] of Object.entries(FLAT_BINDINGS.flatFrag)) {
158
+ if (!variant.includes('environmentProbe'))
159
+ continue;
160
+ const listed = Object.values(bindings.textures ?? {});
161
+ textures = Math.max(textures, listed.length);
162
+ samplers = Math.max(samplers, new Set(listed.map((entry) => entry.sampler)).size);
163
+ }
164
+ return { textures, samplers };
165
+ }
147
166
  /** Everything the generator recorded about one permutation of the fragment stage. */
148
167
  export function flatFragmentBindings(variant) {
149
168
  const found = FRAGMENT_BINDINGS[variant];
@@ -160,7 +179,7 @@ export function flatFragmentBindings(variant) {
160
179
  * "GLASS_SHADOWS" not found*, and the frame drew nothing. Every override the variant declares is
161
180
  * set, so a switch added to the shader and not to the cache is refused here rather than defaulted.
162
181
  */
163
- function flatFragmentConstants(variant, cache, model, skinPass = 'whole') {
182
+ function flatFragmentConstants(variant, cache, model, skinPass = 'whole', reflectionSurface = false) {
164
183
  const overrides = flatFragmentBindings(variant).overrides;
165
184
  if (overrides?.['GLASS_SHADOWS'] === undefined) {
166
185
  throw new Error(`flatPass: variant "${variant}" has no glass switch`);
@@ -178,6 +197,11 @@ function flatFragmentConstants(variant, cache, model, skinPass = 'whole') {
178
197
  constants[String(id)] = skinPass !== 'whole' && name === SKIN_HALF_SWITCH[skinPass] ? 1 : 0;
179
198
  continue;
180
199
  }
200
+ /* The reflection pass's surface half, set from the key too. See `reflectionSurface.ts`. */
201
+ if (name === 'REFLECTION_SURFACE') {
202
+ constants[String(id)] = reflectionSurface ? 1 : 0;
203
+ continue;
204
+ }
181
205
  if (!(name in cache.litSwitches)) {
182
206
  throw new Error(`flatPass: variant "${variant}" declares a switch "${name}" no cache sets`);
183
207
  }
@@ -481,8 +505,13 @@ cloth = false,
481
505
  /** Which shading model its fragment stage shades by, null the standard. The key must carry it. */
482
506
  model = null,
483
507
  /** Which half of a skin it draws. The key must carry it: `|ss`, `|sd` or `|sa`. See `SkinHalf`. */
484
- skinPass = 'whole') {
485
- return cache.get(key, () => flatDescriptor(cache, device, layout, variant, key, present, translucent, skinned, morphed, depthWrite, depthLayer, instanced, oit, doubleSided, alphaToCoverage, skinEight, cloth, model, skinPass), true);
508
+ skinPass = 'whole',
509
+ /**
510
+ * The reflection pass's surface half: the draw again, against its own depth, into the two
511
+ * targets `reflectionSurfacePass.ts` holds. The key must carry it: `|rs`.
512
+ */
513
+ reflectionSurface = false) {
514
+ return cache.get(key, () => flatDescriptor(cache, device, layout, variant, key, present, translucent, skinned, morphed, depthWrite, depthLayer, instanced, oit, doubleSided, alphaToCoverage, skinEight, cloth, model, skinPass, reflectionSurface), true);
486
515
  }
487
516
  /**
488
517
  * The same pipeline, compiled off the main thread.
@@ -562,10 +591,11 @@ oit = 'none',
562
591
  /** Culls nothing: a surface seen from both faces. */
563
592
  doubleSided = false, alphaToCoverage = false, skinEight = false,
564
593
  /** Whether its vertex stage places vertices by a cloth binding. See `clothBinding.ts`. */
565
- cloth = false, model = null, skinPass = 'whole') {
594
+ cloth = false, model = null, skinPass = 'whole', reflectionSurface = false) {
566
595
  const offset = depthOffsetForLayer(depthLayer);
567
- /* The diffuse and colour halves are the same surface again, into the blur's targets. */
568
- const diffuseAlone = skinPass === 'diffuse' || skinPass === 'albedo';
596
+ /* The diffuse and colour halves are the same surface again, into the blur's targets; and so is
597
+ the reflection pass's surface half, into its two. */
598
+ const diffuseAlone = skinPass === 'diffuse' || skinPass === 'albedo' || reflectionSurface;
569
599
  return {
570
600
  label: key,
571
601
  layout: device.createPipelineLayout({ bindGroupLayouts: [layout] }),
@@ -590,31 +620,33 @@ cloth = false, model = null, skinPass = 'whole') {
590
620
  }),
591
621
  entryPoint: 'main',
592
622
  /* Every variant declares it, so every lit pipeline may set it: see `PipelineCache`. */
593
- constants: flatFragmentConstants(variant, cache, model, skinPass),
594
- targets: [
595
- oitTarget(oit) ??
596
- (translucent === 'additive'
597
- ? {
598
- format: cache.format,
599
- blend: {
600
- color: { srcFactor: 'src-alpha', dstFactor: 'one', operation: 'add' },
601
- alpha: { srcFactor: 'zero', dstFactor: 'one', operation: 'add' },
602
- },
603
- }
604
- : translucent
623
+ constants: flatFragmentConstants(variant, cache, model, skinPass, reflectionSurface),
624
+ targets: reflectionSurface
625
+ ? [null, { format: REFLECTION_SURFACE_FORMAT }, { format: REFLECTION_SURFACE_FORMAT }]
626
+ : [
627
+ oitTarget(oit) ??
628
+ (translucent === 'additive'
605
629
  ? {
606
630
  format: cache.format,
607
631
  blend: {
608
- color: {
609
- srcFactor: 'src-alpha',
610
- dstFactor: 'one-minus-src-alpha',
611
- operation: 'add',
612
- },
613
- alpha: SCENE_ALPHA_COVERS,
632
+ color: { srcFactor: 'src-alpha', dstFactor: 'one', operation: 'add' },
633
+ alpha: { srcFactor: 'zero', dstFactor: 'one', operation: 'add' },
614
634
  },
615
635
  }
616
- : { format: cache.format }),
617
- ],
636
+ : translucent
637
+ ? {
638
+ format: cache.format,
639
+ blend: {
640
+ color: {
641
+ srcFactor: 'src-alpha',
642
+ dstFactor: 'one-minus-src-alpha',
643
+ operation: 'add',
644
+ },
645
+ alpha: SCENE_ALPHA_COVERS,
646
+ },
647
+ }
648
+ : { format: cache.format }),
649
+ ],
618
650
  },
619
651
  primitive: { topology: 'triangle-list', cullMode: doubleSided ? 'none' : 'back' },
620
652
  /* Coverage only where a sample count can carry it: WebGPU refuses it at one sample. */
@@ -5,7 +5,7 @@ import { TEMPORAL_RESOLVE_FRAG_WGSL } from '../../shaders/generated/temporalReso
5
5
  import { OIT_RESOLVE_FRAG_WGSL } from '../../shaders/generated/oitResolve.wgsl.ts';
6
6
  import { DECAL_PROJECT_FRAG_WGSL } from '../../shaders/generated/decalProject.wgsl.ts';
7
7
  import { SSR_TRACE_FRAG_WGSL } from '../../shaders/generated/ssrTrace.wgsl.ts';
8
- import { SSR_RESOLVE_FRAG_WGSL } from '../../shaders/generated/ssrResolve.wgsl.ts';
8
+ import { SSR_MATERIAL_RESOLVE_FRAG_WGSL, SSR_RESOLVE_FRAG_WGSL } from '../../shaders/generated/ssrResolve.wgsl.ts';
9
9
  import { MEDIUM_FRAG_WGSL, MEDIUM_UPSAMPLE_FRAG_WGSL } from '../../shaders/generated/globalMedium.wgsl.ts';
10
10
  import type { UniformFields } from './scatterPass.ts';
11
11
  import type { PipelineCache } from './pipelineCache.ts';
@@ -26,7 +26,7 @@ import type { PipelineCache } from './pipelineCache.ts';
26
26
  * float target the generated shaders can sample as an ordinary texture.
27
27
  */
28
28
  export declare const RUSH_FRAG_FIELDS: UniformFields;
29
- export declare const RUSH_FRAG_SIZE: 208;
29
+ export declare const RUSH_FRAG_SIZE: 272;
30
30
  declare const RUSH_UNIFORMS: 1;
31
31
  declare const RUSH_TEXTURES: {
32
32
  readonly uScene: {
@@ -39,29 +39,34 @@ declare const RUSH_TEXTURES: {
39
39
  readonly sampler: 35;
40
40
  readonly type: "sampler2D";
41
41
  };
42
- readonly uAo: {
42
+ readonly uMotion: {
43
43
  readonly texture: 36;
44
44
  readonly sampler: 37;
45
45
  readonly type: "sampler2D";
46
46
  };
47
- readonly uBloom: {
47
+ readonly uAo: {
48
48
  readonly texture: 38;
49
49
  readonly sampler: 39;
50
50
  readonly type: "sampler2D";
51
51
  };
52
- readonly uExposureHeld: {
52
+ readonly uBloom: {
53
53
  readonly texture: 40;
54
54
  readonly sampler: 41;
55
55
  readonly type: "sampler2D";
56
56
  };
57
- readonly uExposureLocal: {
57
+ readonly uExposureHeld: {
58
58
  readonly texture: 42;
59
59
  readonly sampler: 43;
60
60
  readonly type: "sampler2D";
61
61
  };
62
- readonly uGradeLut: {
62
+ readonly uExposureLocal: {
63
63
  readonly texture: 44;
64
64
  readonly sampler: 45;
65
+ readonly type: "sampler2D";
66
+ };
67
+ readonly uGradeLut: {
68
+ readonly texture: 46;
69
+ readonly sampler: 47;
65
70
  readonly type: "sampler3D";
66
71
  };
67
72
  };
@@ -180,6 +185,12 @@ export declare const PREMULTIPLIED_OVER_BLEND: GPUBlendState;
180
185
  * the reflection's own and its alpha is the coverage this resolve reads.
181
186
  */
182
187
  export declare const REFLECTION_OVER_BLEND: GPUBlendState;
188
+ /**
189
+ * The frame's materials' reflection, added: the found colour times the tint, less the probe's share,
190
+ * which is negative where the probe was brighter than what replaced it. A half-float scene takes
191
+ * the negative addend as the subtraction it is. Its alpha kept.
192
+ */
193
+ export declare const FRAME_REFLECTION_BLEND: GPUBlendState;
183
194
  /**
184
195
  * The bloom pyramid's way back up: an octave added onto a level that the blend multiplies by that
185
196
  * level's tint first — the blend constant, which white makes `one, one` exactly. Into the pyramid's
@@ -276,7 +287,7 @@ export declare const DECAL_TEXTURES: {
276
287
  * commands, so one block rewritten between two passes gives both passes the last write.
277
288
  */
278
289
  export declare const SSR_FRAG_FIELDS: UniformFields;
279
- export declare const SSR_FRAG_SIZE: 272;
290
+ export declare const SSR_FRAG_SIZE: 288;
280
291
  export declare const SSR_UNIFORMS: 1;
281
292
  export declare const SSR_TEXTURES: {
282
293
  readonly uSsrDepth: {
@@ -289,6 +300,16 @@ export declare const SSR_TEXTURES: {
289
300
  readonly sampler: 35;
290
301
  readonly type: "sampler2D";
291
302
  };
303
+ readonly uSsrProbeMap: {
304
+ readonly texture: 36;
305
+ readonly sampler: 37;
306
+ readonly type: "sampler2D";
307
+ };
308
+ readonly uSsrTintMap: {
309
+ readonly texture: 38;
310
+ readonly sampler: 39;
311
+ readonly type: "sampler2D";
312
+ };
292
313
  };
293
314
  export declare const SSR_RESOLVE_TEXTURES: {
294
315
  readonly uSsrReflection: {
@@ -297,6 +318,27 @@ export declare const SSR_RESOLVE_TEXTURES: {
297
318
  readonly type: "sampler2D";
298
319
  };
299
320
  };
321
+ /** The material resolve's block — the blur — and its three inputs. See `frameReflections.ts`. */
322
+ export declare const SSR_MATERIAL_FIELDS: UniformFields;
323
+ export declare const SSR_MATERIAL_SIZE: 16;
324
+ export declare const SSR_MATERIAL_UNIFORMS: 1;
325
+ export declare const SSR_MATERIAL_TEXTURES: {
326
+ readonly uSsrReflection: {
327
+ readonly texture: 32;
328
+ readonly sampler: 33;
329
+ readonly type: "sampler2D";
330
+ };
331
+ readonly uSsrProbeMap: {
332
+ readonly texture: 34;
333
+ readonly sampler: 35;
334
+ readonly type: "sampler2D";
335
+ };
336
+ readonly uSsrTintMap: {
337
+ readonly texture: 36;
338
+ readonly sampler: 37;
339
+ readonly type: "sampler2D";
340
+ };
341
+ };
300
342
  /**
301
343
  * The resolve's layout: one texture and no uniform block.
302
344
  *
@@ -341,7 +383,7 @@ export declare const MEDIUM_UPSAMPLE_TEXTURES: {
341
383
  readonly type: "sampler2D";
342
384
  };
343
385
  };
344
- export { RUSH_FRAG_WGSL, AO_FRAG_WGSL, AO_BLUR_FRAG_WGSL, TEMPORAL_RESOLVE_FRAG_WGSL, OIT_RESOLVE_FRAG_WGSL, DECAL_PROJECT_FRAG_WGSL, SSR_TRACE_FRAG_WGSL, SSR_RESOLVE_FRAG_WGSL, };
386
+ export { RUSH_FRAG_WGSL, AO_FRAG_WGSL, AO_BLUR_FRAG_WGSL, TEMPORAL_RESOLVE_FRAG_WGSL, OIT_RESOLVE_FRAG_WGSL, DECAL_PROJECT_FRAG_WGSL, SSR_TRACE_FRAG_WGSL, SSR_RESOLVE_FRAG_WGSL, SSR_MATERIAL_RESOLVE_FRAG_WGSL, };
345
387
  export { BLOOM_PREFILTER_FRAG_WGSL, BLOOM_DOWNSAMPLE_FRAG_WGSL, BLOOM_UPSAMPLE_FRAG_WGSL };
346
388
  export { RUSH_UNIFORMS, RUSH_TEXTURES };
347
389
  export declare const AO_UNIFORMS: 1;
@@ -6,7 +6,7 @@ import { TEMPORALRESOLVE_BINDINGS as TAA_BINDINGS, TEMPORAL_RESOLVE_FRAG_WGSL, }
6
6
  import { OITRESOLVE_BINDINGS as OIT_BINDINGS, OIT_RESOLVE_FRAG_WGSL, } from '../../shaders/generated/oitResolve.wgsl.js';
7
7
  import { DECALPROJECT_BINDINGS as DECAL_BINDINGS, DECAL_PROJECT_FRAG_WGSL, } from '../../shaders/generated/decalProject.wgsl.js';
8
8
  import { SSRTRACE_BINDINGS as SSR_BINDINGS, SSR_TRACE_FRAG_WGSL, } from '../../shaders/generated/ssrTrace.wgsl.js';
9
- import { SSRRESOLVE_BINDINGS as SSR_RESOLVE_BINDINGS, SSR_RESOLVE_FRAG_WGSL, } from '../../shaders/generated/ssrResolve.wgsl.js';
9
+ import { SSRRESOLVE_BINDINGS as SSR_RESOLVE_BINDINGS, SSR_MATERIAL_RESOLVE_FRAG_WGSL, SSR_RESOLVE_FRAG_WGSL, } from '../../shaders/generated/ssrResolve.wgsl.js';
10
10
  import { GLOBALMEDIUM_BINDINGS as MEDIUM_BINDINGS, MEDIUM_FRAG_WGSL, MEDIUM_UPSAMPLE_FRAG_WGSL, } from '../../shaders/generated/globalMedium.wgsl.js';
11
11
  import { shaderModule } from './shaderModules.js';
12
12
  import { SCENE_ALPHA_KEEPS, SCENE_ALPHA_TRANSMITS } from '../../sceneCoverage.js';
@@ -252,6 +252,15 @@ export const REFLECTION_OVER_BLEND = {
252
252
  color: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
253
253
  alpha: SCENE_ALPHA_KEEPS,
254
254
  };
255
+ /**
256
+ * The frame's materials' reflection, added: the found colour times the tint, less the probe's share,
257
+ * which is negative where the probe was brighter than what replaced it. A half-float scene takes
258
+ * the negative addend as the subtraction it is. Its alpha kept.
259
+ */
260
+ export const FRAME_REFLECTION_BLEND = {
261
+ color: { srcFactor: 'one', dstFactor: 'one', operation: 'add' },
262
+ alpha: SCENE_ALPHA_KEEPS,
263
+ };
255
264
  /**
256
265
  * The bloom pyramid's way back up: an octave added onto a level that the blend multiplies by that
257
266
  * level's tint first — the blend constant, which white makes `one, one` exactly. Into the pyramid's
@@ -340,6 +349,11 @@ export const SSR_FRAG_SIZE = SSR_BINDINGS.SSR_TRACE_FRAG.uniformSize;
340
349
  export const SSR_UNIFORMS = SSR_BINDINGS.SSR_TRACE_FRAG.uniforms;
341
350
  export const SSR_TEXTURES = SSR_BINDINGS.SSR_TRACE_FRAG.textures;
342
351
  export const SSR_RESOLVE_TEXTURES = SSR_RESOLVE_BINDINGS.SSR_RESOLVE_FRAG.textures;
352
+ /** The material resolve's block — the blur — and its three inputs. See `frameReflections.ts`. */
353
+ export const SSR_MATERIAL_FIELDS = SSR_RESOLVE_BINDINGS.SSR_MATERIAL_RESOLVE_FRAG.fields;
354
+ export const SSR_MATERIAL_SIZE = SSR_RESOLVE_BINDINGS.SSR_MATERIAL_RESOLVE_FRAG.uniformSize;
355
+ export const SSR_MATERIAL_UNIFORMS = SSR_RESOLVE_BINDINGS.SSR_MATERIAL_RESOLVE_FRAG.uniforms;
356
+ export const SSR_MATERIAL_TEXTURES = SSR_RESOLVE_BINDINGS.SSR_MATERIAL_RESOLVE_FRAG.textures;
343
357
  /**
344
358
  * The resolve's layout: one texture and no uniform block.
345
359
  *
@@ -390,7 +404,7 @@ export const MEDIUM_UNIFORMS = MEDIUM_BINDINGS.MEDIUM_FRAG.uniforms;
390
404
  export const MEDIUM_TEXTURES = MEDIUM_BINDINGS.MEDIUM_FRAG.textures;
391
405
  export const MEDIUM_UPSAMPLE_UNIFORMS = MEDIUM_BINDINGS.MEDIUM_UPSAMPLE_FRAG.uniforms;
392
406
  export const MEDIUM_UPSAMPLE_TEXTURES = MEDIUM_BINDINGS.MEDIUM_UPSAMPLE_FRAG.textures;
393
- export { RUSH_FRAG_WGSL, AO_FRAG_WGSL, AO_BLUR_FRAG_WGSL, TEMPORAL_RESOLVE_FRAG_WGSL, OIT_RESOLVE_FRAG_WGSL, DECAL_PROJECT_FRAG_WGSL, SSR_TRACE_FRAG_WGSL, SSR_RESOLVE_FRAG_WGSL, };
407
+ export { RUSH_FRAG_WGSL, AO_FRAG_WGSL, AO_BLUR_FRAG_WGSL, TEMPORAL_RESOLVE_FRAG_WGSL, OIT_RESOLVE_FRAG_WGSL, DECAL_PROJECT_FRAG_WGSL, SSR_TRACE_FRAG_WGSL, SSR_RESOLVE_FRAG_WGSL, SSR_MATERIAL_RESOLVE_FRAG_WGSL, };
394
408
  export { BLOOM_PREFILTER_FRAG_WGSL, BLOOM_DOWNSAMPLE_FRAG_WGSL, BLOOM_UPSAMPLE_FRAG_WGSL };
395
409
  export { RUSH_UNIFORMS, RUSH_TEXTURES };
396
410
  export const AO_UNIFORMS = AO_BINDINGS.AO_FRAG.uniforms;
@@ -0,0 +1,30 @@
1
+ import type { DrawCommand } from './drawCommand.ts';
2
+ /** Both targets' format: linear light and a tint, which an eight-bit target would clip and band. */
3
+ export declare const REFLECTION_SURFACE_FORMAT: GPUTextureFormat;
4
+ export declare class ReflectionSurfacePass {
5
+ private readonly device;
6
+ private readonly commands;
7
+ private probe;
8
+ private tint;
9
+ /** The environment as the frame shows it, and the roughness in alpha. */
10
+ probeView: GPUTextureView | null;
11
+ /** What a found reflection is multiplied by. */
12
+ tintView: GPUTextureView | null;
13
+ constructor(device: GPUDevice);
14
+ /** A command for one opaque lit draw's surface half, filled by the caller. */
15
+ take(): DrawCommand;
16
+ /** How many draws the frame kept. */
17
+ get kept(): number;
18
+ /** A new frame keeps nothing yet. */
19
+ reset(): void;
20
+ /** The two targets at the frame's size, made again only when it changes. */
21
+ size(width: number, height: number): void;
22
+ /**
23
+ * Every kept draw into the two targets against the frame's depth, which it reads and does not
24
+ * write; both cleared first, so a pixel no lit surface reached reflects nothing. Called with no
25
+ * pass open on `encoder`, and drawn even with nothing kept, since the clear is what the trace
26
+ * reads there.
27
+ */
28
+ draw(encoder: GPUCommandEncoder, depth: GPUTextureView, issue: (pass: GPURenderPassEncoder, command: DrawCommand) => void): void;
29
+ release(): void;
30
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * The surface half of a reflection traced through the frame's materials, on this backend: the
3
+ * frame's opaque lit draws again, against the depth they wrote, into two half-float targets — the
4
+ * environment as the frame shows it with the surface's roughness, and what a reflection found in the
5
+ * frame is multiplied by to land in its place. `shaders/flat/reflectionSurface.ts` is what the two
6
+ * hold and why the swap is exact; this is where they are drawn.
7
+ *
8
+ * **Why a second pass and not a second output of the frame's.** Every pipeline drawn into the
9
+ * frame's pass would have to declare a target it never writes — the sky, water, particles, text and
10
+ * every pass a consumer registers — so the frame keeps its one attachment, and the lit stage's two
11
+ * extra outputs land nowhere there. Here they land: the same pipelines with `REFLECTION_SURFACE` on,
12
+ * which leaves before the lamps, the same bind groups and ring slots, tested `equal` against the
13
+ * frame's depth so only what the frame kept writes.
14
+ *
15
+ * **The draws are commands, kept the way the skin's halves are** (`skinScatterPass.ts`): the frame
16
+ * graph's pool is spent at every flush, and these are replayed after the flush that ends the frame's
17
+ * pass. Each holds the ring slots its frame draw used, which stay valid until the frame's last
18
+ * submit, after this has run.
19
+ *
20
+ * **What it costs**: every opaque lit draw twice, the second without its lamps; two half-float
21
+ * targets the size of the frame. Single-sampled only, as the trace that reads them is.
22
+ */
23
+ import { createCommandPool, resetPool, takeCommand } from './drawCommand.js';
24
+ /** Both targets' format: linear light and a tint, which an eight-bit target would clip and band. */
25
+ export const REFLECTION_SURFACE_FORMAT = 'rgba16float';
26
+ const USAGE_TARGET = 0x10 | 0x4; // RENDER_ATTACHMENT | TEXTURE_BINDING
27
+ export class ReflectionSurfacePass {
28
+ device;
29
+ commands = createCommandPool(256);
30
+ probe = null;
31
+ tint = null;
32
+ /** The environment as the frame shows it, and the roughness in alpha. */
33
+ probeView = null;
34
+ /** What a found reflection is multiplied by. */
35
+ tintView = null;
36
+ constructor(device) {
37
+ this.device = device;
38
+ }
39
+ /** A command for one opaque lit draw's surface half, filled by the caller. */
40
+ take() {
41
+ return this.commands.commands[takeCommand(this.commands)];
42
+ }
43
+ /** How many draws the frame kept. */
44
+ get kept() {
45
+ return this.commands.taken;
46
+ }
47
+ /** A new frame keeps nothing yet. */
48
+ reset() {
49
+ resetPool(this.commands);
50
+ }
51
+ /** The two targets at the frame's size, made again only when it changes. */
52
+ size(width, height) {
53
+ if (this.probe !== null && this.probe.width === width && this.probe.height === height)
54
+ return;
55
+ this.release();
56
+ const make = (label) => this.device.createTexture({
57
+ label,
58
+ size: [width, height],
59
+ format: REFLECTION_SURFACE_FORMAT,
60
+ usage: USAGE_TARGET,
61
+ });
62
+ this.probe = make('reflection.probe');
63
+ this.tint = make('reflection.tint');
64
+ this.probeView = this.probe.createView();
65
+ this.tintView = this.tint.createView();
66
+ }
67
+ /**
68
+ * Every kept draw into the two targets against the frame's depth, which it reads and does not
69
+ * write; both cleared first, so a pixel no lit surface reached reflects nothing. Called with no
70
+ * pass open on `encoder`, and drawn even with nothing kept, since the clear is what the trace
71
+ * reads there.
72
+ */
73
+ draw(encoder, depth, issue) {
74
+ const probe = this.probeView;
75
+ const tint = this.tintView;
76
+ if (probe === null || tint === null)
77
+ return;
78
+ const pass = encoder.beginRenderPass({
79
+ label: 'reflection.surface',
80
+ colorAttachments: [
81
+ null,
82
+ { view: probe, clearValue: [0, 0, 0, 1], loadOp: 'clear', storeOp: 'store' },
83
+ { view: tint, clearValue: [0, 0, 0, 0], loadOp: 'clear', storeOp: 'store' },
84
+ ],
85
+ depthStencilAttachment: { view: depth, depthReadOnly: true },
86
+ });
87
+ for (let i = 0; i < this.commands.taken; i += 1) {
88
+ issue(pass, this.commands.commands[i]);
89
+ }
90
+ pass.end();
91
+ }
92
+ release() {
93
+ this.probe?.destroy();
94
+ this.tint?.destroy();
95
+ this.probe = null;
96
+ this.tint = null;
97
+ this.probeView = null;
98
+ this.tintView = null;
99
+ }
100
+ }