@driftengine/core 4.8.1 → 4.8.3

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 (159) hide show
  1. package/CHANGELOG.json +142 -0
  2. package/README.md +1 -1
  3. package/dist/index.d.ts +3 -1
  4. package/dist/index.js +8 -0
  5. package/dist/input/actionMap.d.ts +6 -1
  6. package/dist/input/actionMap.js +20 -7
  7. package/dist/input/input.d.ts +28 -0
  8. package/dist/input/input.js +71 -9
  9. package/dist/input/inputHarness.d.ts +9 -0
  10. package/dist/input/inputHarness.js +34 -2
  11. package/dist/render/backend/materialChanges.d.ts +7 -2
  12. package/dist/render/backend/materialChanges.js +8 -3
  13. package/dist/render/backend/webgl2/renderer.d.ts +75 -3
  14. package/dist/render/backend/webgl2/renderer.js +209 -22
  15. package/dist/render/backend/webgpu/buffers.d.ts +2 -0
  16. package/dist/render/backend/webgpu/buffers.js +2 -0
  17. package/dist/render/backend/webgpu/causticsPass.js +2 -1
  18. package/dist/render/backend/webgpu/depthPass.d.ts +13 -3
  19. package/dist/render/backend/webgpu/depthPass.js +4 -3
  20. package/dist/render/backend/webgpu/filmPass.js +2 -1
  21. package/dist/render/backend/webgpu/flatPass.d.ts +5 -0
  22. package/dist/render/backend/webgpu/flatPass.js +18 -8
  23. package/dist/render/backend/webgpu/linePass.js +2 -1
  24. package/dist/render/backend/webgpu/panelPass.js +2 -1
  25. package/dist/render/backend/webgpu/particlePass.js +2 -1
  26. package/dist/render/backend/webgpu/pipelineCache.d.ts +40 -8
  27. package/dist/render/backend/webgpu/pipelineCache.js +76 -9
  28. package/dist/render/backend/webgpu/plumePass.js +2 -1
  29. package/dist/render/backend/webgpu/postPass.d.ts +12 -2
  30. package/dist/render/backend/webgpu/postPass.js +19 -5
  31. package/dist/render/backend/webgpu/renderer.d.ts +66 -2
  32. package/dist/render/backend/webgpu/renderer.js +189 -38
  33. package/dist/render/backend/webgpu/sdfTextPass.js +2 -1
  34. package/dist/render/backend/webgpu/textPass.js +2 -1
  35. package/dist/render/backend/webgpu/waterPass.js +2 -1
  36. package/dist/render/backend/webgpu/windStreakPass.js +2 -1
  37. package/dist/render/boltBatch.js +2 -1
  38. package/dist/render/causticsRenderer.js +2 -1
  39. package/dist/render/cutoutCaster.d.ts +3 -1
  40. package/dist/render/cutoutCaster.js +4 -0
  41. package/dist/render/decalPass.js +2 -1
  42. package/dist/render/globalMediumPass.js +3 -2
  43. package/dist/render/lineBatch.js +5 -1
  44. package/dist/render/mapsWithoutUvs.d.ts +24 -0
  45. package/dist/render/mapsWithoutUvs.js +24 -0
  46. package/dist/render/mesh.d.ts +2 -0
  47. package/dist/render/mesh.js +3 -0
  48. package/dist/render/oitPass.js +2 -1
  49. package/dist/render/particleBatch.js +3 -2
  50. package/dist/render/particlePool.d.ts +21 -2
  51. package/dist/render/particlePool.js +72 -37
  52. package/dist/render/pass.d.ts +12 -0
  53. package/dist/render/plumeRenderer.js +3 -2
  54. package/dist/render/renderQuality.d.ts +16 -8
  55. package/dist/render/sceneCoverage.d.ts +54 -0
  56. package/dist/render/sceneCoverage.js +72 -0
  57. package/dist/render/sdfTextRenderer.js +2 -1
  58. package/dist/render/shaders/depth.js +3 -2
  59. package/dist/render/shaders/flat/glassTint.d.ts +28 -0
  60. package/dist/render/shaders/flat/glassTint.js +31 -0
  61. package/dist/render/shaders/flat/index.d.ts +12 -0
  62. package/dist/render/shaders/flat/index.js +13 -2
  63. package/dist/render/shaders/flat/main.d.ts +1 -1
  64. package/dist/render/shaders/flat/main.js +60 -25
  65. package/dist/render/shaders/flat/surfaceEffects.d.ts +1 -1
  66. package/dist/render/shaders/flat/surfaceEffects.js +6 -2
  67. package/dist/render/shaders/generated/depth.wgsl.d.ts +32 -12
  68. package/dist/render/shaders/generated/depth.wgsl.js +32 -12
  69. package/dist/render/shaders/generated/flat.wgsl.d.ts +89 -0
  70. package/dist/render/shaders/generated/flat.wgsl.js +127 -38
  71. package/dist/render/shaders/generated/line.wgsl.d.ts +1 -1
  72. package/dist/render/shaders/generated/line.wgsl.js +1 -1
  73. package/dist/render/shaders/generated/particle.wgsl.d.ts +3 -3
  74. package/dist/render/shaders/generated/particle.wgsl.js +3 -3
  75. package/dist/render/shaders/generated/rush.wgsl.d.ts +1 -1
  76. package/dist/render/shaders/generated/rush.wgsl.js +1 -1
  77. package/dist/render/shaders/generated/sdfText.wgsl.d.ts +1 -1
  78. package/dist/render/shaders/generated/sdfText.wgsl.js +1 -1
  79. package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +1 -1
  80. package/dist/render/shaders/generated/temporalResolve.wgsl.js +1 -1
  81. package/dist/render/shaders/line.d.ts +1 -1
  82. package/dist/render/shaders/outputTransform.d.ts +14 -1
  83. package/dist/render/shaders/outputTransform.js +23 -1
  84. package/dist/render/shaders/particle.d.ts +3 -3
  85. package/dist/render/shaders/recon/resolve.wgsl.js +15 -1
  86. package/dist/render/shaders/rush.js +13 -2
  87. package/dist/render/shaders/sdfText.d.ts +1 -1
  88. package/dist/render/shaders/temporalResolve.js +12 -5
  89. package/dist/render/ssrPass.js +2 -1
  90. package/dist/render/surfaceTexture.d.ts +13 -0
  91. package/dist/render/surfaceTexture.js +4 -0
  92. package/dist/render/textRenderer.js +2 -1
  93. package/dist/render/vertexDefaults.js +1 -0
  94. package/dist/render/viewModel.d.ts +29 -0
  95. package/dist/render/viewModel.js +35 -0
  96. package/dist/render/waterRenderer.js +2 -1
  97. package/dist/render/windStreakRenderer.js +2 -1
  98. package/package.json +6 -6
  99. package/src/index.ts +22 -1
  100. package/src/input/actionMap.ts +23 -8
  101. package/src/input/input.ts +79 -9
  102. package/src/input/inputHarness.ts +27 -2
  103. package/src/render/backend/materialChanges.ts +8 -3
  104. package/src/render/backend/webgl2/renderer.ts +223 -29
  105. package/src/render/backend/webgpu/buffers.ts +5 -0
  106. package/src/render/backend/webgpu/causticsPass.ts +2 -1
  107. package/src/render/backend/webgpu/depthPass.ts +4 -3
  108. package/src/render/backend/webgpu/filmPass.ts +2 -1
  109. package/src/render/backend/webgpu/flatPass.ts +43 -26
  110. package/src/render/backend/webgpu/linePass.ts +2 -1
  111. package/src/render/backend/webgpu/panelPass.ts +2 -1
  112. package/src/render/backend/webgpu/particlePass.ts +2 -1
  113. package/src/render/backend/webgpu/pipelineCache.ts +82 -8
  114. package/src/render/backend/webgpu/plumePass.ts +2 -1
  115. package/src/render/backend/webgpu/postPass.ts +20 -5
  116. package/src/render/backend/webgpu/renderer.ts +191 -37
  117. package/src/render/backend/webgpu/sdfTextPass.ts +2 -1
  118. package/src/render/backend/webgpu/textPass.ts +2 -1
  119. package/src/render/backend/webgpu/waterPass.ts +2 -1
  120. package/src/render/backend/webgpu/windStreakPass.ts +2 -1
  121. package/src/render/boltBatch.ts +2 -1
  122. package/src/render/causticsRenderer.ts +2 -1
  123. package/src/render/cutoutCaster.ts +15 -2
  124. package/src/render/decalPass.ts +2 -1
  125. package/src/render/globalMediumPass.ts +3 -2
  126. package/src/render/lineBatch.ts +3 -1
  127. package/src/render/mapsWithoutUvs.ts +43 -0
  128. package/src/render/mesh.ts +3 -0
  129. package/src/render/oitPass.ts +2 -1
  130. package/src/render/particleBatch.ts +3 -2
  131. package/src/render/particlePool.ts +107 -56
  132. package/src/render/pass.ts +12 -0
  133. package/src/render/plumeRenderer.ts +3 -2
  134. package/src/render/renderQuality.ts +16 -8
  135. package/src/render/sceneCoverage.ts +78 -0
  136. package/src/render/sdfTextRenderer.ts +2 -1
  137. package/src/render/shaders/depth.ts +3 -2
  138. package/src/render/shaders/flat/glassTint.ts +48 -0
  139. package/src/render/shaders/flat/index.ts +25 -2
  140. package/src/render/shaders/flat/main.ts +60 -25
  141. package/src/render/shaders/flat/surfaceEffects.ts +6 -2
  142. package/src/render/shaders/generated/depth.wgsl.ts +32 -12
  143. package/src/render/shaders/generated/flat.wgsl.ts +127 -38
  144. package/src/render/shaders/generated/line.wgsl.ts +1 -1
  145. package/src/render/shaders/generated/particle.wgsl.ts +3 -3
  146. package/src/render/shaders/generated/rush.wgsl.ts +1 -1
  147. package/src/render/shaders/generated/sdfText.wgsl.ts +1 -1
  148. package/src/render/shaders/generated/temporalResolve.wgsl.ts +1 -1
  149. package/src/render/shaders/outputTransform.ts +24 -1
  150. package/src/render/shaders/recon/resolve.wgsl.ts +15 -1
  151. package/src/render/shaders/rush.ts +13 -2
  152. package/src/render/shaders/temporalResolve.ts +12 -5
  153. package/src/render/ssrPass.ts +2 -1
  154. package/src/render/surfaceTexture.ts +16 -0
  155. package/src/render/textRenderer.ts +2 -1
  156. package/src/render/vertexDefaults.ts +1 -0
  157. package/src/render/viewModel.ts +41 -0
  158. package/src/render/waterRenderer.ts +2 -1
  159. package/src/render/windStreakRenderer.ts +2 -1
