@aura3d/rendering 4.0.2 → 4.0.4

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 (240) hide show
  1. package/dist/EnvironmentPlatform.d.ts.map +1 -1
  2. package/dist/EnvironmentPlatform.js +6 -2
  3. package/dist/EnvironmentPlatform.js.map +1 -1
  4. package/dist/ForwardPass.d.ts +2 -0
  5. package/dist/ForwardPass.d.ts.map +1 -1
  6. package/dist/ForwardPass.js +70 -5
  7. package/dist/ForwardPass.js.map +1 -1
  8. package/dist/Geometry.d.ts +40 -0
  9. package/dist/Geometry.d.ts.map +1 -1
  10. package/dist/Geometry.js +39 -2
  11. package/dist/Geometry.js.map +1 -1
  12. package/dist/IndexBuffer.d.ts +16 -1
  13. package/dist/IndexBuffer.d.ts.map +1 -1
  14. package/dist/IndexBuffer.js +46 -1
  15. package/dist/IndexBuffer.js.map +1 -1
  16. package/dist/InstancedPBRMaterial.d.ts +2 -0
  17. package/dist/InstancedPBRMaterial.d.ts.map +1 -1
  18. package/dist/InstancedPBRMaterial.js +1 -0
  19. package/dist/InstancedPBRMaterial.js.map +1 -1
  20. package/dist/Material.d.ts +9 -0
  21. package/dist/Material.d.ts.map +1 -1
  22. package/dist/Material.js +3 -0
  23. package/dist/Material.js.map +1 -1
  24. package/dist/PBRMaterial.d.ts +2 -0
  25. package/dist/PBRMaterial.d.ts.map +1 -1
  26. package/dist/PBRMaterial.js +1 -0
  27. package/dist/PBRMaterial.js.map +1 -1
  28. package/dist/PortableShaderMaterial.d.ts +13 -3
  29. package/dist/PortableShaderMaterial.d.ts.map +1 -1
  30. package/dist/PortableShaderMaterial.js +24 -12
  31. package/dist/PortableShaderMaterial.js.map +1 -1
  32. package/dist/RenderDevice.d.ts +98 -0
  33. package/dist/RenderDevice.d.ts.map +1 -1
  34. package/dist/RenderDevice.js +129 -0
  35. package/dist/RenderDevice.js.map +1 -1
  36. package/dist/Renderer.d.ts +94 -0
  37. package/dist/Renderer.d.ts.map +1 -1
  38. package/dist/Renderer.js +321 -26
  39. package/dist/Renderer.js.map +1 -1
  40. package/dist/Sampler.d.ts +35 -0
  41. package/dist/Sampler.d.ts.map +1 -1
  42. package/dist/Sampler.js +96 -0
  43. package/dist/Sampler.js.map +1 -1
  44. package/dist/ShaderLibraryCore.d.ts +14 -0
  45. package/dist/ShaderLibraryCore.d.ts.map +1 -1
  46. package/dist/ShaderLibraryCore.js +94 -4
  47. package/dist/ShaderLibraryCore.js.map +1 -1
  48. package/dist/Texture.d.ts +53 -1
  49. package/dist/Texture.d.ts.map +1 -1
  50. package/dist/Texture.js +53 -2
  51. package/dist/Texture.js.map +1 -1
  52. package/dist/UnlitMaterial.d.ts +46 -0
  53. package/dist/UnlitMaterial.d.ts.map +1 -1
  54. package/dist/UnlitMaterial.js +32 -3
  55. package/dist/UnlitMaterial.js.map +1 -1
  56. package/dist/VertexFormat.d.ts +19 -1
  57. package/dist/VertexFormat.d.ts.map +1 -1
  58. package/dist/VertexFormat.js +40 -1
  59. package/dist/VertexFormat.js.map +1 -1
  60. package/dist/WebGL2Device.d.ts +16 -0
  61. package/dist/WebGL2Device.d.ts.map +1 -1
  62. package/dist/WebGL2Device.js +31 -6
  63. package/dist/WebGL2Device.js.map +1 -1
  64. package/dist/WebGPUDevice.d.ts +92 -1
  65. package/dist/WebGPUDevice.d.ts.map +1 -1
  66. package/dist/WebGPUDevice.js +417 -44
  67. package/dist/WebGPUDevice.js.map +1 -1
  68. package/dist/batching/BatchedGeometryPool.d.ts +22 -1
  69. package/dist/batching/BatchedGeometryPool.d.ts.map +1 -1
  70. package/dist/batching/BatchedGeometryPool.js +50 -2
  71. package/dist/batching/BatchedGeometryPool.js.map +1 -1
  72. package/dist/batching/DrawDataTexture.d.ts +21 -1
  73. package/dist/batching/DrawDataTexture.d.ts.map +1 -1
  74. package/dist/batching/DrawDataTexture.js +30 -4
  75. package/dist/batching/DrawDataTexture.js.map +1 -1
  76. package/dist/batching/MultiDrawBatch.d.ts +26 -0
  77. package/dist/batching/MultiDrawBatch.d.ts.map +1 -1
  78. package/dist/batching/MultiDrawBatch.js +64 -2
  79. package/dist/batching/MultiDrawBatch.js.map +1 -1
  80. package/dist/batching/index.d.ts +2 -2
  81. package/dist/batching/index.d.ts.map +1 -1
  82. package/dist/batching/index.js +2 -2
  83. package/dist/batching/index.js.map +1 -1
  84. package/dist/compute/ComputeContext.d.ts +84 -0
  85. package/dist/compute/ComputeContext.d.ts.map +1 -0
  86. package/dist/compute/ComputeContext.js +371 -0
  87. package/dist/compute/ComputeContext.js.map +1 -0
  88. package/dist/compute/index.d.ts +4 -0
  89. package/dist/compute/index.d.ts.map +1 -0
  90. package/dist/compute/index.js +3 -0
  91. package/dist/compute/index.js.map +1 -0
  92. package/dist/compute/types.d.ts +108 -0
  93. package/dist/compute/types.d.ts.map +1 -0
  94. package/dist/compute/types.js +25 -0
  95. package/dist/compute/types.js.map +1 -0
  96. package/dist/contracts/core.d.ts +1 -1
  97. package/dist/contracts/core.d.ts.map +1 -1
  98. package/dist/contracts/core.js.map +1 -1
  99. package/dist/contracts/flags.state.d.ts.map +1 -1
  100. package/dist/contracts/flags.state.js +20 -1
  101. package/dist/contracts/flags.state.js.map +1 -1
  102. package/dist/contracts/output.d.ts +1 -1
  103. package/dist/contracts/output.d.ts.map +1 -1
  104. package/dist/contracts/output.js.map +1 -1
  105. package/dist/contracts/post.d.ts +7 -0
  106. package/dist/contracts/post.d.ts.map +1 -1
  107. package/dist/contracts/post.js.map +1 -1
  108. package/dist/contracts/renderItem.d.ts +15 -0
  109. package/dist/contracts/renderItem.d.ts.map +1 -1
  110. package/dist/contracts/renderSource.d.ts +3 -1
  111. package/dist/contracts/renderSource.d.ts.map +1 -1
  112. package/dist/contracts/sampling.d.ts +23 -0
  113. package/dist/contracts/sampling.d.ts.map +1 -1
  114. package/dist/contracts/sampling.js.map +1 -1
  115. package/dist/environment/EnvironmentCache.d.ts.map +1 -1
  116. package/dist/environment/EnvironmentCache.js +7 -3
  117. package/dist/environment/EnvironmentCache.js.map +1 -1
  118. package/dist/environment/RoomEnvironmentScene.d.ts +2 -2
  119. package/dist/environment/RoomEnvironmentScene.d.ts.map +1 -1
  120. package/dist/environment/RoomEnvironmentScene.js.map +1 -1
  121. package/dist/geometry/Circle.d.ts +14 -0
  122. package/dist/geometry/Circle.d.ts.map +1 -0
  123. package/dist/geometry/Circle.js +31 -0
  124. package/dist/geometry/Circle.js.map +1 -0
  125. package/dist/geometry/Cone.d.ts +37 -0
  126. package/dist/geometry/Cone.d.ts.map +1 -0
  127. package/dist/geometry/Cone.js +129 -0
  128. package/dist/geometry/Cone.js.map +1 -0
  129. package/dist/geometry/Convex.d.ts +19 -0
  130. package/dist/geometry/Convex.d.ts.map +1 -0
  131. package/dist/geometry/Convex.js +27 -0
  132. package/dist/geometry/Convex.js.map +1 -0
  133. package/dist/geometry/Edges.d.ts +15 -0
  134. package/dist/geometry/Edges.d.ts.map +1 -0
  135. package/dist/geometry/Edges.js +98 -0
  136. package/dist/geometry/Edges.js.map +1 -0
  137. package/dist/geometry/Extrude.d.ts +29 -0
  138. package/dist/geometry/Extrude.d.ts.map +1 -0
  139. package/dist/geometry/Extrude.js +403 -0
  140. package/dist/geometry/Extrude.js.map +1 -0
  141. package/dist/geometry/Lathe.d.ts +20 -0
  142. package/dist/geometry/Lathe.d.ts.map +1 -0
  143. package/dist/geometry/Lathe.js +94 -0
  144. package/dist/geometry/Lathe.js.map +1 -0
  145. package/dist/geometry/Polyhedron.d.ts +21 -0
  146. package/dist/geometry/Polyhedron.d.ts.map +1 -0
  147. package/dist/geometry/Polyhedron.js +201 -0
  148. package/dist/geometry/Polyhedron.js.map +1 -0
  149. package/dist/geometry/Primitives.d.ts +38 -2
  150. package/dist/geometry/Primitives.d.ts.map +1 -1
  151. package/dist/geometry/Primitives.js +114 -0
  152. package/dist/geometry/Primitives.js.map +1 -1
  153. package/dist/geometry/Ring.d.ts +16 -0
  154. package/dist/geometry/Ring.d.ts.map +1 -0
  155. package/dist/geometry/Ring.js +44 -0
  156. package/dist/geometry/Ring.js.map +1 -0
  157. package/dist/geometry/RoundedBox.d.ts +22 -0
  158. package/dist/geometry/RoundedBox.d.ts.map +1 -0
  159. package/dist/geometry/RoundedBox.js +191 -0
  160. package/dist/geometry/RoundedBox.js.map +1 -0
  161. package/dist/geometry/Shape.d.ts +15 -0
  162. package/dist/geometry/Shape.d.ts.map +1 -0
  163. package/dist/geometry/Shape.js +44 -0
  164. package/dist/geometry/Shape.js.map +1 -0
  165. package/dist/geometry/Support.d.ts +137 -0
  166. package/dist/geometry/Support.d.ts.map +1 -0
  167. package/dist/geometry/Support.js +1066 -0
  168. package/dist/geometry/Support.js.map +1 -0
  169. package/dist/geometry/TorusKnot.d.ts +18 -0
  170. package/dist/geometry/TorusKnot.d.ts.map +1 -0
  171. package/dist/geometry/TorusKnot.js +83 -0
  172. package/dist/geometry/TorusKnot.js.map +1 -0
  173. package/dist/geometry/Tube.d.ts +19 -0
  174. package/dist/geometry/Tube.d.ts.map +1 -0
  175. package/dist/geometry/Tube.js +70 -0
  176. package/dist/geometry/Tube.js.map +1 -0
  177. package/dist/geometry/Wireframe.d.ts +19 -0
  178. package/dist/geometry/Wireframe.d.ts.map +1 -0
  179. package/dist/geometry/Wireframe.js +63 -0
  180. package/dist/geometry/Wireframe.js.map +1 -0
  181. package/dist/index.d.ts +38 -7
  182. package/dist/index.d.ts.map +1 -1
  183. package/dist/index.js +25 -3
  184. package/dist/index.js.map +1 -1
  185. package/dist/lanes/prd11.js +1 -1
  186. package/dist/lanes/prd11.js.map +1 -1
  187. package/dist/output/ToneMappingOperators.glsl.d.ts +1 -1
  188. package/dist/output/ToneMappingOperators.glsl.d.ts.map +1 -1
  189. package/dist/output/ToneMappingOperators.glsl.js +9 -1
  190. package/dist/output/ToneMappingOperators.glsl.js.map +1 -1
  191. package/dist/post/v2Entry.d.ts +1 -0
  192. package/dist/post/v2Entry.d.ts.map +1 -1
  193. package/dist/post/v2Entry.js.map +1 -1
  194. package/dist/post/v2Stages.d.ts +30 -1
  195. package/dist/post/v2Stages.d.ts.map +1 -1
  196. package/dist/post/v2Stages.js +87 -21
  197. package/dist/post/v2Stages.js.map +1 -1
  198. package/dist/program/ProgramCache.d.ts.map +1 -1
  199. package/dist/program/ProgramCache.js +12 -0
  200. package/dist/program/ProgramCache.js.map +1 -1
  201. package/dist/program/ProgramGenerator.d.ts.map +1 -1
  202. package/dist/program/ProgramGenerator.js +3 -0
  203. package/dist/program/ProgramGenerator.js.map +1 -1
  204. package/dist/reference/PostProcessPass.d.ts +1 -1
  205. package/dist/reference/PostProcessPass.d.ts.map +1 -1
  206. package/dist/reference/PostProcessPass.js +8 -1
  207. package/dist/reference/PostProcessPass.js.map +1 -1
  208. package/dist/renderer/CullingBatching.d.ts +44 -2
  209. package/dist/renderer/CullingBatching.d.ts.map +1 -1
  210. package/dist/renderer/CullingBatching.js +66 -11
  211. package/dist/renderer/CullingBatching.js.map +1 -1
  212. package/dist/shaders/chunks/lighting_mirrors.d.ts +2 -2
  213. package/dist/shaders/chunks/lighting_mirrors.d.ts.map +1 -1
  214. package/dist/shaders/chunks/lighting_mirrors.js.map +1 -1
  215. package/dist/shadows/DirectionalCascadeFitter.d.ts +8 -8
  216. package/dist/shadows/DirectionalCascadeFitter.d.ts.map +1 -1
  217. package/dist/shadows/DirectionalCascadeFitter.js.map +1 -1
  218. package/dist/shadows/ShadowAtlas.d.ts +5 -5
  219. package/dist/shadows/ShadowAtlas.d.ts.map +1 -1
  220. package/dist/shadows/ShadowAtlas.js.map +1 -1
  221. package/dist/vfx/ProceduralVolumeEmitter.d.ts +11 -0
  222. package/dist/vfx/ProceduralVolumeEmitter.d.ts.map +1 -1
  223. package/dist/vfx/ProceduralVolumeEmitter.js +20 -6
  224. package/dist/vfx/ProceduralVolumeEmitter.js.map +1 -1
  225. package/dist/webgl2/ContextLifecycle.d.ts +36 -0
  226. package/dist/webgl2/ContextLifecycle.d.ts.map +1 -1
  227. package/dist/webgl2/ContextLifecycle.js +110 -2
  228. package/dist/webgl2/ContextLifecycle.js.map +1 -1
  229. package/dist/webgl2/LegacyPost.d.ts +16 -1
  230. package/dist/webgl2/LegacyPost.d.ts.map +1 -1
  231. package/dist/webgl2/LegacyPost.js +49 -16
  232. package/dist/webgl2/LegacyPost.js.map +1 -1
  233. package/dist/webgl2/MultiDraw.d.ts.map +1 -1
  234. package/dist/webgl2/MultiDraw.js +42 -2
  235. package/dist/webgl2/MultiDraw.js.map +1 -1
  236. package/dist/webgl2/TextureUpload.d.ts +7 -0
  237. package/dist/webgl2/TextureUpload.d.ts.map +1 -1
  238. package/dist/webgl2/TextureUpload.js +54 -10
  239. package/dist/webgl2/TextureUpload.js.map +1 -1
  240. package/package.json +3 -3
