@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
package/src/materials.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { color } from '@forgeax/engine-math';
1
2
  import { DEFAULT_STANDARD_PBR_PARAM_SCHEMA } from '@forgeax/engine-shader';
2
3
  import type {
3
4
  MaterialAsset,
@@ -7,6 +8,7 @@ import type {
7
8
  MaterialRenderState,
8
9
  MaterialValue,
9
10
  } from '@forgeax/engine-types';
11
+ import { deriveStandardLayerPlan } from '@forgeax/engine-types';
10
12
 
11
13
  export type MaterialTransmissionValidationReason =
12
14
  | 'non-finite'
@@ -60,6 +62,49 @@ const STANDARD_MODULE = 'forgeax_material::standard';
60
62
  const UNLIT_MODULE = 'forgeax_material::unlit';
61
63
  const SPRITE_MODULE = 'forgeax_material::sprite';
62
64
 
65
+ export type MaterialColorTuple3 = readonly [number, number, number];
66
+ export type MaterialColorTuple4 = readonly [number, number, number, number];
67
+ export type MaterialColorInput3 = MaterialColorTuple3 | string | number;
68
+ export type MaterialColorInput4 = MaterialColorTuple4 | string | number;
69
+
70
+ function colorHexFromNumber(value: number): string {
71
+ if (!Number.isInteger(value) || value < 0 || value > 0xffffff) {
72
+ throw new Error(`material color number must be an integer in [0, 0xffffff], got ${value}`);
73
+ }
74
+ return `#${value.toString(16).padStart(6, '0')}`;
75
+ }
76
+
77
+ function linearColorFromInput(
78
+ input: MaterialColorInput3 | MaterialColorInput4,
79
+ channels: 3 | 4,
80
+ ): readonly number[] {
81
+ if (typeof input === 'number' || typeof input === 'string') {
82
+ const parsed = color.create();
83
+ color.fromCss(parsed, typeof input === 'number' ? colorHexFromNumber(input) : input);
84
+ return channels === 3
85
+ ? [parsed[0] as number, parsed[1] as number, parsed[2] as number]
86
+ : Array.from(parsed);
87
+ }
88
+ if (input.length !== channels) {
89
+ throw new Error(`material color tuple must contain ${channels} channels, got ${input.length}`);
90
+ }
91
+ return [...input];
92
+ }
93
+
94
+ /** Convert an explicit sRGB numeric tuple into the linear tuple factories store. */
95
+ export function srgb(input: MaterialColorTuple3): MaterialColorTuple3;
96
+ export function srgb(input: MaterialColorTuple4): MaterialColorTuple4;
97
+ export function srgb(input: readonly number[]): readonly number[] {
98
+ if (input.length !== 3 && input.length !== 4) {
99
+ throw new Error(`sRGB material color tuple must contain 3 or 4 channels, got ${input.length}`);
100
+ }
101
+ const parsed = color.create(input[0] ?? 0, input[1] ?? 0, input[2] ?? 0, input[3] ?? 1);
102
+ color.srgbToLinear(parsed, parsed);
103
+ return input.length === 3
104
+ ? [parsed[0] as number, parsed[1] as number, parsed[2] as number]
105
+ : [parsed[0] as number, parsed[1] as number, parsed[2] as number, parsed[3] as number];
106
+ }
107
+
63
108
  function authoredColorParameter(
64
109
  name: string,
65
110
  type: 'color' | 'vec3',
@@ -74,27 +119,148 @@ function authoredColorParameter(
74
119
  };
75
120
  }
76
121
 
77
- function standardParameters(colorSpace: MaterialColorSpace): readonly MaterialParameter[] {
78
- return DEFAULT_STANDARD_PBR_PARAM_SCHEMA.map((entry) => {
79
- const type = entry.type.startsWith('texture') ? 'texture' : entry.type;
80
- const isRequired =
81
- entry.name === 'baseColor' || entry.name === 'metallic' || entry.name === 'roughness';
82
- const authoredColor =
83
- entry.type === 'color' || (entry.type === 'vec3' && entry.name !== 'attenuationColor');
84
- const schemaColorSpace = 'colorSpace' in entry ? entry.colorSpace : undefined;
85
- const colorSpaceProjection: { colorSpace?: MaterialColorSpace } = {};
86
- if (authoredColor && !(type === 'color' && colorSpace === 'srgb')) {
87
- colorSpaceProjection.colorSpace = colorSpace;
88
- } else if (!authoredColor && schemaColorSpace !== undefined) {
89
- colorSpaceProjection.colorSpace = schemaColorSpace;
122
+ function standardParameters(
123
+ colorSpace: MaterialColorSpace,
124
+ opts: StandardOpts,
125
+ ): readonly MaterialParameter[] {
126
+ const physicalNames = new Set<string>();
127
+ const addPhysical = (...names: readonly string[]) => {
128
+ for (const name of names) physicalNames.add(name);
129
+ };
130
+ if (
131
+ opts.clearcoat !== undefined ||
132
+ opts.clearcoatRoughness !== undefined ||
133
+ opts.clearcoatNormalScale !== undefined ||
134
+ opts.clearcoatTexture !== undefined ||
135
+ opts.clearcoatRoughnessTexture !== undefined ||
136
+ opts.clearcoatNormalTexture !== undefined
137
+ ) {
138
+ addPhysical(
139
+ 'clearcoat',
140
+ 'clearcoatRoughness',
141
+ 'clearcoatNormalScale',
142
+ ...(opts.clearcoatTexture === undefined ? [] : ['clearcoatTexture']),
143
+ ...(opts.clearcoatRoughnessTexture === undefined ? [] : ['clearcoatRoughnessTexture']),
144
+ ...(opts.clearcoatNormalTexture === undefined ? [] : ['clearcoatNormalTexture']),
145
+ );
146
+ }
147
+ if (
148
+ opts.anisotropyStrength !== undefined ||
149
+ opts.anisotropyRotation !== undefined ||
150
+ opts.anisotropyTexture !== undefined
151
+ ) {
152
+ addPhysical('anisotropyStrength', 'anisotropyRotation');
153
+ if (opts.anisotropyTexture !== undefined) addPhysical('anisotropyTexture');
154
+ }
155
+ if (
156
+ opts.sheenColor !== undefined ||
157
+ opts.sheenRoughness !== undefined ||
158
+ opts.sheenColorTexture !== undefined ||
159
+ opts.sheenRoughnessTexture !== undefined
160
+ ) {
161
+ addPhysical('sheenColor', 'sheenRoughness');
162
+ if (opts.sheenColorTexture !== undefined) addPhysical('sheenColorTexture');
163
+ if (opts.sheenRoughnessTexture !== undefined) addPhysical('sheenRoughnessTexture');
164
+ }
165
+ if (
166
+ opts.iridescence !== undefined ||
167
+ opts.iridescenceIor !== undefined ||
168
+ opts.iridescenceThicknessMinimum !== undefined ||
169
+ opts.iridescenceThicknessMaximum !== undefined ||
170
+ opts.iridescenceTexture !== undefined ||
171
+ opts.iridescenceThicknessTexture !== undefined
172
+ ) {
173
+ addPhysical(
174
+ 'iridescence',
175
+ 'iridescenceIor',
176
+ 'iridescenceThicknessMinimum',
177
+ 'iridescenceThicknessMaximum',
178
+ ...(opts.iridescenceTexture === undefined ? [] : ['iridescenceTexture']),
179
+ ...(opts.iridescenceThicknessTexture === undefined ? [] : ['iridescenceThicknessTexture']),
180
+ );
181
+ }
182
+ const baseNames = new Set([
183
+ 'baseColor',
184
+ 'metallic',
185
+ 'roughness',
186
+ 'metallicChannel',
187
+ 'roughnessChannel',
188
+ 'aoChannel',
189
+ 'extraChannel',
190
+ 'emissive',
191
+ 'emissiveIntensity',
192
+ 'occlusionStrength',
193
+ 'alphaCutoff',
194
+ 'normalScale',
195
+ 'baseColorTexture',
196
+ 'metallicRoughnessTexture',
197
+ 'normalTexture',
198
+ 'emissiveTexture',
199
+ 'occlusionTexture',
200
+ 'specular',
201
+ 'specularColor',
202
+ // IOR is part of the always-present dielectric F0 contract. The
203
+ // transmission layer may be absent, but the Standard shader still
204
+ // consumes this scalar for the base reflection path.
205
+ 'ior',
206
+ ]);
207
+ if (opts.specularTexture !== undefined) baseNames.add('specularTexture');
208
+ if (opts.specularColorTexture !== undefined) baseNames.add('specularColorTexture');
209
+ if (
210
+ opts.transmission !== undefined ||
211
+ opts.ior !== undefined ||
212
+ opts.thickness !== undefined ||
213
+ opts.transmissionTexture !== undefined ||
214
+ opts.thicknessTexture !== undefined ||
215
+ opts.attenuationColor !== undefined ||
216
+ opts.attenuationDistance !== undefined
217
+ ) {
218
+ for (const name of [
219
+ 'transmission',
220
+ 'ior',
221
+ 'thickness',
222
+ 'attenuationColor',
223
+ 'attenuationDistance',
224
+ 'transmissionTexture',
225
+ 'thicknessTexture',
226
+ ]) {
227
+ baseNames.add(name);
90
228
  }
91
- return {
92
- name: entry.name,
93
- type,
94
- ...colorSpaceProjection,
95
- ...(isRequired ? {} : { optional: true }),
96
- } as MaterialParameter;
97
- });
229
+ }
230
+ // All Standard fields come from one root schema. Physical fields are
231
+ // projected by the authored option membership above; appending a second
232
+ // physical schema would create a parallel ABI authority.
233
+ const availableSchema = DEFAULT_STANDARD_PBR_PARAM_SCHEMA;
234
+ // Keep the generated Standard UBO's numeric run contiguous before any
235
+ // texture coordinate records. The built-in WGSL places the complete
236
+ // physical root (including clearcoat scalars) after the base numeric run
237
+ // and before all coordinate pairs; appending physical entries after the
238
+ // base texture records would otherwise derive a different ABI.
239
+ const numericSchema = availableSchema.filter((entry) => !entry.type.startsWith('texture'));
240
+ const textureSchema = availableSchema.filter((entry) => entry.type.startsWith('texture'));
241
+ const orderedSchema = [...numericSchema, ...textureSchema];
242
+ return orderedSchema
243
+ .filter((entry) => baseNames.has(entry.name) || physicalNames.has(entry.name))
244
+ .map((entry) => {
245
+ const type = entry.type.startsWith('texture') ? 'texture' : entry.type;
246
+ const isRequired =
247
+ entry.name === 'baseColor' || entry.name === 'metallic' || entry.name === 'roughness';
248
+ const authoredColor =
249
+ entry.type === 'color' || (entry.type === 'vec3' && entry.name !== 'attenuationColor');
250
+ const schemaColorSpace = 'colorSpace' in entry ? entry.colorSpace : undefined;
251
+ const colorSpaceProjection: { colorSpace?: MaterialColorSpace } = {};
252
+ if (authoredColor && !(type === 'color' && colorSpace === 'srgb')) {
253
+ colorSpaceProjection.colorSpace = colorSpace;
254
+ } else if (!authoredColor && schemaColorSpace !== undefined) {
255
+ colorSpaceProjection.colorSpace = schemaColorSpace;
256
+ }
257
+ return {
258
+ name: entry.name,
259
+ type,
260
+ ...colorSpaceProjection,
261
+ ...(isRequired ? {} : { optional: true }),
262
+ } as MaterialParameter;
263
+ });
98
264
  }
99
265
 
100
266
  function unlitParameters(colorSpace: MaterialColorSpace): readonly MaterialParameter[] {
@@ -141,8 +307,6 @@ function shadowCasterRenderState(
141
307
  }
142
308
 
143
309
  interface UnlitOpts {
144
- /** Asset-side authored color space. Defaults to sRGB. */
145
- readonly colorSpace?: MaterialColorSpace;
146
310
  readonly castShadow?: boolean;
147
311
  readonly baseColorTexture?: MaterialValue;
148
312
  readonly alphaCutoff?: number;
@@ -150,11 +314,11 @@ interface UnlitOpts {
150
314
  readonly queue?: number;
151
315
  }
152
316
 
153
- function unlit(rgba: readonly [number, number, number, number], opts?: UnlitOpts): MaterialAsset {
317
+ function unlit(rgba: MaterialColorInput4, opts?: UnlitOpts): MaterialAsset {
154
318
  if (opts?.alphaCutoff !== undefined && (opts.alphaCutoff < 0 || opts.alphaCutoff > 1)) {
155
319
  throw new Error(`Materials.unlit: alphaCutoff must be in [0, 1], got ${opts.alphaCutoff}`);
156
320
  }
157
- const values: Record<string, MaterialValue> = { baseColor: rgba };
321
+ const values: Record<string, MaterialValue> = { baseColor: linearColorFromInput(rgba, 4) };
158
322
  if (opts?.baseColorTexture !== undefined) values.baseColorTexture = opts.baseColorTexture;
159
323
  if (opts?.alphaCutoff !== undefined) values.alphaCutoff = opts.alphaCutoff;
160
324
  const passes: [MaterialPass, ...MaterialPass[]] = [
@@ -165,19 +329,16 @@ function unlit(rgba: readonly [number, number, number, number], opts?: UnlitOpts
165
329
  }
166
330
  return {
167
331
  kind: 'material',
168
- ...(opts?.colorSpace === undefined || opts.colorSpace === 'srgb'
169
- ? {}
170
- : { colorSpace: opts.colorSpace }),
332
+ colorSpace: 'linear',
171
333
  passes,
172
- parameters: unlitParameters(opts?.colorSpace ?? 'srgb'),
334
+ parameters: unlitParameters('linear'),
173
335
  values,
174
336
  };
175
337
  }
176
338
 
177
339
  interface StandardOpts {
178
- /** Asset-side authored color space for baseColor/emissive/specularTint. Defaults to sRGB. */
179
- readonly colorSpace?: MaterialColorSpace;
180
- readonly baseColor: readonly [number, number, number, number];
340
+ /** Numeric tuples are linear; Hex/CSS strings and numbers are sRGB inputs. */
341
+ readonly baseColor: MaterialColorInput4;
181
342
  readonly metallic?: number;
182
343
  readonly roughness?: number;
183
344
  /** Texture channel used for the metallic factor; glTF defaults to B (2). */
@@ -186,9 +347,28 @@ interface StandardOpts {
186
347
  readonly roughnessChannel?: number;
187
348
  readonly clearcoat?: number;
188
349
  readonly clearcoatRoughness?: number;
189
- readonly specularTint?: readonly [number, number, number];
190
- readonly specularTintTexture?: MaterialValue;
191
- readonly emissive?: readonly [number, number, number];
350
+ readonly clearcoatTexture?: MaterialValue;
351
+ readonly clearcoatRoughnessTexture?: MaterialValue;
352
+ readonly clearcoatNormalTexture?: MaterialValue;
353
+ readonly clearcoatNormalScale?: number;
354
+ readonly anisotropyStrength?: number;
355
+ readonly anisotropyRotation?: number;
356
+ readonly anisotropyTexture?: MaterialValue;
357
+ readonly sheenColor?: MaterialColorInput3;
358
+ readonly sheenRoughness?: number;
359
+ readonly sheenColorTexture?: MaterialValue;
360
+ readonly sheenRoughnessTexture?: MaterialValue;
361
+ readonly iridescence?: number;
362
+ readonly iridescenceIor?: number;
363
+ readonly iridescenceThicknessMinimum?: number;
364
+ readonly iridescenceThicknessMaximum?: number;
365
+ readonly iridescenceTexture?: MaterialValue;
366
+ readonly iridescenceThicknessTexture?: MaterialValue;
367
+ readonly specular?: number;
368
+ readonly specularColor?: MaterialColorInput3;
369
+ readonly specularTexture?: MaterialValue;
370
+ readonly specularColorTexture?: MaterialValue;
371
+ readonly emissive?: MaterialColorInput3;
192
372
  readonly emissiveIntensity?: number;
193
373
  readonly emissiveTexture?: MaterialValue;
194
374
  readonly baseColorTexture?: MaterialValue;
@@ -266,11 +446,13 @@ function standard(opts: StandardOpts): MaterialAsset {
266
446
  throw new MaterialTransmissionContractError('transmission', 'depth-write', true);
267
447
  }
268
448
  const values: Record<string, MaterialValue> = {
269
- baseColor: opts.baseColor,
449
+ baseColor: linearColorFromInput(opts.baseColor, 4),
270
450
  metallic: opts.metallic ?? 0,
271
451
  roughness: opts.roughness ?? 0.5,
272
452
  occlusionStrength,
273
- specularTint: opts.specularTint ?? [1, 1, 1],
453
+ specular: opts.specular ?? 1,
454
+ specularColor:
455
+ opts.specularColor === undefined ? [1, 1, 1] : linearColorFromInput(opts.specularColor, 3),
274
456
  transmission,
275
457
  ior,
276
458
  thickness,
@@ -280,7 +462,37 @@ function standard(opts: StandardOpts): MaterialAsset {
280
462
  if (opts.roughnessChannel !== undefined) values.roughnessChannel = opts.roughnessChannel;
281
463
  if (opts.clearcoat !== undefined) values.clearcoat = opts.clearcoat;
282
464
  if (opts.clearcoatRoughness !== undefined) values.clearcoatRoughness = opts.clearcoatRoughness;
283
- if (opts.emissive !== undefined) values.emissive = opts.emissive;
465
+ if (opts.clearcoatTexture !== undefined) values.clearcoatTexture = opts.clearcoatTexture;
466
+ if (opts.clearcoatRoughnessTexture !== undefined) {
467
+ values.clearcoatRoughnessTexture = opts.clearcoatRoughnessTexture;
468
+ }
469
+ if (opts.clearcoatNormalTexture !== undefined) {
470
+ values.clearcoatNormalTexture = opts.clearcoatNormalTexture;
471
+ }
472
+ if (opts.clearcoatNormalScale !== undefined)
473
+ values.clearcoatNormalScale = opts.clearcoatNormalScale;
474
+ if (opts.anisotropyStrength !== undefined) values.anisotropyStrength = opts.anisotropyStrength;
475
+ if (opts.anisotropyRotation !== undefined) values.anisotropyRotation = opts.anisotropyRotation;
476
+ if (opts.anisotropyTexture !== undefined) values.anisotropyTexture = opts.anisotropyTexture;
477
+ if (opts.sheenColor !== undefined) values.sheenColor = linearColorFromInput(opts.sheenColor, 3);
478
+ if (opts.sheenRoughness !== undefined) values.sheenRoughness = opts.sheenRoughness;
479
+ if (opts.sheenColorTexture !== undefined) values.sheenColorTexture = opts.sheenColorTexture;
480
+ if (opts.sheenRoughnessTexture !== undefined) {
481
+ values.sheenRoughnessTexture = opts.sheenRoughnessTexture;
482
+ }
483
+ if (opts.iridescence !== undefined) values.iridescence = opts.iridescence;
484
+ if (opts.iridescenceIor !== undefined) values.iridescenceIor = opts.iridescenceIor;
485
+ if (opts.iridescenceThicknessMinimum !== undefined) {
486
+ values.iridescenceThicknessMinimum = opts.iridescenceThicknessMinimum;
487
+ }
488
+ if (opts.iridescenceThicknessMaximum !== undefined) {
489
+ values.iridescenceThicknessMaximum = opts.iridescenceThicknessMaximum;
490
+ }
491
+ if (opts.iridescenceTexture !== undefined) values.iridescenceTexture = opts.iridescenceTexture;
492
+ if (opts.iridescenceThicknessTexture !== undefined) {
493
+ values.iridescenceThicknessTexture = opts.iridescenceThicknessTexture;
494
+ }
495
+ if (opts.emissive !== undefined) values.emissive = linearColorFromInput(opts.emissive, 3);
284
496
  if (opts.emissiveIntensity !== undefined) values.emissiveIntensity = opts.emissiveIntensity;
285
497
  if (opts.emissiveTexture !== undefined) values.emissiveTexture = opts.emissiveTexture;
286
498
  if (opts.baseColorTexture !== undefined) values.baseColorTexture = opts.baseColorTexture;
@@ -289,8 +501,9 @@ function standard(opts: StandardOpts): MaterialAsset {
289
501
  }
290
502
  if (opts.normalTexture !== undefined) values.normalTexture = opts.normalTexture;
291
503
  if (opts.occlusionTexture !== undefined) values.occlusionTexture = opts.occlusionTexture;
292
- if (opts.specularTintTexture !== undefined) {
293
- values.specularTintTexture = opts.specularTintTexture;
504
+ if (opts.specularTexture !== undefined) values.specularTexture = opts.specularTexture;
505
+ if (opts.specularColorTexture !== undefined) {
506
+ values.specularColorTexture = opts.specularColorTexture;
294
507
  }
295
508
  if (opts.alphaCutoff !== undefined) values.alphaCutoff = opts.alphaCutoff;
296
509
  if (opts.transmissionTexture !== undefined) values.transmissionTexture = opts.transmissionTexture;
@@ -298,10 +511,16 @@ function standard(opts: StandardOpts): MaterialAsset {
298
511
  if (opts.attenuationDistance !== undefined) values.attenuationDistance = opts.attenuationDistance;
299
512
  const forwardRenderState =
300
513
  transmission > 0 ? { ...(opts.renderState ?? {}), depthWriteEnabled: false } : opts.renderState;
514
+ const parameters = standardParameters('linear', opts);
515
+ const declaredNames = new Set(parameters.map((parameter) => parameter.name));
516
+ for (const name of Object.keys(values)) {
517
+ if (!declaredNames.has(name)) delete values[name];
518
+ }
519
+ const physical = deriveStandardLayerPlan(parameters).mode === 'physical';
301
520
  const passes: [MaterialPass, ...MaterialPass[]] = [
302
521
  pass('forward', STANDARD_MODULE, forwardRenderState, 'fs_main', opts.queue),
303
522
  ];
304
- if (transmission === 0) {
523
+ if (!physical && transmission === 0) {
305
524
  passes.push(pass('deferred', STANDARD_MODULE, opts.renderState, 'fs_gbuffer', opts.queue));
306
525
  }
307
526
  if (opts.castShadow !== false) {
@@ -309,15 +528,13 @@ function standard(opts: StandardOpts): MaterialAsset {
309
528
  }
310
529
  return {
311
530
  kind: 'material',
312
- ...(opts.colorSpace === undefined || opts.colorSpace === 'srgb'
313
- ? {}
314
- : { colorSpace: opts.colorSpace }),
531
+ colorSpace: 'linear',
315
532
  passes,
316
- parameters: standardParameters(opts.colorSpace ?? 'srgb'),
533
+ parameters,
317
534
  values,
318
535
  };
319
536
  }
320
537
 
321
- export const Materials = { unlit, standard } as const;
538
+ export const Materials = { unlit, standard, srgb } as const;
322
539
 
323
540
  export { SPRITE_MODULE };
@@ -12,6 +12,65 @@ export interface MeshMaterialBindingDiagnostic {
12
12
  readonly detail?: Readonly<AssetErrorDetail>;
13
13
  }
14
14
 
15
+ /** Detached final-frame receipt for the Standard material IBL binding chain. */
16
+ export interface IblBindingResourceInspection {
17
+ readonly viewIdentity: number;
18
+ readonly samplerIdentity: number;
19
+ readonly deviceGeneration: number;
20
+ }
21
+
22
+ export interface IblBindingEntryInspection {
23
+ readonly binding: number;
24
+ readonly kind: 'textureView' | 'sampler' | 'buffer';
25
+ readonly resourceIdentity: number;
26
+ }
27
+
28
+ export interface IblBindingInspection {
29
+ readonly status: 'binding-chain-consistent' | 'binding-chain-mismatch';
30
+ readonly frameId: number;
31
+ readonly deviceGeneration: number;
32
+ readonly active: 'active' | 'fallback';
33
+ readonly cache: {
34
+ readonly identity: number;
35
+ readonly generation: number;
36
+ readonly prefilterMipCount: number;
37
+ readonly prefilterViewMipCount: number;
38
+ };
39
+ readonly sampler: {
40
+ readonly expected: {
41
+ readonly magFilter: 'linear';
42
+ readonly minFilter: 'linear';
43
+ readonly mipmapFilter: 'linear';
44
+ readonly addressModeU: 'clamp-to-edge';
45
+ readonly addressModeV: 'clamp-to-edge';
46
+ readonly addressModeW: 'clamp-to-edge';
47
+ };
48
+ readonly identities: readonly number[];
49
+ };
50
+ readonly resources: {
51
+ readonly irradiance: IblBindingResourceInspection;
52
+ readonly prefilter: IblBindingResourceInspection;
53
+ readonly brdfLut: IblBindingResourceInspection;
54
+ readonly intensityBufferIdentity: number;
55
+ };
56
+ readonly material?: {
57
+ readonly bindGroupIdentity: number;
58
+ readonly materialBglIdentity: number;
59
+ readonly cache: 'hit' | 'miss';
60
+ readonly skylightBindingStart: number;
61
+ readonly entries: readonly IblBindingEntryInspection[];
62
+ readonly reflectionBindings: readonly number[];
63
+ };
64
+ readonly pipeline?: {
65
+ readonly pipelineIdentity: number;
66
+ readonly effectiveMaterialLayoutIdentity: string | undefined;
67
+ readonly materialBglIdentity: number;
68
+ readonly bindGroupIdentity: number;
69
+ readonly drawFrameId: number;
70
+ };
71
+ readonly errors: readonly string[];
72
+ }
73
+
15
74
  export interface ResolvedMeshMaterialBinding {
16
75
  readonly handle: number;
17
76
  readonly source: MeshMaterialBindingSource;
@@ -0,0 +1,64 @@
1
+ import { mat4 } from '@forgeax/engine-math';
2
+ import { computeProjectionMatrix, computeViewMatrix } from './record/helpers';
3
+ import type { CameraSnapshot } from './render-contract';
4
+
5
+ /** Build the camera-facing proxy geometry used by GPU occlusion queries. */
6
+ export function buildOcclusionProxyVertices(
7
+ aabb: ArrayLike<number>,
8
+ world: ArrayLike<number>,
9
+ camera: CameraSnapshot,
10
+ ): Float32Array {
11
+ const view = computeViewMatrix(camera);
12
+ const projection = computeProjectionMatrix(camera);
13
+ const viewProjection = mat4.create();
14
+ mat4.multiply(viewProjection, projection, view);
15
+ const corners = [
16
+ [aabb[0] ?? 0, aabb[1] ?? 0, aabb[2] ?? 0],
17
+ [aabb[3] ?? 0, aabb[1] ?? 0, aabb[2] ?? 0],
18
+ [aabb[3] ?? 0, aabb[4] ?? 0, aabb[2] ?? 0],
19
+ [aabb[0] ?? 0, aabb[4] ?? 0, aabb[2] ?? 0],
20
+ [aabb[0] ?? 0, aabb[1] ?? 0, aabb[5] ?? 0],
21
+ [aabb[3] ?? 0, aabb[1] ?? 0, aabb[5] ?? 0],
22
+ [aabb[3] ?? 0, aabb[4] ?? 0, aabb[5] ?? 0],
23
+ [aabb[0] ?? 0, aabb[4] ?? 0, aabb[5] ?? 0],
24
+ ];
25
+ const triangles = [
26
+ 0, 1, 2, 0, 2, 3, 4, 6, 5, 4, 7, 6, 0, 4, 5, 0, 5, 1, 3, 2, 6, 3, 6, 7, 0, 3, 7, 0, 7, 4, 1, 5,
27
+ 6, 1, 6, 2,
28
+ ];
29
+ const output = new Float32Array(triangles.length * 4);
30
+ for (let index = 0; index < triangles.length; index += 1) {
31
+ const corner = corners[triangles[index] ?? 0] ?? [0, 0, 0];
32
+ const cx = corner[0] ?? 0;
33
+ const cy = corner[1] ?? 0;
34
+ const cz = corner[2] ?? 0;
35
+ const x = (world[0] ?? 0) * cx + (world[4] ?? 0) * cy + (world[8] ?? 0) * cz + (world[12] ?? 0);
36
+ const y = (world[1] ?? 0) * cx + (world[5] ?? 0) * cy + (world[9] ?? 0) * cz + (world[13] ?? 0);
37
+ const z =
38
+ (world[2] ?? 0) * cx + (world[6] ?? 0) * cy + (world[10] ?? 0) * cz + (world[14] ?? 0);
39
+ const w =
40
+ (world[3] ?? 0) * cx + (world[7] ?? 0) * cy + (world[11] ?? 0) * cz + (world[15] ?? 1);
41
+ const base = index * 4;
42
+ output[base] =
43
+ (viewProjection[0] ?? 0) * x +
44
+ (viewProjection[4] ?? 0) * y +
45
+ (viewProjection[8] ?? 0) * z +
46
+ (viewProjection[12] ?? 0) * w;
47
+ output[base + 1] =
48
+ (viewProjection[1] ?? 0) * x +
49
+ (viewProjection[5] ?? 0) * y +
50
+ (viewProjection[9] ?? 0) * z +
51
+ (viewProjection[13] ?? 0) * w;
52
+ output[base + 2] =
53
+ (viewProjection[2] ?? 0) * x +
54
+ (viewProjection[6] ?? 0) * y +
55
+ (viewProjection[10] ?? 0) * z +
56
+ (viewProjection[14] ?? 0) * w;
57
+ output[base + 3] =
58
+ (viewProjection[3] ?? 0) * x +
59
+ (viewProjection[7] ?? 0) * y +
60
+ (viewProjection[11] ?? 0) * z +
61
+ (viewProjection[15] ?? 0) * w;
62
+ }
63
+ return output;
64
+ }