@@ -1113,8 +1113,17 @@ export declare class WebGPURenderer implements RendererApi {
1113
1113
  private blankAlbedoBindGroup;
1114
1114
  /** Repeats across the mesh's own UV range. Per draw, because `uUvScale` is a vertex uniform. */
1115
1115
  private readonly uvScale;
1116
+ /** Where the texture starts, after the scale. Per draw, beside `uvScale`. */
1117
+ private readonly uvOffset;
1118
+ /**
1119
+ * How the surfaces drawn next meet the medium: what `setSurfaceFog` set, and `FOG_RECEDE` from
1120
+ * each `bindMeshPass`. A translucent draw's own fog is measured against this and puts it back.
1121
+ */
1122
+ private surfaceFog;
1116
1123
  /** Whether the bound material is seen from both faces. See `SurfaceMaterial.doubleSided`. */
1117
1124
  private materialDoubleSided;
1125
+ /** Whether the bound material has a map, for the warning a mesh with no coordinates earns. */
1126
+ private materialHasMaps;
1118
1127
  createSurfaceTexture(source: TexImageSource, options?: SurfaceTextureOptions): GpuSurfaceTexture;
1119
1128
  /** Several images of one size as an array's layers. See `RendererApi.createSurfaceTextureArray`. */
1120
1129
  createSurfaceTextureArray(sources: readonly TexImageSource[], options?: SurfaceTextureOptions): GpuSurfaceTexture;
@@ -1196,6 +1205,8 @@ export declare class WebGPURenderer implements RendererApi {
1196
1205
  /** How bright the environment the surfaces drawn next reflect is. See `renderer.ts`. */
1197
1206
  setEnvironmentGain(gain: number): void;
1198
1207
  setSurfaceGrain(amount: number): void;
1208
+ /** Whether the surfaces drawn next meet the medium. See `renderer.ts`. */
1209
+ setSurfaceFog(enabled: boolean): void;
1199
1210
  /**
1200
1211
  * How much of the surfaces drawn next survives a screen-door dither. See `renderer.ts`.
1201
1212
  *
@@ -1814,7 +1825,7 @@ export declare class WebGPURenderer implements RendererApi {
1814
1825
  */
1815
1826
  /** The cutout bind group for `texture` in the open layer, built the first time it casts there. */
1816
1827
  private cutoutGroup;
1817
- /** The cutoff and UV scale into a cutout draw's slot, at the offsets its own variant declares. */
1828
+ /** The cutoff and UV scale and offset into a cutout draw's slot, at its own variant's offsets. */
1818
1829
  private writeCutout;
1819
1830
  private shadowGroup;
1820
1831
  /**
@@ -1834,6 +1845,16 @@ export declare class WebGPURenderer implements RendererApi {
1834
1845
  */
1835
1846
  /** The glass the open shadow pass was offered, kept for its own passes: `glassCasters.ts`. */
1836
1847
  private readonly glassCasters;
1848
+ /** Whether a glass caster has ever been offered, which is when lit pipelines start reading it. */
1849
+ private glassSeen;
1850
+ /**
1851
+ * The first glass caster switches every lit pipeline to reading glass. See
1852
+ * `PipelineCache.litSwitches`: until a pane exists the lookups would find nothing, and they cost
1853
+ * the lit pass on every surface whether they find anything or not.
1854
+ */
1855
+ private noteGlassCaster;
1856
+ /** One of the lit stage's switches on, in every cache a lit pipeline can come from. */
1857
+ private enableLit;
1837
1858
  /** One resolution every glass caster is read through, so recording one allocates nothing. */
1838
1859
  private readonly glassCasterScratch;
1839
1860
  private readonly casterSink;
@@ -2944,6 +2965,22 @@ export declare class WebGPURenderer implements RendererApi {
2944
2965
  */
2945
2966
  private canDraw;
2946
2967
  private openPass;
2968
+ private openAnyPass;
2969
+ /**
2970
+ * Submit what is drawn after the present on a microtask, which runs before the browser presents.
2971
+ *
2972
+ * There is no second `endFrame` to hang this on — a consumer thinks the frame is over — and
2973
+ * presentation happens when the task ends, after the microtask checkpoint. Anything that needs
2974
+ * the pixels sooner flushes synchronously; `copyRegionTo` is the one that does.
2975
+ *
2976
+ * **Queued wherever such work begins: where the overlay pass opens, and where a verb records.**
2977
+ * With the graph on a verb records rather than opening a pass, and until 4.8.2 only an opening
2978
+ * queued this, so `fillPanel` and a contributed pass drawn after `endFrame` waited for the next
2979
+ * `beginFrame` — whose replay drew onto a canvas texture already presented and destroyed.
2980
+ */
2981
+ private queueOverlayFlush;
2982
+ /** `flushOverlay` bound once, so queueing it each frame allocates nothing. */
2983
+ private readonly flushOverlayBound;
2947
2984
  /**
2948
2985
  * The cache whose pipelines match the pass this draw will land in.
2949
2986
  *
@@ -3159,6 +3196,15 @@ export declare class WebGPURenderer implements RendererApi {
3159
3196
  * context is valid for the duration of the call for exactly this reason.
3160
3197
  */
3161
3198
  private readonly passContext;
3199
+ /**
3200
+ * Fill the context a contributed pass draws with, for the pass it is about to draw into.
3201
+ *
3202
+ * **The attachments as well as the encoder**: a pass drawn after `endFrame` lands on the canvas
3203
+ * at one sample where the frame's own is the scene target at the scene's samples, and a pipeline
3204
+ * built for the one is refused by the other. The cache a mesh drawn here would use is the answer,
3205
+ * so the two cannot disagree.
3206
+ */
3207
+ private fillPassContext;
3162
3208
  /**
3163
3209
  * Let something outside this file draw into the frame. See `Renderer.registerPass`.
3164
3210
  *
@@ -3313,9 +3359,27 @@ export declare class WebGPURenderer implements RendererApi {
3313
3359
  * **The clear is a drawn quad rather than a clear**, because WebGPU clears whole attachments
3314
3360
  * and cannot confine one to a scissor. See `insetPass.ts`.
3315
3361
  *
3316
- * Returns the drawing-buffer height of the inset, as the other backend does.
3362
+ * Returns the **aspect** of the inset, width over height in the pixels its viewport got, as the
3363
+ * other backend does — for `camera.updateMatrices`. With no pass to draw into, a lost device,
3364
+ * it is the aspect of the box asked for: there are no pixels to measure, and a zero would be a
3365
+ * projection of infinities in the caller's camera.
3317
3366
  */
3318
3367
  beginInset(rect: InsetRect, clearColor: Vec3 | null): number;
3368
+ /**
3369
+ * Draw what follows as a **view model**, in the nearest sliver of depth, until `endViewModel`.
3370
+ *
3371
+ * The twin of the WebGL2 method, which carries the reasons. Here the squeeze is the viewport's
3372
+ * depth bounds, which are pass state applied when a draw is issued, so both ends flush the graph
3373
+ * as an inset's do, and a pass opened in between is given the bounds again in `openPass`.
3374
+ */
3375
+ beginViewModel(share?: number): void;
3376
+ /** Give the whole depth range back. Safe to call without a matching `beginViewModel`. */
3377
+ endViewModel(): void;
3378
+ /** The view model's depth bounds on the whole of the pass. */
3379
+ private squeezeViewport;
3380
+ /** Whether a view model is being drawn: `openPass` re-applies it, `endFrame` closes it. */
3381
+ private viewModelActive;
3382
+ private readonly viewModelRange;
3319
3383
  /** Give the whole canvas back. Safe to call without a matching `beginInset`. */
3320
3384
  endInset(): void;
3321
3385
  /**
@@ -61,7 +61,7 @@ import { ExposurePass } from './exposurePass.js';
61
61
  import { AO_STORE } from '../../shaders/ambientOcclusion.js';
62
62
  import { clampAutoExposure } from '../../shaders/exposure.js';
63
63
  import { clampLocalExposure } from '../../shaders/localExposure.js';
64
- import { createDepthResolveLayout, createPostStageLayout, depthResolvePipeline, postPipeline, postPipelineTargets, sceneColorFormat, AO_BLUR_FIELDS, AO_BLUR_FRAG_WGSL, AO_BLUR_SIZE, AO_BLUR_TEXTURES, AO_BLUR_UNIFORMS, AO_FRAG_FIELDS, AO_FRAG_SIZE, AO_FRAG_WGSL, AO_TEXTURES, AO_UNIFORMS, ADDITIVE_BLEND, BLOOM_DOWNSAMPLE_FIELDS, BLOOM_DOWNSAMPLE_FRAG_WGSL, BLOOM_PREFILTER_FIELDS, BLOOM_PREFILTER_FRAG_WGSL, BLOOM_STAGE_SIZE, BLOOM_TEXTURES, BLOOM_UNIFORMS, BLOOM_UPSAMPLE_FIELDS, BLOOM_UPSAMPLE_FRAG_WGSL, RESOLVED_DEPTH_FORMAT, RUSH_FRAG_FIELDS, RUSH_FRAG_SIZE, RUSH_FRAG_WGSL, RUSH_TEXTURES, RUSH_UNIFORMS, TAA_FRAG_FIELDS, TAA_FRAG_SIZE, TAA_TEXTURES, TAA_UNIFORMS, TEMPORAL_RESOLVE_FRAG_WGSL, MEDIUM_FRAG_FIELDS, MEDIUM_FRAG_SIZE, MEDIUM_FRAG_WGSL, MEDIUM_TEXTURES, MEDIUM_TRANSMIT_BLEND, MEDIUM_UNIFORMS, MEDIUM_UPSAMPLE_FIELDS, MEDIUM_UPSAMPLE_FRAG_WGSL, MEDIUM_UPSAMPLE_SIZE, MEDIUM_UPSAMPLE_TEXTURES, MEDIUM_UPSAMPLE_UNIFORMS, OIT_COMPOSITE_BLEND, OIT_RESOLVE_FRAG_WGSL, OIT_TEXTURES, DECAL_FRAG_FIELDS, DECAL_FRAG_SIZE, DECAL_MULTIPLY_BLEND, DECAL_PROJECT_FRAG_WGSL, DECAL_TEXTURES, DECAL_UNIFORMS, PREMULTIPLIED_OVER_BLEND, SSR_FRAG_FIELDS, SSR_FRAG_SIZE, SSR_RESOLVE_FRAG_WGSL, SSR_RESOLVE_TEXTURES, SSR_TEXTURES, SSR_TRACE_FRAG_WGSL, SSR_UNIFORMS, createOitResolveLayout, createSsrResolveLayout, } from './postPass.js';
64
+ import { createDepthResolveLayout, createPostStageLayout, depthResolvePipeline, postPipeline, postPipelineTargets, sceneColorFormat, AO_BLUR_FIELDS, AO_BLUR_FRAG_WGSL, AO_BLUR_SIZE, AO_BLUR_TEXTURES, AO_BLUR_UNIFORMS, AO_FRAG_FIELDS, AO_FRAG_SIZE, AO_FRAG_WGSL, AO_TEXTURES, AO_UNIFORMS, ADDITIVE_BLEND, BLOOM_DOWNSAMPLE_FIELDS, BLOOM_DOWNSAMPLE_FRAG_WGSL, BLOOM_PREFILTER_FIELDS, BLOOM_PREFILTER_FRAG_WGSL, BLOOM_STAGE_SIZE, BLOOM_TEXTURES, BLOOM_UNIFORMS, BLOOM_UPSAMPLE_FIELDS, BLOOM_UPSAMPLE_FRAG_WGSL, RESOLVED_DEPTH_FORMAT, RUSH_FRAG_FIELDS, RUSH_FRAG_SIZE, RUSH_FRAG_WGSL, RUSH_TEXTURES, RUSH_UNIFORMS, TAA_FRAG_FIELDS, TAA_FRAG_SIZE, TAA_TEXTURES, TAA_UNIFORMS, TEMPORAL_RESOLVE_FRAG_WGSL, MEDIUM_FRAG_FIELDS, MEDIUM_FRAG_SIZE, MEDIUM_FRAG_WGSL, MEDIUM_TEXTURES, MEDIUM_TRANSMIT_BLEND, MEDIUM_UNIFORMS, MEDIUM_UPSAMPLE_FIELDS, MEDIUM_UPSAMPLE_FRAG_WGSL, MEDIUM_UPSAMPLE_SIZE, MEDIUM_UPSAMPLE_TEXTURES, MEDIUM_UPSAMPLE_UNIFORMS, OIT_COMPOSITE_BLEND, OIT_RESOLVE_FRAG_WGSL, OIT_TEXTURES, DECAL_FRAG_FIELDS, DECAL_FRAG_SIZE, DECAL_MULTIPLY_BLEND, DECAL_PROJECT_FRAG_WGSL, DECAL_TEXTURES, DECAL_UNIFORMS, PREMULTIPLIED_OVER_BLEND, REFLECTION_OVER_BLEND, SSR_FRAG_FIELDS, SSR_FRAG_SIZE, SSR_RESOLVE_FRAG_WGSL, SSR_RESOLVE_TEXTURES, SSR_TEXTURES, SSR_TRACE_FRAG_WGSL, SSR_UNIFORMS, createOitResolveLayout, createSsrResolveLayout, } from './postPass.js';
65
65
  import { GRADE_PLACEHOLDER_SIZE, identityGradeLut, validateGradeLut, } from '../../colourGrade.js';
66
66
  import { FILM_LOOK_WITHOUT_COMPOSITE, clampGrain, clampVignette, grainSeed, } from '../../shaders/filmLook.js';
67
67
  import { createProbeMipBindGroupLayout, createProbePrefilterBindGroupLayout, PREFILTER_UNIFORM_OFFSETS, probeLevels, probeMipPipeline, probePrefilterPipeline, PROBE_FACES, PROBE_FACE_BASIS, PROBE_FAR_M, PROBE_FOV_DEG, PROBE_MIP_UNIFORM_SIZE, PROBE_NEAR_M, } from './probePass.js';
@@ -77,6 +77,8 @@ import { resolvePointLights } from '../../lightBudget.js';
77
77
  import { resolveRenderQuality } from '../../renderQuality.js';
78
78
  import { DEFAULT_GLOBAL_MEDIUM, mediumActive, mediumTargetSize, WEAK_GPU_MEDIUM_STEPS, resolveGlobalMedium, } from '../../globalMedium.js';
79
79
  import { castsDepth, glassOf } from '../../shadowCasters.js';
80
+ import { materialHasMaps, noteMapsWithoutUvs } from '../../mapsWithoutUvs.js';
81
+ import { VIEW_MODEL_DEPTH_SHARE, viewModelDepthRange } from '../../viewModel.js';
80
82
  import { GlassCasterList } from '../../glassCasters.js';
81
83
  import { GpuGlassTint } from './glassTintPass.js';
82
84
  import { SUN_DYNAMIC_LAYER, SUN_GLASS_MOVING_LAYER, SUN_GLASS_STATIC_LAYER, SUN_PEELED_LAYER, SUN_STATIC_LAYER, } from '../../shadowMap.js';
@@ -98,7 +100,7 @@ import { LIGHT_VOLUME_VERT_FIELDS, LIGHT_VOLUME_VERT_SIZE, createLightVolumeBind
98
100
  import { aimReflection, createGpuReflection } from './reflectionPass.js';
99
101
  import { FrameBudget } from '../budget.js';
100
102
  import { MaterialChanges, ownsMaterial } from '../materialChanges.js';
101
- import { FOG_RECEDE, fogModeOf } from '../drawFog.js';
103
+ import { FOG_OFF, FOG_RECEDE, fogModeOf } from '../drawFog.js';
102
104
  import { DYNAMIC_ALIGNMENT as DYNAMIC_UNIFORM_ALIGNMENT, UniformRing } from './uniformRing.js';
103
105
  import { DecalQueue, MAX_DRAWN_DECALS } from '../../decalQueue.js';
104
106
  import { DistanceFieldScene } from '../../gi/fieldScene.js';
@@ -2129,8 +2131,17 @@ export class WebGPURenderer {
2129
2131
  blankAlbedoBindGroup;
2130
2132
  /** Repeats across the mesh's own UV range. Per draw, because `uUvScale` is a vertex uniform. */
2131
2133
  uvScale = new Float32Array([1, 1]);
2134
+ /** Where the texture starts, after the scale. Per draw, beside `uvScale`. */
2135
+ uvOffset = new Float32Array([0, 0]);
2136
+ /**
2137
+ * How the surfaces drawn next meet the medium: what `setSurfaceFog` set, and `FOG_RECEDE` from
2138
+ * each `bindMeshPass`. A translucent draw's own fog is measured against this and puts it back.
2139
+ */
2140
+ surfaceFog = FOG_RECEDE;
2132
2141
  /** Whether the bound material is seen from both faces. See `SurfaceMaterial.doubleSided`. */
2133
2142
  materialDoubleSided = false;
2143
+ /** Whether the bound material has a map, for the warning a mesh with no coordinates earns. */
2144
+ materialHasMaps = false;
2134
2145
  createSurfaceTexture(source, options = {}) {
2135
2146
  return new GpuSurfaceTexture(this.surface.device, this.pipelines, source, options);
2136
2147
  }
@@ -2256,7 +2267,10 @@ export class WebGPURenderer {
2256
2267
  const albedo = material?.albedo ?? null;
2257
2268
  this.uvScale[0] = material?.uScale ?? 1;
2258
2269
  this.uvScale[1] = material?.vScale ?? 1;
2270
+ this.uvOffset[0] = material?.uOffset ?? 0;
2271
+ this.uvOffset[1] = material?.vOffset ?? 0;
2259
2272
  this.materialDoubleSided = material?.doubleSided === true;
2273
+ this.materialHasMaps = materialHasMaps(material);
2260
2274
  const normal = (material?.normal ?? null);
2261
2275
  const orm = (material?.orm ?? null);
2262
2276
  const emissiveMap = (material?.emissive ?? null);
@@ -2403,6 +2417,13 @@ export class WebGPURenderer {
2403
2417
  f[this.materialField('uGrain')] = Math.min(1, Math.max(0, amount));
2404
2418
  });
2405
2419
  }
2420
+ /** Whether the surfaces drawn next meet the medium. See `renderer.ts`. */
2421
+ setSurfaceFog(enabled) {
2422
+ this.surfaceFog = enabled ? FOG_RECEDE : FOG_OFF;
2423
+ this.material((_f, i) => {
2424
+ i[this.materialField('uFogEnabled')] = this.surfaceFog;
2425
+ });
2426
+ }
2406
2427
  /**
2407
2428
  * How much of the surfaces drawn next survives a screen-door dither. See `renderer.ts`.
2408
2429
  *
@@ -4358,10 +4379,12 @@ export class WebGPURenderer {
4358
4379
  groups.set(texture, group);
4359
4380
  return group;
4360
4381
  }
4361
- /** The cutoff and UV scale into a cutout draw's slot, at the offsets its own variant declares. */
4382
+ /** The cutoff and UV scale and offset into a cutout draw's slot, at its own variant's offsets. */
4362
4383
  writeCutout(slot, cutout, fields) {
4363
4384
  this.shadowDraws.writeFloat(slot, fields.uUvScale.offset, cutout.u);
4364
4385
  this.shadowDraws.writeFloat(slot, fields.uUvScale.offset + 4, cutout.v);
4386
+ this.shadowDraws.writeFloat(slot, fields.uUvOffset.offset, cutout.uOffset);
4387
+ this.shadowDraws.writeFloat(slot, fields.uUvOffset.offset + 4, cutout.vOffset);
4365
4388
  this.shadowDraws.writeFloat(slot, fields.uAlphaCutout.offset, cutout.cutoff);
4366
4389
  }
4367
4390
  shadowGroup() {
@@ -4400,6 +4423,29 @@ export class WebGPURenderer {
4400
4423
  */
4401
4424
  /** The glass the open shadow pass was offered, kept for its own passes: `glassCasters.ts`. */
4402
4425
  glassCasters = new GlassCasterList();
4426
+ /** Whether a glass caster has ever been offered, which is when lit pipelines start reading it. */
4427
+ glassSeen = false;
4428
+ /**
4429
+ * The first glass caster switches every lit pipeline to reading glass. See
4430
+ * `PipelineCache.litSwitches`: until a pane exists the lookups would find nothing, and they cost
4431
+ * the lit pass on every surface whether they find anything or not.
4432
+ */
4433
+ noteGlassCaster() {
4434
+ if (this.glassSeen)
4435
+ return;
4436
+ this.glassSeen = true;
4437
+ this.enableLit('GLASS_SHADOWS');
4438
+ }
4439
+ /** One of the lit stage's switches on, in every cache a lit pipeline can come from. */
4440
+ enableLit(feature) {
4441
+ if (this.pipelines.litSwitches[feature])
4442
+ return;
4443
+ void this.pipelines.enable(feature);
4444
+ if (this.overlayPipelines !== this.pipelines)
4445
+ void this.overlayPipelines.enable(feature);
4446
+ if (this.latePipelines !== this.pipelines)
4447
+ void this.latePipelines.enable(feature);
4448
+ }
4403
4449
  /** One resolution every glass caster is read through, so recording one allocates nothing. */
4404
4450
  glassCasterScratch = {
4405
4451
  transmission: 0,
@@ -4412,6 +4458,7 @@ export class WebGPURenderer {
4412
4458
  /* Glass: kept aside for its own two passes rather than dropped. See `glassCasters.ts`. */
4413
4459
  if (this.quality.glassShadows !== 'off' && glassOf(material, this.glassCasterScratch)) {
4414
4460
  this.glassCasters.recordMesh(mesh, model, material, this.glassCasterScratch);
4461
+ this.noteGlassCaster();
4415
4462
  }
4416
4463
  return;
4417
4464
  }
@@ -4451,6 +4498,7 @@ export class WebGPURenderer {
4451
4498
  if (!this.drawingGlassDepth && !castsDepth(material)) {
4452
4499
  if (this.quality.glassShadows !== 'off' && glassOf(material, this.glassCasterScratch)) {
4453
4500
  this.glassCasters.recordInstanced(batch, data, material, this.glassCasterScratch);
4501
+ this.noteGlassCaster();
4454
4502
  }
4455
4503
  return;
4456
4504
  }
@@ -4491,6 +4539,7 @@ export class WebGPURenderer {
4491
4539
  if (!this.drawingGlassDepth && !castsDepth(material)) {
4492
4540
  if (this.quality.glassShadows !== 'off' && glassOf(material, this.glassCasterScratch)) {
4493
4541
  this.glassCasters.recordSkinned(mesh, model, palette, material, this.glassCasterScratch);
4542
+ this.noteGlassCaster();
4494
4543
  }
4495
4544
  return;
4496
4545
  }
@@ -4833,7 +4882,7 @@ export class WebGPURenderer {
4833
4882
  */
4834
4883
  /* Off compiles none of the glass lookup into a lit pipeline: see `PipelineCache.glassShadows`. */
4835
4884
  const glassShadows = quality.glassShadows !== 'off';
4836
- this.pipelines = new PipelineCache(device, quality.screenEffects ? sceneColorFormat(quality.hdrScene) : surface.format, this.samples, glassShadows);
4885
+ this.pipelines = new PipelineCache(device, quality.screenEffects ? sceneColorFormat(quality.hdrScene) : surface.format, this.samples, glassShadows, quality.clusteredLights);
4837
4886
  /*
4838
4887
  * The canvas' own state, for the pass `ensurePass` reopens after `endFrame`.
4839
4888
  *
@@ -4845,11 +4894,11 @@ export class WebGPURenderer {
4845
4894
  this.overlayPipelines =
4846
4895
  this.pipelines.format === surface.format && this.samples === 1
4847
4896
  ? this.pipelines
4848
- : new PipelineCache(device, surface.format, 1, glassShadows);
4897
+ : new PipelineCache(device, surface.format, 1, glassShadows, quality.clusteredLights);
4849
4898
  this.latePipelines =
4850
4899
  this.pipelines.format === RECON_HISTORY_FORMAT && this.samples === 1
4851
4900
  ? this.pipelines
4852
- : new PipelineCache(device, RECON_HISTORY_FORMAT, 1, glassShadows);
4901
+ : new PipelineCache(device, RECON_HISTORY_FORMAT, 1, glassShadows, quality.clusteredLights);
4853
4902
  this.issueBound = this.issueCommand.bind(this);
4854
4903
  this.pipelineTargets =
4855
4904
  this.overlayPipelines === this.pipelines
@@ -5123,7 +5172,11 @@ export class WebGPURenderer {
5123
5172
  /* The albedo's effects table, or a one-texel stand-in the shader's size test reads as none.
5124
5173
  The albedo is the group's key, so its table needs no key of its own. */
5125
5174
  if (name === 'uSurfaceEffects') {
5126
- return { view: this.albedo?.effectsView ?? blankView, sampler: blankSampler };
5175
+ const effects = this.albedo?.effectsView ?? null;
5176
+ /* The first table bound is when the lit pipelines start reading one. See `litSwitches`. */
5177
+ if (effects !== null)
5178
+ this.enableLit('SURFACE_EFFECTS');
5179
+ return { view: effects ?? blankView, sampler: blankSampler };
5127
5180
  }
5128
5181
  /*
5129
5182
  * The room, once a bake has filled it. Unbaked reads as the blank cube: nothing to mirror.
@@ -6213,8 +6266,10 @@ export class WebGPURenderer {
6213
6266
  * these per draw through the material block, the same shape `uOpacity` already uses.
6214
6267
  */
6215
6268
  i[at('uLightingEnabled')] = 1;
6216
- i[at('uFogEnabled')] = 1;
6269
+ i[at('uFogEnabled')] = FOG_RECEDE;
6270
+ this.surfaceFog = FOG_RECEDE;
6217
6271
  i[at('uAlbedoEnabled')] = 0;
6272
+ this.materialHasMaps = false;
6218
6273
  /*
6219
6274
  * **The camera medium, which this pass did not upload until 2026-08-29.**
6220
6275
  *
@@ -6630,6 +6685,8 @@ export class WebGPURenderer {
6630
6685
  this.bindGroup = this.blankAlbedoBindGroup;
6631
6686
  this.uvScale[0] = 1;
6632
6687
  this.uvScale[1] = 1;
6688
+ this.uvOffset[0] = 0;
6689
+ this.uvOffset[1] = 0;
6633
6690
  }
6634
6691
  /**
6635
6692
  * The slot the next draw's material lives in, taking a new one when something dirtied it.
@@ -6707,7 +6764,11 @@ export class WebGPURenderer {
6707
6764
  */
6708
6765
  if (this.tracksMotionNow())
6709
6766
  this.recordDrawMotion(mesh, model, previousModel);
6710
- this.submitMesh(mesh, model, tint, 1, false, { depthLayer });
6767
+ noteMapsWithoutUvs(mesh, mesh.hasUvs, this.materialHasMaps);
6768
+ /* An opaque draw meets the medium as the dial says: `submitMesh` reads an absent `fog` as
6769
+ true, which is a translucent draw's default and would put every opaque draw back in the fog
6770
+ `setSurfaceFog(false)` took it out of. */
6771
+ this.submitMesh(mesh, model, tint, 1, false, { depthLayer, fog: this.surfaceFog !== FOG_OFF });
6711
6772
  }
6712
6773
  /**
6713
6774
  * Draw a mesh that lets the world through, at `opacity`.
@@ -6801,6 +6862,7 @@ export class WebGPURenderer {
6801
6862
  this.translucentQueue.record(mesh, model, opacity, options);
6802
6863
  return;
6803
6864
  }
6865
+ noteMapsWithoutUvs(mesh, mesh.hasUvs, this.materialHasMaps);
6804
6866
  /* The tint travels in the options here, where `drawMesh` takes it positionally — see
6805
6867
  `TranslucentMeshOptions.tint` for why, and for the fade it was added for. */
6806
6868
  this.submitMesh(mesh, model, options.tint ?? null, Math.min(opacity, 1), true, options);
@@ -6856,6 +6918,7 @@ export class WebGPURenderer {
6856
6918
  /* The material's repeats, not a constant: geometry authored in metres is textured at
6857
6919
  whatever density the material asks for without rebuilding it. */
6858
6920
  this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvScale.offset, this.uvScale);
6921
+ this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvOffset.offset, this.uvOffset);
6859
6922
  this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uLightViewProj.offset, this.lightViewProj);
6860
6923
  /*
6861
6924
  * Whether this mesh's tangent frame is real. WebGPU has no disabled attribute, so location 10
@@ -6888,7 +6951,7 @@ export class WebGPURenderer {
6888
6951
  /* Added light fades in the medium rather than receding into it. See `drawFog.ts`. */
6889
6952
  const fog = fogModeOf(options.fog ?? true, blend && options.additive === true);
6890
6953
  const toneMapped = options.toneMapped ?? true;
6891
- const unlitOrUnfogged = !lit || fog !== FOG_RECEDE;
6954
+ const unlitOrUnfogged = !lit || fog !== this.surfaceFog;
6892
6955
  if (unlitOrUnfogged) {
6893
6956
  if (!lit)
6894
6957
  this.perFrameInts[this.materialField('uLightingEnabled')] = 0;
@@ -6911,7 +6974,7 @@ export class WebGPURenderer {
6911
6974
  const refracting = this.bindSeeThrough(options);
6912
6975
  /* Taken for itself when any of those differs from the pass, and put back below: the rule both
6913
6976
  backends count material changes by. See `materialChanges.ts`. */
6914
- const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting });
6977
+ const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting }, this.surfaceFog);
6915
6978
  if (own)
6916
6979
  this.materials.dirty();
6917
6980
  const base = mesh.key ?? 'flat:s0:u0';
@@ -7041,7 +7104,7 @@ export class WebGPURenderer {
7041
7104
  if (unlitOrUnfogged) {
7042
7105
  if (!lit)
7043
7106
  this.perFrameInts[this.materialField('uLightingEnabled')] = 1;
7044
- this.perFrameInts[this.materialField('uFogEnabled')] = FOG_RECEDE;
7107
+ this.perFrameInts[this.materialField('uFogEnabled')] = this.surfaceFog;
7045
7108
  }
7046
7109
  /* Back to off for every other draw in the frame, exactly as `uOpacity` is: this scratch is
7047
7110
  shared, so a strength left set is worn by everything drawn after it. */
@@ -7246,6 +7309,7 @@ export class WebGPURenderer {
7246
7309
  const lands = blend && options.reconstructed !== true && this.drawsLate();
7247
7310
  this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uViewProj.offset, lands ? this.correctedViewProj : this.viewProj);
7248
7311
  this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvScale.offset, this.uvScale);
7312
+ this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvOffset.offset, this.uvOffset);
7249
7313
  this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uLightViewProj.offset, this.lightViewProj);
7250
7314
  this.perDraw.writeInt(slot, FLAT_VERT_FIELDS.uHasTangents.offset, mesh.hasTangents ? 1 : 0);
7251
7315
  const dimmed = opacity < 1;
@@ -7255,7 +7319,7 @@ export class WebGPURenderer {
7255
7319
  const lit = options.lit ?? true;
7256
7320
  const fog = fogModeOf(options.fog ?? true, blend && options.additive === true);
7257
7321
  const toneMapped = options.toneMapped ?? true;
7258
- const unlitOrUnfogged = !lit || fog !== FOG_RECEDE;
7322
+ const unlitOrUnfogged = !lit || fog !== this.surfaceFog;
7259
7323
  if (unlitOrUnfogged) {
7260
7324
  if (!lit)
7261
7325
  this.perFrameInts[this.materialField('uLightingEnabled')] = 0;
@@ -7268,7 +7332,7 @@ export class WebGPURenderer {
7268
7332
  const layer = Math.min(Math.max(Math.round(options.depthLayer ?? 0), 0), MAX_DEPTH_LAYER);
7269
7333
  /* A blended batch shows what is behind it — refraction or glass — as a single draw does. */
7270
7334
  const refracting = blend && this.bindSeeThrough(options);
7271
- const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting });
7335
+ const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting }, this.surfaceFog);
7272
7336
  if (own)