package/dist/Renderer.js CHANGED
@@ -258,6 +258,27 @@ export class Renderer {
258
258
  }
259
259
  return this.device.getDiagnostics();
260
260
  }
261
+ /**
262
+ * threejs-parity L7/SR-07: `renderer.precompile` — compiles (and awaits) the
263
+ * generated programs a source needs before first render, without submitting
264
+ * a frame. Returns the number of programs this call compiled and wall time.
265
+ */
266
+ async precompile(sourceOrInput, camera) {
267
+ this.assertAlive();
268
+ const startedAt = typeof performance !== "undefined" ? performance.now() : Date.now();
269
+ const { source, camera: inputCamera } = normalizeRendererInput(sourceOrInput, camera);
270
+ sceneFromSource(source)?.updateWorldTransforms();
271
+ const resolvedCamera = resolveCamera(source, inputCamera, { width: this.width, height: this.height }, { allowSceneCamera: true });
272
+ const cameraViewProjection = resolvedCamera?.viewProjectionMatrix;
273
+ const collected = collectRenderItemsWithDiagnostics(source, cameraViewProjection, resolvedCamera?.camera);
274
+ const before = this.device.getDiagnostics().programCompileCount ?? 0;
275
+ await this.warmGeneratedPrograms(collected.items, collectRenderLights(source), collectEnvironmentLighting(source), collectEnvironmentFog(source), collectForwardShadowMap(source), cameraViewProjection);
276
+ const after = this.device.getDiagnostics().programCompileCount ?? before;
277
+ return {
278
+ compiledPrograms: Math.max(0, after - before),
279
+ durationMs: (typeof performance !== "undefined" ? performance.now() : Date.now()) - startedAt
280
+ };
281
+ }
261
282
  /** C-28 (#91): run `fn` under the frameStats "post" scope when a monitor exists. */
