@forgeax/engine-render 0.0.0-dev.8d955ade1c79

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 (891) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +647 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/__tests__/authoring-path.unit.test.d.ts +2 -0
  5. package/dist/__tests__/authoring-path.unit.test.d.ts.map +1 -0
  6. package/dist/__tests__/backend-selection-rhi-capture.test-d.d.ts +2 -0
  7. package/dist/__tests__/backend-selection-rhi-capture.test-d.d.ts.map +1 -0
  8. package/dist/__tests__/callback-inference.test-d.d.ts +2 -0
  9. package/dist/__tests__/callback-inference.test-d.d.ts.map +1 -0
  10. package/dist/__tests__/cluster-binner.test.d.ts +2 -0
  11. package/dist/__tests__/cluster-binner.test.d.ts.map +1 -0
  12. package/dist/__tests__/cluster-grid-default-surface.unit.test.d.ts +2 -0
  13. package/dist/__tests__/cluster-grid-default-surface.unit.test.d.ts.map +1 -0
  14. package/dist/__tests__/construct-renderer-identity.unit.test.d.ts +2 -0
  15. package/dist/__tests__/construct-renderer-identity.unit.test.d.ts.map +1 -0
  16. package/dist/__tests__/device-scope-lifecycle.unit.test.d.ts +2 -0
  17. package/dist/__tests__/device-scope-lifecycle.unit.test.d.ts.map +1 -0
  18. package/dist/__tests__/device-scope-stale-matrix.unit.test.d.ts +2 -0
  19. package/dist/__tests__/device-scope-stale-matrix.unit.test.d.ts.map +1 -0
  20. package/dist/__tests__/direct-extract-consumer-contract.test-d.d.ts +2 -0
  21. package/dist/__tests__/direct-extract-consumer-contract.test-d.d.ts.map +1 -0
  22. package/dist/__tests__/direct-light-snapshot.unit.test.d.ts +2 -0
  23. package/dist/__tests__/direct-light-snapshot.unit.test.d.ts.map +1 -0
  24. package/dist/__tests__/factory-contract.integration.test.d.ts +2 -0
  25. package/dist/__tests__/factory-contract.integration.test.d.ts.map +1 -0
  26. package/dist/__tests__/fallback-row-stride-surface.unit.test.d.ts +2 -0
  27. package/dist/__tests__/fallback-row-stride-surface.unit.test.d.ts.map +1 -0
  28. package/dist/__tests__/frame-observation.contract.test.d.ts +2 -0
  29. package/dist/__tests__/frame-observation.contract.test.d.ts.map +1 -0
  30. package/dist/__tests__/fullscreen-feature-plan.unit.test.d.ts +2 -0
  31. package/dist/__tests__/fullscreen-feature-plan.unit.test.d.ts.map +1 -0
  32. package/dist/__tests__/glyph-text-layout-recovery.unit.test.d.ts +2 -0
  33. package/dist/__tests__/glyph-text-layout-recovery.unit.test.d.ts.map +1 -0
  34. package/dist/__tests__/gpu-driven-indirect-raster-evidence.d.ts +6 -0
  35. package/dist/__tests__/gpu-driven-indirect-raster-evidence.d.ts.map +1 -0
  36. package/dist/__tests__/gpu-driven-indirect-raster.browser.test.d.ts +2 -0
  37. package/dist/__tests__/gpu-driven-indirect-raster.browser.test.d.ts.map +1 -0
  38. package/dist/__tests__/gpu-driven-indirect-raster.dawn.test.d.ts +2 -0
  39. package/dist/__tests__/gpu-driven-indirect-raster.dawn.test.d.ts.map +1 -0
  40. package/dist/__tests__/gpu-driven-production.integration.test.d.ts +2 -0
  41. package/dist/__tests__/gpu-driven-production.integration.test.d.ts.map +1 -0
  42. package/dist/__tests__/gpu-driven-scaling.unit.test.d.ts +2 -0
  43. package/dist/__tests__/gpu-driven-scaling.unit.test.d.ts.map +1 -0
  44. package/dist/__tests__/gpu-driven-view-gpu-evidence.d.ts +18 -0
  45. package/dist/__tests__/gpu-driven-view-gpu-evidence.d.ts.map +1 -0
  46. package/dist/__tests__/gpu-driven-view-graph.integration.test.d.ts +2 -0
  47. package/dist/__tests__/gpu-driven-view-graph.integration.test.d.ts.map +1 -0
  48. package/dist/__tests__/gpu-driven-view-reference.d.ts +15 -0
  49. package/dist/__tests__/gpu-driven-view-reference.d.ts.map +1 -0
  50. package/dist/__tests__/gpu-driven-view.browser.test.d.ts +2 -0
  51. package/dist/__tests__/gpu-driven-view.browser.test.d.ts.map +1 -0
  52. package/dist/__tests__/gpu-driven-view.dawn.test.d.ts +2 -0
  53. package/dist/__tests__/gpu-driven-view.dawn.test.d.ts.map +1 -0
  54. package/dist/__tests__/gpu-driven-view.unit.test.d.ts +2 -0
  55. package/dist/__tests__/gpu-driven-view.unit.test.d.ts.map +1 -0
  56. package/dist/__tests__/gpu-resource-store-stride.unit.test.d.ts +2 -0
  57. package/dist/__tests__/gpu-resource-store-stride.unit.test.d.ts.map +1 -0
  58. package/dist/__tests__/gpu-scene-render-graph-gpu.d.ts +7 -0
  59. package/dist/__tests__/gpu-scene-render-graph-gpu.d.ts.map +1 -0
  60. package/dist/__tests__/gpu-scene-render-graph.browser.test.d.ts +2 -0
  61. package/dist/__tests__/gpu-scene-render-graph.browser.test.d.ts.map +1 -0
  62. package/dist/__tests__/gpu-scene-render-graph.dawn.test.d.ts +2 -0
  63. package/dist/__tests__/gpu-scene-render-graph.dawn.test.d.ts.map +1 -0
  64. package/dist/__tests__/gpu-scene-schema.unit.test.d.ts +2 -0
  65. package/dist/__tests__/gpu-scene-schema.unit.test.d.ts.map +1 -0
  66. package/dist/__tests__/gpu-scene.dawn.test.d.ts +2 -0
  67. package/dist/__tests__/gpu-scene.dawn.test.d.ts.map +1 -0
  68. package/dist/__tests__/gpu-scene.unit.test.d.ts +2 -0
  69. package/dist/__tests__/gpu-scene.unit.test.d.ts.map +1 -0
  70. package/dist/__tests__/ibl/face-uniforms.unit.test.d.ts +2 -0
  71. package/dist/__tests__/ibl/face-uniforms.unit.test.d.ts.map +1 -0
  72. package/dist/__tests__/infinite-grid-no-vertex-probe.unit.test.d.ts +2 -0
  73. package/dist/__tests__/infinite-grid-no-vertex-probe.unit.test.d.ts.map +1 -0
  74. package/dist/__tests__/injection-kind-owner.test-d.d.ts +2 -0
  75. package/dist/__tests__/injection-kind-owner.test-d.d.ts.map +1 -0
  76. package/dist/__tests__/ktx2-basis-gpu-consumer.dawn.test.d.ts +2 -0
  77. package/dist/__tests__/ktx2-basis-gpu-consumer.dawn.test.d.ts.map +1 -0
  78. package/dist/__tests__/light-buffer-layout.unit.test.d.ts +2 -0
  79. package/dist/__tests__/light-buffer-layout.unit.test.d.ts.map +1 -0
  80. package/dist/__tests__/material-contract-inventory.unit.test.d.ts +2 -0
  81. package/dist/__tests__/material-contract-inventory.unit.test.d.ts.map +1 -0
  82. package/dist/__tests__/material-cooked-projection.unit.test.d.ts +2 -0
  83. package/dist/__tests__/material-cooked-projection.unit.test.d.ts.map +1 -0
  84. package/dist/__tests__/material-snapshot-mutation.integration.test.d.ts +2 -0
  85. package/dist/__tests__/material-snapshot-mutation.integration.test.d.ts.map +1 -0
  86. package/dist/__tests__/material-static-dynamic-route.unit.test.d.ts +2 -0
  87. package/dist/__tests__/material-static-dynamic-route.unit.test.d.ts.map +1 -0
  88. package/dist/__tests__/material-stride-surface.unit.test.d.ts +2 -0
  89. package/dist/__tests__/material-stride-surface.unit.test.d.ts.map +1 -0
  90. package/dist/__tests__/mesh-buffer-usage-surface.unit.test.d.ts +2 -0
  91. package/dist/__tests__/mesh-buffer-usage-surface.unit.test.d.ts.map +1 -0
  92. package/dist/__tests__/mesh-material-bindings.unit.test.d.ts +2 -0
  93. package/dist/__tests__/mesh-material-bindings.unit.test.d.ts.map +1 -0
  94. package/dist/__tests__/mesh-stride-surface.unit.test.d.ts +2 -0
  95. package/dist/__tests__/mesh-stride-surface.unit.test.d.ts.map +1 -0
  96. package/dist/__tests__/morph-culling-reentry.dawn.test.d.ts +2 -0
  97. package/dist/__tests__/morph-culling-reentry.dawn.test.d.ts.map +1 -0
  98. package/dist/__tests__/morph-feature-gpu-work.integration.test.d.ts +2 -0
  99. package/dist/__tests__/morph-feature-gpu-work.integration.test.d.ts.map +1 -0
  100. package/dist/__tests__/morph-feature-plan.unit.test.d.ts +2 -0
  101. package/dist/__tests__/morph-feature-plan.unit.test.d.ts.map +1 -0
  102. package/dist/__tests__/morph-visual-evidence.integration.test.d.ts +2 -0
  103. package/dist/__tests__/morph-visual-evidence.integration.test.d.ts.map +1 -0
  104. package/dist/__tests__/nineslice-handle-surface.unit.test.d.ts +2 -0
  105. package/dist/__tests__/nineslice-handle-surface.unit.test.d.ts.map +1 -0
  106. package/dist/__tests__/post-process-error-policy-owner.unit.test.d.ts +2 -0
  107. package/dist/__tests__/post-process-error-policy-owner.unit.test.d.ts.map +1 -0
  108. package/dist/__tests__/prepared-buffer-kind-owner.test-d.d.ts +2 -0
  109. package/dist/__tests__/prepared-buffer-kind-owner.test-d.d.ts.map +1 -0
  110. package/dist/__tests__/prepared-graphics-capability.unit.test.d.ts +2 -0
  111. package/dist/__tests__/prepared-graphics-capability.unit.test.d.ts.map +1 -0
  112. package/dist/__tests__/prepared-graphics-generation.unit.test.d.ts +2 -0
  113. package/dist/__tests__/prepared-graphics-generation.unit.test.d.ts.map +1 -0
  114. package/dist/__tests__/prepared-graphics-lifecycle.unit.test.d.ts +2 -0
  115. package/dist/__tests__/prepared-graphics-lifecycle.unit.test.d.ts.map +1 -0
  116. package/dist/__tests__/prepared-graphics-pipeline-warmup.unit.test.d.ts +2 -0
  117. package/dist/__tests__/prepared-graphics-pipeline-warmup.unit.test.d.ts.map +1 -0
  118. package/dist/__tests__/prepared-graphics-resolver.unit.test.d.ts +2 -0
  119. package/dist/__tests__/prepared-graphics-resolver.unit.test.d.ts.map +1 -0
  120. package/dist/__tests__/prepared-graphics-snapshot.unit.test.d.ts +2 -0
  121. package/dist/__tests__/prepared-graphics-snapshot.unit.test.d.ts.map +1 -0
  122. package/dist/__tests__/prepared-graphics-store.unit.test.d.ts +2 -0
  123. package/dist/__tests__/prepared-graphics-store.unit.test.d.ts.map +1 -0
  124. package/dist/__tests__/prepared-graphics-validation.unit.test.d.ts +2 -0
  125. package/dist/__tests__/prepared-graphics-validation.unit.test.d.ts.map +1 -0
  126. package/dist/__tests__/profiler-default-off.test.d.ts +2 -0
  127. package/dist/__tests__/profiler-default-off.test.d.ts.map +1 -0
  128. package/dist/__tests__/profiler-phase-catalog.test.d.ts +2 -0
  129. package/dist/__tests__/profiler-phase-catalog.test.d.ts.map +1 -0
  130. package/dist/__tests__/profiler-phase-integration.test.d.ts +2 -0
  131. package/dist/__tests__/profiler-phase-integration.test.d.ts.map +1 -0
  132. package/dist/__tests__/profiler-phase-token.test.d.ts +2 -0
  133. package/dist/__tests__/profiler-phase-token.test.d.ts.map +1 -0
  134. package/dist/__tests__/public-surface.test-d.d.ts +2 -0
  135. package/dist/__tests__/public-surface.test-d.d.ts.map +1 -0
  136. package/dist/__tests__/recover-error-policy-owner.unit.test.d.ts +2 -0
  137. package/dist/__tests__/recover-error-policy-owner.unit.test.d.ts.map +1 -0
  138. package/dist/__tests__/render-error-code-owner.test-d.d.ts +2 -0
  139. package/dist/__tests__/render-error-code-owner.test-d.d.ts.map +1 -0
  140. package/dist/__tests__/render-error-detail-ssot.test-d.d.ts +2 -0
  141. package/dist/__tests__/render-error-detail-ssot.test-d.d.ts.map +1 -0
  142. package/dist/__tests__/render-error-exhaustive.test-d.d.ts +2 -0
  143. package/dist/__tests__/render-error-exhaustive.test-d.d.ts.map +1 -0
  144. package/dist/__tests__/render-feature-assembly.test-d.d.ts +2 -0
  145. package/dist/__tests__/render-feature-assembly.test-d.d.ts.map +1 -0
  146. package/dist/__tests__/render-feature-context.test-d.d.ts +2 -0
  147. package/dist/__tests__/render-feature-context.test-d.d.ts.map +1 -0
  148. package/dist/__tests__/render-feature-contract.test-d.d.ts +2 -0
  149. package/dist/__tests__/render-feature-contract.test-d.d.ts.map +1 -0
  150. package/dist/__tests__/render-feature-diagnostics.unit.test.d.ts +2 -0
  151. package/dist/__tests__/render-feature-diagnostics.unit.test.d.ts.map +1 -0
  152. package/dist/__tests__/render-feature-docs.unit.test.d.ts +2 -0
  153. package/dist/__tests__/render-feature-docs.unit.test.d.ts.map +1 -0
  154. package/dist/__tests__/render-feature-errors.unit.test.d.ts +2 -0
  155. package/dist/__tests__/render-feature-errors.unit.test.d.ts.map +1 -0
  156. package/dist/__tests__/render-feature-gpu-work.integration.test.d.ts +2 -0
  157. package/dist/__tests__/render-feature-gpu-work.integration.test.d.ts.map +1 -0
  158. package/dist/__tests__/render-feature-plan.test-d.d.ts +2 -0
  159. package/dist/__tests__/render-feature-plan.test-d.d.ts.map +1 -0
  160. package/dist/__tests__/render-feature-plan.unit.test.d.ts +2 -0
  161. package/dist/__tests__/render-feature-plan.unit.test.d.ts.map +1 -0
  162. package/dist/__tests__/render-feature-prepared-diagnostics.unit.test.d.ts +2 -0
  163. package/dist/__tests__/render-feature-prepared-diagnostics.unit.test.d.ts.map +1 -0
  164. package/dist/__tests__/render-feature-prepared-errors.test-d.d.ts +2 -0
  165. package/dist/__tests__/render-feature-prepared-errors.test-d.d.ts.map +1 -0
  166. package/dist/__tests__/render-feature-prepared-graphics-docs.unit.test.d.ts +2 -0
  167. package/dist/__tests__/render-feature-prepared-graphics-docs.unit.test.d.ts.map +1 -0
  168. package/dist/__tests__/render-feature-prepared-graphics-isolation.unit.test.d.ts +2 -0
  169. package/dist/__tests__/render-feature-prepared-graphics-isolation.unit.test.d.ts.map +1 -0
  170. package/dist/__tests__/render-feature-prepared-graphics-negative.test-d.d.ts +2 -0
  171. package/dist/__tests__/render-feature-prepared-graphics-negative.test-d.d.ts.map +1 -0
  172. package/dist/__tests__/render-feature-prepared-graphics.test-d.d.ts +2 -0
  173. package/dist/__tests__/render-feature-prepared-graphics.test-d.d.ts.map +1 -0
  174. package/dist/__tests__/render-feature-prepared-graphics.unit.test.d.ts +2 -0
  175. package/dist/__tests__/render-feature-prepared-graphics.unit.test.d.ts.map +1 -0
  176. package/dist/__tests__/render-feature-public-surface.test-d.d.ts +2 -0
  177. package/dist/__tests__/render-feature-public-surface.test-d.d.ts.map +1 -0
  178. package/dist/__tests__/render-feature-retry.unit.test.d.ts +2 -0
  179. package/dist/__tests__/render-feature-retry.unit.test.d.ts.map +1 -0
  180. package/dist/__tests__/render-feature-scheduling.unit.test.d.ts +2 -0
  181. package/dist/__tests__/render-feature-scheduling.unit.test.d.ts.map +1 -0
  182. package/dist/__tests__/render-feature-stage-failed-policy-owner.unit.test.d.ts +2 -0
  183. package/dist/__tests__/render-feature-stage-failed-policy-owner.unit.test.d.ts.map +1 -0
  184. package/dist/__tests__/render-feature-stage-fault-recovery.unit.test.d.ts +2 -0
  185. package/dist/__tests__/render-feature-stage-fault-recovery.unit.test.d.ts.map +1 -0
  186. package/dist/__tests__/render-feature-visibility-context.test-d.d.ts +2 -0
  187. package/dist/__tests__/render-feature-visibility-context.test-d.d.ts.map +1 -0
  188. package/dist/__tests__/render-feature-visibility-context.unit.test.d.ts +2 -0
  189. package/dist/__tests__/render-feature-visibility-context.unit.test.d.ts.map +1 -0
  190. package/dist/__tests__/render-feature-zero-work.unit.test.d.ts +2 -0
  191. package/dist/__tests__/render-feature-zero-work.unit.test.d.ts.map +1 -0
  192. package/dist/__tests__/render-graph-compute-raster-gpu.d.ts +7 -0
  193. package/dist/__tests__/render-graph-compute-raster-gpu.d.ts.map +1 -0
  194. package/dist/__tests__/render-graph-compute-raster.browser.test.d.ts +2 -0
  195. package/dist/__tests__/render-graph-compute-raster.browser.test.d.ts.map +1 -0
  196. package/dist/__tests__/render-graph-compute-raster.dawn.test.d.ts +2 -0
  197. package/dist/__tests__/render-graph-compute-raster.dawn.test.d.ts.map +1 -0
  198. package/dist/__tests__/render-graph-hzb-gpu.d.ts +7 -0
  199. package/dist/__tests__/render-graph-hzb-gpu.d.ts.map +1 -0
  200. package/dist/__tests__/render-graph-hzb.browser.test.d.ts +2 -0
  201. package/dist/__tests__/render-graph-hzb.browser.test.d.ts.map +1 -0
  202. package/dist/__tests__/render-graph-hzb.dawn.test.d.ts +2 -0
  203. package/dist/__tests__/render-graph-hzb.dawn.test.d.ts.map +1 -0
  204. package/dist/__tests__/render-graph-primitives.unit.test.d.ts +2 -0
  205. package/dist/__tests__/render-graph-primitives.unit.test.d.ts.map +1 -0
  206. package/dist/__tests__/render-graph-storage-indirect-gpu.d.ts +12 -0
  207. package/dist/__tests__/render-graph-storage-indirect-gpu.d.ts.map +1 -0
  208. package/dist/__tests__/render-graph-storage-indirect.browser.test.d.ts +2 -0
  209. package/dist/__tests__/render-graph-storage-indirect.browser.test.d.ts.map +1 -0
  210. package/dist/__tests__/render-graph-storage-indirect.dawn.test.d.ts +2 -0
  211. package/dist/__tests__/render-graph-storage-indirect.dawn.test.d.ts.map +1 -0
  212. package/dist/__tests__/render-scene-projection.unit.test.d.ts +2 -0
  213. package/dist/__tests__/render-scene-projection.unit.test.d.ts.map +1 -0
  214. package/dist/__tests__/renderer-factory-material-contract.unit.test.d.ts +2 -0
  215. package/dist/__tests__/renderer-factory-material-contract.unit.test.d.ts.map +1 -0
  216. package/dist/__tests__/renderer-lifecycle.integration.test.d.ts +2 -0
  217. package/dist/__tests__/renderer-lifecycle.integration.test.d.ts.map +1 -0
  218. package/dist/__tests__/rhi-backend-package-boundary.unit.test.d.ts +2 -0
  219. package/dist/__tests__/rhi-backend-package-boundary.unit.test.d.ts.map +1 -0
  220. package/dist/__tests__/scriptable-pack-consumer.test-d.d.ts +2 -0
  221. package/dist/__tests__/scriptable-pack-consumer.test-d.d.ts.map +1 -0
  222. package/dist/__tests__/shader-stage-surface.unit.test.d.ts +2 -0
  223. package/dist/__tests__/shader-stage-surface.unit.test.d.ts.map +1 -0
  224. package/dist/__tests__/ssao-config.unit.test.d.ts +2 -0
  225. package/dist/__tests__/ssao-config.unit.test.d.ts.map +1 -0
  226. package/dist/__tests__/ssao-kernel-surface.unit.test.d.ts +2 -0
  227. package/dist/__tests__/ssao-kernel-surface.unit.test.d.ts.map +1 -0
  228. package/dist/__tests__/standard-clustered-pipeline.unit.test.d.ts +2 -0
  229. package/dist/__tests__/standard-clustered-pipeline.unit.test.d.ts.map +1 -0
  230. package/dist/__tests__/standard-descriptors.test-d.d.ts +2 -0
  231. package/dist/__tests__/standard-descriptors.test-d.d.ts.map +1 -0
  232. package/dist/__tests__/standard-pipeline.dawn.test.d.ts +2 -0
  233. package/dist/__tests__/standard-pipeline.dawn.test.d.ts.map +1 -0
  234. package/dist/__tests__/standard-pipeline.integration.test.d.ts +2 -0
  235. package/dist/__tests__/standard-pipeline.integration.test.d.ts.map +1 -0
  236. package/dist/__tests__/standard-profile-oracle.unit.test.d.ts +2 -0
  237. package/dist/__tests__/standard-profile-oracle.unit.test.d.ts.map +1 -0
  238. package/dist/__tests__/surface-configuration.unit.test.d.ts +2 -0
  239. package/dist/__tests__/surface-configuration.unit.test.d.ts.map +1 -0
  240. package/dist/__tests__/texture-usage-surface.unit.test.d.ts +2 -0
  241. package/dist/__tests__/texture-usage-surface.unit.test.d.ts.map +1 -0
  242. package/dist/__tests__/typed-pipeline-topology.unit.test.d.ts +2 -0
  243. package/dist/__tests__/typed-pipeline-topology.unit.test.d.ts.map +1 -0
  244. package/dist/__tests__/urp-pipeline-targets.unit.test.d.ts +2 -0
  245. package/dist/__tests__/urp-pipeline-targets.unit.test.d.ts.map +1 -0
  246. package/dist/__tests__/urp-pipeline.unit.test.d.ts +2 -0
  247. package/dist/__tests__/urp-pipeline.unit.test.d.ts.map +1 -0
  248. package/dist/__tests__/vertex-color-pipeline.unit.test.d.ts +2 -0
  249. package/dist/__tests__/vertex-color-pipeline.unit.test.d.ts.map +1 -0
  250. package/dist/__tests__/vertex-layout-consumer.unit.test.d.ts +2 -0
  251. package/dist/__tests__/vertex-layout-consumer.unit.test.d.ts.map +1 -0
  252. package/dist/__tests__/visibility-diagnostics.integration.test.d.ts +2 -0
  253. package/dist/__tests__/visibility-diagnostics.integration.test.d.ts.map +1 -0
  254. package/dist/__tests__/visibility-extract.integration.test.d.ts +2 -0
  255. package/dist/__tests__/visibility-extract.integration.test.d.ts.map +1 -0
  256. package/dist/__tests__/visibility-extract.perf.test.d.ts +2 -0
  257. package/dist/__tests__/visibility-extract.perf.test.d.ts.map +1 -0
  258. package/dist/__tests__/visibility-feature-dedup.integration.test.d.ts +2 -0
  259. package/dist/__tests__/visibility-feature-dedup.integration.test.d.ts.map +1 -0
  260. package/dist/__tests__/visibility-hidden-report.unit.test.d.ts +2 -0
  261. package/dist/__tests__/visibility-hidden-report.unit.test.d.ts.map +1 -0
  262. package/dist/__tests__/visibility-instances.integration.test.d.ts +2 -0
  263. package/dist/__tests__/visibility-instances.integration.test.d.ts.map +1 -0
  264. package/dist/__tests__/visibility-multi-world.integration.test.d.ts +2 -0
  265. package/dist/__tests__/visibility-multi-world.integration.test.d.ts.map +1 -0
  266. package/dist/__tests__/visibility-producer-matrix.integration.test.d.ts +2 -0
  267. package/dist/__tests__/visibility-producer-matrix.integration.test.d.ts.map +1 -0
  268. package/dist/__tests__/visibility-producer-matrix.unit.test.d.ts +2 -0
  269. package/dist/__tests__/visibility-producer-matrix.unit.test.d.ts.map +1 -0
  270. package/dist/__tests__/visibility-public-surface.test-d.d.ts +2 -0
  271. package/dist/__tests__/visibility-public-surface.test-d.d.ts.map +1 -0
  272. package/dist/__tests__/visibility-resolver.unit.test.d.ts +2 -0
  273. package/dist/__tests__/visibility-resolver.unit.test.d.ts.map +1 -0
  274. package/dist/__tests__/visibility-resource-short-circuit.unit.test.d.ts +2 -0
  275. package/dist/__tests__/visibility-resource-short-circuit.unit.test.d.ts.map +1 -0
  276. package/dist/__tests__/visibility-snapshot-contract.unit.test.d.ts +2 -0
  277. package/dist/__tests__/visibility-snapshot-contract.unit.test.d.ts.map +1 -0
  278. package/dist/__tests__/vocabulary.unit.test.d.ts +2 -0
  279. package/dist/__tests__/vocabulary.unit.test.d.ts.map +1 -0
  280. package/dist/assembly/backend-contract.d.ts +37 -0
  281. package/dist/assembly/backend-contract.d.ts.map +1 -0
  282. package/dist/assembly/factory.d.ts +207 -0
  283. package/dist/assembly/factory.d.ts.map +1 -0
  284. package/dist/assembly/host-contract.d.ts +84 -0
  285. package/dist/assembly/host-contract.d.ts.map +1 -0
  286. package/dist/assembly/material/assembly.d.ts +21 -0
  287. package/dist/assembly/material/assembly.d.ts.map +1 -0
  288. package/dist/assembly/material/pipeline-projection.d.ts +30 -0
  289. package/dist/assembly/material/pipeline-projection.d.ts.map +1 -0
  290. package/dist/assets/asset-decoders.d.ts +5 -0
  291. package/dist/assets/asset-decoders.d.ts.map +1 -0
  292. package/dist/assets/render-asset-port.d.ts +17 -0
  293. package/dist/assets/render-asset-port.d.ts.map +1 -0
  294. package/dist/assets/resolve-render-asset.d.ts +6 -0
  295. package/dist/assets/resolve-render-asset.d.ts.map +1 -0
  296. package/dist/authoring.d.ts +12 -0
  297. package/dist/authoring.d.ts.map +1 -0
  298. package/dist/authoring.mjs +27 -0
  299. package/dist/authoring.mjs.map +1 -0
  300. package/dist/chunk-CQXZC5XI.mjs +182 -0
  301. package/dist/chunk-CQXZC5XI.mjs.map +1 -0
  302. package/dist/chunk-HCOGPHKS.mjs +93 -0
  303. package/dist/chunk-HCOGPHKS.mjs.map +1 -0
  304. package/dist/chunk-YEJIUW7D.mjs +1152 -0
  305. package/dist/chunk-YEJIUW7D.mjs.map +1 -0
  306. package/dist/cluster-binner.d.ts +152 -0
  307. package/dist/cluster-binner.d.ts.map +1 -0
  308. package/dist/components/__tests__/points-lines-admission.unit.test.d.ts +2 -0
  309. package/dist/components/__tests__/points-lines-admission.unit.test.d.ts.map +1 -0
  310. package/dist/components/__tests__/points-lines-schema.unit.test.d.ts +2 -0
  311. package/dist/components/__tests__/points-lines-schema.unit.test.d.ts.map +1 -0
  312. package/dist/components/__tests__/schema-contract.unit.test.d.ts +2 -0
  313. package/dist/components/__tests__/schema-contract.unit.test.d.ts.map +1 -0
  314. package/dist/components/__tests__/schema-parity.unit.test.d.ts +2 -0
  315. package/dist/components/__tests__/schema-parity.unit.test.d.ts.map +1 -0
  316. package/dist/components/__tests__/visibility-schema.unit.test.d.ts +2 -0
  317. package/dist/components/__tests__/visibility-schema.unit.test.d.ts.map +1 -0
  318. package/dist/components/camera.d.ts +311 -0
  319. package/dist/components/camera.d.ts.map +1 -0
  320. package/dist/components/directional-light.d.ts +76 -0
  321. package/dist/components/directional-light.d.ts.map +1 -0
  322. package/dist/components/glyph-text.d.ts +26 -0
  323. package/dist/components/glyph-text.d.ts.map +1 -0
  324. package/dist/components/index.d.ts +24 -0
  325. package/dist/components/index.d.ts.map +1 -0
  326. package/dist/components/instances.d.ts +41 -0
  327. package/dist/components/instances.d.ts.map +1 -0
  328. package/dist/components/layer.d.ts +34 -0
  329. package/dist/components/layer.d.ts.map +1 -0
  330. package/dist/components/light-helpers.d.ts +53 -0
  331. package/dist/components/light-helpers.d.ts.map +1 -0
  332. package/dist/components/lines.d.ts +5 -0
  333. package/dist/components/lines.d.ts.map +1 -0
  334. package/dist/components/mesh-filter.d.ts +24 -0
  335. package/dist/components/mesh-filter.d.ts.map +1 -0
  336. package/dist/components/mesh-renderer.d.ts +33 -0
  337. package/dist/components/mesh-renderer.d.ts.map +1 -0
  338. package/dist/components/point-light-shadow.d.ts +27 -0
  339. package/dist/components/point-light-shadow.d.ts.map +1 -0
  340. package/dist/components/point-light.d.ts +35 -0
  341. package/dist/components/point-light.d.ts.map +1 -0
  342. package/dist/components/points.d.ts +15 -0
  343. package/dist/components/points.d.ts.map +1 -0
  344. package/dist/components/post-process-params.d.ts +5 -0
  345. package/dist/components/post-process-params.d.ts.map +1 -0
  346. package/dist/components/scene-instance.d.ts +140 -0
  347. package/dist/components/scene-instance.d.ts.map +1 -0
  348. package/dist/components/skybox-background.d.ts +56 -0
  349. package/dist/components/skybox-background.d.ts.map +1 -0
  350. package/dist/components/skylight.d.ts +55 -0
  351. package/dist/components/skylight.d.ts.map +1 -0
  352. package/dist/components/sort-key.d.ts +44 -0
  353. package/dist/components/sort-key.d.ts.map +1 -0
  354. package/dist/components/spot-light.d.ts +81 -0
  355. package/dist/components/spot-light.d.ts.map +1 -0
  356. package/dist/components/sprite-animation.d.ts +74 -0
  357. package/dist/components/sprite-animation.d.ts.map +1 -0
  358. package/dist/components/sprite-instances.d.ts +51 -0
  359. package/dist/components/sprite-instances.d.ts.map +1 -0
  360. package/dist/components/sprite-playback-mode.d.ts +42 -0
  361. package/dist/components/sprite-playback-mode.d.ts.map +1 -0
  362. package/dist/components/sprite-region-override.d.ts +46 -0
  363. package/dist/components/sprite-region-override.d.ts.map +1 -0
  364. package/dist/components/tile-layer.d.ts +106 -0
  365. package/dist/components/tile-layer.d.ts.map +1 -0
  366. package/dist/components/tilemap.d.ts +25 -0
  367. package/dist/components/tilemap.d.ts.map +1 -0
  368. package/dist/components/visibility.d.ts +22 -0
  369. package/dist/components/visibility.d.ts.map +1 -0
  370. package/dist/construct-renderer.d.ts +19 -0
  371. package/dist/construct-renderer.d.ts.map +1 -0
  372. package/dist/construct-renderer.mjs +27462 -0
  373. package/dist/construct-renderer.mjs.map +1 -0
  374. package/dist/debug-draw-glue.d.ts +5 -0
  375. package/dist/debug-draw-glue.d.ts.map +1 -0
  376. package/dist/device/device-scope.d.ts +83 -0
  377. package/dist/device/device-scope.d.ts.map +1 -0
  378. package/dist/device/gpu-residency.d.ts +337 -0
  379. package/dist/device/gpu-residency.d.ts.map +1 -0
  380. package/dist/device/resource-types.d.ts +8 -0
  381. package/dist/device/resource-types.d.ts.map +1 -0
  382. package/dist/engine-metrics.d.ts +23 -0
  383. package/dist/engine-metrics.d.ts.map +1 -0
  384. package/dist/errors/ecs-validation.d.ts +3 -0
  385. package/dist/errors/ecs-validation.d.ts.map +1 -0
  386. package/dist/errors/index.d.ts +4 -0
  387. package/dist/errors/index.d.ts.map +1 -0
  388. package/dist/errors/recover.d.ts +105 -0
  389. package/dist/errors/recover.d.ts.map +1 -0
  390. package/dist/errors/render.d.ts +705 -0
  391. package/dist/errors/render.d.ts.map +1 -0
  392. package/dist/extract/visibility.d.ts +26 -0
  393. package/dist/extract/visibility.d.ts.map +1 -0
  394. package/dist/features/fullscreen.d.ts +19 -0
  395. package/dist/features/fullscreen.d.ts.map +1 -0
  396. package/dist/features/host.d.ts +106 -0
  397. package/dist/features/host.d.ts.map +1 -0
  398. package/dist/features/morph/morph-feature.d.ts +79 -0
  399. package/dist/features/morph/morph-feature.d.ts.map +1 -0
  400. package/dist/features/plan.d.ts +182 -0
  401. package/dist/features/plan.d.ts.map +1 -0
  402. package/dist/features/prepared-gpu-work.d.ts +117 -0
  403. package/dist/features/prepared-gpu-work.d.ts.map +1 -0
  404. package/dist/features/prepared-graphics-store.d.ts +66 -0
  405. package/dist/features/prepared-graphics-store.d.ts.map +1 -0
  406. package/dist/features/prepared-graphics.d.ts +215 -0
  407. package/dist/features/prepared-graphics.d.ts.map +1 -0
  408. package/dist/features/render-graph-compute.d.ts +13 -0
  409. package/dist/features/render-graph-compute.d.ts.map +1 -0
  410. package/dist/features/render-graph-contribution.d.ts +12 -0
  411. package/dist/features/render-graph-contribution.d.ts.map +1 -0
  412. package/dist/features/render-graph-raster.d.ts +38 -0
  413. package/dist/features/render-graph-raster.d.ts.map +1 -0
  414. package/dist/features/render-graph-resources.d.ts +11 -0
  415. package/dist/features/render-graph-resources.d.ts.map +1 -0
  416. package/dist/features/targets.d.ts +32 -0
  417. package/dist/features/targets.d.ts.map +1 -0
  418. package/dist/features/types.d.ts +68 -0
  419. package/dist/features/types.d.ts.map +1 -0
  420. package/dist/features/vocabulary.d.ts +14 -0
  421. package/dist/features/vocabulary.d.ts.map +1 -0
  422. package/dist/fullscreen-post-process-pass.d.ts +157 -0
  423. package/dist/fullscreen-post-process-pass.d.ts.map +1 -0
  424. package/dist/glyph-text-layout-system.d.ts +19 -0
  425. package/dist/glyph-text-layout-system.d.ts.map +1 -0
  426. package/dist/gpu-driven/batch-topology.d.ts +55 -0
  427. package/dist/gpu-driven/batch-topology.d.ts.map +1 -0
  428. package/dist/gpu-driven/production-raster.d.ts +51 -0
  429. package/dist/gpu-driven/production-raster.d.ts.map +1 -0
  430. package/dist/gpu-driven/view-gpu.d.ts +85 -0
  431. package/dist/gpu-driven/view-gpu.d.ts.map +1 -0
  432. package/dist/gpu-resource.d.ts +73 -0
  433. package/dist/gpu-resource.d.ts.map +1 -0
  434. package/dist/gpu-scene-schema.d.ts +78 -0
  435. package/dist/gpu-scene-schema.d.ts.map +1 -0
  436. package/dist/gpu-scene.d.ts +55 -0
  437. package/dist/gpu-scene.d.ts.map +1 -0
  438. package/dist/gpu-stage.d.ts +4 -0
  439. package/dist/gpu-stage.d.ts.map +1 -0
  440. package/dist/gpu-texture-usage.d.ts +6 -0
  441. package/dist/gpu-texture-usage.d.ts.map +1 -0
  442. package/dist/gpu-usage.d.ts +9 -0
  443. package/dist/gpu-usage.d.ts.map +1 -0
  444. package/dist/hdrp-buffers.d.ts +163 -0
  445. package/dist/hdrp-buffers.d.ts.map +1 -0
  446. package/dist/ibl/IblPipelineCache.d.ts +168 -0
  447. package/dist/ibl/IblPipelineCache.d.ts.map +1 -0
  448. package/dist/ibl/skylight-bind-group.d.ts +130 -0
  449. package/dist/ibl/skylight-bind-group.d.ts.map +1 -0
  450. package/dist/index.d.ts +31 -0
  451. package/dist/index.d.ts.map +1 -0
  452. package/dist/index.mjs +169 -0
  453. package/dist/index.mjs.map +1 -0
  454. package/dist/inspection-types.d.ts +72 -0
  455. package/dist/inspection-types.d.ts.map +1 -0
  456. package/dist/instance-buffer-cache.d.ts +56 -0
  457. package/dist/instance-buffer-cache.d.ts.map +1 -0
  458. package/dist/internal.d.ts +2 -0
  459. package/dist/internal.d.ts.map +1 -0
  460. package/dist/internal.mjs +3 -0
  461. package/dist/internal.mjs.map +1 -0
  462. package/dist/lifecycle.d.ts +62 -0
  463. package/dist/lifecycle.d.ts.map +1 -0
  464. package/dist/light-buffer-layout.d.ts +164 -0
  465. package/dist/light-buffer-layout.d.ts.map +1 -0
  466. package/dist/materials.d.ts +47 -0
  467. package/dist/materials.d.ts.map +1 -0
  468. package/dist/mesh-material-bindings.d.ts +43 -0
  469. package/dist/mesh-material-bindings.d.ts.map +1 -0
  470. package/dist/pbr-pipeline.d.ts +301 -0
  471. package/dist/pbr-pipeline.d.ts.map +1 -0
  472. package/dist/pipeline/standard-forward-lane.d.ts +5 -0
  473. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -0
  474. package/dist/pipeline/standard-pipeline.d.ts +31 -0
  475. package/dist/pipeline/standard-pipeline.d.ts.map +1 -0
  476. package/dist/pipeline/standard-profile.d.ts +40 -0
  477. package/dist/pipeline/standard-profile.d.ts.map +1 -0
  478. package/dist/pipeline-builder.d.ts +122 -0
  479. package/dist/pipeline-builder.d.ts.map +1 -0
  480. package/dist/pipeline-spec-types.d.ts +24 -0
  481. package/dist/pipeline-spec-types.d.ts.map +1 -0
  482. package/dist/pipeline-spec.d.ts +367 -0
  483. package/dist/pipeline-spec.d.ts.map +1 -0
  484. package/dist/plugin.d.ts +4 -0
  485. package/dist/plugin.d.ts.map +1 -0
  486. package/dist/points-lines/__tests__/bounds.unit.test.d.ts +2 -0
  487. package/dist/points-lines/__tests__/bounds.unit.test.d.ts.map +1 -0
  488. package/dist/points-lines/__tests__/cache-recovery.unit.test.d.ts +2 -0
  489. package/dist/points-lines/__tests__/cache-recovery.unit.test.d.ts.map +1 -0
  490. package/dist/points-lines/__tests__/inspection-lanes.integration.test.d.ts +2 -0
  491. package/dist/points-lines/__tests__/inspection-lanes.integration.test.d.ts.map +1 -0
  492. package/dist/points-lines/__tests__/inspection.unit.test.d.ts +2 -0
  493. package/dist/points-lines/__tests__/inspection.unit.test.d.ts.map +1 -0
  494. package/dist/points-lines/__tests__/lane-structural.integration.test.d.ts +2 -0
  495. package/dist/points-lines/__tests__/lane-structural.integration.test.d.ts.map +1 -0
  496. package/dist/points-lines/__tests__/points-lines.bench.d.ts +2 -0
  497. package/dist/points-lines/__tests__/points-lines.bench.d.ts.map +1 -0
  498. package/dist/points-lines/__tests__/raster-lane.integration.test.d.ts +2 -0
  499. package/dist/points-lines/__tests__/raster-lane.integration.test.d.ts.map +1 -0
  500. package/dist/points-lines/__tests__/snapshot.unit.test.d.ts +2 -0
  501. package/dist/points-lines/__tests__/snapshot.unit.test.d.ts.map +1 -0
  502. package/dist/points-lines/__tests__/view-ubo.unit.test.d.ts +2 -0
  503. package/dist/points-lines/__tests__/view-ubo.unit.test.d.ts.map +1 -0
  504. package/dist/points-lines/admission.d.ts +36 -0
  505. package/dist/points-lines/admission.d.ts.map +1 -0
  506. package/dist/points-lines/bounds.d.ts +8 -0
  507. package/dist/points-lines/bounds.d.ts.map +1 -0
  508. package/dist/points-lines/expansion-cache.d.ts +28 -0
  509. package/dist/points-lines/expansion-cache.d.ts.map +1 -0
  510. package/dist/points-lines/inspection.d.ts +53 -0
  511. package/dist/points-lines/inspection.d.ts.map +1 -0
  512. package/dist/points-lines/prepare.d.ts +67 -0
  513. package/dist/points-lines/prepare.d.ts.map +1 -0
  514. package/dist/points-lines/record.d.ts +60 -0
  515. package/dist/points-lines/record.d.ts.map +1 -0
  516. package/dist/points-lines/snapshot.d.ts +49 -0
  517. package/dist/points-lines/snapshot.d.ts.map +1 -0
  518. package/dist/post-process-errors.d.ts +182 -0
  519. package/dist/post-process-errors.d.ts.map +1 -0
  520. package/dist/prepare/prepared-graphics-resolver.d.ts +59 -0
  521. package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -0
  522. package/dist/record/__tests__/material-bg-cache.unit.test.d.ts +2 -0
  523. package/dist/record/__tests__/material-bg-cache.unit.test.d.ts.map +1 -0
  524. package/dist/record/__tests__/material-slot-plan.unit.test.d.ts +2 -0
  525. package/dist/record/__tests__/material-slot-plan.unit.test.d.ts.map +1 -0
  526. package/dist/record/__tests__/typed-frame-graph-plan.unit.test.d.ts +2 -0
  527. package/dist/record/__tests__/typed-frame-graph-plan.unit.test.d.ts.map +1 -0
  528. package/dist/record/__tests__/video-texture-view.unit.test.d.ts +2 -0
  529. package/dist/record/__tests__/video-texture-view.unit.test.d.ts.map +1 -0
  530. package/dist/record/frame-lighting.d.ts +121 -0
  531. package/dist/record/frame-lighting.d.ts.map +1 -0
  532. package/dist/record/frame-snapshot.d.ts +429 -0
  533. package/dist/record/frame-snapshot.d.ts.map +1 -0
  534. package/dist/record/frame-targets.d.ts +20 -0
  535. package/dist/record/frame-targets.d.ts.map +1 -0
  536. package/dist/record/frame.d.ts +71 -0
  537. package/dist/record/frame.d.ts.map +1 -0
  538. package/dist/record/helpers.d.ts +102 -0
  539. package/dist/record/helpers.d.ts.map +1 -0
  540. package/dist/record/main-pass-geometry.d.ts +28 -0
  541. package/dist/record/main-pass-geometry.d.ts.map +1 -0
  542. package/dist/record/main-pass-material.d.ts +204 -0
  543. package/dist/record/main-pass-material.d.ts.map +1 -0
  544. package/dist/record/main-pass-sprite-draws.d.ts +43 -0
  545. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -0
  546. package/dist/record/main-pass.d.ts +20 -0
  547. package/dist/record/main-pass.d.ts.map +1 -0
  548. package/dist/record/mesh-ssbo.d.ts +446 -0
  549. package/dist/record/mesh-ssbo.d.ts.map +1 -0
  550. package/dist/record/render-context.d.ts +252 -0
  551. package/dist/record/render-context.d.ts.map +1 -0
  552. package/dist/record/shadow-pass.d.ts +52 -0
  553. package/dist/record/shadow-pass.d.ts.map +1 -0
  554. package/dist/record/skybox-post-pass.d.ts +33 -0
  555. package/dist/record/skybox-post-pass.d.ts.map +1 -0
  556. package/dist/record/typed-frame-graph.d.ts +35 -0
  557. package/dist/record/typed-frame-graph.d.ts.map +1 -0
  558. package/dist/record/view-ubo.d.ts +45 -0
  559. package/dist/record/view-ubo.d.ts.map +1 -0
  560. package/dist/render-contract.d.ts +424 -0
  561. package/dist/render-contract.d.ts.map +1 -0
  562. package/dist/render-data.d.ts +143 -0
  563. package/dist/render-data.d.ts.map +1 -0
  564. package/dist/render-graph-primitives.d.ts +76 -0
  565. package/dist/render-graph-primitives.d.ts.map +1 -0
  566. package/dist/render-pipeline.d.ts +110 -0
  567. package/dist/render-pipeline.d.ts.map +1 -0
  568. package/dist/render-system-extract.d.ts +868 -0
  569. package/dist/render-system-extract.d.ts.map +1 -0
  570. package/dist/render-system.d.ts +125 -0
  571. package/dist/render-system.d.ts.map +1 -0
  572. package/dist/scene/render-scene-types.d.ts +61 -0
  573. package/dist/scene/render-scene-types.d.ts.map +1 -0
  574. package/dist/scene/render-scene.d.ts +112 -0
  575. package/dist/scene/render-scene.d.ts.map +1 -0
  576. package/dist/scene-instances/post-spawn-resolve-joints.d.ts +30 -0
  577. package/dist/scene-instances/post-spawn-resolve-joints.d.ts.map +1 -0
  578. package/dist/shadow-atlas.d.ts +117 -0
  579. package/dist/shadow-atlas.d.ts.map +1 -0
  580. package/dist/ssao-buffers.d.ts +50 -0
  581. package/dist/ssao-buffers.d.ts.map +1 -0
  582. package/dist/ssao-config.d.ts +28 -0
  583. package/dist/ssao-config.d.ts.map +1 -0
  584. package/dist/systems/active-camera.d.ts +53 -0
  585. package/dist/systems/active-camera.d.ts.map +1 -0
  586. package/dist/systems/pass-selector.d.ts +24 -0
  587. package/dist/systems/pass-selector.d.ts.map +1 -0
  588. package/dist/systems/skin-palette-allocator.d.ts +49 -0
  589. package/dist/systems/skin-palette-allocator.d.ts.map +1 -0
  590. package/dist/systems/skin-palette-types.d.ts +14 -0
  591. package/dist/systems/skin-palette-types.d.ts.map +1 -0
  592. package/dist/systems/transparent-sort-config.d.ts +112 -0
  593. package/dist/systems/transparent-sort-config.d.ts.map +1 -0
  594. package/dist/tilemap-chunk-extract-system.d.ts +143 -0
  595. package/dist/tilemap-chunk-extract-system.d.ts.map +1 -0
  596. package/dist/typed-render-graph-primitives.d.ts +57 -0
  597. package/dist/typed-render-graph-primitives.d.ts.map +1 -0
  598. package/dist/typed-shadow-passes.d.ts +11 -0
  599. package/dist/typed-shadow-passes.d.ts.map +1 -0
  600. package/package.json +94 -0
  601. package/src/__tests__/authoring-path.unit.test.ts +23 -0
  602. package/src/__tests__/backend-selection-rhi-capture.test-d.ts +21 -0
  603. package/src/__tests__/callback-inference.test-d.ts +8 -0
  604. package/src/__tests__/cluster-binner.test.ts +272 -0
  605. package/src/__tests__/cluster-grid-default-surface.unit.test.ts +20 -0
  606. package/src/__tests__/construct-renderer-identity.unit.test.ts +23 -0
  607. package/src/__tests__/device-scope-lifecycle.unit.test.ts +151 -0
  608. package/src/__tests__/device-scope-stale-matrix.unit.test.ts +42 -0
  609. package/src/__tests__/direct-extract-consumer-contract.test-d.ts +23 -0
  610. package/src/__tests__/direct-light-snapshot.unit.test.ts +61 -0
  611. package/src/__tests__/factory-contract.integration.test.ts +280 -0
  612. package/src/__tests__/fallback-row-stride-surface.unit.test.ts +15 -0
  613. package/src/__tests__/frame-observation.contract.test.ts +112 -0
  614. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +32 -0
  615. package/src/__tests__/glyph-text-layout-recovery.unit.test.ts +167 -0
  616. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +350 -0
  617. package/src/__tests__/gpu-driven-indirect-raster.browser.test.ts +12 -0
  618. package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +16 -0
  619. package/src/__tests__/gpu-driven-production.integration.test.ts +436 -0
  620. package/src/__tests__/gpu-driven-scaling.unit.test.ts +87 -0
  621. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +271 -0
  622. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +290 -0
  623. package/src/__tests__/gpu-driven-view-reference.ts +111 -0
  624. package/src/__tests__/gpu-driven-view.browser.test.ts +30 -0
  625. package/src/__tests__/gpu-driven-view.dawn.test.ts +28 -0
  626. package/src/__tests__/gpu-driven-view.unit.test.ts +199 -0
  627. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +59 -0
  628. package/src/__tests__/gpu-scene-render-graph-gpu.ts +308 -0
  629. package/src/__tests__/gpu-scene-render-graph.browser.test.ts +19 -0
  630. package/src/__tests__/gpu-scene-render-graph.dawn.test.ts +19 -0
  631. package/src/__tests__/gpu-scene-schema.unit.test.ts +16 -0
  632. package/src/__tests__/gpu-scene.dawn.test.ts +205 -0
  633. package/src/__tests__/gpu-scene.unit.test.ts +147 -0
  634. package/src/__tests__/ibl/face-uniforms.unit.test.ts +46 -0
  635. package/src/__tests__/infinite-grid-no-vertex-probe.unit.test.ts +92 -0
  636. package/src/__tests__/injection-kind-owner.test-d.ts +14 -0
  637. package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +728 -0
  638. package/src/__tests__/light-buffer-layout.unit.test.ts +40 -0
  639. package/src/__tests__/material-contract-inventory.unit.test.ts +24 -0
  640. package/src/__tests__/material-cooked-projection.unit.test.ts +150 -0
  641. package/src/__tests__/material-snapshot-mutation.integration.test.ts +53 -0
  642. package/src/__tests__/material-static-dynamic-route.unit.test.ts +94 -0
  643. package/src/__tests__/material-stride-surface.unit.test.ts +32 -0
  644. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +106 -0
  645. package/src/__tests__/mesh-material-bindings.unit.test.ts +89 -0
  646. package/src/__tests__/mesh-stride-surface.unit.test.ts +16 -0
  647. package/src/__tests__/morph-culling-reentry.dawn.test.ts +157 -0
  648. package/src/__tests__/morph-feature-gpu-work.integration.test.ts +80 -0
  649. package/src/__tests__/morph-feature-plan.unit.test.ts +53 -0
  650. package/src/__tests__/morph-visual-evidence.integration.test.ts +80 -0
  651. package/src/__tests__/nineslice-handle-surface.unit.test.ts +19 -0
  652. package/src/__tests__/post-process-error-policy-owner.unit.test.ts +201 -0
  653. package/src/__tests__/prepared-buffer-kind-owner.test-d.ts +29 -0
  654. package/src/__tests__/prepared-graphics-capability.unit.test.ts +65 -0
  655. package/src/__tests__/prepared-graphics-generation.unit.test.ts +161 -0
  656. package/src/__tests__/prepared-graphics-lifecycle.unit.test.ts +138 -0
  657. package/src/__tests__/prepared-graphics-pipeline-warmup.unit.test.ts +29 -0
  658. package/src/__tests__/prepared-graphics-resolver.unit.test.ts +285 -0
  659. package/src/__tests__/prepared-graphics-snapshot.unit.test.ts +105 -0
  660. package/src/__tests__/prepared-graphics-store.unit.test.ts +154 -0
  661. package/src/__tests__/prepared-graphics-validation.unit.test.ts +176 -0
  662. package/src/__tests__/profiler-default-off.test.ts +22 -0
  663. package/src/__tests__/profiler-phase-catalog.test.ts +66 -0
  664. package/src/__tests__/profiler-phase-integration.test.ts +62 -0
  665. package/src/__tests__/profiler-phase-token.test.ts +79 -0
  666. package/src/__tests__/public-surface.test-d.ts +94 -0
  667. package/src/__tests__/recover-error-policy-owner.unit.test.ts +107 -0
  668. package/src/__tests__/render-error-code-owner.test-d.ts +116 -0
  669. package/src/__tests__/render-error-detail-ssot.test-d.ts +139 -0
  670. package/src/__tests__/render-error-exhaustive.test-d.ts +110 -0
  671. package/src/__tests__/render-feature-assembly.test-d.ts +43 -0
  672. package/src/__tests__/render-feature-context.test-d.ts +57 -0
  673. package/src/__tests__/render-feature-contract.test-d.ts +49 -0
  674. package/src/__tests__/render-feature-diagnostics.unit.test.ts +149 -0
  675. package/src/__tests__/render-feature-docs.unit.test.ts +89 -0
  676. package/src/__tests__/render-feature-errors.unit.test.ts +47 -0
  677. package/src/__tests__/render-feature-gpu-work.integration.test.ts +316 -0
  678. package/src/__tests__/render-feature-plan.test-d.ts +161 -0
  679. package/src/__tests__/render-feature-plan.unit.test.ts +170 -0
  680. package/src/__tests__/render-feature-prepared-diagnostics.unit.test.ts +90 -0
  681. package/src/__tests__/render-feature-prepared-errors.test-d.ts +79 -0
  682. package/src/__tests__/render-feature-prepared-graphics-docs.unit.test.ts +80 -0
  683. package/src/__tests__/render-feature-prepared-graphics-isolation.unit.test.ts +72 -0
  684. package/src/__tests__/render-feature-prepared-graphics-negative.test-d.ts +36 -0
  685. package/src/__tests__/render-feature-prepared-graphics.test-d.ts +74 -0
  686. package/src/__tests__/render-feature-prepared-graphics.unit.test.ts +121 -0
  687. package/src/__tests__/render-feature-public-surface.test-d.ts +57 -0
  688. package/src/__tests__/render-feature-retry.unit.test.ts +87 -0
  689. package/src/__tests__/render-feature-scheduling.unit.test.ts +110 -0
  690. package/src/__tests__/render-feature-stage-failed-policy-owner.unit.test.ts +137 -0
  691. package/src/__tests__/render-feature-stage-fault-recovery.unit.test.ts +146 -0
  692. package/src/__tests__/render-feature-visibility-context.test-d.ts +33 -0
  693. package/src/__tests__/render-feature-visibility-context.unit.test.ts +37 -0
  694. package/src/__tests__/render-feature-zero-work.unit.test.ts +76 -0
  695. package/src/__tests__/render-graph-compute-raster-gpu.ts +342 -0
  696. package/src/__tests__/render-graph-compute-raster.browser.test.ts +16 -0
  697. package/src/__tests__/render-graph-compute-raster.dawn.test.ts +22 -0
  698. package/src/__tests__/render-graph-hzb-gpu.ts +239 -0
  699. package/src/__tests__/render-graph-hzb.browser.test.ts +16 -0
  700. package/src/__tests__/render-graph-hzb.dawn.test.ts +23 -0
  701. package/src/__tests__/render-graph-primitives.unit.test.ts +22 -0
  702. package/src/__tests__/render-graph-storage-indirect-gpu.ts +162 -0
  703. package/src/__tests__/render-graph-storage-indirect.browser.test.ts +21 -0
  704. package/src/__tests__/render-graph-storage-indirect.dawn.test.ts +28 -0
  705. package/src/__tests__/render-scene-projection.unit.test.ts +168 -0
  706. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +121 -0
  707. package/src/__tests__/renderer-lifecycle.integration.test.ts +58 -0
  708. package/src/__tests__/rhi-backend-package-boundary.unit.test.ts +11 -0
  709. package/src/__tests__/scriptable-pack-consumer.test-d.ts +14 -0
  710. package/src/__tests__/shader-stage-surface.unit.test.ts +43 -0
  711. package/src/__tests__/ssao-config.unit.test.ts +36 -0
  712. package/src/__tests__/ssao-kernel-surface.unit.test.ts +14 -0
  713. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +28 -0
  714. package/src/__tests__/standard-descriptors.test-d.ts +22 -0
  715. package/src/__tests__/standard-pipeline.dawn.test.ts +61 -0
  716. package/src/__tests__/standard-pipeline.integration.test.ts +124 -0
  717. package/src/__tests__/standard-profile-oracle.unit.test.ts +62 -0
  718. package/src/__tests__/surface-configuration.unit.test.ts +91 -0
  719. package/src/__tests__/texture-usage-surface.unit.test.ts +58 -0
  720. package/src/__tests__/typed-pipeline-topology.unit.test.ts +223 -0
  721. package/src/__tests__/urp-pipeline-targets.unit.test.ts +42 -0
  722. package/src/__tests__/urp-pipeline.unit.test.ts +14 -0
  723. package/src/__tests__/vertex-color-pipeline.unit.test.ts +132 -0
  724. package/src/__tests__/vertex-layout-consumer.unit.test.ts +25 -0
  725. package/src/__tests__/visibility-diagnostics.integration.test.ts +62 -0
  726. package/src/__tests__/visibility-extract.integration.test.ts +98 -0
  727. package/src/__tests__/visibility-extract.perf.test.ts +130 -0
  728. package/src/__tests__/visibility-feature-dedup.integration.test.ts +55 -0
  729. package/src/__tests__/visibility-hidden-report.unit.test.ts +36 -0
  730. package/src/__tests__/visibility-instances.integration.test.ts +46 -0
  731. package/src/__tests__/visibility-multi-world.integration.test.ts +86 -0
  732. package/src/__tests__/visibility-producer-matrix.integration.test.ts +168 -0
  733. package/src/__tests__/visibility-producer-matrix.unit.test.ts +96 -0
  734. package/src/__tests__/visibility-public-surface.test-d.ts +27 -0
  735. package/src/__tests__/visibility-resolver.unit.test.ts +85 -0
  736. package/src/__tests__/visibility-resource-short-circuit.unit.test.ts +80 -0
  737. package/src/__tests__/visibility-snapshot-contract.unit.test.ts +60 -0
  738. package/src/__tests__/vocabulary.unit.test.ts +21 -0
  739. package/src/assembly/backend-contract.ts +57 -0
  740. package/src/assembly/factory.ts +8130 -0
  741. package/src/assembly/host-contract.ts +101 -0
  742. package/src/assembly/material/assembly.ts +48 -0
  743. package/src/assembly/material/pipeline-projection.ts +63 -0
  744. package/src/assets/asset-decoders.ts +158 -0
  745. package/src/assets/render-asset-port.ts +44 -0
  746. package/src/assets/resolve-render-asset.ts +13 -0
  747. package/src/authoring.ts +17 -0
  748. package/src/cluster-binner.ts +700 -0
  749. package/src/components/__tests__/points-lines-admission.unit.test.ts +219 -0
  750. package/src/components/__tests__/points-lines-schema.unit.test.ts +56 -0
  751. package/src/components/__tests__/schema-contract.unit.test.ts +10 -0
  752. package/src/components/__tests__/schema-parity.unit.test.ts +25 -0
  753. package/src/components/__tests__/visibility-schema.unit.test.ts +35 -0
  754. package/src/components/camera.ts +512 -0
  755. package/src/components/directional-light.ts +120 -0
  756. package/src/components/glyph-text.ts +64 -0
  757. package/src/components/index.ts +54 -0
  758. package/src/components/instances.ts +206 -0
  759. package/src/components/layer.ts +69 -0
  760. package/src/components/light-helpers.ts +188 -0
  761. package/src/components/lines.ts +18 -0
  762. package/src/components/mesh-filter.ts +60 -0
  763. package/src/components/mesh-renderer.ts +61 -0
  764. package/src/components/point-light-shadow.ts +43 -0
  765. package/src/components/point-light.ts +65 -0
  766. package/src/components/points.ts +40 -0
  767. package/src/components/post-process-params.ts +35 -0
  768. package/src/components/scene-instance.ts +193 -0
  769. package/src/components/skybox-background.ts +103 -0
  770. package/src/components/skylight.ts +97 -0
  771. package/src/components/sort-key.ts +75 -0
  772. package/src/components/spot-light.ts +124 -0
  773. package/src/components/sprite-animation.ts +168 -0
  774. package/src/components/sprite-instances.ts +179 -0
  775. package/src/components/sprite-playback-mode.ts +79 -0
  776. package/src/components/sprite-region-override.ts +105 -0
  777. package/src/components/tile-layer.ts +167 -0
  778. package/src/components/tilemap.ts +48 -0
  779. package/src/components/visibility.ts +53 -0
  780. package/src/construct-renderer.ts +67 -0
  781. package/src/debug-draw-glue.ts +48 -0
  782. package/src/device/device-scope.ts +306 -0
  783. package/src/device/gpu-residency.ts +1822 -0
  784. package/src/device/resource-types.ts +20 -0
  785. package/src/engine-metrics.ts +82 -0
  786. package/src/errors/ecs-validation.ts +16 -0
  787. package/src/errors/index.ts +3 -0
  788. package/src/errors/recover.ts +161 -0
  789. package/src/errors/render.ts +1284 -0
  790. package/src/extract/visibility.ts +120 -0
  791. package/src/features/fullscreen.ts +46 -0
  792. package/src/features/host.ts +1363 -0
  793. package/src/features/morph/morph-feature.ts +377 -0
  794. package/src/features/plan.ts +447 -0
  795. package/src/features/prepared-gpu-work.ts +959 -0
  796. package/src/features/prepared-graphics-store.ts +449 -0
  797. package/src/features/prepared-graphics.ts +420 -0
  798. package/src/features/render-graph-compute.ts +69 -0
  799. package/src/features/render-graph-contribution.ts +75 -0
  800. package/src/features/render-graph-raster.ts +332 -0
  801. package/src/features/render-graph-resources.ts +38 -0
  802. package/src/features/targets.ts +70 -0
  803. package/src/features/types.ts +103 -0
  804. package/src/features/vocabulary.ts +24 -0
  805. package/src/fullscreen-post-process-pass.ts +363 -0
  806. package/src/glyph-text-layout-system.ts +421 -0
  807. package/src/gpu-driven/batch-topology.ts +245 -0
  808. package/src/gpu-driven/production-raster.ts +695 -0
  809. package/src/gpu-driven/view-gpu.ts +703 -0
  810. package/src/gpu-resource.ts +159 -0
  811. package/src/gpu-scene-schema.ts +136 -0
  812. package/src/gpu-scene.ts +722 -0
  813. package/src/gpu-stage.ts +9 -0
  814. package/src/gpu-texture-usage.ts +12 -0
  815. package/src/gpu-usage.ts +14 -0
  816. package/src/hdrp-buffers.ts +480 -0
  817. package/src/ibl/IblPipelineCache.ts +1006 -0
  818. package/src/ibl/skylight-bind-group.ts +513 -0
  819. package/src/index.ts +129 -0
  820. package/src/inspection-types.ts +80 -0
  821. package/src/instance-buffer-cache.ts +95 -0
  822. package/src/internal.ts +9 -0
  823. package/src/lifecycle.ts +168 -0
  824. package/src/light-buffer-layout.ts +346 -0
  825. package/src/materials.ts +214 -0
  826. package/src/mesh-material-bindings.ts +103 -0
  827. package/src/pbr-pipeline.ts +1137 -0
  828. package/src/pipeline/standard-forward-lane.ts +229 -0
  829. package/src/pipeline/standard-pipeline.ts +555 -0
  830. package/src/pipeline/standard-profile.ts +65 -0
  831. package/src/pipeline-builder.ts +382 -0
  832. package/src/pipeline-spec-types.ts +28 -0
  833. package/src/pipeline-spec.ts +1231 -0
  834. package/src/plugin.ts +69 -0
  835. package/src/points-lines/__tests__/bounds.unit.test.ts +21 -0
  836. package/src/points-lines/__tests__/cache-recovery.unit.test.ts +157 -0
  837. package/src/points-lines/__tests__/inspection-lanes.integration.test.ts +146 -0
  838. package/src/points-lines/__tests__/inspection.unit.test.ts +173 -0
  839. package/src/points-lines/__tests__/lane-structural.integration.test.ts +122 -0
  840. package/src/points-lines/__tests__/points-lines.bench.ts +400 -0
  841. package/src/points-lines/__tests__/raster-lane.integration.test.ts +44 -0
  842. package/src/points-lines/__tests__/snapshot.unit.test.ts +101 -0
  843. package/src/points-lines/__tests__/view-ubo.unit.test.ts +73 -0
  844. package/src/points-lines/admission.ts +238 -0
  845. package/src/points-lines/bounds.ts +23 -0
  846. package/src/points-lines/expansion-cache.ts +215 -0
  847. package/src/points-lines/inspection.ts +97 -0
  848. package/src/points-lines/prepare.ts +177 -0
  849. package/src/points-lines/record.ts +125 -0
  850. package/src/points-lines/snapshot.ts +130 -0
  851. package/src/post-process-errors.ts +302 -0
  852. package/src/prepare/prepared-graphics-resolver.ts +456 -0
  853. package/src/record/__tests__/material-bg-cache.unit.test.ts +41 -0
  854. package/src/record/__tests__/material-slot-plan.unit.test.ts +44 -0
  855. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +41 -0
  856. package/src/record/__tests__/video-texture-view.unit.test.ts +90 -0
  857. package/src/record/frame-lighting.ts +1074 -0
  858. package/src/record/frame-snapshot.ts +500 -0
  859. package/src/record/frame-targets.ts +137 -0
  860. package/src/record/frame.ts +1598 -0
  861. package/src/record/helpers.ts +284 -0
  862. package/src/record/main-pass-geometry.ts +1065 -0
  863. package/src/record/main-pass-material.ts +1043 -0
  864. package/src/record/main-pass-sprite-draws.ts +1137 -0
  865. package/src/record/main-pass.ts +610 -0
  866. package/src/record/mesh-ssbo.ts +885 -0
  867. package/src/record/render-context.ts +384 -0
  868. package/src/record/shadow-pass.ts +698 -0
  869. package/src/record/skybox-post-pass.ts +593 -0
  870. package/src/record/typed-frame-graph.ts +461 -0
  871. package/src/record/view-ubo.ts +222 -0
  872. package/src/render-contract.ts +582 -0
  873. package/src/render-data.ts +385 -0
  874. package/src/render-graph-primitives.ts +811 -0
  875. package/src/render-pipeline.ts +259 -0
  876. package/src/render-system-extract.ts +4585 -0
  877. package/src/render-system.ts +1933 -0
  878. package/src/scene/render-scene-types.ts +67 -0
  879. package/src/scene/render-scene.ts +1087 -0
  880. package/src/scene-instances/post-spawn-resolve-joints.ts +165 -0
  881. package/src/shadow-atlas.ts +244 -0
  882. package/src/ssao-buffers.ts +334 -0
  883. package/src/ssao-config.ts +61 -0
  884. package/src/systems/active-camera.ts +116 -0
  885. package/src/systems/pass-selector.ts +49 -0
  886. package/src/systems/skin-palette-allocator.ts +251 -0
  887. package/src/systems/skin-palette-types.ts +14 -0
  888. package/src/systems/transparent-sort-config.ts +233 -0
  889. package/src/tilemap-chunk-extract-system.ts +1290 -0
  890. package/src/typed-render-graph-primitives.ts +541 -0
  891. package/src/typed-shadow-passes.ts +165 -0