7273
7337
  this.materials.dirty();
7274
7338
  const base = mesh.key ?? 'flat:s0:u0';
@@ -7337,7 +7401,7 @@ export class WebGPURenderer {
7337
7401
  }
7338
7402
  if (unlitOrUnfogged) {
7339
7403
  this.perFrameInts[this.materialField('uLightingEnabled')] = 1;
7340
- this.perFrameInts[this.materialField('uFogEnabled')] = FOG_RECEDE;
7404
+ this.perFrameInts[this.materialField('uFogEnabled')] = this.surfaceFog;
7341
7405
  }
7342
7406
  if (!toneMapped) {
7343
7407
  this.perFrameInts[this.materialField('uOutputTransform')] = this.gradeCode();
@@ -9545,7 +9609,7 @@ export class WebGPURenderer {
9545
9609
  label: 'post.ssr.resolve',
9546
9610
  colorAttachments: [{ view: scene, loadOp: 'load', storeOp: 'store' }],
9547
9611
  });
9548
- resolve.setPipeline(postPipeline(this.pipelines, device, this.ssrResolveLayout, 'post.ssrResolve', SSR_RESOLVE_FRAG_WGSL, this.pipelines.format, PREMULTIPLIED_OVER_BLEND));
9612
+ resolve.setPipeline(postPipeline(this.pipelines, device, this.ssrResolveLayout, 'post.ssrResolve', SSR_RESOLVE_FRAG_WGSL, this.pipelines.format, REFLECTION_OVER_BLEND));
9549
9613
  resolve.setBindGroup(0, resolveGroup);
