@forgeax/engine-render 0.1.23 → 0.1.24

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 (271) hide show
  1. package/README.md +131 -9
  2. package/dist/assembly/bundler-contract.d.ts +9 -0
  3. package/dist/assembly/bundler-contract.d.ts.map +1 -0
  4. package/dist/assembly/device-feature-admission.d.ts +8 -0
  5. package/dist/assembly/device-feature-admission.d.ts.map +1 -1
  6. package/dist/assembly/extended-lighting-fallback.d.ts +13 -0
  7. package/dist/assembly/extended-lighting-fallback.d.ts.map +1 -0
  8. package/dist/assembly/factory.d.ts +7 -243
  9. package/dist/assembly/factory.d.ts.map +1 -1
  10. package/dist/assembly/material/assembly.d.ts +3 -2
  11. package/dist/assembly/material/assembly.d.ts.map +1 -1
  12. package/dist/assembly/material/standard-layer-projection.d.ts +10 -0
  13. package/dist/assembly/material/standard-layer-projection.d.ts.map +1 -0
  14. package/dist/assembly/material-shader-policy.d.ts +168 -0
  15. package/dist/assembly/material-shader-policy.d.ts.map +1 -0
  16. package/dist/assembly/material-shader-prewarm.d.ts +21 -0
  17. package/dist/assembly/material-shader-prewarm.d.ts.map +1 -0
  18. package/dist/assembly/mesh-ssbo-grow.d.ts +88 -0
  19. package/dist/assembly/mesh-ssbo-grow.d.ts.map +1 -0
  20. package/dist/assembly/recovery/device-loss-fanout.d.ts +14 -0
  21. package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -0
  22. package/dist/assembly/recovery/generation.d.ts +46 -0
  23. package/dist/assembly/recovery/generation.d.ts.map +1 -0
  24. package/dist/assembly/recovery/recovery-attempt.d.ts +63 -0
  25. package/dist/assembly/recovery/recovery-attempt.d.ts.map +1 -0
  26. package/dist/assembly/recovery/renderer-recover.d.ts +115 -0
  27. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -0
  28. package/dist/assembly/renderer-facade.d.ts +10 -0
  29. package/dist/assembly/renderer-facade.d.ts.map +1 -0
  30. package/dist/assembly/renderer-frame-transaction.d.ts +17 -0
  31. package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
  32. package/dist/assembly/renderer-helpers.d.ts +19 -0
  33. package/dist/assembly/renderer-helpers.d.ts.map +1 -0
  34. package/dist/assembly/renderer-lifecycle.d.ts +34 -0
  35. package/dist/assembly/renderer-lifecycle.d.ts.map +1 -1
  36. package/dist/assembly/shader-prewarm-policy.d.ts +15 -0
  37. package/dist/assembly/shader-prewarm-policy.d.ts.map +1 -0
  38. package/dist/assembly/webgpu-ready-contract.d.ts +9 -0
  39. package/dist/assembly/webgpu-ready-contract.d.ts.map +1 -0
  40. package/dist/assembly/webgpu-ready.d.ts +55 -0
  41. package/dist/assembly/webgpu-ready.d.ts.map +1 -0
  42. package/dist/assembly/webgpu-renderer.d.ts +152 -0
  43. package/dist/assembly/webgpu-renderer.d.ts.map +1 -0
  44. package/dist/authoring.mjs +1 -1
  45. package/dist/{chunk-4L3P6DT5.mjs → chunk-GE3SDD72.mjs} +37 -5
  46. package/dist/chunk-GE3SDD72.mjs.map +1 -0
  47. package/dist/{chunk-HZUPSYW7.mjs → chunk-KZFTK6DO.mjs} +102 -15
  48. package/dist/chunk-KZFTK6DO.mjs.map +1 -0
  49. package/dist/{chunk-FF7K3FSO.mjs → chunk-Q752SHI5.mjs} +89 -89
  50. package/dist/{chunk-FF7K3FSO.mjs.map → chunk-Q752SHI5.mjs.map} +1 -1
  51. package/dist/{chunk-QODA5UKU.mjs → chunk-RR5L4SRS.mjs} +2 -2
  52. package/dist/chunk-RR5L4SRS.mjs.map +1 -0
  53. package/dist/{chunk-YALPS44A.mjs → chunk-TFH5I7JJ.mjs} +47 -47
  54. package/dist/chunk-TFH5I7JJ.mjs.map +1 -0
  55. package/dist/{chunk-FBC4R44J.mjs → chunk-UQYS7FRM.mjs} +57 -57
  56. package/dist/chunk-UQYS7FRM.mjs.map +1 -0
  57. package/dist/{chunk-4NPUASZR.mjs → chunk-YKB5DQV7.mjs} +161 -19
  58. package/dist/chunk-YKB5DQV7.mjs.map +1 -0
  59. package/dist/{chunk-ZUWKAZ2I.mjs → chunk-YRUYGA4A.mjs} +615 -237
  60. package/dist/chunk-YRUYGA4A.mjs.map +1 -0
  61. package/dist/{chunk-DIYW3M5D.mjs → chunk-Z3YYH2MB.mjs} +2210 -2213
  62. package/dist/chunk-Z3YYH2MB.mjs.map +1 -0
  63. package/dist/construct-renderer.mjs +19841 -16190
  64. package/dist/construct-renderer.mjs.map +1 -1
  65. package/dist/device/device-scope.d.ts +2 -0
  66. package/dist/device/device-scope.d.ts.map +1 -1
  67. package/dist/device/gpu-residency.d.ts +24 -4
  68. package/dist/device/gpu-residency.d.ts.map +1 -1
  69. package/dist/environment/frame.d.ts +5 -0
  70. package/dist/environment/frame.d.ts.map +1 -1
  71. package/dist/environment/inspection.d.ts +7 -0
  72. package/dist/environment/inspection.d.ts.map +1 -1
  73. package/dist/environment/lifecycle.d.ts +20 -2
  74. package/dist/environment/lifecycle.d.ts.map +1 -1
  75. package/dist/errors/recover.d.ts +58 -2
  76. package/dist/errors/recover.d.ts.map +1 -1
  77. package/dist/errors/render.d.ts +13 -0
  78. package/dist/errors/render.d.ts.map +1 -1
  79. package/dist/extract/contracts.d.ts +899 -0
  80. package/dist/extract/contracts.d.ts.map +1 -0
  81. package/dist/features/host.d.ts +2 -0
  82. package/dist/features/host.d.ts.map +1 -1
  83. package/dist/features/prepared-gpu-work.d.ts +3 -0
  84. package/dist/features/prepared-gpu-work.d.ts.map +1 -1
  85. package/dist/features/prepared-graphics-store.d.ts +2 -0
  86. package/dist/features/prepared-graphics-store.d.ts.map +1 -1
  87. package/dist/gpu-driven/production-raster.d.ts +28 -0
  88. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  89. package/dist/gpu-scene.d.ts +1 -0
  90. package/dist/gpu-scene.d.ts.map +1 -1
  91. package/dist/ibl/IblPipelineCache.d.ts +7 -6
  92. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  93. package/dist/ibl/skylight-bind-group.d.ts +7 -1
  94. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  95. package/dist/index.d.ts +1 -1
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.mjs +6 -6
  98. package/dist/internal.mjs +6 -6
  99. package/dist/light-buffer-layout.d.ts +3 -0
  100. package/dist/light-buffer-layout.d.ts.map +1 -1
  101. package/dist/materials.d.ts +34 -10
  102. package/dist/materials.d.ts.map +1 -1
  103. package/dist/mesh-material-bindings.d.ts +56 -0
  104. package/dist/mesh-material-bindings.d.ts.map +1 -1
  105. package/dist/occlusion-proxy.d.ts +4 -0
  106. package/dist/occlusion-proxy.d.ts.map +1 -0
  107. package/dist/pbr-pipeline.d.ts +32 -9
  108. package/dist/pbr-pipeline.d.ts.map +1 -1
  109. package/dist/points-lines/prepare.d.ts +3 -0
  110. package/dist/points-lines/prepare.d.ts.map +1 -1
  111. package/dist/points-lines/standard-owner.d.ts +33 -0
  112. package/dist/points-lines/standard-owner.d.ts.map +1 -0
  113. package/dist/record/frame-snapshot.d.ts +5 -0
  114. package/dist/record/frame-snapshot.d.ts.map +1 -1
  115. package/dist/record/frame-targets.d.ts.map +1 -1
  116. package/dist/record/frame.d.ts +14 -2
  117. package/dist/record/frame.d.ts.map +1 -1
  118. package/dist/record/main-pass-geometry.d.ts +14 -1
  119. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  120. package/dist/record/main-pass-material.d.ts +7 -17
  121. package/dist/record/main-pass-material.d.ts.map +1 -1
  122. package/dist/record/main-pass.d.ts.map +1 -1
  123. package/dist/record/mesh-ssbo.d.ts +11 -3
  124. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  125. package/dist/record/recovery-pipeline.d.ts +30 -0
  126. package/dist/record/recovery-pipeline.d.ts.map +1 -0
  127. package/dist/record/render-context.d.ts +26 -4
  128. package/dist/record/render-context.d.ts.map +1 -1
  129. package/dist/record/typed-frame-graph.d.ts +1 -1
  130. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  131. package/dist/recovery/render-system-candidate.d.ts +65 -0
  132. package/dist/recovery/render-system-candidate.d.ts.map +1 -0
  133. package/dist/render-contract.d.ts +64 -7
  134. package/dist/render-contract.d.ts.map +1 -1
  135. package/dist/render-system-extract-tail.d.ts +12 -0
  136. package/dist/render-system-extract-tail.d.ts.map +1 -0
  137. package/dist/render-system-extract.d.ts +156 -22
  138. package/dist/render-system-extract.d.ts.map +1 -1
  139. package/dist/render-system.d.ts +128 -4
  140. package/dist/render-system.d.ts.map +1 -1
  141. package/dist/scene/render-scene.d.ts +19 -0
  142. package/dist/scene/render-scene.d.ts.map +1 -1
  143. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  144. package/dist/temporal/index.mjs +6 -6
  145. package/dist/temporal/taa-history-store.d.ts +15 -1
  146. package/dist/temporal/taa-history-store.d.ts.map +1 -1
  147. package/dist/temporal/transaction.d.ts +5 -0
  148. package/dist/temporal/transaction.d.ts.map +1 -1
  149. package/package.json +20 -20
  150. package/src/__tests__/device-feature-admission.unit.test.ts +6 -0
  151. package/src/__tests__/device-loss-fanout.unit.test.ts +107 -0
  152. package/src/__tests__/device-scope-lifecycle.unit.test.ts +21 -0
  153. package/src/__tests__/environment-generation.integration.test.ts +23 -0
  154. package/src/__tests__/expose-renderer-errors.unit.test.ts +34 -0
  155. package/src/__tests__/factory-contract.integration.test.ts +122 -7
  156. package/src/__tests__/fallback-row-stride-surface.unit.test.ts +4 -1
  157. package/src/__tests__/ibl/face-uniforms.unit.test.ts +2 -0
  158. package/src/__tests__/ibl-residency-publication.unit.test.ts +113 -0
  159. package/src/__tests__/material-contract-inventory.unit.test.ts +24 -9
  160. package/src/__tests__/material-cooked-projection.unit.test.ts +19 -1
  161. package/src/__tests__/material-static-dynamic-route.unit.test.ts +29 -0
  162. package/src/__tests__/material-stride-surface.unit.test.ts +7 -3
  163. package/src/__tests__/materials-standard-contract.unit.test.ts +104 -0
  164. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +1 -1
  165. package/src/__tests__/physical-clearcoat.integration.test.ts +89 -0
  166. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +1 -1
  167. package/src/__tests__/prepared-graphics-pipeline-warmup.unit.test.ts +1 -1
  168. package/src/__tests__/recover-error-policy-owner.unit.test.ts +6 -3
  169. package/src/__tests__/recovery-candidate-prepare.contract.test.ts +140 -0
  170. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +85 -18
  171. package/src/__tests__/renderer-frame-generation-fence.integration.test.ts +47 -0
  172. package/src/__tests__/renderer-generation-assembly.unit.test.ts +77 -0
  173. package/src/__tests__/renderer-generation-failure.unit.test.ts +108 -0
  174. package/src/__tests__/renderer-producer-root-matrix.integration.test.ts +33 -0
  175. package/src/__tests__/renderer-recovery-capability-matrix.unit.test.ts +59 -0
  176. package/src/__tests__/renderer-recovery-contract.unit.test.ts +73 -0
  177. package/src/__tests__/renderer-recovery-lifecycle.unit.test.ts +216 -0
  178. package/src/__tests__/renderer-target-history-recovery.integration.test.ts +80 -0
  179. package/src/__tests__/shader-manifest-fixture.ts +13 -16
  180. package/src/__tests__/shader-stage-surface.unit.test.ts +1 -1
  181. package/src/__tests__/standard-layer-projection.unit.test.ts +23 -0
  182. package/src/__tests__/transmission-prewarm.unit.test.ts +95 -85
  183. package/src/__tests__/volumetric-fog-topology.unit.test.ts +3 -1
  184. package/src/assembly/bundler-contract.ts +9 -0
  185. package/src/assembly/device-feature-admission.ts +15 -5
  186. package/src/assembly/extended-lighting-fallback.ts +216 -0
  187. package/src/assembly/factory.ts +81 -10088
  188. package/src/assembly/material/assembly.ts +18 -2
  189. package/src/assembly/material/standard-layer-projection.ts +16 -0
  190. package/src/assembly/material-shader-policy.ts +854 -0
  191. package/src/assembly/material-shader-prewarm.ts +88 -0
  192. package/src/assembly/mesh-ssbo-grow.ts +270 -0
  193. package/src/assembly/recovery/device-loss-fanout.ts +89 -0
  194. package/src/assembly/recovery/generation.ts +141 -0
  195. package/src/assembly/recovery/recovery-attempt.ts +800 -0
  196. package/src/assembly/recovery/renderer-recover.ts +906 -0
  197. package/src/assembly/renderer-facade.ts +342 -0
  198. package/src/assembly/renderer-frame-transaction.ts +45 -0
  199. package/src/assembly/renderer-helpers.ts +99 -0
  200. package/src/assembly/renderer-lifecycle.ts +159 -0
  201. package/src/assembly/shader-prewarm-policy.ts +157 -0
  202. package/src/assembly/webgpu-ready-contract.ts +14 -0
  203. package/src/assembly/webgpu-ready.ts +4079 -0
  204. package/src/assembly/webgpu-renderer.ts +4095 -0
  205. package/src/components/camera.ts +1 -1
  206. package/src/debug-draw-glue.ts +1 -1
  207. package/src/device/device-scope.ts +35 -6
  208. package/src/device/gpu-residency.ts +194 -26
  209. package/src/environment/frame.ts +6 -0
  210. package/src/environment/inspection.ts +10 -0
  211. package/src/environment/lifecycle.ts +114 -8
  212. package/src/errors/recover.ts +76 -5
  213. package/src/errors/render.ts +17 -0
  214. package/src/extract/contracts.ts +974 -0
  215. package/src/features/host.ts +16 -0
  216. package/src/features/prepared-gpu-work.ts +20 -2
  217. package/src/features/prepared-graphics-store.ts +17 -0
  218. package/src/gpu-driven/production-raster.ts +97 -2
  219. package/src/gpu-scene.ts +3 -2
  220. package/src/ibl/IblPipelineCache.ts +134 -50
  221. package/src/ibl/skylight-bind-group.ts +26 -0
  222. package/src/index.ts +9 -1
  223. package/src/light-buffer-layout.ts +3 -0
  224. package/src/materials.ts +262 -45
  225. package/src/mesh-material-bindings.ts +59 -0
  226. package/src/occlusion-proxy.ts +64 -0
  227. package/src/pbr-pipeline.ts +139 -15
  228. package/src/points-lines/__tests__/cache-recovery.unit.test.ts +22 -0
  229. package/src/points-lines/prepare.ts +9 -0
  230. package/src/points-lines/standard-owner.ts +403 -0
  231. package/src/record/__tests__/frame-targets-recovery.unit.test.ts +60 -0
  232. package/src/record/__tests__/main-pass-geometry.unit.test.ts +26 -1
  233. package/src/record/__tests__/mesh-ssbo.unit.test.ts +38 -0
  234. package/src/record/__tests__/recovery-pipeline.unit.test.ts +124 -0
  235. package/src/record/__tests__/sprite-material-abi.unit.test.ts +28 -13
  236. package/src/record/__tests__/standard-pbr-ubo-layout.unit.test.ts +47 -4
  237. package/src/record/__tests__/temporal-payload.unit.test.ts +22 -43
  238. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +1 -1
  239. package/src/record/frame-snapshot.ts +15 -0
  240. package/src/record/frame-targets.ts +11 -1
  241. package/src/record/frame.ts +6 -2
  242. package/src/record/helpers.ts +1 -1
  243. package/src/record/main-pass-geometry.ts +230 -5
  244. package/src/record/main-pass-material.ts +316 -209
  245. package/src/record/main-pass-sprite-draws.ts +6 -7
  246. package/src/record/main-pass.ts +94 -6
  247. package/src/record/mesh-ssbo.ts +32 -10
  248. package/src/record/recovery-pipeline.ts +695 -0
  249. package/src/record/render-context.ts +35 -4
  250. package/src/record/typed-frame-graph.ts +45 -1
  251. package/src/record/view-ubo.ts +3 -3
  252. package/src/recovery/render-system-candidate.ts +839 -0
  253. package/src/render-contract.ts +65 -7
  254. package/src/render-system-extract-tail.ts +2983 -0
  255. package/src/render-system-extract.ts +124 -2977
  256. package/src/render-system.ts +868 -659
  257. package/src/scene/render-scene.ts +124 -1
  258. package/src/temporal/frame-coordinator.ts +4 -1
  259. package/src/temporal/taa-history-store.ts +46 -2
  260. package/src/temporal/temporal-view.ts +1 -1
  261. package/src/temporal/transaction.ts +9 -0
  262. package/src/tilemap-chunk-extract-system.ts +1 -1
  263. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +19 -12
  264. package/dist/chunk-4L3P6DT5.mjs.map +0 -1
  265. package/dist/chunk-4NPUASZR.mjs.map +0 -1
  266. package/dist/chunk-DIYW3M5D.mjs.map +0 -1
  267. package/dist/chunk-FBC4R44J.mjs.map +0 -1
  268. package/dist/chunk-HZUPSYW7.mjs.map +0 -1
  269. package/dist/chunk-QODA5UKU.mjs.map +0 -1
  270. package/dist/chunk-YALPS44A.mjs.map +0 -1
  271. package/dist/chunk-ZUWKAZ2I.mjs.map +0 -1