262
283
  scopePost(fn) {
263
284
  return this.frameStatsMonitor ? this.frameStatsMonitor.scope("post", fn) : fn();
@@ -314,7 +335,7 @@ export class Renderer {
314
335
  const devicePixelRatio = resolveCanvasPixelRatio({
315
336
  devicePixelRatio: globalThis.devicePixelRatio ?? 1,
316
337
  tier: this.qualityTier ?? this.liveQualityTier()?.settings ?? { maxPixelRatio: Number.POSITIVE_INFINITY },
317
- explicit: options.devicePixelRatio,
338
+ explicit: options.devicePixelRatio ?? this.pixelRatioOverride,
318
339
  resolution: this.resolutionOptions
319
340
  });
320
341
  if (![cssWidth, cssHeight, devicePixelRatio].every(Number.isFinite) || cssWidth <= 0 || cssHeight <= 0 || devicePixelRatio <= 0) {
@@ -362,6 +383,68 @@ export class Renderer {
362
383
  backingHeight: this.height
363
384
  };
364
385
  }
386
+ /**
387
+ * threejs-parity L7/SH-05: `setPixelRatio` — overrides the device pixel
388
+ * ratio used by `resizeToDisplay` (a `devicePixelRatio` option there still
389
+ * wins for that call). Applies immediately on a canvas-backed renderer;
390
+ * stored for the next resize otherwise.
391
+ */
392
+ pixelRatioOverride;
393
+ setPixelRatio(ratio) {
394
+ this.assertAlive();
395
+ if (!Number.isFinite(ratio) || ratio <= 0) {
396
+ throw new RenderDeviceError("Pixel ratio must be a positive finite number", "INVALID_PIXEL_RATIO", { ratio });
397
+ }
398
+ this.pixelRatioOverride = ratio;
399
+ if (this.canvas)
400
+ this.resizeToDisplay();
401
+ }
402
+ get pixelRatio() {
403
+ return this.pixelRatioOverride ?? this.resolutionReport.pixelRatio;
404
+ }
405
+ /**
406
+ * threejs-parity L7/SH-05: `setClearColor` — the default clear used when the
407
+ * scene has no background (three's `setClearColor(color, alpha)`).
408
+ */
409
+ setClearColor(color, alpha) {
410
+ this.assertAlive();
411
+ const [r, g, b, a] = color;
412
+ const resolved = [r ?? 0, g ?? 0, b ?? 0, alpha ?? a ?? 1];
413
+ if (!resolved.every((v) => Number.isFinite(v) && v >= 0 && v <= 1)) {
414
+ throw new RenderDeviceError("Clear color channels must be finite numbers in [0, 1]", "INVALID_CLEAR_COLOR", { color: [...color], alpha });
415
+ }
416
+ this.clearColor = resolved;
417
+ }
418
+ get clearColorValue() {
419
+ return this.clearColor;
420
+ }
421
+ /**
422
+ * threejs-parity L7/SR-05: three's `WebGLRenderer.info` — per-frame render
423
+ * counters plus live resource gauges from the device diagnostics.
424
+ */
425
+ submittedFrames = 0;
426
+ get info() {
427
+ const d = this.device.getDiagnostics();
428
+ const memoryBytes = (d.bufferBytes ?? 0) + (d.textureBytes ?? 0);
429
+ return {
430
+ render: {
431
+ calls: d.drawCalls,
432
+ triangles: d.triangles ?? 0,
433
+ points: d.points ?? 0,
434
+ lines: d.lines ?? 0,
435
+ frame: this.submittedFrames
436
+ },
437
+ memory: {
438
+ geometries: d.buffers,
439
+ textures: d.textures ?? 0,
440
+ ...(d.renderTargets !== undefined ? { renderTargets: d.renderTargets } : {}),
441
+ programs: d.shaders
442
+ },
443
+ ...(d.approximateGpuMemoryBytes !== undefined || memoryBytes > 0
444
+ ? { approximateGpuMemoryBytes: d.approximateGpuMemoryBytes ?? memoryBytes }
445
+ : {})
446
+ };
447
+ }
365
448
  /**
366
449
  * Feeds one frame's timings to the resolution governor (C-28 `gpuMs` wins
367
450
  * over the CPU interval when lane 11's timer queries provide it).
@@ -399,10 +482,101 @@ export class Renderer {
399
482
  this.temporalHistory.commit();
400
483
  }
401
484
  resetTemporalHistory(_reason = "explicit-reset") { this.temporalHistory.reset(); this.lastTemporalCameraPosition = undefined; }
485
+ /**
486
+ * threejs-parity L7/SH-06: a `{ target }` (or `source.renderTarget`) frame
487
+ * executes at the target's own dimensions — three's `setRenderTarget` +
488
+ * `render` semantics. Viewport state restores when the frame exits.
489
+ */
490
+ adoptExplicitTargetSize(source, run) {
491
+ const target = collectRenderTarget(source);
492
+ if (!target || (target.width === this.width && target.height === this.height))
493
+ return run();
494
+ const prevWidth = this.width;
495
+ const prevHeight = this.height;
496
+ this.width = target.width;
497
+ this.height = target.height;
498
+ // Stale temporal history at the old size would misproject; drop it.
499
+ this.temporalHistory.dispose();
500
+ try {
501
+ return run();
502
+ }
503
+ finally {
504
+ this.width = prevWidth;
505
+ this.height = prevHeight;
506
+ }
507
+ }
508
+ async adoptExplicitTargetSizeAsync(source, run) {
509
+ const target = collectRenderTarget(source);
510
+ if (!target || (target.width === this.width && target.height === this.height))
511
+ return run();
512
+ const prevWidth = this.width;
513
+ const prevHeight = this.height;
514
+ this.width = target.width;
515
+ this.height = target.height;
516
+ this.temporalHistory.dispose();
517
+ try {
518
+ return await run();
519
+ }
520
+ finally {
521
+ this.width = prevWidth;
522
+ this.height = prevHeight;
523
+ }
524
+ }
402
525
  render(sourceOrInput, camera) {
403
526
  // T0-28: ambient `rendererQrFlags()` readers (Lighting, Background, post…)
404
527
  // see this renderer's flags for the duration of the synchronous frame.
405
- return withRendererQrFlags(this.device, () => this.renderFrame(sourceOrInput, camera));
528
+ const views = collectRendererViews(sourceOrInput);
529
+ if (views) {
530
+ let last;
531
+ views.forEach((view, index) => {
532
+ const viewInput = viewFrameInput(sourceOrInput, camera, view);
533
+ this.pendingViewState = { viewport: view.viewport, scissor: view.scissor ?? view.viewport, skipClear: index > 0 };
534
+ try {
535
+ last = withRendererQrFlags(this.device, () => this.adoptExplicitTargetSize(viewInput.source, () => this.renderFrame(viewInput)));
536
+ }
537
+ finally {
538
+ this.pendingViewState = null;
539
+ }
540
+ });
541
+ return last;
542
+ }
543
+ this.pendingViewState = collectSingleViewState(sourceOrInput);
544
+ try {
545
+ return withRendererQrFlags(this.device, () => this.adoptExplicitTargetSize(normalizeRendererInput(sourceOrInput, camera).source, () => this.renderFrame(sourceOrInput, camera)));
546
+ }
547
+ finally {
548
+ this.pendingViewState = null;
549
+ }
550
+ }
551
+ /** SC-01: viewport/scissor requested for the frame about to bind its main target; consumed by `clearAndApplyView`. */
552
+ pendingViewState = null;
553
+ clearAndApplyView(clearColor) {
554
+ const state = this.pendingViewState;
555
+ this.pendingViewState = null;
556
+ if (!state) {
557
+ this.device.clear(clearColor);
558
+ return;
559
+ }
560
+ if (!this.device.setViewport || !this.device.setScissor || !this.device.setScissorTest) {
561
+ throw new RenderDeviceError("This render device does not support viewport/scissor rendering.", "VIEWPORT_UNSUPPORTED", { backend: this.device.kind });
562
+ }
563
+ // The clear runs before the viewport/scissor apply so it covers the whole
564
+ // target (a WebGPU clear is a full-attachment load op and cannot be scissored).
565
+ if (!state.skipClear)
566
+ this.device.clear(clearColor);
567
+ validateViewRect("viewport", state.viewport, this.width, this.height);
568
+ this.device.setViewport(state.viewport.x, state.viewport.y, state.viewport.width, state.viewport.height);
569
+ if (state.scissor) {
570
+ validateViewRect("scissor", state.scissor, this.width, this.height);
571
+ this.device.setScissor(state.scissor.x, state.scissor.y, state.scissor.width, state.scissor.height);
572
+ this.device.setScissorTest(true);
573
+ }
574
+ }
575
+ assertViewsSupported(postprocess) {
576
+ if (this.pendingViewState && postprocess) {
577
+ this.pendingViewState = null;
578
+ throw new RenderDeviceError("Viewport/scissor views (ArrayCamera / StereoCamera) cannot be combined with postprocess; render the views without effects.", "VIEWPORT_POSTPROCESS_UNSUPPORTED", { backend: this.device.kind });
579
+ }
406
580
  }
407
581
  renderFrame(sourceOrInput, camera) {
408
582
  this.assertAlive();
@@ -426,7 +600,7 @@ export class Renderer {
426
600
  throw new RenderDeviceError("RenderSource requires an explicit camera but none was supplied or found in the scene.", "CAMERA_REQUIRED");
427
601
  }
428
602
  let cameraViewProjection = resolvedCamera?.viewProjectionMatrix;
429
- const collectedItems = collectRenderItemsWithDiagnostics(source, cameraViewProjection, resolvedCamera?.camera, rendererQrFlags().on("A3D_QR_LIGHTING"));
603
+ const collectedItems = collectRenderItemsWithDiagnostics(source, cameraViewProjection, resolvedCamera?.camera, rendererQrFlags().on("A3D_QR_LIGHTING"), resolvedCamera?.layerMask);
430
604
  const collectionDiagnostics = collectedItems.diagnostics;
431
605
  let items = collectedItems.items;
432
606
  const lights = collectRenderLights(source);
@@ -440,7 +614,7 @@ export class Renderer {
440
614
  this.warmGeneratedProgramsSync(items, lights, environmentLighting, environmentFog, explicitShadowMap, cameraViewProjection);
441
615
  const sourceCameraPosition = collectSourceCameraPosition(source);
442
616
  const explicitRenderTarget = collectRenderTarget(source);
443
- validateExplicitRenderTarget(explicitRenderTarget, this.width, this.height);
617
+ validateExplicitRenderTarget(explicitRenderTarget);
444
618
  let cameraPosition = sourceCameraPosition ?? resolvedCamera?.cameraPosition;
445
619
  // C-05/T0-07: decide the frame's output route before the hooks exist, so
446
620
  // contributors see the same v2/legacy resource names the graph writes.
@@ -464,6 +638,7 @@ export class Renderer {
464
638
  }
465
639
  }
466
640
  let postprocess = collectPostprocess(source);
641
+ this.assertViewsSupported(postprocess);
467
642
  if (!postprocess?.temporal || (!postprocess.motionBlur && !postprocess.taa)) {
468
643
  this.temporalHistory.reset();
469
644
  this.lastTemporalCameraPosition = undefined;
@@ -588,7 +763,7 @@ export class Renderer {
588
763
  else {
589
764
  this.device.setRenderTarget(explicitRenderTarget ?? null);
590
765
  }
591
- this.device.clear(qrOutput && !postprocess ? this.outputClearColor ?? this.clearColor : this.clearColor);
766
+ this.clearAndApplyView(qrOutput && !postprocess ? this.outputClearColor ?? this.clearColor : this.clearColor);
592
767
  if (environmentBackground) {
593
768
  this.graph.addPass(new EnvironmentBackgroundPass({
594
769
  ...environmentBackground,
@@ -646,6 +821,9 @@ export class Renderer {
646
821
  cameraPosition,
647
822
  cameraViewMatrix: resolvedCamera?.viewMatrix,
648
823
  cameraViewProjectionMatrix: rasterViewProjection,
824
+ // SH-02: `material.custom` frame built-ins (u_projectionMatrix, u_time).
825
+ auraFrameCamera: resolvedCamera?.projectionMatrix ? { projectionMatrix: resolvedCamera.projectionMatrix } : undefined,
826
+ timeSeconds: typeof performance !== "undefined" ? performance.now() / 1000 : Date.now() / 1000,
649
827
  outputColorSpace: "linear",
650
828
  shaderLibrary: this.shaderLibrary,
651
829
  backgroundCoverage: coverage,
@@ -687,6 +865,9 @@ export class Renderer {
687
865
  cameraPosition,
688
866
  cameraViewMatrix: resolvedCamera?.viewMatrix,
689
867
  cameraViewProjectionMatrix: rasterViewProjection,
868
+ // SH-02: `material.custom` frame built-ins (u_projectionMatrix, u_time).
869
+ auraFrameCamera: resolvedCamera?.projectionMatrix ? { projectionMatrix: resolvedCamera.projectionMatrix } : undefined,
870
+ timeSeconds: typeof performance !== "undefined" ? performance.now() / 1000 : Date.now() / 1000,
690
871
  outputColorSpace: postprocess ? "linear" : "srgb",
691
872
  shaderLibrary: this.shaderLibrary
692
873
  }));
@@ -760,6 +941,7 @@ export class Renderer {
760
941
  target.dispose();
761
942
  }
762
943
  }
944
+ this.submittedFrames += 1;
763
945
  this.submittedShadowFrameId += 1;
764
946
  if (this.lastShadowEvidence)
765
947
  this.lastShadowEvidence = { ...this.lastShadowEvidence, submissionFrameId: this.submittedShadowFrameId };
@@ -770,6 +952,32 @@ export class Renderer {
770
952
  }));
771
953
  }
772
954
  async renderAsync(sourceOrInput, camera) {
955
+ const views = collectRendererViews(sourceOrInput);
956
+ if (views) {
957
+ let last;
958
+ for (let index = 0; index < views.length; index += 1) {
959
+ const view = views[index];
960
+ const viewInput = viewFrameInput(sourceOrInput, camera, view);
961
+ this.pendingViewState = { viewport: view.viewport, scissor: view.scissor ?? view.viewport, skipClear: index > 0 };
962
+ try {
963
+ last = await this.adoptExplicitTargetSizeAsync(viewInput.source, () => this.renderAsyncFrame(viewInput));
964
+ }
965
+ finally {
966
+ this.pendingViewState = null;
967
+ }
968
+ }
969
+ return last;
970
+ }
971
+ this.pendingViewState = collectSingleViewState(sourceOrInput);
972
+ // threejs-parity L7/SH-06: `{ target }` frames run at the target's size.
973
+ try {
974
+ return await this.adoptExplicitTargetSizeAsync(normalizeRendererInput(sourceOrInput, camera).source, () => this.renderAsyncFrame(sourceOrInput, camera));
975
+ }
976
+ finally {
977
+ this.pendingViewState = null;
978
+ }
979
+ }
980
+ async renderAsyncFrame(sourceOrInput, camera) {
773
981
  this.assertAlive();
774
982
  this.frameStatsMonitor?.begin(typeof performance !== "undefined" ? performance.now() : Date.now());
775
983
  // T0-06: reset the per-frame sync-compile bound for this render.
@@ -790,7 +998,7 @@ export class Renderer {
790
998
  throw new RenderDeviceError("RenderSource requires an explicit camera but none was supplied or found in the scene.", "CAMERA_REQUIRED");
791
999
  }
792
1000
  let cameraViewProjection = resolvedCamera?.viewProjectionMatrix;
793
- const collectedItems = collectRenderItemsWithDiagnostics(source, cameraViewProjection, resolvedCamera?.camera, rendererQrFlags().on("A3D_QR_LIGHTING"));
1001
+ const collectedItems = collectRenderItemsWithDiagnostics(source, cameraViewProjection, resolvedCamera?.camera, rendererQrFlags().on("A3D_QR_LIGHTING"), resolvedCamera?.layerMask);
794
1002
  const collectionDiagnostics = collectedItems.diagnostics;
795
1003
  let items = collectedItems.items;
796
1004
  const lights = collectRenderLights(source);
@@ -807,7 +1015,7 @@ export class Renderer {
807
1015
  }
808
1016
  const sourceCameraPosition = collectSourceCameraPosition(source);
809
1017
  const explicitRenderTarget = collectRenderTarget(source);
810
- validateExplicitRenderTarget(explicitRenderTarget, this.width, this.height);
1018
+ validateExplicitRenderTarget(explicitRenderTarget);
811
1019
  let cameraPosition = sourceCameraPosition ?? resolvedCamera?.cameraPosition;
812
1020
  // C-05/T0-07: decide the frame's output route before the hooks exist, so
813
1021
  // contributors see the same v2/legacy resource names the graph writes.
@@ -831,6 +1039,7 @@ export class Renderer {
831
1039
  }
832
1040
  }
833
1041
  let postprocess = collectPostprocess(source);
1042
+ this.assertViewsSupported(postprocess);
834
1043
  if (!postprocess?.temporal || (!postprocess.motionBlur && !postprocess.taa)) {
835
1044
  this.temporalHistory.reset();
836
1045
  this.lastTemporalCameraPosition = undefined;
@@ -955,7 +1164,7 @@ export class Renderer {
955
1164
  else {
956
1165
  this.device.setRenderTarget(explicitRenderTarget ?? null);
957
1166
  }
958
- this.device.clear(qrOutput && !postprocess ? this.outputClearColor ?? this.clearColor : this.clearColor);
1167
+ this.clearAndApplyView(qrOutput && !postprocess ? this.outputClearColor ?? this.clearColor : this.clearColor);
959
1168
  if (environmentBackground) {
960
1169
  this.graph.addPass(new EnvironmentBackgroundPass({
961
1170
  ...environmentBackground,
@@ -1013,6 +1222,9 @@ export class Renderer {
1013
1222
  cameraPosition,
1014
1223
  cameraViewMatrix: resolvedCamera?.viewMatrix,
1015
1224
  cameraViewProjectionMatrix: rasterViewProjection,
1225
+ // SH-02: `material.custom` frame built-ins (u_projectionMatrix, u_time).
1226
+ auraFrameCamera: resolvedCamera?.projectionMatrix ? { projectionMatrix: resolvedCamera.projectionMatrix } : undefined,
1227
+ timeSeconds: typeof performance !== "undefined" ? performance.now() / 1000 : Date.now() / 1000,
1016
1228
  outputColorSpace: "linear",
1017
1229
  shaderLibrary: this.shaderLibrary,
1018
1230
  backgroundCoverage: coverage,
@@ -1054,6 +1266,9 @@ export class Renderer {
1054
1266
  cameraPosition,
1055
1267
  cameraViewMatrix: resolvedCamera?.viewMatrix,
1056
1268
  cameraViewProjectionMatrix: rasterViewProjection,
1269
+ // SH-02: `material.custom` frame built-ins (u_projectionMatrix, u_time).
1270
+ auraFrameCamera: resolvedCamera?.projectionMatrix ? { projectionMatrix: resolvedCamera.projectionMatrix } : undefined,
1271
+ timeSeconds: typeof performance !== "undefined" ? performance.now() / 1000 : Date.now() / 1000,
1057
1272
  outputColorSpace: postprocess ? "linear" : "srgb",
1058
1273
  shaderLibrary: this.shaderLibrary
1059
1274
  }));
@@ -1135,6 +1350,7 @@ export class Renderer {
1135
1350
  target.dispose();
1136
1351
  }
1137
1352
  }
1353
+ this.submittedFrames += 1;
1138
1354
  this.submittedShadowFrameId += 1;
1139
1355
  if (this.lastShadowEvidence)
1140
1356
  this.lastShadowEvidence = { ...this.lastShadowEvidence, submissionFrameId: this.submittedShadowFrameId };
@@ -1548,7 +1764,7 @@ function inferInitialCanvasDimension(canvas, axis) {
1548
1764
  function collectRenderTarget(source) {
1549
1765
  return source instanceof Scene || isIterable(source) ? undefined : source.renderTarget;
1550
1766
  }
1551
- function validateExplicitRenderTarget(target, width, height) {
1767
+ function validateExplicitRenderTarget(target) {
1552
1768
  if (!target)
1553
1769
  return;
1554
1770
  if (target.disposed) {
@@ -1556,25 +1772,81 @@ function validateExplicitRenderTarget(target, width, height) {
1556
1772
  renderTarget: target.label
1557
1773
  });
1558
1774
  }
1559
- if (target.width !== width || target.height !== height) {
1560
- throw new RenderDeviceError("Renderer renderTarget dimensions must match the renderer viewport.", "RENDER_TARGET_VIEWPORT_MISMATCH", {
1561
- renderTarget: target.label,
1562
- targetWidth: target.width,
1563
- targetHeight: target.height,
1564
- rendererWidth: width,
1565
- rendererHeight: height
1566
- });
1567
- }
1775
+ // threejs-parity L7/SH-06: no viewport-dimensions check — a `{ target }`
1776
+ // frame executes at the target's own size (`adoptExplicitTargetSize`).
1568
1777
  }
1569
1778
  function normalizeRendererInput(sourceOrInput, camera) {
1570
1779
  if (isRendererInput(sourceOrInput)) {
1571
- return { source: sourceOrInput.source, camera: camera ?? sourceOrInput.camera };
1780
+ const target = sourceOrInput.target;
1781
+ const source = target ? withExplicitRenderTarget(sourceOrInput.source, target) : sourceOrInput.source;
1782
+ return { source, camera: camera ?? sourceOrInput.camera };
1572
1783
  }
1573
1784
  return { source: sourceOrInput, ...(camera ? { camera } : {}) };
1574
1785
  }
1786
+ function collectRendererViews(sourceOrInput) {
1787
+ if (sourceOrInput instanceof Scene || isIterable(sourceOrInput))
1788
+ return undefined;
1789
+ const views = isRendererInput(sourceOrInput)
1790
+ ? sourceOrInput.views ?? (sourceOrInput.source instanceof Scene || isIterable(sourceOrInput.source) ? undefined : sourceOrInput.source.views)
1791
+ : sourceOrInput.views;
1792
+ if (!views)
1793
+ return undefined;
1794
+ if (views.length === 0) {
1795
+ throw new RenderDeviceError("Renderer views must contain at least one view.", "INVALID_VIEWPORT", { views: 0 });
1796
+ }
1797
+ return views;
1798
+ }
1799
+ function collectSingleViewState(sourceOrInput) {
1800
+ if (sourceOrInput instanceof Scene || isIterable(sourceOrInput) || !isRendererInput(sourceOrInput))
1801
+ return null;
1802
+ if (!sourceOrInput.viewport && !sourceOrInput.scissor)
1803
+ return null;
1804
+ const viewport = sourceOrInput.viewport;
1805
+ const scissor = sourceOrInput.scissor ?? viewport;
1806
+ return {
1807
+ viewport: viewport ?? scissor,
1808
+ scissor,
1809
+ skipClear: false
1810
+ };
1811
+ }
1812
+ function viewFrameInput(sourceOrInput, camera, view) {
1813
+ const normalized = normalizeRendererInput(sourceOrInput, camera);
1814
+ const base = normalized.source instanceof Scene
1815
+ ? { scene: normalized.source }
1816
+ : isIterable(normalized.source) ? { renderItems: normalized.source } : normalized.source;
1817
+ const { views: _views, ...rest } = base;
1818
+ return {
1819
+ source: { ...rest, ...(view.cameraPosition ? { cameraPosition: view.cameraPosition } : {}) },
1820
+ camera: view.camera
1821
+ };
1822
+ }
1823
+ function validateViewRect(label, rect, targetWidth, targetHeight) {
1824
+ const values = [rect.x, rect.y, rect.width, rect.height];
1825
+ if (values.some((value) => !Number.isFinite(value))) {
1826
+ throw new RenderDeviceError(`Renderer ${label} values must be finite.`, "INVALID_VIEWPORT", { label, rect });
1827
+ }
1828
+ if (rect.width <= 0 || rect.height <= 0) {
1829
+ throw new RenderDeviceError(`Renderer ${label} dimensions must be positive.`, "INVALID_VIEWPORT", { label, rect });
1830
+ }
1831
+ if (rect.x < 0 || rect.y < 0 || rect.x + rect.width > targetWidth || rect.y + rect.height > targetHeight) {
1832
+ throw new RenderDeviceError(`Renderer ${label} must lie inside the ${targetWidth}x${targetHeight} target.`, "INVALID_VIEWPORT", { label, rect, targetWidth, targetHeight });
1833
+ }
1834
+ }
1575
1835
  function isRendererInput(value) {
1576
1836
  return !(value instanceof Scene) && !isIterable(value) && isRecord(value) && "source" in value;
1577
1837
  }
1838
+ /**
1839
+ * threejs-parity L7/SH-06: fold a `{ target }` render input into the source's
1840
+ * `renderTarget` carry — Scene wraps as `{ scene }`, iterables as
1841
+ * `{ renderItems }`, a RenderSource keeps every other authored field.
1842
+ */
1843
+ function withExplicitRenderTarget(source, target) {
1844
+ if (source instanceof Scene)
1845
+ return { scene: source, renderTarget: target };
1846
+ if (isIterable(source))
1847
+ return { renderItems: source, renderTarget: target };
1848
+ return { ...source, renderTarget: target };
1849
+ }
1578
1850
  function isRecord(value) {
1579
1851
  return typeof value === "object" && value !== null;
1580
1852
  }
@@ -1605,7 +1877,7 @@ function collectSourceCameraPosition(source) {
1605
1877
  export function collectRenderItems(source, cameraViewProjection, camera) {
1606
1878
  return collectRenderItemsWithDiagnostics(source, cameraViewProjection, camera).items;
1607
1879
  }
1608
- function collectRenderItemsWithDiagnostics(source, cameraViewProjection, camera, keepCulledShadowCasters = false) {
1880
+ function collectRenderItemsWithDiagnostics(source, cameraViewProjection, camera, keepCulledShadowCasters = false, cameraLayerMask) {
1609
1881
  const diagnostics = createRenderCollectionDiagnostics();
1610
1882
  // 02-S5 (A3D_QR_LIGHTING): frustum-culled items that still cast. A caster
1611
1883
  // outside the view (a pole behind the camera under a low sun) must reach
@@ -1618,7 +1890,7 @@ function collectRenderItemsWithDiagnostics(source, cameraViewProjection, camera,
1618
1890
  return { items, diagnostics, culledShadowCasters: culledShadowCasters ?? [] };
1619
1891
  }
1620
1892
  if (source instanceof Scene) {
1621
- const items = collectSceneRenderItems(source, {}, cameraViewProjection, camera, diagnostics, culledShadowCasters);
1893
+ const items = collectSceneRenderItems(source, {}, cameraViewProjection, camera, diagnostics, culledShadowCasters, cameraLayerMask);
1622
1894
  return { items, diagnostics, culledShadowCasters: culledShadowCasters ?? [], ...(camera ? { cullingFrustum: camera.frustum } : {}) };
1623
1895
  }
1624
1896
  const items = [];
@@ -1626,7 +1898,7 @@ function collectRenderItemsWithDiagnostics(source, cameraViewProjection, camera,
1626
1898
  if (source.scene) {
1627
1899
  if (source.frustumCulling !== false && camera)
1628
1900
  cullingFrustum = camera.frustum;
1629
- items.push(...collectSceneRenderItems(source.scene, source, cameraViewProjection, source.frustumCulling === false ? undefined : camera, diagnostics, culledShadowCasters));
1901
+ items.push(...collectSceneRenderItems(source.scene, source, cameraViewProjection, source.frustumCulling === false ? undefined : camera, diagnostics, culledShadowCasters, cameraLayerMask));
1630
1902
  }
1631
1903
  const explicitItems = [];
1632
1904
  if (source.collectRenderItems) {
@@ -1638,14 +1910,14 @@ function collectRenderItemsWithDiagnostics(source, cameraViewProjection, camera,
1638
1910
  if (explicitItems.length > 0) {
1639
1911
  const explicitFrustum = explicitCullingFrustum(source, cameraViewProjection, camera);
1640
1912
  cullingFrustum ??= explicitFrustum;
1641
- const visibleItems = cullExplicitRenderItems(explicitItems, explicitFrustum, diagnostics, culledShadowCasters);
1913
+ const visibleItems = cullExplicitRenderItems(explicitItems, explicitFrustum, diagnostics, culledShadowCasters, cameraLayerMask ?? camera?.layerMask);
1642
1914
  const optimizedItems = applyRendererOwnedStaticBatching(source, applyRendererOwnedStaticMeshConsolidation(source, visibleItems));
1643
1915
  const projectedItems = applyViewProjection(optimizedItems, cameraViewProjection);
1644
1916
  items.push(...projectedItems);
1645
1917
  }
1646
1918
  return { items, diagnostics, culledShadowCasters: culledShadowCasters ?? [], ...(cullingFrustum ? { cullingFrustum } : {}) };
1647
1919
  }
1648
- function collectSceneRenderItems(scene, source, cameraViewProjection, camera, diagnostics, culledShadowCasters) {
1920
+ function collectSceneRenderItems(scene, source, cameraViewProjection, camera, diagnostics, culledShadowCasters, explicitCameraLayerMask) {
1649
1921
  scene.updateWorldTransforms();
1650
1922
  const renderables = scene.collectRenderables();
1651
1923
  if (renderables.length === 0) {
@@ -1657,7 +1929,12 @@ function collectSceneRenderItems(scene, source, cameraViewProjection, camera, di
1657
1929
  });
1658
1930
  }
1659
1931
  const items = [];
1932
+ const cameraLayerMask = explicitCameraLayerMask ?? camera?.layerMask;
1660
1933
  for (const { node, renderable } of renderables) {
1934
+ // threejs-parity L7/SG-10: three's camera.layers test — a node whose
1935
+ // layers do not intersect the camera mask is absent from the frame.
1936
+ if (cameraLayerMask !== undefined && (node.layerMask & cameraLayerMask) === 0)
1937
+ continue;
1661
1938
  const geometry = lookupRenderResource(source.geometryLibrary, renderable.geometry);
1662
1939
  const material = lookupRenderResource(source.materialLibrary, renderable.material);
1663
1940
  if (!geometry) {
@@ -1687,7 +1964,9 @@ function collectSceneRenderItems(scene, source, cameraViewProjection, camera, di
1687
1964
  if (camera)
1688
1965
  diagnostics.frustumTestedObjects += 1;
1689
1966
  }
1690
- const culled = camera !== undefined && !camera.frustum.intersectsBox(bounds.toMathBox());
1967
+ // threejs-parity L7/SG-09: `node.frustumCulled === false` skips the test
1968
+ // (three.js `object.frustumCulled` semantics).
1969
+ const culled = camera !== undefined && node.frustumCulled !== false && !camera.frustum.intersectsBox(bounds.toMathBox());
1691
1970
  if (culled && diagnostics)
1692
1971
  diagnostics.culledObjects += 1;
1693
1972
  if (culled && !culledShadowCasters)
@@ -1699,6 +1978,15 @@ function collectSceneRenderItems(scene, source, cameraViewProjection, camera, di
1699
1978
  material,
1700
1979
  label: node.name,
1701
1980
  modelMatrix,
1981
+ // threejs-parity L7/SG-09: carry the node's parity fields onto the item
1982
+ // so sort/cull/onBeforeRender behave like three's Object3D.
1983
+ renderOrder: node.renderOrder,
1984
+ layerMask: node.layerMask,
1985
+ frustumCulled: node.frustumCulled,
1986
+ userData: node.userData,
1987
+ ...(node.onBeforeRender
1988
+ ? { onBeforeRender: () => node.onBeforeRender(undefined, node) }
1989
+ : {}),
1702
1990
  normalMatrix: normalMatrixFromModel(modelMatrix),
1703
1991
  modelViewProjectionMatrix: multiplyMat4(cameraViewProjection ?? identityMat4(), modelMatrix),
1704
1992
  ...(renderable.skinning ? { skinning: renderable.skinning } : {}),
@@ -2055,6 +2343,7 @@ function resolveCamera(source, camera, viewport, options = {}) {
2055
2343
  if (!resolved) {
2056
2344
  return undefined;
2057
2345
  }
2346
+ const resolvedLayerMask = typeof resolved.layerMask === "number" ? { layerMask: resolved.layerMask } : {};
2058
2347
  const resize = maybeCameraResize(resolved);
2059
2348
  if (viewport && resize) {
2060
2349
  resize(viewport.width, viewport.height);
@@ -2064,7 +2353,10 @@ function resolveCamera(source, camera, viewport, options = {}) {
2064
2353
  const viewMatrix = resolved.viewMatrix ? toMat4(resolved.viewMatrix, "viewMatrix") : undefined;
2065
2354
  return {
2066
2355
  viewProjectionMatrix: toMat4(resolved.viewProjectionMatrix, "viewProjectionMatrix"),
2356
+ ...resolvedLayerMask,
2067
2357
  ...(viewMatrix ? { viewMatrix } : {}),
2358
+ // threejs-parity SH-02: custom-shader `u_projectionMatrix` source.
2359
+ ...(resolved.projectionMatrix ? { projectionMatrix: toMat4(resolved.projectionMatrix, "projectionMatrix") } : {}),
2068
2360
  ...(resolved instanceof Camera
2069
2361
  ? { camera: resolved, cameraPosition: cameraWorldPosition(resolved) }
2070
2362
  : viewMatrix
@@ -2074,9 +2366,12 @@ function resolveCamera(source, camera, viewport, options = {}) {
2074
2366
  }
2075
2367
  if (resolved.projectionMatrix && resolved.viewMatrix) {
2076
2368
  const viewMatrix = toMat4(resolved.viewMatrix, "viewMatrix");
2369
+ const projectionMatrix = toMat4(resolved.projectionMatrix, "projectionMatrix");
2077
2370
  return {
2078
- viewProjectionMatrix: multiplyMat4(toMat4(resolved.projectionMatrix, "projectionMatrix"), viewMatrix),
2371
+ viewProjectionMatrix: multiplyMat4(projectionMatrix, viewMatrix),
2372
+ ...resolvedLayerMask,
2079
2373
  viewMatrix,
2374
+ projectionMatrix,
2080
2375
  ...(resolved instanceof Camera
2081
2376
  ? { camera: resolved, cameraPosition: cameraWorldPosition(resolved) }
2082
2377
  : { cameraPosition: cameraPositionFromViewMatrix(viewMatrix) })