9550
9614
  resolve.draw(3);
9551
9615
  resolve.end();
@@ -10442,6 +10506,8 @@ export class WebGPURenderer {
10442
10506
  command.instances = 1;
10443
10507
  command.indirect = null;
10444
10508
  recordNode(this.arena, VERB_DRAW, reads, writes, at, 0);
10509
+ if (this.framePresented)
10510
+ this.queueOverlayFlush();
10445
10511
  return command;
10446
10512
  }
10447
10513
  /** Replay one recorded draw into an open pass. */
@@ -10457,11 +10523,7 @@ export class WebGPURenderer {
10457
10523
  const definition = passAt(this.passes, nodeState(this.arena, at));
10458
10524
  if (definition === undefined)
10459
10525
  return;
10460
- this.passContext.pass = pass;
10461
- /* The frame's grade, so a contributed pass can obey the 2026-08-17 rule. See `PassContext`. */
10462
- this.passContext.outputTransform = this.gradeCode();
10463
- this.passContext.outputExposure = this.gradeExposure();
10464
- definition.draw(this.passContext);
10526
+ definition.draw(this.fillPassContext(pass));
10465
10527
  return;
10466
10528
  }
10467
10529
  if (verb !== VERB_DRAW)
@@ -10558,6 +10620,15 @@ export class WebGPURenderer {
10558
10620
  return this.framePresented;
10559
10621
  }