@@ -1,6 +1,7 @@
1
1
  import type { EntityHandle } from '@forgeax/engine-ecs';
2
2
  import type { RhiCaps, TextureFormat } from '@forgeax/engine-rhi';
3
3
  import { err, ok, type Result } from '@forgeax/engine-types';
4
+ import type { DeviceScope, LifecycleResourceSpec } from '../device/device-scope';
4
5
  import {
5
6
  type RenderError,
6
7
  RenderFeatureCapabilityMissingError,
@@ -175,6 +176,7 @@ export interface RenderFeatureHost {
175
176
  batches: readonly RenderFeaturePreparedResourceBatch[],
176
177
  ): Result<void, RenderError>;
177
178
  recover(input: RenderFeatureRecoverInput): Result<void, RenderError>;
179
+ createRecoveryRoot(scope: DeviceScope): LifecycleResourceSpec<unknown>;
178
180
  diagnostics(): readonly RenderFeatureDiagnostics[];
179
181
  /** Subscribe to lifecycle projection changes; unchanged active frames are silent. */
180
182
  subscribeDiagnostics(listener: () => void): () => void;
@@ -915,6 +917,20 @@ class FeatureHostImpl implements RenderFeatureHost {
915
917
  return this.slots.map((slot) => slot.feature);
916
918
  }
917
919
 
920
+ createRecoveryRoot(scope: DeviceScope): LifecycleResourceSpec<unknown> {
921
+ return {
922
+ kind: 'feature',
923
+ create: () => {
924
+ if (!scope.isAlive()) throw new Error('Feature candidate scope is not active.');
925
+ // The root carries the detached host owner itself. A scalar marker is
926
+ // not evidence that feature planning/preparation actually ran; the
927
+ // candidate graph and host own the prepared resources.
928
+ return this;
929
+ },
930
+ cleanup: () => undefined,
931
+ };
932
+ }
933
+
918
934
  private publishDiagnosticsChanged(): void {
919
935
  for (const listener of this.diagnosticsListeners) {
920
936
  try {
@@ -11,6 +11,7 @@ import type {
11
11
  ShaderModule,
12
12
  } from '@forgeax/engine-rhi';
13
13
  import { type BindGroupLayoutDescriptor, err, ok, type Result } from '@forgeax/engine-types';
14
+ import type { DeviceScope, LifecycleResourceSpec } from '../device/device-scope';
14
15
  import { type RenderError, RenderFeaturePreparationFailedError } from '../errors/render';
15
16
  import type { PipelineBuilderShaderModuleFactory } from '../pipeline-builder';
16
17
  import type { RenderFeatureShaderModuleMode } from './types';
@@ -910,6 +911,8 @@ function createRenderFeatureGpuWorkSession(input: {
910
911
  * retains the per-feature prepared GPU state and recreates it after recovery.
911
912
  */
912
913
  export interface RenderFeatureGpuWorkOwner {
914
+ /** Candidate root carries this detached GPU-work owner itself. */
915
+ createRecoveryRoot(scope: DeviceScope): LifecycleResourceSpec<unknown>;
913
916
  beginFeature(
914
917
  featureIdentity: string,
915
918
  generation: number,
@@ -935,9 +938,21 @@ export function createRenderFeatureGpuWorkOwner(input: {
935
938
  session: RenderFeatureGpuPrepareSession;
936
939
  }
937
940
  >();
938
-
939
- return {
941
+ let disposed = false;
942
+
943
+ const owner: RenderFeatureGpuWorkOwner = {
944
+ createRecoveryRoot: (scope) => ({
945
+ kind: 'feature',
946
+ create: () => {
947
+ if (!scope.isAlive()) throw new Error('Feature GPU candidate scope is not active.');
948
+ return owner;
949
+ },
950
+ cleanup: () => undefined,
951
+ }),
940
952
  beginFeature: (featureIdentity, generation, requestedShaderModuleMode = 'validated') => {
953
+ if (disposed) {
954
+ throw new Error('Render feature GPU work owner is disposed.');
955
+ }
941
956
  const shaderModuleMode = requestedShaderModuleMode;
942
957
  const existing = sessions.get(featureIdentity);
943
958
  if (existing?.generation === generation && existing.shaderModuleMode === shaderModuleMode) {
@@ -962,6 +977,8 @@ export function createRenderFeatureGpuWorkOwner(input: {
962
977
  resolveBuffer: (featureIdentity, reference) =>
963
978
  sessions.get(featureIdentity)?.session.resolveBuffer(reference),
964
979
  dispose: () => {
980
+ if (disposed) return ok(undefined);
981
+ disposed = true;
965
982
  let first: RenderError | undefined;
966
983
  for (const { session } of sessions.values()) {
967
984
  const disposed = session.dispose();
@@ -971,4 +988,5 @@ export function createRenderFeatureGpuWorkOwner(input: {
971
988
  return first === undefined ? ok(undefined) : err(first);
972
989
  },
973
990
  };
991
+ return owner;
974
992
  }
@@ -1,5 +1,6 @@
1
1
  import type { TextureFormat } from '@forgeax/engine-rhi';
2
2
  import { err, ok, type Result } from '@forgeax/engine-types';
3
+ import type { DeviceScope, LifecycleResourceSpec } from '../device/device-scope';
3
4
  import { type RenderError, RenderFeaturePreparationFailedError } from '../errors/render';
4
5
  import type {
5
6
  PreparedKind,
@@ -81,6 +82,7 @@ export interface PreparedGraphicsStore {
81
82
  beginFrame(featureIdentity: string, generation: number): PreparedGraphicsTransaction;
82
83
  snapshot(featureIdentity: string): PreparedGraphicsStoreSnapshot;
83
84
  invalidate(featureIdentity: string, generation: number): void;
85
+ createRecoveryRoot(scope: DeviceScope): LifecycleResourceSpec<unknown>;
84
86
  }
85
87
 
86
88
  interface Slot {
@@ -418,6 +420,21 @@ class PreparedGraphicsStoreImpl implements PreparedGraphicsStore {
418
420
  }
419
421
  }
420
422
 
423
+ createRecoveryRoot(scope: DeviceScope): LifecycleResourceSpec<unknown> {
424
+ return {
425
+ kind: 'buffer',
426
+ create: () => {
427
+ if (!scope.isAlive()) throw new Error('Prepared graphics candidate scope is not active.');
428
+ const itemCount = [...this.slots.values()].reduce(
429
+ (count, slot) => count + slot.items.size,
430
+ 0,
431
+ );
432
+ return Object.freeze({ generation: scope.generation, itemCount });
433
+ },
434
+ cleanup: () => undefined,
435
+ };
436
+ }
437
+
421
438
  registerReference(reference: object, item: PreparedGraphicsItem): void {
422
439
  this.references.set(reference, item);
423
440
  }
@@ -20,6 +20,7 @@ import type {
20
20
  import { err, ok, RhiError } from '@forgeax/engine-rhi';
21
21
  import type { MaterialShaderArtifact } from '@forgeax/engine-shader';
22
22
  import type { MaterialRenderState } from '@forgeax/engine-types';
23
+ import type { DeviceScope, LifecycleResourceSpec } from '../device/device-scope';
23
24
  import type { MeshGpuHandles } from '../device/gpu-residency';
24
25
  import { GPU_SCENE_WGSL } from '../gpu-scene-schema';
25
26
  import {
@@ -261,8 +262,8 @@ function activeFrustum(camera: CameraSnapshot): Float32Array {
261
262
  projection,
262
263
  camera.orthoLeft,
263
264
  camera.orthoRight,
264
- camera.orthoBottom,
265
265
  camera.orthoTop,
266
+ camera.orthoBottom,
266
267
  camera.near,
267
268
  camera.far,
268
269
  );
@@ -297,6 +298,9 @@ function productionEligible(snapshot: RenderableSnapshot, batch: GpuDrivenBatch)
297
298
  const shader = material.materialShaderId ?? 'forgeax::default-unlit';
298
299
  return (
299
300
  shader === 'forgeax::default-unlit' &&
301
+ // The production rigid shader owns triangle-list raster only. Lines and
302
+ // points stay with their CPU/specialized topology owners.
303
+ batch.key.topology === 'triangle-list' &&
300
304
  batch.key.materialResourceClass === EMPTY_RESOURCE_CLASS &&
301
305
  material.baseColorTexture === undefined &&
302
306
  material.transparent !== true
@@ -928,7 +932,62 @@ export interface PreparedGpuDrivenFrame {
928
932
  ): Result<RenderPipelineGpuDrivenProjection, RenderGraphError | RhiError>;
929
933
  }
930
934
 
935
+ export interface RecoveryCapabilityFacts {
936
+ readonly generation: number;
937
+ readonly shaderCompilation: boolean;
938
+ readonly compute: boolean;
939
+ readonly storageBuffer: boolean;
940
+ readonly indirectDrawing: boolean;
941
+ readonly multisample: boolean;
942
+ }
943
+
944
+ export type RecoveryCapabilityOutcome =
945
+ | {
946
+ readonly status: 'ready' | 'fallback';
947
+ readonly generation: number;
948
+ readonly disabled: readonly string[];
949
+ }
950
+ | {
951
+ readonly status: 'disabled';
952
+ readonly generation: number;
953
+ readonly disabled: readonly string[];
954
+ readonly allocations: 0;
955
+ }
956
+ | {
957
+ readonly status: 'refused';
958
+ readonly generation: number;
959
+ readonly failedOwner: 'shader-material-pipeline';
960
+ readonly resourceKind: 'pipeline';
961
+ };
962
+
963
+ export function classifyRecoveryCapability(
964
+ facts: RecoveryCapabilityFacts,
965
+ ): RecoveryCapabilityOutcome {
966
+ if (!facts.shaderCompilation) {
967
+ return {
968
+ status: 'refused',
969
+ generation: facts.generation,
970
+ failedOwner: 'shader-material-pipeline',
971
+ resourceKind: 'pipeline',
972
+ };
973
+ }
974
+ if (!facts.compute || !facts.storageBuffer || !facts.indirectDrawing) {
975
+ return {
976
+ status: 'disabled',
977
+ generation: facts.generation,
978
+ disabled: ['gpu-driven'],
979
+ allocations: 0,
980
+ };
981
+ }
982
+ return {
983
+ status: facts.multisample ? 'ready' : 'fallback',
984
+ generation: facts.generation,
985
+ disabled: [],
986
+ };
987
+ }
988
+
931
989
  export class GpuDrivenProduction {
990
+ private disposed = false;
932
991
  private view: GpuDrivenView | undefined;
933
992
  private raster: GpuDrivenRigidRaster | undefined;
934
993
  private standardRaster: StandardPbrRasterAdapter | undefined;
@@ -972,6 +1031,27 @@ export class GpuDrivenProduction {
972
1031
  private readonly shaderModuleFactory: PipelineBuilderShaderModuleFactory,
973
1032
  ) {}
974
1033
 
1034
+ static forDevice(
1035
+ device: RhiDevice,
1036
+ shaderModuleFactory: PipelineBuilderShaderModuleFactory,
1037
+ ): GpuDrivenProduction {
1038
+ return new GpuDrivenProduction(device, shaderModuleFactory);
1039
+ }
1040
+
1041
+ createRecoveryRoot(scope: DeviceScope): LifecycleResourceSpec<unknown> {
1042
+ return {
1043
+ kind: 'scene-table',
1044
+ create: () => {
1045
+ if (!scope.isAlive()) throw new Error('GPU-driven candidate scope is not active.');
1046
+ // Keep the actual detached production owner in the generation
1047
+ // aggregate. The recovery transaction, not a marker object, is the
1048
+ // owner that is published or discarded exactly once.
1049
+ return this;
1050
+ },
1051
+ cleanup: () => undefined,
1052
+ };
1053
+ }
1054
+
975
1055
  prepare(input: {
976
1056
  readonly scene: PersistentGpuDrivenState | undefined;
977
1057
  readonly camera: CameraSnapshot;
@@ -1000,7 +1080,20 @@ export class GpuDrivenProduction {
1000
1080
  this.telemetryGeometryWork = 0;
1001
1081
  this.telemetryRootGeometryWork = 0;
1002
1082
  this.indirectDrawCount = 0;
1003
- if (input.hdrp || input.scene === undefined || selectGpuLodLane(this.device.caps) === 'cpu') {
1083
+ const capability = classifyRecoveryCapability({
1084
+ generation: 0,
1085
+ shaderCompilation: true,
1086
+ compute: this.device.caps.compute,
1087
+ storageBuffer: this.device.caps.storageBuffer,
1088
+ indirectDrawing: this.device.caps.indirectDrawing,
1089
+ multisample: true,
1090
+ });
1091
+ if (
1092
+ input.hdrp ||
1093
+ input.scene === undefined ||
1094
+ capability.status === 'disabled' ||
1095
+ selectGpuLodLane(this.device.caps) === 'cpu'
1096
+ ) {
1004
1097
  this.gpuOwnedEntityCount = 0;
1005
1098
  this.sceneRowsRequired = false;
1006
1099
  return ok(undefined);
@@ -1457,6 +1550,8 @@ export class GpuDrivenProduction {
1457
1550
  }
1458
1551
 
1459
1552
  dispose(): void {
1553
+ if (this.disposed) return;
1554
+ this.disposed = true;
1460
1555
  this.view?.dispose();
1461
1556
  this.view = undefined;
1462
1557
  this.raster = undefined;
package/src/gpu-scene.ts CHANGED
@@ -201,6 +201,7 @@ function coalesceSlots(
201
201
 
202
202
  /** Persistent GPU tables derived only from renderer projection slots. */
203
203
  export class GpuScene {
204
+ private disposed = false;
204
205
  private tableBytes: Record<GpuSceneTableName, ArrayBuffer>;
205
206
  private transformBytes: Uint8Array;
206
207
  private transformView: DataView;
@@ -432,6 +433,8 @@ export class GpuScene {
432
433
  }
433
434
 
434
435
  dispose(): void {
436
+ if (this.disposed) return;
437
+ this.disposed = true;
435
438
  for (const buffer of Object.values(this.buffers)) this.device.destroyBuffer(buffer);
436
439
  }
437
440
 
@@ -583,8 +586,6 @@ export class GpuScene {
583
586
  writeVec4(material, materialOffset + offset(MATERIAL, 'params1'), [
584
587
  snapshot.metallic ?? 0,
585
588
  snapshot.roughness ?? 1,
586
- snapshot.clearcoat ?? 0,
587
- snapshot.clearcoatRoughness ?? 0,
588
589
  ]);
589
590
  writeVec4(material, materialOffset + offset(MATERIAL, 'params2'), [
590
591
  snapshot.emissive?.[0] ?? 0,