@@ -0,0 +1,1933 @@
1
+ // @forgeax/engine-runtime - RenderSystem main entry (D-S2 three-stage Extract ->
2
+ // Prepare -> Record + 4-tier error fan-out).
3
+ //
4
+ // Engine-internal phase: NOT registered to World schedule (AC-09);
5
+ // `Renderer.draw([world], { cameraOwner: 0, resourceOwner: 0 })` invokes once per frame. See `Renderer` JSDoc in
6
+ // `./render-contract.ts` for the public error/frame contract and AGENTS.md
7
+ // "ECS render bridge" section for the AI-user-facing contract.
8
+ //
9
+ // Stage carve-out (review round 1 finding #3 - 505 line cap fallback):
10
+ // - render-system.ts (this file) types + orchestration
11
+ // - render-system-extract.ts Extract ECS query phase + snapshot helpers
12
+ // - render-system-record.ts Prepare + Record GPU phase + matrix helpers
13
+ //
14
+ // @forgeax/engine-math is referenced through render-system-record.ts which builds
15
+ // view/proj matrices and worldFromLocal via `mat4.compose / .multiply /
16
+ // .invert` (charter proposition 5: no math reinvention; render-system.test.ts
17
+ // asserts `/@forgeax\/engine-math/` shows up in render-system.ts source).
18
+
19
+ import { resolveAssetHandle } from '@forgeax/engine-assets-runtime';
20
+ import type { World } from '@forgeax/engine-ecs';
21
+ import type { RenderReadLease } from '@forgeax/engine-ecs/projection';
22
+ import type { RecorderSession } from '@forgeax/engine-profiler';
23
+ import type { BindGroupEntry, BindGroupLayout, Buffer, RenderPipeline } from '@forgeax/engine-rhi';
24
+ import { err, ok, type Result, RhiError } from '@forgeax/engine-rhi';
25
+ import {
26
+ derive,
27
+ type Handle,
28
+ type MaterialAsset,
29
+ type MaterialTextureValue,
30
+ type MeshAsset,
31
+ type RenderPipelineAsset,
32
+ RenderQueue,
33
+ type SamplerAsset,
34
+ toShared,
35
+ } from '@forgeax/engine-types';
36
+ import { createClusterBinScratch } from './cluster-binner';
37
+ import {
38
+ type ObservationUnavailableError,
39
+ PointsLinesMaterialUnsupportedError,
40
+ type RenderError,
41
+ } from './errors/render';
42
+ import {
43
+ type RenderFeaturePreparedGraphicsResolverInput,
44
+ runRenderFeatureFrame,
45
+ settlePreparedGraphicsCompletion,
46
+ } from './features/host';
47
+ import {
48
+ createRenderFeatureGpuWorkOwner,
49
+ type RenderFeatureGpuWorkOwner,
50
+ } from './features/prepared-gpu-work';
51
+ import { resolveStandardRenderFeatureTargets } from './features/targets';
52
+ import {
53
+ buildFullscreenPostProcessPass,
54
+ DEPTH_MIN_PARAMS_BYTE_SIZE,
55
+ entryHasDepthRead,
56
+ type PostProcessShaderEntry,
57
+ } from './fullscreen-post-process-pass';
58
+ import { GpuDrivenProduction } from './gpu-driven/production-raster';
59
+ import type { RenderSceneInspection } from './inspection-types';
60
+ import { admitPointsLines } from './points-lines/admission';
61
+ import { PointsLinesExpansionCache } from './points-lines/expansion-cache';
62
+ import {
63
+ inspectPointsLines,
64
+ type PointsLinesInspection,
65
+ type PointsLinesSourceError,
66
+ } from './points-lines/inspection';
67
+ import {
68
+ createPointsLinesLanePreparationAdapter,
69
+ type PointsLinesLanePreparationAdapter,
70
+ } from './points-lines/prepare';
71
+ import { createPointsLinesLaneAdapter, type PointsLinesBackend } from './points-lines/record';
72
+ import type { PointsLinesRetainedSnapshot } from './points-lines/snapshot';
73
+ import type { PointsLinesRecordOwner, PointsLinesRecordSubmission } from './record/render-context';
74
+
75
+ export type { RenderSceneInspection } from './inspection-types';
76
+
77
+ import { deriveVertexLayoutProjection } from '@forgeax/engine-geometry';
78
+ import {
79
+ GPU_BUFFER_USAGE_COPY_DST,
80
+ GPU_BUFFER_USAGE_INDEX,
81
+ GPU_BUFFER_USAGE_UNIFORM,
82
+ GPU_BUFFER_USAGE_VERTEX,
83
+ } from './gpu-usage';
84
+ import { assembleMaterialWithSkylightEntries } from './ibl/skylight-bind-group';
85
+ import { disposeInstanceBuffers, disposeTransientInstanceBuffers } from './instance-buffer-cache';
86
+ import type { MeshMaterialBindingObservation } from './mesh-material-bindings';
87
+ import { validateClusterGrid } from './pipeline/standard-pipeline';
88
+ import { DEFAULT_CLUSTER_GRID } from './pipeline/standard-profile';
89
+ import { PostProcessError } from './post-process-errors';
90
+ import {
91
+ createPreparedGraphicsResolver,
92
+ type PreparedGraphicsResolver,
93
+ } from './prepare/prepared-graphics-resolver';
94
+ import {
95
+ type FrameObservation,
96
+ type FrameObservationOptions,
97
+ observeCurrentFrame,
98
+ type RecordProfileRunner,
99
+ recordFrame,
100
+ } from './record/frame';
101
+ import { buildPerFrameBindGroups } from './record/frame-lighting';
102
+ import type { RenderFrameState } from './record/frame-snapshot';
103
+ import { applyParamSnapshotToUbo, residentTextureView } from './record/main-pass-material';
104
+ import type { RenderSystemInternals } from './record/render-context';
105
+ import {
106
+ type RenderFeatureGraphCandidate,
107
+ resetRenderFeatureGraphState,
108
+ } from './record/typed-frame-graph';
109
+ import {
110
+ type CameraSnapshot,
111
+ type DrawOwnerOptions,
112
+ RENDER_PHASE_CATALOG,
113
+ type RenderPhase,
114
+ type RenderPhaseSkipReason,
115
+ type RenderRecordPhase,
116
+ } from './render-contract';
117
+ import type { DispatchEntry, ExtractedFrame, RenderableSnapshot } from './render-system-extract';
118
+ import { extractFrames } from './render-system-extract';
119
+ import { PersistentRenderScene } from './scene/render-scene';
120
+ import { resolveSsaoParameters } from './ssao-config';
121
+ import {
122
+ getTransparentSortConfig,
123
+ TRANSPARENT_SORT_MODE_DISTANCE,
124
+ TRANSPARENT_SORT_MODE_LAYER_Y,
125
+ TRANSPARENT_SORT_MODE_LAYER_YZ,
126
+ TRANSPARENT_SORT_MODE_LAYER_Z,
127
+ } from './systems/transparent-sort-config';
128
+
129
+ export type {
130
+ _InternalRenderPipelineContext,
131
+ _StandardForwardSceneView,
132
+ PerPassResources,
133
+ PipelineState,
134
+ RenderSystemInternals,
135
+ RenderSystemRuntime,
136
+ SurfaceBackendKind,
137
+ SwapChainFormatPair,
138
+ } from './record/render-context';
139
+ export {
140
+ configureSurface,
141
+ MATERIAL_PER_ENTITY_STRIDE,
142
+ resolveSurfaceFormatPair,
143
+ STANDARD_PBR_UBO_SIZE,
144
+ selectSwapChainFormat,
145
+ } from './record/render-context';
146
+
147
+ /**
148
+ * Unified transparent-queue sub-sort covering all four
149
+ * {@link TransparentSortConfig} modes. Reorders the
150
+ * `queue === RenderQueue.Transparent` segment of the dispatch list;
151
+ * all other queue segments keep their relative order.
152
+ *
153
+ * | mode | primary key | secondary key | tertiary key |
154
+ * |:--:|:--|:--|:--|
155
+ * | 0 (LAYER_Z) | `layer` ASC | `posZ` ASC | `materialHandle` ASC |
156
+ * | 1 (LAYER_Y) | `layer` ASC | `-(posY - pivotY * sizeY)` ASC | `materialHandle` ASC |
157
+ * | 2 (LAYER_YZ) | `layer` ASC | `(posY - pivotY * sizeY) + yzAlpha * posZ` ASC | `materialHandle` ASC |
158
+ * | 3 (DISTANCE) | `-(dist² from camera)` ASC (back-to-front, layer ignored) | — |
159
+ *
160
+ * The `materialHandle` tertiary key for modes 0/1/2 groups same-material
161
+ * entries together whenever the primary+secondary sort values are equal
162
+ * (e.g. tilemap tiles in the same row/layer share `posY` in LAYER_Y mode).
163
+ * Consecutive same-material groups then collapse into fold buckets in the
164
+ * record-stage fold operator, significantly reducing draw call count for
165
+ * tilemap-heavy scenes.
166
+ *
167
+ * `posX/Y/Z` = translation column of the entity's world mat4 (indices 12/13/14).
168
+ * `pivotY` = `RenderableSnapshot.material.paramSnapshot.pivotAndSize[1]` (default 0.5).
169
+ * `sizeY` = length of the Y-axis column of the world mat4 (indices 4/5/6).
170
+ */
171
+ function sortTransparentDispatch(
172
+ dispatch: DispatchEntry[],
173
+ world: World,
174
+ cameras: readonly CameraSnapshot[],
175
+ renderables: readonly RenderableSnapshot[],
176
+ ): DispatchEntry[] {
177
+ const cfg = getTransparentSortConfig(world);
178
+ const mode = cfg.mode;
179
+
180
+ // Indices of Transparent-queue entries within the dispatch list.
181
+ const transparentSlots: number[] = [];
182
+ for (let i = 0; i < dispatch.length; i++) {
183
+ if (dispatch[i]?.queue === RenderQueue.Transparent) transparentSlots.push(i);
184
+ }
185
+ if (transparentSlots.length <= 1) return dispatch;
186
+
187
+ let sortedSlotOrder: number[];
188
+
189
+ if (mode === TRANSPARENT_SORT_MODE_DISTANCE) {
190
+ const camera = cameras[0];
191
+ if (camera === undefined) return dispatch;
192
+ const camPos = camera.position;
193
+
194
+ // Squared camera distance per entry (negated → ascending = back-to-front).
195
+ // D-3: world position = translation column of Transform.world (m[12,13,14]).
196
+ const negDistSq = (entry: DispatchEntry): number => {
197
+ const tx = renderables[entry.renderableIndex]?.transform;
198
+ if (tx === undefined) return 0;
199
+ const w = tx.world;
200
+ const dx = (w[12] ?? 0) - (camPos[0] ?? 0);
201
+ const dy = (w[13] ?? 0) - (camPos[1] ?? 0);
202
+ const dz = (w[14] ?? 0) - (camPos[2] ?? 0);
203
+ return -(dx * dx + dy * dy + dz * dz);
204
+ };
205
+
206
+ sortedSlotOrder = transparentSlots.slice().sort((a, b) => {
207
+ const da = negDistSq(dispatch[a] as DispatchEntry);
208
+ const db = negDistSq(dispatch[b] as DispatchEntry);
209
+ if (da < db) return -1;
210
+ if (da > db) return 1;
211
+ return 0;
212
+ });
213
+ } else {
214
+ // Modes 0/1/2: primary = layer ASC, secondary = mode-formula ASC.
215
+ // sizeY = length of the Y-axis column (col1 = indices 4,5,6) of the
216
+ // world mat4; rotation-invariant and handles flipV sign correctly.
217
+ const sortVal = (entry: DispatchEntry): number => {
218
+ const tx = renderables[entry.renderableIndex]?.transform;
219
+ const w = tx?.world;
220
+ const posY = (w?.[13] ?? 0) as number;
221
+ const posZ = (w?.[14] ?? 0) as number;
222
+ if (mode === TRANSPARENT_SORT_MODE_LAYER_Z) return posZ;
223
+ const mat = renderables[entry.renderableIndex]?.material;
224
+ // feat-20260625-refactor-sprite-as-transparent-mesh M3 / w15: post
225
+ // SpriteFieldsSnapshot ablation, pivot lives in paramSnapshot
226
+ // .pivotAndSize[0..1] (UBO-aligned vec4 slot 2, plan D-6). Non-sprite
227
+ // materials carry paramSnapshot=undefined and fall back to 0.5.
228
+ const pivotAndSize = mat?.paramSnapshot?.pivotAndSize as readonly number[] | undefined;
229
+ const pivotY = (pivotAndSize?.[1] ?? 0.5) as number;
230
+ const wy4 = (w?.[4] ?? 0) as number;
231
+ const wy5 = (w?.[5] ?? 1) as number;
232
+ const wy6 = (w?.[6] ?? 0) as number;
233
+ const sizeY = Math.sqrt(wy4 * wy4 + wy5 * wy5 + wy6 * wy6);
234
+ const footY = posY - pivotY * sizeY;
235
+ if (mode === TRANSPARENT_SORT_MODE_LAYER_Y) return -footY;
236
+ if (mode === TRANSPARENT_SORT_MODE_LAYER_YZ) return footY + cfg.yzAlpha * posZ;
237
+ // Defensive fallback for an unknown mode that slips past setTransparentSortConfig.
238
+ return posZ;
239
+ };
240
+
241
+ sortedSlotOrder = transparentSlots.slice().sort((a, b) => {
242
+ const da = dispatch[a] as DispatchEntry;
243
+ const db = dispatch[b] as DispatchEntry;
244
+ const la = da.layer;
245
+ const lb = db.layer;
246
+ if (la !== lb) return la - lb;
247
+ const va = sortVal(da);
248
+ const vb = sortVal(db);
249
+ if (va < vb) return -1;
250
+ if (va > vb) return 1;
251
+ // Tertiary tiebreaker: group same-materialHandle entries together so
252
+ // fold-eligible consecutive runs form. Entries at equal (layer,
253
+ // sortVal) — e.g. tilemap tiles in the same row under LAYER_Y — are
254
+ // depth-equivalent; reordering them by material does not change the
255
+ // visual result but maximises fold-bucket width.
256
+ return da.materialHandle - db.materialHandle;
257
+ });
258
+ }
259
+
260
+ // Scatter the reordered Transparent entries back into their original slots.
261
+ const result = dispatch.slice();
262
+ for (let k = 0; k < transparentSlots.length; k++) {
263
+ const targetSlot = transparentSlots[k] as number;
264
+ const sourceSlot = sortedSlotOrder[k] as number;
265
+ result[targetSlot] = dispatch[sourceSlot] as DispatchEntry;
266
+ }
267
+ return result;
268
+ }
269
+
270
+ /**
271
+ * Engine-internal Extract / Prepare / Record driver; constructed by createRenderer.
272
+ *
273
+ * w15 M5 dual-pipeline dispatch: `pipelineDispatchCounts` surfaces per-frame
274
+ * counters of how many entities were routed to each pipeline (plan-strategy
275
+ * D-P4 / requirements AC-07). The counts roll over monotonically — test
276
+ * callers read them after `draw([world], { cameraOwner: 0, resourceOwner: 0 })` to assert each tag saw >= 1 draw.
277
+ * Reset is intentional on every `draw([world], { cameraOwner: 0, resourceOwner: 0 })` entry so per-frame assertions
278
+ * stay local (charter proposition 4 explicit failure: test code sees exact
279
+ * per-draw counts, not stale cross-frame totals).
280
+ *
281
+ * bug-20260519: BUILTIN cube migrated to 12F so the legacy `unlitBuiltin`
282
+ * counter is gone; the surface collapses to `unlit` (every entity whose
283
+ * shader identity is `forgeax::default-unlit`) + `standard` (every entity
284
+ * whose shader identity is `forgeax::default-standard-pbr`).
285
+ */
286
+ export interface RenderSystem {
287
+ /** Returns true only when this invocation reached queue submission. */
288
+ draw(
289
+ worlds: readonly World[],
290
+ opts: DrawOwnerOptions,
291
+ renderReadLeases?: readonly RenderReadLease[],
292
+ ): boolean;
293
+ /** Release renderer-owned persistent state for one detached World. */
294
+ detachScene(world: World): void;
295
+ /** Release the profiler catalog contribution owned by this RenderSystem. */
296
+ releaseProfilerCatalog(): void;
297
+ observeCurrentFrame(
298
+ options: FrameObservationOptions,
299
+ ): Promise<Result<FrameObservation, ObservationUnavailableError>>;
300
+ readonly pipelineDispatchCounts: {
301
+ readonly unlit: number;
302
+ };
303
+ /**
304
+ * feat-20260528-frustum-culling M5 / w14: per-frame frustum-culling counters.
305
+ * Updated by `draw([world], { cameraOwner: 0, resourceOwner: 0 })` on every call from the Extract stage.
306
+ */
307
+ readonly frustumStats: { culled: number; total: number };
308
+ /** Per-frame candidate entities rejected by author visibility. */
309
+ readonly visibilityStats: { explicitlyHidden: number };
310
+ /** Persistent scene maintenance evidence from the ordinary single-World path. */
311
+ readonly renderScene: RenderSceneInspection;
312
+ /** Retained Points/Lines authoring facts from the single scene projection. */
313
+ readonly pointsLinesSnapshots: readonly PointsLinesRetainedSnapshot[];
314
+ /** Current mesh-slot provenance and active diagnostics from the last frame. */
315
+ readonly meshMaterialBindings: readonly MeshMaterialBindingObservation[];
316
+ /**
317
+ * feat-20260531-bloom-first-declarative-render-graph-pass M4 fix-up w19:
318
+ * per-frame render-graph pass names in declaration order. Empty array
319
+ * before the first `draw([world], { cameraOwner: 0, resourceOwner: 0 })` call; populated after the per-frame
320
+ * graph is built (lazily on first draw). Read-only introspection surface
321
+ * so smoke tests can assert the declarative pass chain is wired without
322
+ * reaching into engine internals.
323
+ */
324
+ readonly perFramePassNames: readonly string[];
325
+ /**
326
+ * feat-20260531-per-frame-bind-group-cache M1 / w4: per-frame
327
+ * createBindGroup counter. Reset to 0 on every `draw([world], { cameraOwner: 0, resourceOwner: 0 })` entry,
328
+ * bumped on each cache-miss createBindGroup call in the record stage.
329
+ * Aligns with pipelineDispatchCounts precedent: closure-mutable object
330
+ * + draw-entry reset + readonly getter. Stable-frame AC-03 asserts
331
+ * createBindGroup == 0 when all bind groups are cache-resident.
332
+ *
333
+ * M5 / w19 type-safe finalization: the return type is purposely the
334
+ * narrowest inline object literal `{ readonly createBindGroup: number }`
335
+ * rather than a wider Record/alias — this ensures TS language service
336
+ * hover shows the exact field name + type, and AC-09 consumption sites
337
+ * infer `number` without `as` casts (plan-strategy D-7 + sec.8
338
+ * discoverability).
339
+ */
340
+ readonly bindGroupCounts: {
341
+ readonly createBindGroup: number;
342
+ readonly keys: readonly string[];
343
+ };
344
+ /** Configure the sole Standard graph owner before the first frame. */
345
+ configureStandard(config: RenderPipelineAsset['config']): void;
346
+ /** Register one engine-owned post-process shader used by the Standard lane. */
347
+ registerBuiltinPostProcess(id: string, entry: PostProcessShaderEntry): () => void;
348
+ /**
349
+ * feat-20260612-rhi-destroy-renderer-dispose-gpu-lifecycle / M5 / w21:
350
+ * release the per-RenderSystem frame-state GPU bookkeeping during
351
+ * `Renderer.dispose()`. Retires the compiled graph and clears the instance-buffer cache.
352
+ *
353
+ * Idempotent (architecture-principles §6): a second call after both
354
+ * structures are cleared is a no-op. Per-step failures inside drain /
355
+ * disposeInstanceBuffers fall through silently -- the Renderer.dispose
356
+ * cascade owns the try/catch + errorRegistry.fire fan-out (D-3).
357
+ */
358
+ disposeFrameState(): void;
359
+
360
+ /**
361
+ * feat-20260622-s5 M3 / B-2 / w18: drop the device-bound state the recover()
362
+ * rebuild must shed before re-running the pipeline build against a fresh
363
+ * device. Two effects, both keyed to the lost device:
364
+ * 1. the per-frame render-graph's pendingDestroy queue (PooledTextures
365
+ * minted by the lost device — clearPendingDestroy skips destroyTexture);
366
+ * 2. post-process declarations + their eager param UBOs. Declarations are
367
+ * CPU-owned author intent and survive; UBOs are stale lost-device
368
+ * handles and are rebuilt after the replacement device is live.
369
+ * Idempotent and graph-optional (no-op when nothing has compiled yet).
370
+ */
371
+ resetForRecover(): void;
372
+ /** Recreate post-process GPU parameter resources after a device rebuild. */
373
+ restorePostProcessResources(): void;
374
+ }
375
+
376
+ function isStructuredRendererError(error: unknown): error is RhiError | RenderError {
377
+ if (!(error instanceof Error)) return false;
378
+ const structured = error as {
379
+ readonly code?: unknown;
380
+ readonly expected?: unknown;
381
+ readonly hint?: unknown;
382
+ readonly detail?: unknown;
383
+ };
384
+ return (
385
+ typeof structured.code === 'string' &&
386
+ typeof structured.expected === 'string' &&
387
+ typeof structured.hint === 'string' &&
388
+ structured.detail !== undefined
389
+ );
390
+ }
391
+
392
+ function reportPreparedGraphicsCompletionError(
393
+ internals: RenderSystemInternals,
394
+ error: unknown,
395
+ ): void {
396
+ if (isStructuredRendererError(error)) {
397
+ internals.errorRegistry.fire(error);
398
+ return;
399
+ }
400
+ const innerError =
401
+ error instanceof RhiError
402
+ ? error
403
+ : { code: 'unknown' as const, message: String(error), name: (error as Error)?.name };
404
+ internals.errorRegistry.fire(
405
+ new RhiError({
406
+ code: 'webgpu-runtime-error',
407
+ expected: 'prepared graphics completion cleanup reports its errors',
408
+ hint: 'inspect detail.error for the underlying retirement or recovery failure',
409
+ detail: { error: innerError },
410
+ }),
411
+ );
412
+ }
413
+
414
+ function isPendingRenderFeaturePreparation(error: RenderError): boolean {
415
+ return (
416
+ error.code === 'render-feature-preparation-failed' &&
417
+ error.detail.reason.startsWith('rhi-not-available:')
418
+ );
419
+ }
420
+
421
+ function makePreparedPipelinePendingError(): RhiError {
422
+ return new RhiError({
423
+ code: 'rhi-not-available',
424
+ expected: 'prepared pipeline shader module warm-up to finish asynchronously',
425
+ hint: 'retry the prepared graphics pass on the next frame',
426
+ });
427
+ }
428
+
429
+ type PointsLinesGpuResources = {
430
+ readonly vertexBuffer: Buffer;
431
+ readonly indexBuffer: Buffer;
432
+ };
433
+ type PointsLinesPreparationAdapter = PointsLinesLanePreparationAdapter<PointsLinesGpuResources>;
434
+
435
+ /**
436
+ * The Standard renderer's single Points/Lines prepare owner.
437
+ *
438
+ * The retained snapshot is the identity boundary; this owner only resolves
439
+ * the source assets, runs admission, and publishes the existing preparation
440
+ * and record contracts to the main geometry loop. The expansion cache is
441
+ * shared across lanes and this owner keeps the one prepared vertex/index
442
+ * resource pair for each retained identity.
443
+ */
444
+ class StandardPointsLinesOwner implements PointsLinesRecordOwner {
445
+ private readonly cache = new PointsLinesExpansionCache();
446
+ private readonly preparations = new Map<string, PointsLinesPreparationAdapter>();
447
+ private readonly active = new Map<string, PointsLinesInspection>();
448
+ private readonly layoutProjection = deriveVertexLayoutProjection({
449
+ position: new Float32Array(0),
450
+ normal: new Float32Array(0),
451
+ uv: new Float32Array(0),
452
+ tangent: new Float32Array(0),
453
+ });
454
+
455
+ constructor(private readonly internals: RenderSystemInternals) {}
456
+
457
+ beginFrame(): void {
458
+ this.active.clear();
459
+ }
460
+
461
+ prepare(
462
+ entry: import('./record/frame-snapshot').ValidatedRenderable,
463
+ clustered: boolean,
464
+ ): PointsLinesRecordSubmission | undefined {
465
+ const snapshot = entry.source.pointsLines;
466
+ if (snapshot === undefined || snapshot.component === undefined || entry.world === undefined) {
467
+ return undefined;
468
+ }
469
+ const key = `${snapshot.worldId}:${snapshot.entityKey}`;
470
+ const topology = snapshot.component === 'Points' ? 'point-list' : 'line-list';
471
+ const backend = this.backend();
472
+ const lane = backend === 'wgpu-webgl2' ? 'cpu-webgl2' : clustered ? 'clustered' : 'direct';
473
+ const meshResult = resolveAssetHandle<MeshAsset>(
474
+ entry.world,
475
+ toShared<'MeshAsset'>(entry.source.assetHandle),
476
+ );
477
+ const materialResult = resolveAssetHandle<MaterialAsset>(
478
+ entry.world,
479
+ toShared<'MaterialAsset'>(snapshot.materialHandle),
480
+ );
481
+ if (!meshResult.ok || !materialResult.ok) {
482
+ this.publishRefusal(
483
+ snapshot,
484
+ new PointsLinesMaterialUnsupportedError({
485
+ entity: snapshot.entityKey,
486
+ material: 'unresolved',
487
+ pass: 'forward',
488
+ module: 'asset-resolution',
489
+ reason: 'source MeshAsset or MaterialAsset could not be resolved',
490
+ }),
491
+ );
492
+ return undefined;
493
+ }
494
+ const admission = admitPointsLines({
495
+ entity: snapshot.entityKey,
496
+ mesh: meshResult.value,
497
+ material: materialResult.value,
498
+ ...(snapshot.style?.kind === 'points'
499
+ ? {
500
+ points: {
501
+ sizePx: snapshot.style.sizePx,
502
+ shape: snapshot.style.shape === 'circle' ? 1 : 0,
503
+ },
504
+ }
505
+ : { lines: { widthPx: snapshot.style?.widthPx ?? 1 } }),
506
+ });
507
+ if (!admission.ok) {
508
+ this.publishRefusal(snapshot, admission.error);
509
+ return undefined;
510
+ }
511
+ const geometryKey = this.cache.getOrCreate(snapshot, meshResult.value).key;
512
+ const prepKey = `${geometryKey}:${lane}:${backend}`;
513
+ let preparation = this.preparations.get(prepKey);
514
+ if (preparation === undefined) {
515
+ preparation = createPointsLinesLanePreparationAdapter(lane, backend, {
516
+ cache: this.cache,
517
+ adapter: {
518
+ create: (geometry) => {
519
+ const vertex = this.internals.device.createBuffer({
520
+ label: `points-lines-vertices:${geometryKey}`,
521
+ size: Math.max(4, geometry.vertices.byteLength),
522
+ usage: GPU_BUFFER_USAGE_VERTEX | GPU_BUFFER_USAGE_COPY_DST,
523
+ mappedAtCreation: false,
524
+ });
525
+ if (!vertex.ok) return err(vertex.error);
526
+ const index = this.internals.device.createBuffer({
527
+ label: `points-lines-indices:${geometryKey}`,
528
+ size: Math.max(4, geometry.indices.byteLength),
529
+ usage: GPU_BUFFER_USAGE_INDEX | GPU_BUFFER_USAGE_COPY_DST,
530
+ mappedAtCreation: false,
531
+ });
532
+ if (!index.ok) {
533
+ this.internals.device.destroyBuffer(vertex.value);
534
+ return err(index.error);
535
+ }
536
+ return ok({ vertexBuffer: vertex.value, indexBuffer: index.value });
537
+ },
538
+ upload: (resource, geometry) => {
539
+ if (geometry.vertices.byteLength > 0) {
540
+ const vertexWrite = this.internals.device.queue.writeBuffer(
541
+ resource.vertexBuffer,
542
+ 0,
543
+ geometry.vertices,
544
+ );
545
+ if (!vertexWrite.ok) return err(vertexWrite.error);
546
+ }
547
+ if (geometry.indices.byteLength > 0) {
548
+ const indexWrite = this.internals.device.queue.writeBuffer(
549
+ resource.indexBuffer,
550
+ 0,
551
+ geometry.indices,
552
+ );
553
+ if (!indexWrite.ok) return err(indexWrite.error);
554
+ }
555
+ return ok(geometry.derivedBytes);
556
+ },
557
+ validate: (_resource, geometry) =>
558
+ geometry.expandedVertexCount > 0 && geometry.expandedIndexCount > 0
559
+ ? ok(undefined)
560
+ : err(new Error('Points/Lines expansion contains no drawable triangles')),
561
+ destroy: (resource) => {
562
+ this.internals.device.destroyBuffer(resource.vertexBuffer);
563
+ this.internals.device.destroyBuffer(resource.indexBuffer);
564
+ },
565
+ },
566
+ });
567
+ this.preparations.set(prepKey, preparation);
568
+ }
569
+ const prepared = preparation.prepare(snapshot, meshResult.value);
570
+ if (!prepared.ok) {
571
+ const lkg = preparation.lastKnownGood();
572
+ if (lkg !== undefined) {
573
+ const lkgPlan = createPointsLinesLaneAdapter(lane, backend).createRecordPlan(
574
+ lkg.snapshot,
575
+ lkg.geometry,
576
+ );
577
+ const state = preparation.inspect();
578
+ this.active.set(
579
+ key,
580
+ inspectPointsLines({
581
+ snapshot,
582
+ topology,
583
+ lane,
584
+ pointCount: lkg.geometry.pointCount,
585
+ segmentCount: lkg.geometry.segmentCount,
586
+ sourceBytes: lkg.geometry.sourceBytes,
587
+ derivedBytes: lkg.geometry.derivedBytes,
588
+ cache: { hit: true, rebuilds: state.rebuilds, evictions: 0 },
589
+ drawCount: lkgPlan.drawCount,
590
+ uploadBytes: 0,
591
+ lastKnownGood: true,
592
+ refusal: {
593
+ code: prepared.error.code,
594
+ expected: prepared.error.expected,
595
+ hint: prepared.error.hint,
596
+ detail: prepared.error.detail,
597
+ generation: prepared.error.detail.generation,
598
+ lastKnownGood: true,
599
+ },
600
+ }),
601
+ );
602
+ return {
603
+ plan: lkgPlan,
604
+ vertexBuffer: lkg.resource.vertexBuffer,
605
+ indexBuffer: lkg.resource.indexBuffer,
606
+ layoutProjection: this.layoutProjection,
607
+ };
608
+ }
609
+ this.publishRefusal(snapshot, prepared.error);
610
+ return undefined;
611
+ }
612
+ const plan = createPointsLinesLaneAdapter(lane, backend).createRecordPlan(
613
+ snapshot,
614
+ prepared.value.geometry,
615
+ );
616
+ const state = preparation.inspect();
617
+ this.active.set(
618
+ key,
619
+ inspectPointsLines({
620
+ snapshot,
621
+ topology,
622
+ lane,
623
+ pointCount: prepared.value.geometry.pointCount,
624
+ segmentCount: prepared.value.geometry.segmentCount,
625
+ sourceBytes: prepared.value.geometry.sourceBytes,
626
+ derivedBytes: prepared.value.geometry.derivedBytes,
627
+ cache: {
628
+ hit: prepared.value.uploadedBytes === 0,
629
+ rebuilds: state.rebuilds,
630
+ evictions: 0,
631
+ },
632
+ drawCount: plan.drawCount,
633
+ uploadBytes: prepared.value.uploadedBytes,
634
+ lastKnownGood: prepared.value.lastKnownGood,
635
+ }),
636
+ );
637
+ return {
638
+ plan,
639
+ vertexBuffer: prepared.value.resource.vertexBuffer,
640
+ indexBuffer: prepared.value.resource.indexBuffer,
641
+ layoutProjection: this.layoutProjection,
642
+ };
643
+ }
644
+
645
+ resetForDeviceLoss(): void {
646
+ for (const preparation of this.preparations.values()) preparation.resetForDeviceLoss();
647
+ this.active.clear();
648
+ }
649
+
650
+ inspections(): readonly PointsLinesInspection[] {
651
+ return [...this.active.values()];
652
+ }
653
+
654
+ private backend(): PointsLinesBackend {
655
+ switch (this.internals.device.caps.backendKind) {
656
+ case 'wgpu-webgl2':
657
+ return 'wgpu-webgl2';
658
+ case 'null':
659
+ return 'null';
660
+ default:
661
+ return 'webgpu';
662
+ }
663
+ }
664
+
665
+ private publishRefusal(
666
+ snapshot: PointsLinesRetainedSnapshot,
667
+ error: PointsLinesSourceError,
668
+ ): void {
669
+ this.active.set(
670
+ `${snapshot.worldId}:${snapshot.entityKey}`,
671
+ inspectPointsLines({
672
+ snapshot,
673
+ topology: snapshot.component === 'Points' ? 'point-list' : 'line-list',
674
+ lane: 'refused',
675
+ pointCount: 0,
676
+ segmentCount: 0,
677
+ sourceBytes: 0,
678
+ derivedBytes: 0,
679
+ cache: { hit: false, rebuilds: 0, evictions: 0 },
680
+ drawCount: 0,
681
+ uploadBytes: 0,
682
+ lastKnownGood: false,
683
+ refusal: {
684
+ code: error.code,
685
+ expected: error.expected,
686
+ hint: error.hint,
687
+ detail: error.detail,
688
+ generation: snapshot.meshGeneration,
689
+ lastKnownGood: false,
690
+ },
691
+ }),
692
+ );
693
+ }
694
+ }
695
+
696
+ export function createRenderSystem(internals: RenderSystemInternals): RenderSystem {
697
+ const phaseCatalogRegistration = internals.profiler?.registerPhaseCatalog(
698
+ 'render',
699
+ RENDER_PHASE_CATALOG,
700
+ );
701
+ let releaseProfilerCatalog =
702
+ phaseCatalogRegistration?.ok === true ? phaseCatalogRegistration.value : undefined;
703
+ let preparedWorlds: readonly World[] = [];
704
+ const pointsLinesOwner = new StandardPointsLinesOwner(internals);
705
+ const persistentRenderScene = new PersistentRenderScene({
706
+ getDevice: () => internals.device,
707
+ onGpuError: (error) => internals.errorRegistry.fire(error),
708
+ onSharedRefMutation: (worldId, handle) => {
709
+ internals.gpuStore.invalidateMesh(handle, preparedWorlds[worldId] ?? worldId);
710
+ },
711
+ });
712
+ const gpuDrivenShaderFactory =
713
+ internals.shaderModuleFactory ??
714
+ ({
715
+ createShaderModule: () =>
716
+ err(
717
+ new RhiError({
718
+ code: 'rhi-not-available',
719
+ expected: 'the renderer backend exposes shader module creation',
720
+ hint: 'construct the renderer through the backend pack before activating GPU-driven rendering',
721
+ }),
722
+ ),
723
+ } satisfies import('./pipeline-builder').PipelineBuilderShaderModuleFactory);
724
+ let gpuDrivenProduction = new GpuDrivenProduction(internals.device, gpuDrivenShaderFactory);
725
+ const featureGpuWork: RenderFeatureGpuWorkOwner = createRenderFeatureGpuWorkOwner({
726
+ getDevice: () => internals.device,
727
+ getShaderModuleFactory: () =>
728
+ internals.shaderModuleFactory ??
729
+ ({
730
+ createShaderModule: () =>
731
+ err(
732
+ new RhiError({
733
+ code: 'rhi-not-available',
734
+ expected: 'the renderer backend exposes shader module creation',
735
+ hint: 'construct the renderer through the backend pack before activating GPU features',
736
+ }),
737
+ ),
738
+ } satisfies import('./pipeline-builder').PipelineBuilderShaderModuleFactory),
739
+ });
740
+ const disposeFeatureGpuWork = (): void => {
741
+ const disposed = featureGpuWork.dispose();
742
+ if (!disposed.ok) internals.errorRegistry.fire(disposed.error);
743
+ };
744
+ // Per-RenderSystem frame state: closure-internal frameNumber + the
745
+ // per-entity instance GPU buffer cache (feat-20260514 M3 / w15: the
746
+ // record stage owns GPU storage buffer allocation for Instances entities;
747
+ // the `instanceBuffers` map is keyed by the packed Entity u32 surfaced
748
+ // through `InstancesSnapshot.cacheKey` and rebuilds buffers on archetype
749
+ // version bump or byte-length change). The legacy
750
+ // `lastFiredLimitExceededFrame` engine-side dedup field was removed in
751
+ // feat-20260513-instanced-mesh M5 (T-M5-1 + T-M5-3); the active
752
+ // `'limit-exceeded'` emit point is now the record stage upload path.
753
+ //
754
+ // feat-20260708 M1 / D-1a #1: positive-half keys are worldEntityKey(worldId,
755
+ // cacheKey) composites. Negative-half fold-bucket keys (sprite fold) stay
756
+ // raw (material-handle-based, cross-world collision semantically correct).
757
+ const frameState: RenderFrameState = {
758
+ frameNumber: 0,
759
+ directionalShadowCache: null,
760
+ directionalShadowCacheRecorded: false,
761
+ compiledFrameGraph: null,
762
+ compiledFrameGraphTopologyKey: null,
763
+ retiredCompiledFrameGraphs: new Set(),
764
+ currentFrameObservationSource: undefined,
765
+ currentDirectionalShadowView: null,
766
+ currentSpotShadowView: null,
767
+ instanceBuffers: new Map(),
768
+ morphBuffers: new Map(),
769
+ hdrpClusterBinScratch: createClusterBinScratch(),
770
+ hdrpClusterGridScratch: null,
771
+ hdrpLightIndexListScratch: null,
772
+ hdrpClusterMembershipBindGroup: null,
773
+ transientInstanceBuffers: [],
774
+ warnedZeroLightStandard: false,
775
+ warnedMultiLightDirectional: false,
776
+ warnedMultiLightPoint: false,
777
+ warnedMultiLightSpot: false,
778
+ // feat-20260630-equirect-kind-internalized-ibl-declarative-skyligh M3 / w19:
779
+ // once-warn latches for >1 Skylight / >1 SkyboxBackground (names winner).
780
+ warnedMultiSkylight: false,
781
+ warnedMultiSkybox: false,
782
+ warnedSkyboxTonemapNone: false,
783
+ // feat-20260520-2d-sprite-layer-mvp M-3 / w25 (AC-18 path 4): per-
784
+ // handle warn-once anchor for the sprite-bucket missing-texture
785
+ // fallback. Set<number> keyed by raw Handle<TextureAsset>; never
786
+ // cleared (charter F1 minimal surface — the per-RenderSystem lifetime
787
+ // is the natural upper bound).
788
+ warnedMissingBaseColorTextureHandles: new Set<number>(),
789
+ // feat-20260527-sprite-nineslice M2 / w11 + M4 / w16 (AC-16): once-per-
790
+ // renderable guard for the runtime `nineslice.scale-too-small` metric
791
+ // counter (`runtime.metrics.increment(...)` in render-system-record.ts).
792
+ warnedNineSliceScaleEntities: new Set<number>(),
793
+ // feat-20260630-equirect-kind-internalized-ibl-declarative-skyligh M3 / w18:
794
+ // per-handle fire-once anchor for the lazy equirect projection failure
795
+ // (EquirectProjectionFailedError). Set<number> keyed by raw
796
+ // Handle<EquirectAsset>; never cleared (per-RenderSystem lifetime upper bound).
797
+ firedEquirectProjectionFailedHandles: new Set<number>(),
798
+ // feat-20260622-handle-to-id-allocator-elimination M1 / w3: per-frame
799
+ // bind group caches as nested WeakMap chain roots. viewBindGroupCache
800
+ // covers main and shadow variants; meshBindGroupCache keys on inner
801
+ // buffer handles (D-3). Roots are stable between device recoveries.
802
+ viewBindGroupCache: new WeakMap(),
803
+ meshBindGroupCache: new WeakMap(),
804
+ // feat-20260622-handle-to-id-allocator-elimination M1 / w2: per-entity
805
+ // material and instances caches (outer Map<entityKey, WeakMap>).
806
+ materialBgPerEntity: new Map(),
807
+ instancesBgPerEntity: new Map(),
808
+ instancesBgShared: new WeakMap(),
809
+ // cross-entity shared material cache (outer Map<shaderId, WeakMap>).
810
+ materialBgShared: new Map(),
811
+ // cross-frame material assembly cache; entries are only retained when all
812
+ // explicit texture/sampler handles resolved to resident GPU resources.
813
+ materialBgAssemblyCache: new Map(),
814
+ // singleton material cache (flat Map<variant, BindGroup>; D-6).
815
+ singletonMaterialCache: new Map(),
816
+ // post-process bind group cache (bloom / fxaa / ssao): identity-keyed
817
+ // WeakMap chain so resize-retired transient targets rebuild automatically.
818
+ postProcessBgCache: new WeakMap(),
819
+ // feat-20260601-customizable-render-pipeline-seam M1 / w7: installed-pipeline state.
820
+ // 0 = nothing installed yet (createRenderer dogfood installs the default before a
821
+ // draw). activePipeline defaults to the built-in forward pipeline.
822
+ installedPipelineHandle: 0,
823
+ activePipeline: internals.standardPipeline,
824
+ // feat-20260601 verify round 2: the standard forward pipeline installs with no config
825
+ // (its topology is frame-invariant). Standard configuration overwrites this with the
826
+ // resolved asset config on every swap so the active graph reads its current config.
827
+ installedPipelineConfig: undefined,
828
+ // feat-20260608-cluster-lighting M2 / w10 + M5 / w20: HDRP active flag + once-per-frame
829
+ // warn dedup set for hdrp-light-budget-exceeded / hdrp-index-list-overflow.
830
+ isHdrpActive: false,
831
+ hdrpOncePerFrameFired: new Set(),
832
+ // feat-20260612-point-light-shadows-urp-hdrp M3 / T-M3-2 (plan-strategy §D-1):
833
+ // cube_array shadow atlas + per-frame snapshot list. Atlas is null until the
834
+ // first frame whose extracted lights.pointShadow is non-empty (zero-shadow
835
+ // scenes never allocate; AC-09); the snapshot list defaults to an empty
836
+ // tuple so the typed point-shadow graph sees zero shadow lights as the
837
+ // initial steady state.
838
+ pointShadowAtlas: null,
839
+ pointShadowSnapshots: [],
840
+ // feat-20260622-chunk-gpu-instancing-sprite-tilemap M1 / w4 (D-1):
841
+ // initial fold-bucket count is 0; recordFrame writes the per-frame
842
+ // value from foldDispatchBuckets(...) before dispatch.
843
+ lastFoldBucketCount: 0,
844
+ // feat-20260625-spot-light-shadow-mapping M2 / w9 (D-2): empty initial spot
845
+ // shadow snapshot list so the spotShadowDepth caster pass renders zero
846
+ // tiles until the first frame with a castShadow spot (AC-03).
847
+ spotShadowSnapshots: [],
848
+ };
849
+ let directFrameId = 0;
850
+
851
+ function beginProfilePhase(session: RecorderSession | undefined, phase: RenderPhase): boolean {
852
+ if (session === undefined) return false;
853
+ try {
854
+ return session.beginPhase('render', phase).ok;
855
+ } catch {
856
+ return false;
857
+ }
858
+ }
859
+
860
+ function endProfilePhase(session: RecorderSession | undefined): void {
861
+ if (session === undefined) return;
862
+ try {
863
+ session.endPhase();
864
+ } catch {
865
+ // Profiler failures never alter rendering.
866
+ }
867
+ }
868
+
869
+ function recordProfileSkip(
870
+ session: RecorderSession | undefined,
871
+ phase: RenderPhase,
872
+ reason: RenderPhaseSkipReason,
873
+ ): void {
874
+ if (session === undefined) return;
875
+ try {
876
+ session.recordSkip({ source: 'render', phase, reason });
877
+ } catch {
878
+ // Profiler failures never alter rendering.
879
+ }
880
+ }
881
+
882
+ function runProfiledRenderPhase<T>(
883
+ session: RecorderSession | undefined,
884
+ phase: RenderPhase,
885
+ action: () => T,
886
+ ): T {
887
+ const opened = beginProfilePhase(session, phase);
888
+ try {
889
+ return action();
890
+ } finally {
891
+ if (opened) endProfilePhase(session);
892
+ }
893
+ }
894
+ let lastBuiltPipelineHandle = 0;
895
+ // Monotonic configuration epoch: bumped on every Standard configuration change to brand the
896
+ // installed pipeline so `draw` can detect a swap and rebuild the per-frame
897
+ // graph. Replaces the prior raw-handle brand (D-19: the pipeline configuration takes a
898
+ // POD, no handle).
899
+ let installEpoch = 0;
900
+ // CPU post-process declarations are owned by the feature host and remain live
901
+ // across recovery. Only device-bound buffers and pipelines are rebuilt here.
902
+ // D-3 / D-8: per-shader params UBO resource table (id -> GPU Buffer).
903
+ // Eager-created at register time when entry.params is present (byteSize >= 16,
904
+ // defaultValue.length === byteSize); reused frame-to-frame via queue.writeBuffer.
905
+ const postProcessParamsBuffers = new Map<string, Buffer>();
906
+ const builtinPostProcessEntries = new Map<string, PostProcessShaderEntry>();
907
+ let activeFeaturePostProcessEntries: ReadonlyMap<string, PostProcessShaderEntry> = new Map();
908
+ const lookupPostProcess = (id: string): PostProcessShaderEntry | undefined =>
909
+ builtinPostProcessEntries.get(id) ?? activeFeaturePostProcessEntries.get(id);
910
+ // feat-20260621 M-A2 / w8: expose the eager-created per-id params UBO through
911
+ // the narrow runtime surface so dispatchFullscreenPass can writeBuffer the
912
+ // per-frame snapshot + bind it at group(1) binding(2).
913
+ const getPostProcessParamsBuffer = (id: string): Buffer | undefined =>
914
+ postProcessParamsBuffers.get(id);
915
+ const ensurePostProcessParamsResources = (
916
+ featureEntries: ReadonlyMap<string, PostProcessShaderEntry>,
917
+ ): void => {
918
+ const declarations = new Map<string, PostProcessShaderEntry>([
919
+ ...builtinPostProcessEntries.entries(),
920
+ ...featureEntries.entries(),
921
+ ]);
922
+ for (const [id, entry] of declarations) {
923
+ const byteSize =
924
+ entry.params?.byteSize ?? (entryHasDepthRead(entry) ? DEPTH_MIN_PARAMS_BYTE_SIZE : 0);
925
+ if (byteSize === 0 || postProcessParamsBuffers.has(id)) continue;
926
+ const created = internals.device.createBuffer({
927
+ label: `post-process-params-${id}`,
928
+ size: byteSize,
929
+ usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
930
+ mappedAtCreation: false,
931
+ });
932
+ if (!created.ok) throw created.error;
933
+ const buffer = created.value;
934
+ if (entry.params !== undefined) {
935
+ const written = internals.device.queue.writeBuffer(buffer, 0, entry.params.defaultValue);
936
+ if (!written.ok) {
937
+ internals.device.destroyBuffer(buffer);
938
+ throw written.error;
939
+ }
940
+ }
941
+ postProcessParamsBuffers.set(id, buffer);
942
+ }
943
+ for (const [id, buffer] of postProcessParamsBuffers) {
944
+ if (declarations.has(id)) continue;
945
+ internals.device.destroyBuffer(buffer);
946
+ postProcessParamsBuffers.delete(id);
947
+ }
948
+ };
949
+ // feat-20260609 M4 / T-10-a: post-process pipeline cache (id|colorFormat -> RhiRenderPipeline).
950
+ // Solves CONCERN-1: dispatcher previously passed `pipeline=null` to
951
+ // built.createHandle because per-frame execute closures cannot await async
952
+ // shader compile. The cache here delegates the actual build to
953
+ // `internals.buildPostProcessPipeline` (sync wrapper over the shared shader
954
+ // adapter; 1-frame warmup), then memoizes by `id|colorFormat`.
955
+ const postProcessPipelineCache = new Map<string, RenderPipeline>();
956
+ const getPostProcessPipeline = (
957
+ id: string,
958
+ bgl: BindGroupLayout,
959
+ colorFormat: GPUTextureFormat,
960
+ ): RenderPipeline | null => {
961
+ const key = `${id}|${colorFormat}`;
962
+ const cached = postProcessPipelineCache.get(key);
963
+ if (cached !== undefined) return cached;
964
+ const entry = lookupPostProcess(id);
965
+ if (entry === undefined) return null;
966
+ const factory = internals.buildPostProcessPipeline;
967
+ if (factory === undefined) return null;
968
+ const built = factory(entry, bgl, colorFormat, `post-process-${id}`);
969
+ if (built === null) return null;
970
+ postProcessPipelineCache.set(key, built);
971
+ return built;
972
+ };
973
+ Object.assign(internals, {
974
+ lookupPostProcess,
975
+ getPostProcessParamsBuffer,
976
+ getPostProcessPipeline,
977
+ });
978
+ // w15 M5 (plan-strategy D-P4 / AC-07): per-frame dispatch counters. Reset
979
+ // on every `draw([world], { cameraOwner: 0, resourceOwner: 0 })` entry; bumped once per actual `pass.setPipeline`
980
+ // dispatch in render-system-record.ts. Two-way split mirrors the two
981
+ // render pipelines on PipelineState (bug-20260519: BUILTIN cube migrated
982
+ // to 12F so `unlitBuiltin` retired): `unlit` covers every
983
+ // unlit material, `standard` covers every PBR material.
984
+ const dispatchCounts: { unlit: number } = {
985
+ unlit: 0,
986
+ };
987
+ // feat-20260531-per-frame-bind-group-cache M1 / w4: per-frame
988
+ // createBindGroup counter scaffolding. Reset on every draw([world], { cameraOwner: 0, resourceOwner: 0 }) entry,
989
+ // bumped on cache-miss in render-system-record.ts (M2-M4 bump points).
990
+ // Aligns with dispatchCounts precedent: closure-mutable object.
991
+ const bindGroupCounts: { createBindGroup: number; keys: string[] } = {
992
+ createBindGroup: 0,
993
+ keys: [],
994
+ };
995
+ const preparedPipelineIds = new WeakMap<object, string>();
996
+ const preparedMaterialPipelineShaders = new WeakMap<object, string>();
997
+ const preparedGroup0Pipelines = new WeakSet<object>();
998
+ const preparedViewOnlyPipelines = new WeakSet<object>();
999
+ const preparedRenderMaterialPipelines = new WeakSet<object>();
1000
+ const preparedAssetHandles = new WeakMap<World, Map<string, number>>();
1001
+ const preparedHandle = <Brand extends string>(
1002
+ world: World,
1003
+ guid: string,
1004
+ brand: Brand,
1005
+ ): Handle<Brand, 'shared'> | undefined => {
1006
+ let handles = preparedAssetHandles.get(world);
1007
+ if (handles === undefined) {
1008
+ handles = new Map();
1009
+ preparedAssetHandles.set(world, handles);
1010
+ }
1011
+ const key = `${brand}:${guid.toLowerCase()}`;
1012
+ const cached = handles.get(key);
1013
+ if (cached !== undefined) return cached as Handle<Brand, 'shared'>;
1014
+ const asset = internals.assets.lookup(guid);
1015
+ if (asset === undefined) return undefined;
1016
+ const handle = world.allocSharedRef(brand, asset);
1017
+ handles.set(key, handle as number);
1018
+ return handle;
1019
+ };
1020
+ const preparedMaterialBindings = (
1021
+ materialShaderId: string,
1022
+ worldIndex: number,
1023
+ materialGuid: string,
1024
+ layout: BindGroupLayout,
1025
+ ) => {
1026
+ const world = preparedWorlds[worldIndex];
1027
+ const material = internals.assets.lookup(materialGuid) as MaterialAsset | undefined;
1028
+ const pipelineState = internals.getPipelineState();
1029
+ if (world === undefined || material?.kind !== 'material' || pipelineState === null) {
1030
+ return err(new Error('prepared material asset is unavailable'));
1031
+ }
1032
+ const schema = internals.getParamSchema?.(materialShaderId) ?? [];
1033
+ const values = material.values ?? {};
1034
+ const derived = derive(schema);
1035
+ const fields = [...derived.textureFieldNames];
1036
+ let buffer: Buffer | undefined;
1037
+ let payloadByteLength = 0;
1038
+ if (derived.uboLayout.totalBytes > 0) {
1039
+ const payload = new Uint8Array(Math.max(16, derived.uboLayout.totalBytes));
1040
+ applyParamSnapshotToUbo(
1041
+ payload,
1042
+ schema,
1043
+ values as Readonly<Record<string, number | readonly number[]>>,
1044
+ );
1045
+ const created = internals.device.createBuffer({
1046
+ label: `prepared-material:${materialGuid}`,
1047
+ size: payload.byteLength,
1048
+ usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
1049
+ });
1050
+ if (!created.ok) return created;
1051
+ buffer = created.value;
1052
+ payloadByteLength = payload.byteLength;
1053
+ const written = internals.device.queue.writeBuffer(buffer, 0, payload);
1054
+ if (!written.ok) {
1055
+ internals.device.destroyBuffer(buffer);
1056
+ return written;
1057
+ }
1058
+ }
1059
+ const textureResources: Array<{
1060
+ sampler: import('@forgeax/engine-rhi').Sampler;
1061
+ view: import('@forgeax/engine-rhi').TextureView;
1062
+ }> = [];
1063
+ for (let index = 0; index < fields.length; index += 1) {
1064
+ const field = fields[index];
1065
+ const value = field === undefined ? undefined : values[field];
1066
+ const textureValue =
1067
+ typeof value === 'object' && value !== null && 'texture' in value
1068
+ ? (value as MaterialTextureValue)
1069
+ : undefined;
1070
+ let sampler = pipelineState.defaultSampler;
1071
+ if (textureValue?.sampler !== undefined) {
1072
+ const handle = preparedHandle(world, String(textureValue.sampler), 'SamplerAsset');
1073
+ if (handle !== undefined) {
1074
+ const asset = resolveAssetHandle<SamplerAsset>(world, handle);
1075
+ if (asset.ok) {
1076
+ const resident = internals.gpuStore.ensureSamplerResident(handle, asset.value, world);
1077
+ if (resident.ok) sampler = resident.value;
1078
+ }
1079
+ }
1080
+ }
1081
+ let view = pipelineState.defaultWhiteTextureView;
1082
+ if (textureValue !== undefined) {
1083
+ const handle = preparedHandle(world, String(textureValue.texture), 'TextureAsset');
1084
+ if (handle !== undefined) {
1085
+ view = residentTextureView(world, internals.gpuStore, internals, handle) ?? view;
1086
+ }
1087
+ }
1088
+ textureResources.push({ sampler, view });
1089
+ }
1090
+ const entries: BindGroupEntry[] = [];
1091
+ let textureIndex = 0;
1092
+ for (let index = 0; index < derived.bglEntries.length; index += 1) {
1093
+ const expected = derived.bglEntries[index];
1094
+ if (expected?.buffer?.type === 'uniform' && buffer !== undefined) {
1095
+ entries.push({
1096
+ binding: expected.binding,
1097
+ resource: { kind: 'buffer', value: { buffer, size: payloadByteLength } },
1098
+ });
1099
+ continue;
1100
+ }
1101
+ const textureResource = textureResources[textureIndex];
1102
+ if (
1103
+ expected?.sampler?.type === 'filtering' &&
1104
+ derived.bglEntries[index + 1]?.texture !== undefined &&
1105
+ textureResource !== undefined
1106
+ ) {
1107
+ entries.push({
1108
+ binding: expected.binding,
1109
+ resource: { kind: 'sampler', value: textureResource.sampler },
1110
+ });
1111
+ continue;
1112
+ }
1113
+ if (expected?.texture !== undefined && textureResource !== undefined) {
1114
+ entries.push({
1115
+ binding: expected.binding,
1116
+ resource: { kind: 'textureView', value: textureResource.view },
1117
+ });
1118
+ textureIndex += 1;
1119
+ continue;
1120
+ }
1121
+ if (buffer !== undefined) internals.device.destroyBuffer(buffer);
1122
+ return err(new Error('prepared material schema contains unsupported binding kinds'));
1123
+ }
1124
+ for (let index = fields.length; index < 4; index += 1) {
1125
+ const binding = derived.userRegionBindingEnd + (index - fields.length) * 2;
1126
+ entries.push(
1127
+ {
1128
+ binding,
1129
+ resource: { kind: 'sampler', value: pipelineState.defaultSampler },
1130
+ },
1131
+ {
1132
+ binding: binding + 1,
1133
+ resource: { kind: 'textureView', value: pipelineState.defaultWhiteTextureView },
1134
+ },
1135
+ );
1136
+ }
1137
+ const fallback = pipelineState.skylightFallback;
1138
+ if (fallback === null) {
1139
+ if (buffer !== undefined) internals.device.destroyBuffer(buffer);
1140
+ return err(new Error('prepared material skylight fallback is unavailable'));
1141
+ }
1142
+ const completeEntries = assembleMaterialWithSkylightEntries(
1143
+ entries,
1144
+ {
1145
+ irradianceView: fallback.irradianceView,
1146
+ irradianceSampler: fallback.sampler,
1147
+ prefilterView: fallback.prefilterView,
1148
+ prefilterSampler: fallback.sampler,
1149
+ brdfLutView: fallback.brdfLutView,
1150
+ brdfLutSampler: fallback.sampler,
1151
+ intensityBuffer: fallback.intensityBuffer,
1152
+ },
1153
+ {
1154
+ emissiveSampler: pipelineState.defaultSampler,
1155
+ emissiveView: pipelineState.defaultWhiteTextureView,
1156
+ occlusionSampler: pipelineState.defaultSampler,
1157
+ occlusionView: pipelineState.defaultWhiteTextureView,
1158
+ },
1159
+ );
1160
+ const group = internals.device.createBindGroup({ layout, entries: completeEntries });
1161
+ if (!group.ok) {
1162
+ if (buffer !== undefined) internals.device.destroyBuffer(buffer);
1163
+ return group;
1164
+ }
1165
+ if (buffer === undefined) return group;
1166
+ return ok({
1167
+ handle: group.value,
1168
+ dynamicOffsets: [0],
1169
+ release: () => internals.device.destroyBuffer(buffer),
1170
+ });
1171
+ };
1172
+ const lastFrustumStats: { culled: number; total: number } = { culled: 0, total: 0 };
1173
+ const lastVisibilityStats: { explicitlyHidden: number } = { explicitlyHidden: 0 };
1174
+ let lastMeshMaterialBindings: readonly MeshMaterialBindingObservation[] = [];
1175
+ let lastMeshMaterialBindingFrame: ExtractedFrame | undefined;
1176
+ const preparedResolverFactory = (
1177
+ input: RenderFeaturePreparedGraphicsResolverInput,
1178
+ ): PreparedGraphicsResolver => {
1179
+ activeFeaturePostProcessEntries = input.fullscreenEffects;
1180
+ ensurePostProcessParamsResources(input.fullscreenEffects);
1181
+ return createPreparedGraphicsResolver({
1182
+ device: internals.device,
1183
+ featureIdentity: input.featureIdentity,
1184
+ generation: input.generation,
1185
+ capabilityAvailable: true,
1186
+ featureOrder: input.order,
1187
+ lookup: input.lookup,
1188
+ resolveGpuBuffer: (reference) =>
1189
+ featureGpuWork.resolveBuffer(input.featureIdentity, reference),
1190
+ resolvePipeline: (descriptor) => {
1191
+ const postProcessEntry =
1192
+ input.fullscreenEffects.get(descriptor.shader) ??
1193
+ builtinPostProcessEntries.get(descriptor.shader);
1194
+ if (postProcessEntry !== undefined) {
1195
+ const fullscreen = buildFullscreenPostProcessPass(
1196
+ { device: internals.device, errorRegistry: internals.errorRegistry },
1197
+ postProcessEntry,
1198
+ );
1199
+ if (fullscreen === null) return err(new Error('prepared post-process layout failed'));
1200
+ const pipeline = internals.getPostProcessPipeline?.(
1201
+ descriptor.shader,
1202
+ fullscreen.bindGroupLayout,
1203
+ descriptor.colorFormats[0] as GPUTextureFormat,
1204
+ );
1205
+ if (pipeline !== null && pipeline !== undefined) {
1206
+ preparedPipelineIds.set(pipeline as object, descriptor.shader);
1207
+ if (internals.getMaterialShaderBindingContract?.(descriptor.shader) === 'group-0') {
1208
+ preparedGroup0Pipelines.add(pipeline as object);
1209
+ }
1210
+ }
1211
+ return pipeline === null || pipeline === undefined
1212
+ ? err(makePreparedPipelinePendingError())
1213
+ : ok(pipeline);
1214
+ }
1215
+ const preparedPipeline = internals.getMaterialShaderPipeline?.(
1216
+ descriptor.shader,
1217
+ descriptor.colorFormats[0] === 'rgba16float',
1218
+ descriptor.renderState,
1219
+ descriptor.topology,
1220
+ descriptor.indexFormat,
1221
+ undefined,
1222
+ 'forward',
1223
+ undefined,
1224
+ descriptor.sampleCount ?? 1,
1225
+ // Prepared graphics own their declared attachment format. Passing
1226
+ // it through keeps the PSO compatible with feature-target views
1227
+ // (the ordinary material path intentionally defaults to the
1228
+ // swap-chain view format).
1229
+ descriptor.colorFormats[0] as GPUTextureFormat,
1230
+ undefined,
1231
+ descriptor.depthFormat === undefined
1232
+ ? null
1233
+ : (descriptor.depthFormat as GPUTextureFormat),
1234
+ descriptor.vertexLayout,
1235
+ );
1236
+ // A requested material shader is an exact pipeline contract. During
1237
+ // async shader warmup, substituting the generic unlit pipeline can
1238
+ // mismatch vertex layouts and HDR attachment formats, turning a
1239
+ // retryable next-frame prepare into an invalid GPU command buffer.
1240
+ const pipeline = preparedPipeline ?? null;
1241
+ if (pipeline !== null) {
1242
+ preparedMaterialPipelineShaders.set(pipeline as object, descriptor.shader);
1243
+ const bindingContract = internals.getMaterialShaderBindingContract?.(descriptor.shader);
1244
+ if (bindingContract === 'group-0') {
1245
+ preparedGroup0Pipelines.add(pipeline as object);
1246
+ } else if (bindingContract === 'view-only') {
1247
+ preparedViewOnlyPipelines.add(pipeline as object);
1248
+ } else {
1249
+ preparedRenderMaterialPipelines.add(pipeline as object);
1250
+ }
1251
+ }
1252
+ return pipeline === null ? err(makePreparedPipelinePendingError()) : ok(pipeline);
1253
+ },
1254
+ resolveBindings: (descriptor, pipeline) => {
1255
+ const materialShaderId = preparedMaterialPipelineShaders.get(pipeline as object);
1256
+ const bindingContract =
1257
+ materialShaderId === undefined
1258
+ ? undefined
1259
+ : internals.getMaterialShaderBindingContract?.(materialShaderId);
1260
+ if (bindingContract === 'group-0-resource') {
1261
+ return descriptor.values.sceneDepth === undefined
1262
+ ? err(new Error('prepared group-0 resource pipeline requires a scene target'))
1263
+ : ok(undefined);
1264
+ }
1265
+ if (bindingContract === 'view-and-scene-depth') {
1266
+ return ok(undefined);
1267
+ }
1268
+ if (preparedGroup0Pipelines.has(pipeline as object)) {
1269
+ const layout =
1270
+ (materialShaderId === undefined
1271
+ ? undefined
1272
+ : internals.getMaterialBindGroupLayout?.(materialShaderId)) ??
1273
+ (
1274
+ pipeline as RenderPipeline & {
1275
+ getBindGroupLayout?: (index: number) => BindGroupLayout;
1276
+ }
1277
+ ).getBindGroupLayout?.(0);
1278
+ return layout === undefined
1279
+ ? err(new Error('prepared group-0 pipeline bind group layout is unavailable'))
1280
+ : internals.device.createBindGroup({ layout, entries: [] });
1281
+ }
1282
+ if (
1283
+ preparedViewOnlyPipelines.has(pipeline as object) ||
1284
+ (preparedRenderMaterialPipelines.has(pipeline as object) &&
1285
+ descriptor.values.group === 0) ||
1286
+ pipeline === internals.getPipelineState()?.unlitPipeline
1287
+ ) {
1288
+ return ok(undefined);
1289
+ }
1290
+ const group = descriptor.values.group;
1291
+ const layout =
1292
+ (materialShaderId !== undefined
1293
+ ? (internals.getMaterialBindGroupLayout?.(materialShaderId) ??
1294
+ (group === 0 ? internals.getPipelineState()?.materialBindGroupLayout : undefined))
1295
+ : undefined) ??
1296
+ (
1297
+ pipeline as RenderPipeline & {
1298
+ getBindGroupLayout?: (index: number) => BindGroupLayout;
1299
+ }
1300
+ ).getBindGroupLayout?.(group === 1 ? 1 : 0);
1301
+ const material = descriptor.values.material;
1302
+ if (
1303
+ group === 1 &&
1304
+ layout !== undefined &&
1305
+ materialShaderId !== undefined &&
1306
+ typeof material === 'object' &&
1307
+ material !== null &&
1308
+ 'world' in material &&
1309
+ 'guid' in material &&
1310
+ typeof material.world === 'number' &&
1311
+ typeof material.guid === 'string'
1312
+ ) {
1313
+ return preparedMaterialBindings(materialShaderId, material.world, material.guid, layout);
1314
+ }
1315
+ return layout === undefined
1316
+ ? err(new Error('prepared pipeline bind group layout is unavailable'))
1317
+ : internals.device.createBindGroup({
1318
+ layout,
1319
+ entries:
1320
+ group === 1 && preparedPipelineIds.has(pipeline as object)
1321
+ ? [
1322
+ {
1323
+ binding: 0,
1324
+ resource: {
1325
+ kind: 'textureView',
1326
+ value: internals.getPipelineState()?.fallbackTextureView as never,
1327
+ },
1328
+ },
1329
+ {
1330
+ binding: 1,
1331
+ resource: {
1332
+ kind: 'sampler',
1333
+ value: internals.getPipelineState()?.defaultSampler as never,
1334
+ },
1335
+ },
1336
+ ...(internals.getPostProcessParamsBuffer?.(
1337
+ preparedPipelineIds.get(pipeline as object) ?? '',
1338
+ ) === undefined
1339
+ ? []
1340
+ : [
1341
+ {
1342
+ binding: 2,
1343
+ resource: {
1344
+ kind: 'buffer' as const,
1345
+ value: {
1346
+ buffer: internals.getPostProcessParamsBuffer?.(
1347
+ preparedPipelineIds.get(pipeline as object) ?? '',
1348
+ ) as never,
1349
+ },
1350
+ },
1351
+ },
1352
+ ]),
1353
+ ]
1354
+ : [],
1355
+ });
1356
+ },
1357
+ });
1358
+ };
1359
+ return {
1360
+ releaseProfilerCatalog(): void {
1361
+ releaseProfilerCatalog?.();
1362
+ releaseProfilerCatalog = undefined;
1363
+ },
1364
+ get renderScene(): RenderSceneInspection {
1365
+ return {
1366
+ ...persistentRenderScene.inspect(),
1367
+ gpuDriven: gpuDrivenProduction.inspect(),
1368
+ };
1369
+ },
1370
+ get pointsLinesSnapshots(): readonly PointsLinesRetainedSnapshot[] {
1371
+ return persistentRenderScene.pointsLinesSnapshots();
1372
+ },
1373
+ detachScene(world: World): void {
1374
+ persistentRenderScene.detach(world);
1375
+ },
1376
+ draw(
1377
+ worlds: readonly World[],
1378
+ opts: DrawOwnerOptions,
1379
+ renderReadLeases?: readonly RenderReadLease[],
1380
+ ): boolean {
1381
+ preparedWorlds = worlds;
1382
+ const profileSession = internals.profiler?.activeSession();
1383
+ let ownsProfileFrame = false;
1384
+ let submitted = false;
1385
+ if (profileSession !== undefined && opts.profileFrame === undefined) {
1386
+ try {
1387
+ ownsProfileFrame = profileSession.beginFrame(++directFrameId).ok;
1388
+ } catch {
1389
+ ownsProfileFrame = false;
1390
+ }
1391
+ }
1392
+ try {
1393
+ pointsLinesOwner.beginFrame();
1394
+ // cameraOwner drives the surfaced cameras + frustum
1395
+ // cull; resourceOwner drives skylight/skybox/postProcess + per-world
1396
+ // record config.
1397
+ const { cameraOwner, resourceOwner } = opts;
1398
+ // Keep the compiled graph as last-known-good until the candidate topology
1399
+ // compiles. The failed candidate frame does not execute it; successful
1400
+ // replacement retires it atomically in ensureCompiledFrameGraph.
1401
+ if (frameState.installedPipelineHandle !== lastBuiltPipelineHandle) {
1402
+ // Feature contributions describe the active pipeline graph. Drop the
1403
+ // old graph before the next frame re-runs feature contribution
1404
+ // so a hot-swap cannot reuse passes compiled for the retired pipeline.
1405
+ resetRenderFeatureGraphState(internals);
1406
+ lastBuiltPipelineHandle = frameState.installedPipelineHandle;
1407
+ persistentRenderScene.invalidate();
1408
+ }
1409
+ dispatchCounts.unlit = 0;
1410
+
1411
+ // feat-20260708-composited-multi-world-rendering M3 / D-2 / m3-i2:
1412
+ // extractFrames merges per-world snapshots (renderables + lights from
1413
+ // every world, cameras + singleton resources from the owner world),
1414
+ // runs read-only extractFrame per world over the already-updated World,
1415
+ // resets frame state once, and isolates
1416
+ // per-world errors (AC-09).
1417
+ // Single-world draw([world], { cameraOwner: 0, resourceOwner: 0 }) is the identity path
1418
+ // (worldId=0), byte-for-byte equivalent to the pre-M3 direct
1419
+ // extractFrame path (AC-03 regression guarantee).
1420
+ //
1421
+ // Entry validation (empty worlds / owner out of range) is enforced by
1422
+ // the public renderer.draw facade (createRenderer.ts, D-5) before this
1423
+ // internal method is reached; the owner world is guaranteed present.
1424
+ //
1425
+ // feat-20260709-editor-world-partition M1 / w5+w6 (D-3): per-world
1426
+ // record configuration (transparent-sort mode, fold buckets, skybox /
1427
+ // asset resolution) is read from the resource-owner world — the world
1428
+ // that owns skylight / skybox / postProcessParams. w6 sources it from
1429
+ // the dedicated resourceOwner index. The variable is named
1430
+ // `resourceWorld` so the record-stage reads are self-describing (they
1431
+ // are resource-owner reads, not camera reads).
1432
+ const resourceWorld = worlds[resourceOwner] as World;
1433
+ const frame = runProfiledRenderPhase(profileSession, 'extract', () => {
1434
+ return persistentRenderScene.extractComposition(
1435
+ worlds,
1436
+ { cameraOwner, resourceOwner },
1437
+ internals.assets.catalogEpoch,
1438
+ () =>
1439
+ extractFrames(
1440
+ worlds,
1441
+ { cameraOwner, resourceOwner },
1442
+ internals.assets,
1443
+ internals.getPipelineState(),
1444
+ persistentRenderScene.materialSnapshotCacheStore(),
1445
+ { cull: 'none' },
1446
+ ),
1447
+ renderReadLeases,
1448
+ );
1449
+ });
1450
+ const {
1451
+ cameras,
1452
+ lights,
1453
+ renderables,
1454
+ dispatch,
1455
+ skylight,
1456
+ skylightCount,
1457
+ skybox,
1458
+ skyboxCount,
1459
+ frustumStats,
1460
+ visibilityStats,
1461
+ postProcessParams,
1462
+ } = frame;
1463
+ if (lastMeshMaterialBindingFrame !== frame) {
1464
+ lastMeshMaterialBindingFrame = frame;
1465
+ lastMeshMaterialBindings = renderables.map((renderable) => ({
1466
+ worldId: renderable.worldId,
1467
+ entityKey: renderable.entityKey,
1468
+ bindings: renderable.materials.map((material, slotIndex) => ({
1469
+ handle: material.materialHandle ?? 0,
1470
+ source: renderable.materialBindingSources[slotIndex] ?? 'engine-default',
1471
+ })),
1472
+ diagnostics: renderable.materialBindingDiagnostics ?? [],
1473
+ }));
1474
+ }
1475
+
1476
+ bindGroupCounts.createBindGroup = 0;
1477
+ bindGroupCounts.keys = [];
1478
+ const preparedPipelineState = internals.getPipelineState();
1479
+ const bindGroupSkipReason: RenderPhaseSkipReason | undefined =
1480
+ internals.featureHost === undefined
1481
+ ? 'feature-host-unavailable'
1482
+ : internals.featureHost.size === 0
1483
+ ? 'feature-host-empty'
1484
+ : preparedPipelineState === null
1485
+ ? 'pipeline-state-unavailable'
1486
+ : frame.cameras.length === 0
1487
+ ? 'camera-unavailable'
1488
+ : undefined;
1489
+ if (bindGroupSkipReason !== undefined || preparedPipelineState === null) {
1490
+ recordProfileSkip(
1491
+ profileSession,
1492
+ 'bind-groups',
1493
+ bindGroupSkipReason ?? 'pipeline-state-unavailable',
1494
+ );
1495
+ } else {
1496
+ runProfiledRenderPhase(profileSession, 'bind-groups', () =>
1497
+ buildPerFrameBindGroups(
1498
+ internals,
1499
+ frameState,
1500
+ preparedPipelineState,
1501
+ true,
1502
+ bindGroupCounts,
1503
+ undefined,
1504
+ false,
1505
+ ),
1506
+ );
1507
+ }
1508
+ lastFrustumStats.culled = frustumStats.culled;
1509
+ lastFrustumStats.total = frustumStats.total;
1510
+ lastVisibilityStats.explicitlyHidden = visibilityStats.explicitlyHidden;
1511
+ const featureTargets =
1512
+ preparedPipelineState === null || cameras[0] === undefined
1513
+ ? []
1514
+ : resolveStandardRenderFeatureTargets({
1515
+ tonemap: cameras[0].tonemap,
1516
+ antialias: cameras[0].antialias,
1517
+ colorAttachmentFormat: preparedPipelineState.colorAttachmentFormat,
1518
+ storageBuffer: internals.device.caps.storageBuffer,
1519
+ multisample: internals.device.caps.backendKind !== 'wgpu-webgl2',
1520
+ });
1521
+ let featureGraphCandidate: RenderFeatureGraphCandidate | undefined;
1522
+ const preparedResourceBatches = runProfiledRenderPhase(profileSession, 'features', () => {
1523
+ if (internals.featureHost === undefined) return [];
1524
+ const featureFrame = runRenderFeatureFrame(internals.featureHost, {
1525
+ worlds,
1526
+ owner: resourceOwner,
1527
+ frameNumber: frameState.frameNumber,
1528
+ visibilitySnapshots: frame.featureVisibilitySnapshots,
1529
+ hiddenEntityReports: frame.hiddenEntityReports,
1530
+ targets: featureTargets,
1531
+ generation: internals.featureHost?.preparedGeneration ?? 0,
1532
+ caps: internals.device.caps,
1533
+ createPreparedGraphicsResolver: preparedResolverFactory,
1534
+ gpuWork: featureGpuWork,
1535
+ });
1536
+ activeFeaturePostProcessEntries = featureFrame.fullscreenEffects;
1537
+ ensurePostProcessParamsResources(featureFrame.fullscreenEffects);
1538
+ lastVisibilityStats.explicitlyHidden = featureFrame.hiddenEntityReports.length;
1539
+ for (const featureError of featureFrame.errors) {
1540
+ if (!isPendingRenderFeaturePreparation(featureError)) {
1541
+ internals.errorRegistry.fire(featureError);
1542
+ }
1543
+ }
1544
+ featureGraphCandidate = {
1545
+ plans: featureFrame.plans,
1546
+ fullscreenEffects: featureFrame.fullscreenEffects,
1547
+ ...(featureFrame.preparedResourceBatches.length === 0
1548
+ ? {}
1549
+ : { preparedResourceKey: `frame-${frameState.frameNumber}` }),
1550
+ onRejected: () => {
1551
+ for (const batch of featureFrame.preparedResourceBatches) {
1552
+ const released = batch.release();
1553
+ if (!released.ok) {
1554
+ internals.errorRegistry.fire(released.error);
1555
+ }
1556
+ }
1557
+ },
1558
+ };
1559
+ return featureFrame.preparedResourceBatches;
1560
+ });
1561
+
1562
+ // Unified transparent-sort: (layer ASC, sortValue ASC) for modes 0/1/2;
1563
+ // distance back-to-front for mode=3. The transparent-sort config is a
1564
+ // per-world resource; it is read from the resource-owner world (the
1565
+ // world that owns skylight / skybox / singleton render state, w5 / D-3).
1566
+ // Only the Transparent segment is reordered; queue ordering between
1567
+ // segments (sortDispatchByQueue, stable) is preserved.
1568
+ const orderedDispatch = runProfiledRenderPhase(profileSession, 'sort', () =>
1569
+ sortTransparentDispatch(dispatch, resourceWorld, cameras, renderables),
1570
+ );
1571
+
1572
+ // M3 / w26: single dispatch list replaces old three-bucket model.
1573
+ // Pass dispatch to recordFrame — the record stage iterates dispatch
1574
+ // entries in queue order per plan-strategy D-3.
1575
+ //
1576
+ // feat-20260709-editor-world-partition ENGINE-fix-round2 (defect 2):
1577
+ // the record stage must resolve each renderable's mesh + material
1578
+ // textures against the world it was EXTRACTED from — never the single
1579
+ // resourceWorld. The extract stage already resolves per-world
1580
+ // (extractFrames loops worlds[] and stamps RenderableSnapshot.worldId);
1581
+ // the record stage regressed to resolving everything against
1582
+ // resourceWorld, so a user-tier mesh living in the cameraOwner world
1583
+ // (e.g. an editor gizmo handle) resolves against resourceWorld's
1584
+ // sharedRefs — either a miss (asset-not-registered) or, when that slot
1585
+ // is occupied by an unrelated user-tier payload (equirect), a
1586
+ // wrong-kind resolve that throws in ensureResident. `worlds` is threaded
1587
+ // so the record stage can index worlds[renderable.worldId]. `resourceWorld`
1588
+ // stays the singleton-resource owner (skybox equirect / transparent-sort
1589
+ // config / video provider) — those ARE resource-owner reads.
1590
+ const recordProfilePhase: RecordProfileRunner | undefined =
1591
+ profileSession === undefined || profileSession.detail === 'owner'
1592
+ ? undefined
1593
+ : function recordProfilePhase<T>(phase: RenderRecordPhase, action: () => T): T {
1594
+ // `passes` deliberately keeps only the graph-pass boundary.
1595
+ // The same runner is also called by geometry/material helpers;
1596
+ // invoking those wrappers would turn a pass probe into the
1597
+ // high-overhead per-draw `nested` probe.
1598
+ if (
1599
+ profileSession.detail === 'passes' &&
1600
+ (!phase.startsWith('record/graph-execute/') ||
1601
+ (phase.slice('record/graph-execute/'.length).includes('/') &&
1602
+ !phase.endsWith('/geometry-loop')))
1603
+ ) {
1604
+ return action();
1605
+ }
1606
+ return runProfiledRenderPhase(profileSession, phase, action);
1607
+ };
1608
+ submitted = runProfiledRenderPhase(profileSession, 'record', () =>
1609
+ recordFrame(
1610
+ internals,
1611
+ resourceWorld,
1612
+ cameras,
1613
+ lights,
1614
+ renderables,
1615
+ orderedDispatch,
1616
+ frameState,
1617
+ dispatchCounts,
1618
+ bindGroupCounts,
1619
+ skylight,
1620
+ skylightCount,
1621
+ skybox,
1622
+ skyboxCount,
1623
+ postProcessParams,
1624
+ worlds,
1625
+ recordProfilePhase,
1626
+ {
1627
+ owner: gpuDrivenProduction,
1628
+ scene: persistentRenderScene.compositionGpuDrivenState(),
1629
+ onCoverage: (ownsAll) => {
1630
+ const key = `${internals.gpuStore.meshResidencyEpoch}:${frameState.isHdrpActive ? 'hdrp' : 'urp'}`;
1631
+ persistentRenderScene.setCompositionGpuDrivenCoverage(key, ownsAll);
1632
+ },
1633
+ },
1634
+ renderReadLeases,
1635
+ featureGraphCandidate,
1636
+ pointsLinesOwner,
1637
+ ),
1638
+ );
1639
+ persistentRenderScene.setPointsLinesInspections(pointsLinesOwner.inspections());
1640
+ if (internals.featureHost !== undefined && preparedResourceBatches.length > 0) {
1641
+ const batches = preparedResourceBatches;
1642
+ if (submitted) {
1643
+ internals.featureHost.markPreparedGraphicsSubmitted(batches);
1644
+ settlePreparedGraphicsCompletion(
1645
+ internals.featureHost,
1646
+ batches,
1647
+ internals.device.queue.onSubmittedWorkDone(),
1648
+ (error) => reportPreparedGraphicsCompletionError(internals, error),
1649
+ );
1650
+ } else {
1651
+ const retired = internals.featureHost.retirePreparedGraphics();
1652
+ if (!retired.ok) internals.errorRegistry.fire(retired.error);
1653
+ }
1654
+ }
1655
+ } catch (err) {
1656
+ const innerError =
1657
+ err instanceof RhiError
1658
+ ? err
1659
+ : { code: 'unknown' as const, message: String(err), name: (err as Error)?.name };
1660
+ internals.errorRegistry.fire(
1661
+ new RhiError({
1662
+ code: 'webgpu-runtime-error',
1663
+ expected: 'RenderSystem to record one frame without an internal exception',
1664
+ hint: 'inspect detail.error for the underlying cause; next frame will retry',
1665
+ detail: { error: innerError },
1666
+ }),
1667
+ );
1668
+ } finally {
1669
+ if (ownsProfileFrame) {
1670
+ try {
1671
+ profileSession?.endFrame();
1672
+ } catch {
1673
+ // Profiler failures never alter rendering.
1674
+ }
1675
+ }
1676
+ }
1677
+ return submitted;
1678
+ },
1679
+ pipelineDispatchCounts: dispatchCounts,
1680
+ observeCurrentFrame(options: FrameObservationOptions) {
1681
+ const currentFrameId = frameState.frameNumber - 1;
1682
+ return observeCurrentFrame(options, frameState.currentFrameObservationSource, currentFrameId);
1683
+ },
1684
+ bindGroupCounts: bindGroupCounts,
1685
+ frustumStats: lastFrustumStats,
1686
+ visibilityStats: lastVisibilityStats,
1687
+ get meshMaterialBindings(): readonly MeshMaterialBindingObservation[] {
1688
+ return lastMeshMaterialBindings;
1689
+ },
1690
+ get perFramePassNames(): readonly string[] {
1691
+ return frameState.compiledFrameGraph?.inspect().passes.map((pass) => pass.name) ?? [];
1692
+ },
1693
+ configureStandard(config: RenderPipelineAsset['config']): void {
1694
+ const profileConfig = internals.standardProfile;
1695
+ const resolvedConfig =
1696
+ profileConfig === undefined
1697
+ ? config
1698
+ : {
1699
+ ...(profileConfig.lighting === 'clustered'
1700
+ ? { clusterGrid: DEFAULT_CLUSTER_GRID }
1701
+ : {}),
1702
+ ...(profileConfig.ssao ? { ssao: { enabled: true } } : {}),
1703
+ ...config,
1704
+ };
1705
+ if (resolvedConfig?.clusterGrid !== undefined) {
1706
+ const grid = resolvedConfig.clusterGrid;
1707
+ const gridResult = validateClusterGrid(grid);
1708
+ if (!gridResult.ok) {
1709
+ throw gridResult.error;
1710
+ }
1711
+ }
1712
+ if (resolvedConfig?.ssao?.enabled === true) {
1713
+ const ssaoResult = resolveSsaoParameters(resolvedConfig.ssao);
1714
+ if (!ssaoResult.ok) {
1715
+ throw ssaoResult.error;
1716
+ }
1717
+ }
1718
+ // Standard configuration no longer carries a handle (D-19: RenderPipelineAsset is
1719
+ // supplied as a POD at boot/swap time before a World exists). The
1720
+ // brand-number that `draw` compares to force a per-frame graph rebuild is
1721
+ // now a monotonic epoch bumped on every install -- distinct configs (and
1722
+ // even identical re-installs) trigger the rebuild, which is correct: install
1723
+ // is a rare boot/swap event, never a per-frame cost.
1724
+ installEpoch += 1;
1725
+ frameState.installedPipelineHandle = installEpoch;
1726
+ frameState.isHdrpActive = resolvedConfig?.clusterGrid !== undefined;
1727
+ frameState.installedPipelineConfig = resolvedConfig;
1728
+ },
1729
+ registerBuiltinPostProcess(id: string, entry: PostProcessShaderEntry): () => void {
1730
+ // D-3: eager-create params UBO at register time + fail-fast
1731
+ // byteSize / defaultValue validation (q5=A).
1732
+ let paramsBuffer: Buffer | undefined;
1733
+ try {
1734
+ if (entry.params !== undefined) {
1735
+ const { byteSize, defaultValue } = entry.params;
1736
+ if (byteSize < 16 || defaultValue.length !== byteSize) {
1737
+ throw new PostProcessError({
1738
+ code: 'params-size-mismatch',
1739
+ detail: { byteSize, actualLength: defaultValue.length },
1740
+ });
1741
+ }
1742
+ const paramsBufferResult = internals.device.createBuffer({
1743
+ label: `post-process-params-${id}`,
1744
+ size: byteSize,
1745
+ usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
1746
+ mappedAtCreation: false,
1747
+ });
1748
+ if (!paramsBufferResult.ok) throw paramsBufferResult.error;
1749
+ paramsBuffer = paramsBufferResult.value;
1750
+ const writeResult = internals.device.queue.writeBuffer(paramsBuffer, 0, defaultValue);
1751
+ if (!writeResult.ok) throw writeResult.error;
1752
+ } else if (entryHasDepthRead(entry)) {
1753
+ const paramsBufferResult = internals.device.createBuffer({
1754
+ label: `post-process-params-${id}`,
1755
+ size: DEPTH_MIN_PARAMS_BYTE_SIZE,
1756
+ usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
1757
+ mappedAtCreation: false,
1758
+ });
1759
+ if (!paramsBufferResult.ok) throw paramsBufferResult.error;
1760
+ paramsBuffer = paramsBufferResult.value;
1761
+ }
1762
+ if (builtinPostProcessEntries.has(id)) {
1763
+ throw new PostProcessError({
1764
+ code: 'post-process-already-registered',
1765
+ detail: { id },
1766
+ });
1767
+ }
1768
+ builtinPostProcessEntries.set(id, entry);
1769
+ if (paramsBuffer !== undefined) postProcessParamsBuffers.set(id, paramsBuffer);
1770
+ return () => {
1771
+ if (builtinPostProcessEntries.get(id) === entry) {
1772
+ builtinPostProcessEntries.delete(id);
1773
+ }
1774
+ const current = postProcessParamsBuffers.get(id);
1775
+ if (current !== undefined) {
1776
+ internals.device.destroyBuffer(current);
1777
+ postProcessParamsBuffers.delete(id);
1778
+ }
1779
+ for (const key of postProcessPipelineCache.keys()) {
1780
+ if (key.startsWith(`${id}|`)) postProcessPipelineCache.delete(key);
1781
+ }
1782
+ };
1783
+ } catch (cause) {
1784
+ if (paramsBuffer !== undefined) internals.device.destroyBuffer(paramsBuffer);
1785
+ throw cause;
1786
+ }
1787
+ },
1788
+ disposeFrameState(): void {
1789
+ persistentRenderScene.dispose();
1790
+ gpuDrivenProduction.dispose();
1791
+ disposeFeatureGpuWork();
1792
+ // Retire graph-owned resources and dispose instance-buffer caches.
1793
+ // Both calls are idempotent + tolerate per-handle errors silently;
1794
+ // the Renderer.dispose() cascade owns the surrounding try/catch
1795
+ // (D-3 method A: void signature, sub-errors fan out via
1796
+ // errorRegistry.fire at the cascade layer, dispose still walks all
1797
+ // 6 steps).
1798
+ frameState.directionalShadowCache = null;
1799
+ frameState.directionalShadowCacheRecorded = false;
1800
+ frameState.currentFrameObservationSource = undefined;
1801
+ frameState.currentDirectionalShadowView = null;
1802
+ frameState.currentSpotShadowView = null;
1803
+ const compiled = frameState.compiledFrameGraph;
1804
+ frameState.compiledFrameGraph = null;
1805
+ frameState.compiledFrameGraphTopologyKey = null;
1806
+ if (compiled !== null) compiled.retire().catch(() => undefined);
1807
+ for (const retired of frameState.retiredCompiledFrameGraphs) {
1808
+ retired.retire().catch(() => undefined);
1809
+ }
1810
+ frameState.retiredCompiledFrameGraphs.clear();
1811
+ // feat-20260619 M4 (D-6): pass errorRegistry to disposeInstanceBuffers
1812
+ // so destroy failures fire structured errors (unified per-frame +
1813
+ // dispose error strategy).
1814
+ disposeInstanceBuffers(frameState.instanceBuffers, internals.errorRegistry);
1815
+ disposeTransientInstanceBuffers(frameState.transientInstanceBuffers, internals.errorRegistry);
1816
+ if (frameState.morphBuffers !== undefined) {
1817
+ for (const entry of frameState.morphBuffers.values()) {
1818
+ if (!entry.buffer.isDestroyed) {
1819
+ const result = entry.buffer.destroy();
1820
+ if (!result.ok) internals.errorRegistry.fire(result.error);
1821
+ }
1822
+ }
1823
+ frameState.morphBuffers.clear();
1824
+ }
1825
+ // feat-20260612-point-light-shadows-urp-hdrp M4 / T-M4-2: dispose the
1826
+ // cube_array shadow atlas owned by the RenderSystem closure. The atlas
1827
+ // is per-RenderSystem (= per Renderer) and is shared transparently
1828
+ // between URP and HDRP pipelines.
1829
+ // Idempotent: dispose() on a null / already-disposed atlas is a no-op.
1830
+ if (frameState.pointShadowAtlas !== null) {
1831
+ frameState.pointShadowAtlas.dispose();
1832
+ frameState.pointShadowAtlas = null;
1833
+ }
1834
+ },
1835
+ resetForRecover(): void {
1836
+ persistentRenderScene.resetGpuForRecover();
1837
+ gpuDrivenProduction.dispose();
1838
+ gpuDrivenProduction = new GpuDrivenProduction(internals.device, gpuDrivenShaderFactory);
1839
+ disposeFeatureGpuWork();
1840
+ // feat-20260622-s5 M3 / B-2 / w18: recover() rebuild drops device-bound
1841
+ // state minted by the lost device. The active graph and per-entity caches
1842
+ // must be discarded, not merely marked for destruction: their opaque
1843
+ // handles cannot be used on the fresh device and the next draw must
1844
+ // lazily build a new graph from the preserved ECS / asset POD caches.
1845
+ frameState.compiledFrameGraph?.retire().catch(() => undefined);
1846
+ frameState.compiledFrameGraph = null;
1847
+ frameState.compiledFrameGraphTopologyKey = null;
1848
+ frameState.directionalShadowCache = null;
1849
+ frameState.directionalShadowCacheRecorded = false;
1850
+ frameState.currentFrameObservationSource = undefined;
1851
+ frameState.currentDirectionalShadowView = null;
1852
+ frameState.currentSpotShadowView = null;
1853
+ for (const retired of frameState.retiredCompiledFrameGraphs) {
1854
+ retired.retire().catch(() => undefined);
1855
+ }
1856
+ frameState.retiredCompiledFrameGraphs.clear();
1857
+ frameState.instanceBuffers.clear();
1858
+ frameState.morphBuffers?.clear();
1859
+ frameState.transientInstanceBuffers = [];
1860
+ frameState.pointShadowAtlas = null;
1861
+ // Bind groups retain opaque handles from the lost device. WeakMap roots
1862
+ // cannot be cleared, so replace them; the Map-backed caches can be
1863
+ // emptied in place. The next frame recreates every binding from the
1864
+ // rebuilt PipelineState and fresh residency handles.
1865
+ frameState.viewBindGroupCache = new WeakMap();
1866
+ frameState.meshBindGroupCache = new WeakMap();
1867
+ frameState.materialBgPerEntity.clear();
1868
+ frameState.instancesBgPerEntity.clear();
1869
+ frameState.materialBgShared.clear();
1870
+ frameState.materialBgAssemblyCache.clear();
1871
+ frameState.singletonMaterialCache.clear();
1872
+ frameState.postProcessBgCache = new WeakMap();
1873
+ // Fullscreen PSOs are cached outside frameState because the normal
1874
+ // path reuses them across frames. They still carry opaque handles from
1875
+ // the lost device, so recovery must invalidate this cache alongside the
1876
+ // feature-host declarations and UBOs below.
1877
+ postProcessPipelineCache.clear();
1878
+ // Logical declarations stay live in the feature host while a replacement
1879
+ // device is being requested.
1880
+ postProcessParamsBuffers.clear();
1881
+ resetRenderFeatureGraphState(internals);
1882
+ },
1883
+ restorePostProcessResources(): void {
1884
+ const declarations = [
1885
+ ...builtinPostProcessEntries.entries(),
1886
+ ...activeFeaturePostProcessEntries.entries(),
1887
+ ];
1888
+ for (const [id, entry] of declarations) {
1889
+ let buffer: Buffer | undefined;
1890
+ if (entry.params !== undefined) {
1891
+ const created = internals.device.createBuffer({
1892
+ label: `post-process-params-${id}`,
1893
+ size: entry.params.byteSize,
1894
+ usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
1895
+ mappedAtCreation: false,
1896
+ });
1897
+ if (!created.ok) throw created.error;
1898
+ buffer = created.value;
1899
+ const written = internals.device.queue.writeBuffer(buffer, 0, entry.params.defaultValue);
1900
+ if (!written.ok) {
1901
+ internals.device.destroyBuffer(buffer);
1902
+ throw written.error;
1903
+ }
1904
+ } else if (entryHasDepthRead(entry)) {
1905
+ const created = internals.device.createBuffer({
1906
+ label: `post-process-params-${id}`,
1907
+ size: DEPTH_MIN_PARAMS_BYTE_SIZE,
1908
+ usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
1909
+ mappedAtCreation: false,
1910
+ });
1911
+ if (!created.ok) throw created.error;
1912
+ buffer = created.value;
1913
+ }
1914
+ if (buffer !== undefined && lookupPostProcess(id) === entry) {
1915
+ postProcessParamsBuffers.set(id, buffer);
1916
+ } else if (buffer !== undefined) {
1917
+ internals.device.destroyBuffer(buffer);
1918
+ }
1919
+ }
1920
+ },
1921
+ };
1922
+ }
1923
+
1924
+ // @forgeax/engine-render - built-in record context extension.
1925
+ //
1926
+ // The public RenderPipelineContext is canonical in render-contract.ts. This
1927
+ // owner adds only concrete assembly state for built-in record closures.
1928
+
1929
+ /**
1930
+ * Package-private extension consumed by the built-in record closures only.
1931
+ * Custom pipeline authors see the leaf RenderPipelineContext contract and
1932
+ * never this surface.
1933
+ */