10560
10622
  openPass() {
10623
+ const was = this.pass;
10624
+ const pass = this.openAnyPass();
10625
+ /* A pass opened while a view model is drawn starts with the whole depth range: squeeze it. */
10626
+ if (this.viewModelActive && pass !== null && pass !== was && !this.reflectionPassActive) {
10627
+ this.squeezeViewport(pass);
10628
+ }
10629
+ return pass;
10630
+ }
10631
+ openAnyPass() {
10561
10632
  /* While the replay is running this is the only pass there is. See `oitEncoder`. */
10562
10633
  if (this.oitEncoder !== null)
10563
10634
  return this.oitEncoder;
@@ -10649,18 +10720,29 @@ export class WebGPURenderer {
10649
10720
  },
10650
10721
  });
10651
10722
  this.overlayActive = true;
10652
- /*
10653
- * **Submitted on a microtask, which runs before the browser presents.** There is no second
10654
- * `endFrame` to hang this on — a consumer thinks the frame is over — and presentation happens
10655
- * when the task ends, after the microtask checkpoint. Anything that needs the pixels sooner
10656
- * flushes synchronously; `copyRegionTo` is the one that does.
10657
- */
10658
- if (!this.overlayFlushQueued) {
10659
- this.overlayFlushQueued = true;
10660
- queueMicrotask(() => this.flushOverlay());
10661
- }
10723
+ this.queueOverlayFlush();
10662
10724
  return this.pass;
10663
10725
  }
10726
+ /**
10727
+ * Submit what is drawn after the present on a microtask, which runs before the browser presents.
10728
+ *
10729
+ * There is no second `endFrame` to hang this on — a consumer thinks the frame is over — and
10730
+ * presentation happens when the task ends, after the microtask checkpoint. Anything that needs
10731
+ * the pixels sooner flushes synchronously; `copyRegionTo` is the one that does.
10732
+ *
10733
+ * **Queued wherever such work begins: where the overlay pass opens, and where a verb records.**
10734
+ * With the graph on a verb records rather than opening a pass, and until 4.8.2 only an opening
10735
+ * queued this, so `fillPanel` and a contributed pass drawn after `endFrame` waited for the next
10736
+ * `beginFrame` — whose replay drew onto a canvas texture already presented and destroyed.
10737
+ */
10738
+ queueOverlayFlush() {
10739
+ if (this.overlayFlushQueued)
10740
+ return;
10741
+ this.overlayFlushQueued = true;
10742
+ queueMicrotask(this.flushOverlayBound);
10743
+ }
10744
+ /** `flushOverlay` bound once, so queueing it each frame allocates nothing. */
10745
+ flushOverlayBound = () => this.flushOverlay();
10664
10746
  /**
10665
10747
  * The cache whose pipelines match the pass this draw will land in.
10666
10748
  *
@@ -10725,6 +10807,9 @@ export class WebGPURenderer {
10725
10807
  this.encoder = null;
10726
10808
  }
10727
10809
  endFrame() {
10810
+ /* A view model left open would squeeze every pass after the world, the composite included. */
10811
+ if (this.viewModelActive)
10812
+ this.endViewModel();
10728
10813
  this.inFrame = false;
10729
10814
  if (this.surface.lost || this.encoder === null)
10730
10815
  return;
@@ -11148,7 +11233,29 @@ export class WebGPURenderer {
11148
11233
  outputTransform: 0,
11149
11234
  outputExposure: 1,
11150
11235
  jitter: this.passJitter,
11236
+ format: 'rgba8unorm',
11237
+ depthFormat: DEPTH_FORMAT,
11238
+ samples: 1,
11151
11239
  };
11240
+ /**
11241
+ * Fill the context a contributed pass draws with, for the pass it is about to draw into.
11242
+ *
11243
+ * **The attachments as well as the encoder**: a pass drawn after `endFrame` lands on the canvas
11244
+ * at one sample where the frame's own is the scene target at the scene's samples, and a pipeline
11245
+ * built for the one is refused by the other. The cache a mesh drawn here would use is the answer,
11246
+ * so the two cannot disagree.
11247
+ */
11248
+ fillPassContext(pass) {
11249
+ const target = this.targetPipelines();
11250
+ const context = this.passContext;
11251
+ context.pass = pass;
11252
+ /* The frame's grade, so a contributed pass can obey the 2026-08-17 rule. See `PassContext`. */
11253
+ context.outputTransform = this.gradeCode();
11254
+ context.outputExposure = this.gradeExposure();
11255
+ context.format = target.format;
11256
+ context.samples = target.sampleCount;
11257
+ return context;
11258
+ }
11152
11259
  /**
11153
11260
  * Let something outside this file draw into the frame. See `Renderer.registerPass`.
11154
11261
  *
@@ -11286,14 +11393,12 @@ export class WebGPURenderer {
11286
11393
  const pass = this.openPass();
11287
11394
  if (pass === null)
11288
11395
  return;
11289
- this.passContext.pass = pass;
11290
- /* The frame's grade, so a contributed pass can obey the 2026-08-17 rule. See `PassContext`. */
11291
- this.passContext.outputTransform = this.gradeCode();
11292
- this.passContext.outputExposure = this.gradeExposure();
11293
- definition.draw(this.passContext);
11396
+ definition.draw(this.fillPassContext(pass));
11294
11397
  return;
11295
11398
  }
11296
11399
  recordNode(this.arena, VERB_PASS, this.passReads.get(handle) ?? 0, this.currentTarget(), handle, 0);
11400
+ if (this.framePresented)
11401
+ this.queueOverlayFlush();
11297
11402
  }
11298
11403
  /** Let go of a pass. A handle kept past this draws nothing; see `PassHandle`. */
11299
11404
  unregisterPass(handle) {
@@ -11485,7 +11590,10 @@ export class WebGPURenderer {
11485
11590
  * **The clear is a drawn quad rather than a clear**, because WebGPU clears whole attachments
11486
11591
  * and cannot confine one to a scissor. See `insetPass.ts`.
11487
11592
  *
11488
- * Returns the drawing-buffer height of the inset, as the other backend does.
11593
+ * Returns the **aspect** of the inset, width over height in the pixels its viewport got, as the
11594
+ * other backend does — for `camera.updateMatrices`. With no pass to draw into, a lost device,
11595
+ * it is the aspect of the box asked for: there are no pixels to measure, and a zero would be a
11596
+ * projection of infinities in the caller's camera.
11489
11597
  */
11490
11598
  beginInset(rect, clearColor) {
11491
11599
  /*
@@ -11497,7 +11605,7 @@ export class WebGPURenderer {
11497
11605
  this.flushGraph();
11498
11606
  const pass = this.openPass();
11499
11607
  if (this.surface.lost || pass === null)
11500
- return 0;
11608
+ return Math.max(rect.width, 1) / Math.max(rect.height, 1);
11501
11609
  const canvas = this.surface.context.canvas;
11502
11610
  const cssWidth = Math.max(canvas.clientWidth, 1);
11503
11611
  const cssHeight = Math.max(canvas.clientHeight, 1);
@@ -11594,6 +11702,44 @@ export class WebGPURenderer {
11594
11702
  */
11595
11703
  return w / h;
11596
11704
  }
11705
+ /**
11706
+ * Draw what follows as a **view model**, in the nearest sliver of depth, until `endViewModel`.
11707
+ *
11708
+ * The twin of the WebGL2 method, which carries the reasons. Here the squeeze is the viewport's
11709
+ * depth bounds, which are pass state applied when a draw is issued, so both ends flush the graph
11710
+ * as an inset's do, and a pass opened in between is given the bounds again in `openPass`.
11711
+ */
11712
+ beginViewModel(share = VIEW_MODEL_DEPTH_SHARE) {
11713
+ if (this.quality.frameGraph)
11714
+ this.flushGraph();
11715
+ viewModelDepthRange(this.reversedDepth, share, this.viewModelRange);
11716
+ this.viewModelActive = true;
11717
+ const pass = this.openPass();
11718
+ if (this.surface.lost || pass === null)
11719
+ return;
11720
+ this.squeezeViewport(pass);
11721
+ }
11722
+ /** Give the whole depth range back. Safe to call without a matching `beginViewModel`. */
11723
+ endViewModel() {
11724
+ if (!this.viewModelActive)
11725
+ return;
11726
+ if (this.quality.frameGraph)
11727
+ this.flushGraph();
11728
+ this.viewModelActive = false;
11729
+ const pass = this.pass;
11730
+ if (this.surface.lost || pass === null)
11731
+ return;
11732
+ const [width, height] = this.passSize();
11733
+ pass.setViewport(0, 0, width, height, 0, 1);
11734
+ }
11735
+ /** The view model's depth bounds on the whole of the pass. */
11736
+ squeezeViewport(pass) {
11737
+ const [width, height] = this.passSize();
11738
+ pass.setViewport(0, 0, width, height, this.viewModelRange[0], this.viewModelRange[1]);
11739
+ }
11740
+ /** Whether a view model is being drawn: `openPass` re-applies it, `endFrame` closes it. */
11741
+ viewModelActive = false;
11742
+ viewModelRange = [0, 1];
11597
11743
  /** Give the whole canvas back. Safe to call without a matching `beginInset`. */
11598
11744
  endInset() {
11599
11745
  /*
@@ -12663,6 +12809,9 @@ export class WebGPURenderer {
12663
12809
  this.iesRows = this.iesAtlas.height;
12664
12810
  this.iesPlanes = this.iesAtlas.planes;
12665
12811
  this.cookieTiles = this.cookieImages.length;
12812
+ /* The first profile or cookie is when the lit pipelines start reading them. See `litSwitches`. */
12813
+ if (this.iesRows > 1 || this.cookieTiles > 0)
12814
+ this.enableLit('LIGHT_FIXTURES');
12666
12815
  return this.fixtureView;
12667
12816
  }
12668
12817
  /**
@@ -12739,6 +12888,8 @@ export class WebGPURenderer {
12739
12888
  device.queue.writeTexture({ texture: atlas }, field.atlas, { bytesPerRow: aw * 8, rowsPerImage: ah }, [aw, ah, ad]);
12740
12889
  this.driftIndex = index;
12741
12890
  this.driftAtlas = atlas;
12891
+ /* The first volume is when the lit pipelines start reading one. See `litSwitches`. */
12892
+ this.enableLit('DRIFT_LIGHT');
12742
12893
  this.driftIndexView = index.createView({ dimension: '3d' });
12743
12894
  this.driftAtlasView = atlas.createView({ dimension: '3d' });
12744
12895
  this.bakeLightField = {
@@ -3,6 +3,7 @@ import { SDFTEXT_BINDINGS, SDF_TEXT_FRAG_WGSL, SDF_TEXT_VERT_WGSL, } from '../..
3
3
  import { DEPTH_FORMAT } from './flatPass.js';
4
4
  import { SdfTextLayout } from '../../sdfTextLayout.js';
5
5
  import { shaderModule } from './shaderModules.js';
6
+ import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.js';
6
7
  /**
7
8
  * The SDF text pass: a string as flat, textured quads, drawn into the scene.
8
9
  *
@@ -183,7 +184,7 @@ export function sdfTextPipeline(cache, device, layout) {
183
184
  format: cache.format,
184
185
  blend: {
185
186
  color: { srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha', operation: 'add' },
186
- alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
187
+ alpha: SCENE_ALPHA_COVERS,
187
188
  },
188
189
  },
189
190
  ],
@@ -3,6 +3,7 @@ import { TEXT_BINDINGS, TEXT_FRAG_WGSL, TEXT_VERT_WGSL, } from '../../shaders/ge
3
3
  import { MAX_CELLS, TEXT_CUBE, TextLayout } from '../../textLayout.js';
4
4
  import { DEPTH_FORMAT } from './flatPass.js';
5
5
  import { shaderModule } from './shaderModules.js';
6
+ import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.js';
6
7
  /**
7
8
  * The text pass: a string as instanced glyph cubes, drawn over the scene.
8
9
  *
@@ -174,7 +175,7 @@ export function textPipeline(cache, device, layout) {
174
175
  format: cache.format,
175
176
  blend: {
176
177
  color: { srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha', operation: 'add' },
177
- alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
178
+ alpha: SCENE_ALPHA_COVERS,
178
179
  },
179
180
  },
180
181
  ],