@forgeax/engine-render 0.1.6 → 0.1.7

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 (151) hide show
  1. package/README.md +139 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/__tests__/gpu-driven-indirect-raster-evidence.d.ts.map +1 -1
  4. package/dist/assembly/factory.d.ts +7 -0
  5. package/dist/assembly/factory.d.ts.map +1 -1
  6. package/dist/assembly/material/assembly.d.ts.map +1 -1
  7. package/dist/authoring.d.ts +1 -0
  8. package/dist/authoring.d.ts.map +1 -1
  9. package/dist/authoring.mjs +1 -1
  10. package/dist/{chunk-QC4VW3XJ.mjs → chunk-HCOGPHKS.mjs} +17 -3
  11. package/dist/chunk-HCOGPHKS.mjs.map +1 -0
  12. package/dist/chunk-YEJIUW7D.mjs +1152 -0
  13. package/dist/chunk-YEJIUW7D.mjs.map +1 -0
  14. package/dist/components/__tests__/points-lines-admission.unit.test.d.ts +2 -0
  15. package/dist/components/__tests__/points-lines-admission.unit.test.d.ts.map +1 -0
  16. package/dist/components/__tests__/points-lines-schema.unit.test.d.ts +2 -0
  17. package/dist/components/__tests__/points-lines-schema.unit.test.d.ts.map +1 -0
  18. package/dist/components/index.d.ts +2 -0
  19. package/dist/components/index.d.ts.map +1 -1
  20. package/dist/components/lines.d.ts +5 -0
  21. package/dist/components/lines.d.ts.map +1 -0
  22. package/dist/components/points.d.ts +15 -0
  23. package/dist/components/points.d.ts.map +1 -0
  24. package/dist/construct-renderer.mjs +952 -497
  25. package/dist/construct-renderer.mjs.map +1 -1
  26. package/dist/errors/render.d.ts +82 -1
  27. package/dist/errors/render.d.ts.map +1 -1
  28. package/dist/features/render-graph-raster.d.ts +5 -1
  29. package/dist/features/render-graph-raster.d.ts.map +1 -1
  30. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  31. package/dist/index.d.ts +4 -0
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.mjs +4 -2
  34. package/dist/index.mjs.map +1 -1
  35. package/dist/inspection-types.d.ts +3 -0
  36. package/dist/inspection-types.d.ts.map +1 -1
  37. package/dist/pbr-pipeline.d.ts +2 -0
  38. package/dist/pbr-pipeline.d.ts.map +1 -1
  39. package/dist/plugin.d.ts.map +1 -1
  40. package/dist/points-lines/__tests__/bounds.unit.test.d.ts +2 -0
  41. package/dist/points-lines/__tests__/bounds.unit.test.d.ts.map +1 -0
  42. package/dist/points-lines/__tests__/cache-recovery.unit.test.d.ts +2 -0
  43. package/dist/points-lines/__tests__/cache-recovery.unit.test.d.ts.map +1 -0
  44. package/dist/points-lines/__tests__/inspection-lanes.integration.test.d.ts +2 -0
  45. package/dist/points-lines/__tests__/inspection-lanes.integration.test.d.ts.map +1 -0
  46. package/dist/points-lines/__tests__/inspection.unit.test.d.ts +2 -0
  47. package/dist/points-lines/__tests__/inspection.unit.test.d.ts.map +1 -0
  48. package/dist/points-lines/__tests__/lane-structural.integration.test.d.ts +2 -0
  49. package/dist/points-lines/__tests__/lane-structural.integration.test.d.ts.map +1 -0
  50. package/dist/points-lines/__tests__/points-lines.bench.d.ts +2 -0
  51. package/dist/points-lines/__tests__/points-lines.bench.d.ts.map +1 -0
  52. package/dist/points-lines/__tests__/raster-lane.integration.test.d.ts +2 -0
  53. package/dist/points-lines/__tests__/raster-lane.integration.test.d.ts.map +1 -0
  54. package/dist/points-lines/__tests__/snapshot.unit.test.d.ts +2 -0
  55. package/dist/points-lines/__tests__/snapshot.unit.test.d.ts.map +1 -0
  56. package/dist/points-lines/__tests__/view-ubo.unit.test.d.ts +2 -0
  57. package/dist/points-lines/__tests__/view-ubo.unit.test.d.ts.map +1 -0
  58. package/dist/points-lines/admission.d.ts +36 -0
  59. package/dist/points-lines/admission.d.ts.map +1 -0
  60. package/dist/points-lines/bounds.d.ts +8 -0
  61. package/dist/points-lines/bounds.d.ts.map +1 -0
  62. package/dist/points-lines/expansion-cache.d.ts +28 -0
  63. package/dist/points-lines/expansion-cache.d.ts.map +1 -0
  64. package/dist/points-lines/inspection.d.ts +53 -0
  65. package/dist/points-lines/inspection.d.ts.map +1 -0
  66. package/dist/points-lines/prepare.d.ts +67 -0
  67. package/dist/points-lines/prepare.d.ts.map +1 -0
  68. package/dist/points-lines/record.d.ts +60 -0
  69. package/dist/points-lines/record.d.ts.map +1 -0
  70. package/dist/points-lines/snapshot.d.ts +49 -0
  71. package/dist/points-lines/snapshot.d.ts.map +1 -0
  72. package/dist/record/frame-lighting.d.ts.map +1 -1
  73. package/dist/record/frame.d.ts +1 -1
  74. package/dist/record/frame.d.ts.map +1 -1
  75. package/dist/record/main-pass-geometry.d.ts +7 -0
  76. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  77. package/dist/record/main-pass.d.ts.map +1 -1
  78. package/dist/record/render-context.d.ts +17 -0
  79. package/dist/record/render-context.d.ts.map +1 -1
  80. package/dist/record/shadow-pass.d.ts.map +1 -1
  81. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  82. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  83. package/dist/record/view-ubo.d.ts +6 -0
  84. package/dist/record/view-ubo.d.ts.map +1 -1
  85. package/dist/render-contract.d.ts +10 -2
  86. package/dist/render-contract.d.ts.map +1 -1
  87. package/dist/render-system-extract.d.ts +3 -0
  88. package/dist/render-system-extract.d.ts.map +1 -1
  89. package/dist/render-system.d.ts +4 -1
  90. package/dist/render-system.d.ts.map +1 -1
  91. package/dist/scene/render-scene-types.d.ts +3 -0
  92. package/dist/scene/render-scene-types.d.ts.map +1 -1
  93. package/dist/scene/render-scene.d.ts +7 -0
  94. package/dist/scene/render-scene.d.ts.map +1 -1
  95. package/package.json +19 -19
  96. package/src/__tests__/authoring-path.unit.test.ts +9 -0
  97. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +12 -2
  98. package/src/__tests__/material-cooked-projection.unit.test.ts +29 -7
  99. package/src/__tests__/material-static-dynamic-route.unit.test.ts +28 -28
  100. package/src/__tests__/render-error-code-owner.test-d.ts +6 -0
  101. package/src/__tests__/render-error-exhaustive.test-d.ts +19 -0
  102. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +38 -0
  103. package/src/assembly/factory.ts +72 -3
  104. package/src/assembly/material/assembly.ts +2 -5
  105. package/src/authoring.ts +1 -0
  106. package/src/components/__tests__/points-lines-admission.unit.test.ts +219 -0
  107. package/src/components/__tests__/points-lines-schema.unit.test.ts +56 -0
  108. package/src/components/index.ts +7 -0
  109. package/src/components/lines.ts +18 -0
  110. package/src/components/points.ts +40 -0
  111. package/src/errors/render.ts +144 -1
  112. package/src/features/render-graph-raster.ts +22 -3
  113. package/src/gpu-driven/production-raster.ts +6 -1
  114. package/src/index.ts +17 -0
  115. package/src/inspection-types.ts +4 -0
  116. package/src/pbr-pipeline.ts +8 -1
  117. package/src/plugin.ts +4 -0
  118. package/src/points-lines/__tests__/bounds.unit.test.ts +21 -0
  119. package/src/points-lines/__tests__/cache-recovery.unit.test.ts +157 -0
  120. package/src/points-lines/__tests__/inspection-lanes.integration.test.ts +146 -0
  121. package/src/points-lines/__tests__/inspection.unit.test.ts +173 -0
  122. package/src/points-lines/__tests__/lane-structural.integration.test.ts +122 -0
  123. package/src/points-lines/__tests__/points-lines.bench.ts +400 -0
  124. package/src/points-lines/__tests__/raster-lane.integration.test.ts +44 -0
  125. package/src/points-lines/__tests__/snapshot.unit.test.ts +101 -0
  126. package/src/points-lines/__tests__/view-ubo.unit.test.ts +73 -0
  127. package/src/points-lines/admission.ts +238 -0
  128. package/src/points-lines/bounds.ts +23 -0
  129. package/src/points-lines/expansion-cache.ts +215 -0
  130. package/src/points-lines/inspection.ts +97 -0
  131. package/src/points-lines/prepare.ts +177 -0
  132. package/src/points-lines/record.ts +125 -0
  133. package/src/points-lines/snapshot.ts +130 -0
  134. package/src/record/frame-lighting.ts +14 -2
  135. package/src/record/frame.ts +12 -1
  136. package/src/record/main-pass-geometry.ts +92 -0
  137. package/src/record/main-pass-sprite-draws.ts +1 -1
  138. package/src/record/main-pass.ts +6 -3
  139. package/src/record/render-context.ts +20 -0
  140. package/src/record/shadow-pass.ts +16 -5
  141. package/src/record/skybox-post-pass.ts +2 -1
  142. package/src/record/typed-frame-graph.ts +17 -16
  143. package/src/record/view-ubo.ts +47 -0
  144. package/src/render-contract.ts +13 -2
  145. package/src/render-system-extract.ts +71 -8
  146. package/src/render-system.ts +304 -2
  147. package/src/scene/render-scene-types.ts +3 -0
  148. package/src/scene/render-scene.ts +57 -2
  149. package/dist/chunk-QC4VW3XJ.mjs.map +0 -1
  150. package/dist/chunk-SCRSAK5B.mjs +0 -480
  151. package/dist/chunk-SCRSAK5B.mjs.map +0 -1
@@ -40,6 +40,16 @@ import type { SkylightFallback } from '../ibl/skylight-bind-group';
40
40
  import type { HealthListenerRegistry, RhiErrorListenerRegistry } from '../lifecycle';
41
41
  import type { StandardProfile } from '../pipeline/standard-profile';
42
42
  import type { PipelineBuilderShaderModuleFactory } from '../pipeline-builder';
43
+ import type { PointsLinesInspection } from '../points-lines/inspection';
44
+ import type { PointsLinesRecordPlan } from '../points-lines/record';
45
+
46
+ export interface PointsLinesRecordSubmission {
47
+ readonly plan: PointsLinesRecordPlan;
48
+ readonly vertexBuffer: Buffer;
49
+ readonly indexBuffer: Buffer;
50
+ readonly layoutProjection: VertexLayoutProjection;
51
+ }
52
+
43
53
  import type {
44
54
  RenderDebugOverlay,
45
55
  RenderPipelineContext,
@@ -103,6 +113,14 @@ export interface RenderSystemRuntime {
103
113
  readonly dynamicTextureStore?: DynamicTextureStore | undefined;
104
114
  }
105
115
 
116
+ /** Renderer-owned bridge from retained Points/Lines facts into main geometry. */
117
+ export interface PointsLinesRecordOwner {
118
+ prepare(entry: ValidatedRenderable, clustered: boolean): PointsLinesRecordSubmission | undefined;
119
+ beginFrame(): void;
120
+ resetForDeviceLoss(): void;
121
+ inspections(): readonly PointsLinesInspection[];
122
+ }
123
+
106
124
  export interface PerPassResources {
107
125
  depthTexture: Texture | null;
108
126
  depthTextureView: TextureView | null;
@@ -184,6 +202,7 @@ export interface PipelineState {
184
202
  readonly materialBindGroupLayout: BindGroupLayout;
185
203
  readonly meshBindGroupLayout: BindGroupLayout;
186
204
  readonly viewUniformBuffer: Buffer;
205
+ readonly pointsLinesViewBuffer?: Buffer;
187
206
  readonly shadowCasterCascadeBuffer: Buffer;
188
207
  readonly materialUniformBuffer: { readonly buffer: Buffer; readonly sizeInBytes: number };
189
208
  readonly meshStorageBuffer: { readonly buffer: Buffer; readonly sizeInBytes: number };
@@ -281,6 +300,7 @@ export interface _StandardForwardSceneView {
281
300
  readonly materialSlots: readonly MaterialSnapshot[];
282
301
  readonly materialSlotOwners: readonly number[];
283
302
  readonly materialSlotCount: number;
303
+ readonly pointsLines: PointsLinesRecordOwner | undefined;
284
304
  readonly materialBgAssemblyCache: Map<string, MaterialBgAssemblyCacheEntry>;
285
305
  materialUboPayloadCache?: {
286
306
  readonly materialSlots: readonly MaterialSnapshot[];
@@ -29,7 +29,7 @@ import {
29
29
  } from './mesh-ssbo';
30
30
  import type { _InternalRenderPipelineContext } from './render-context';
31
31
  import { STANDARD_PBR_UBO_SIZE } from './render-context';
32
- import { pointShadowViewOffset, VIEW_UNIFORM_BYTES } from './view-ubo';
32
+ import { POINTS_LINES_VIEW_BYTES, pointShadowViewOffset, VIEW_UNIFORM_BYTES } from './view-ubo';
33
33
 
34
34
  export const SHADOW_CASTER_SLOT_STRIDE = 256;
35
35
  export const SHADOW_CASTER_BUFFER_SIZE = SHADOW_CASTER_SLOT_STRIDE * 8;
@@ -88,6 +88,7 @@ function ensureTypedShadowViewBg(
88
88
  pipelineState.shadowParamsBuffer,
89
89
  pipelineState.shadowCasterCascadeBuffer,
90
90
  pipelineState.shadowFallbackTextureView,
91
+ pipelineState.pointsLinesViewBuffer ?? pipelineState.viewUniformBuffer,
91
92
  ],
92
93
  variant,
93
94
  () => {
@@ -140,7 +141,7 @@ function ensureTypedShadowViewBg(
140
141
  value: {
141
142
  buffer: pipelineState.shadowCasterCascadeBuffer,
142
143
  offset: cascadeOffset,
143
- size: 80,
144
+ size: POINTS_LINES_VIEW_BYTES,
144
145
  },
145
146
  },
146
147
  },
@@ -148,6 +149,16 @@ function ensureTypedShadowViewBg(
148
149
  binding: 8,
149
150
  resource: { kind: 'textureView', value: pipelineState.shadowFallbackTextureView },
150
151
  },
152
+ {
153
+ binding: 10,
154
+ resource: {
155
+ kind: 'buffer',
156
+ value: {
157
+ buffer: pipelineState.pointsLinesViewBuffer ?? pipelineState.viewUniformBuffer,
158
+ size: 80,
159
+ },
160
+ },
161
+ },
151
162
  ],
152
163
  });
153
164
  if (!created.ok) throw created.error;
@@ -207,7 +218,7 @@ export function encodeDirectionalShadowPass(
207
218
  c.frameState.directionalShadowCacheRecorded = true;
208
219
  pass.setViewport(viewport.x, viewport.y, viewport.w, viewport.h, 0, 1);
209
220
  pass.setPipeline(pipeline);
210
- pass.setBindGroup(0, viewBg);
221
+ pass.setBindGroup(0, viewBg, [0]);
211
222
  pass.setBindGroup(1, materialBg, [0]);
212
223
  recordShadowCasterDraws(
213
224
  c,
@@ -237,7 +248,7 @@ export function encodePointShadowPass(
237
248
  const materialBg = ensureSpotShadowMaterialBg(c);
238
249
  if (pipeline === null || viewBg === null || materialBg === null) return;
239
250
  pass.setPipeline(pipeline);
240
- pass.setBindGroup(0, viewBg);
251
+ pass.setBindGroup(0, viewBg, [0]);
241
252
  pass.setBindGroup(1, materialBg, [0]);
242
253
  recordShadowCasterDraws(
243
254
  c,
@@ -285,7 +296,7 @@ export function encodeSpotShadowPass(
285
296
  1,
286
297
  );
287
298
  pass.setPipeline(pipeline);
288
- pass.setBindGroup(0, viewBg);
299
+ pass.setBindGroup(0, viewBg, [0]);
289
300
  pass.setBindGroup(1, materialBg, [0]);
290
301
  // Reuse the shared caster recorder. The compact spot-only loop used to
291
302
  // depend on instance bindings created by a directional pass, so a scene
@@ -4,6 +4,7 @@ import { toShared } from '@forgeax/engine-types';
4
4
  import { buildBeginRenderPassDescriptor } from '../pipeline-spec';
5
5
  import { getOrCreateFromChain } from './mesh-ssbo';
6
6
  import type { _InternalRenderPipelineContext } from './render-context';
7
+ import { VIEW_UNIFORM_BYTES } from './view-ubo';
7
8
 
8
9
  /**
9
10
  * feat-20260531-skybox-env-background M2 / w8: skybox pass recording stub.
@@ -111,7 +112,7 @@ export function recordSkyboxPass(
111
112
  binding: 2,
112
113
  resource: {
113
114
  kind: 'buffer',
114
- value: { buffer: pipelineState.viewUniformBuffer },
115
+ value: { buffer: pipelineState.viewUniformBuffer, size: VIEW_UNIFORM_BYTES },
115
116
  },
116
117
  },
117
118
  {
@@ -4,7 +4,6 @@ import {
4
4
  RenderGraphBuilder,
5
5
  } from '@forgeax/engine-render-graph';
6
6
  import type {
7
- BindGroup,
8
7
  BindGroupLayout,
9
8
  CommandBuffer,
10
9
  RenderPipeline,
@@ -20,7 +19,10 @@ import {
20
19
  } from '../features/host';
21
20
  import { type RenderFeaturePlannedFrame, renderFeaturePlanSignature } from '../features/plan';
22
21
  import { projectRenderFeaturePlans as projectPlanExecutions } from '../features/render-graph-contribution';
23
- import type { RenderFeatureGraphTargetResolver } from '../features/render-graph-raster';
22
+ import type {
23
+ RenderFeatureGraphBindingsResolution,
24
+ RenderFeatureGraphTargetResolver,
25
+ } from '../features/render-graph-raster';
24
26
  import { isRenderFeatureTargetHandle } from '../features/targets';
25
27
  import type { PreparedGpuDrivenFrame } from '../gpu-driven/production-raster';
26
28
  import { STANDARD_PIPELINE_ID } from '../pipeline/standard-profile';
@@ -251,7 +253,7 @@ function resolveTargetBindings(input: {
251
253
  };
252
254
  readonly resources: GraphResourceResolver;
253
255
  readonly resolveTarget: RenderFeatureGraphTargetResolver;
254
- }): BindGroup | undefined {
256
+ }): RenderFeatureGraphBindingsResolution | undefined {
255
257
  const frame = input.frame as import('./render-context')._InternalRenderPipelineContext;
256
258
  const descriptor = input.binding.descriptor;
257
259
  const pipeline = input.binding.pipeline as
@@ -294,19 +296,18 @@ function resolveTargetBindings(input: {
294
296
  return created.value;
295
297
  }
296
298
  if (sceneDepth === undefined) {
297
- return (
298
- buildPerFrameBindGroups(
299
- frame.runtime as RenderSystemInternals,
300
- frame.frameState,
301
- frame.pipelineState,
302
- frame.validated.length > 0,
303
- frame.bindGroupCounts,
304
- {
305
- directionalShadow: frame.frameState.currentDirectionalShadowView ?? undefined,
306
- spotShadow: frame.frameState.currentSpotShadowView ?? undefined,
307
- },
308
- ).viewBindGroup ?? undefined
309
- );
299
+ const viewBindGroup = buildPerFrameBindGroups(
300
+ frame.runtime as RenderSystemInternals,
301
+ frame.frameState,
302
+ frame.pipelineState,
303
+ frame.validated.length > 0,
304
+ frame.bindGroupCounts,
305
+ {
306
+ directionalShadow: frame.frameState.currentDirectionalShadowView ?? undefined,
307
+ spotShadow: frame.frameState.currentSpotShadowView ?? undefined,
308
+ },
309
+ ).viewBindGroup;
310
+ return viewBindGroup === null ? undefined : { handle: viewBindGroup, dynamicOffsets: [0] };
310
311
  }
311
312
  const target = input.resolveTarget(sceneDepth);
312
313
  if (target === undefined) return undefined;
@@ -8,6 +8,7 @@
8
8
 
9
9
  import { mat4 } from '@forgeax/engine-math';
10
10
  import type { Buffer, RhiQueue } from '@forgeax/engine-rhi';
11
+ import type { PointsLinesStyle } from '../points-lines/snapshot';
11
12
  import type { CameraSnapshot } from '../render-contract';
12
13
  import type {
13
14
  DirectionalLightSnapshot,
@@ -18,6 +19,11 @@ import { clampPcfKernelSize } from './frame-snapshot';
18
19
  import { computeProjectionMatrix, computeViewMatrix } from './helpers';
19
20
 
20
21
  export const VIEW_UNIFORM_BYTES = 960;
22
+ export const POINTS_LINES_VIEW_BYTES = 160;
23
+ export const POINTS_LINES_VIEW_SLOT_STRIDE = 256;
24
+ export const POINTS_LINES_VIEW_SLOT_COUNT = 1024;
25
+ export const POINTS_LINES_VIEW_BUFFER_SIZE =
26
+ POINTS_LINES_VIEW_SLOT_STRIDE * POINTS_LINES_VIEW_SLOT_COUNT;
21
27
  export const VIEW_UNIFORM_SLOT_STRIDE = 1024;
22
28
  export const POINT_SHADOW_VIEW_SLOT_COUNT = 24;
23
29
  export const VIEW_UNIFORM_BUFFER_SIZE =
@@ -27,6 +33,47 @@ export function pointShadowViewOffset(layer: number, face: number): number {
27
33
  return VIEW_UNIFORM_SLOT_STRIDE * (1 + layer * 6 + face);
28
34
  }
29
35
 
36
+ export function writePointsLinesViewUbo(
37
+ queue: RhiQueue,
38
+ buffer: Buffer,
39
+ camera: CameraSnapshot,
40
+ width: number,
41
+ height: number,
42
+ model?: ArrayLike<number>,
43
+ style?: PointsLinesStyle,
44
+ byteOffset = 0,
45
+ ): void {
46
+ const projection = computeProjectionMatrix(camera);
47
+ const view = computeViewMatrix(camera);
48
+ const worldViewProj = mat4.create();
49
+ mat4.multiply(worldViewProj, projection, view);
50
+ const payload = new Float32Array(40);
51
+ payload.set(worldViewProj);
52
+ if (model === undefined) {
53
+ payload[16] = 1;
54
+ payload[21] = 1;
55
+ payload[26] = 1;
56
+ payload[31] = 1;
57
+ } else {
58
+ for (let index = 0; index < 16; index += 1) {
59
+ payload[16 + index] = model[index] ?? 0;
60
+ }
61
+ }
62
+ payload[32] = width;
63
+ payload[33] = height;
64
+ if (style?.kind === 'points') {
65
+ payload[36] = style.sizePx;
66
+ payload[38] = style.shape === 'circle' ? 1 : 0;
67
+ } else if (style?.kind === 'lines') {
68
+ payload[36] = style.widthPx;
69
+ payload[37] = 1;
70
+ } else {
71
+ payload[36] = 1;
72
+ }
73
+ const uploaded = queue.writeBuffer(buffer, byteOffset, payload);
74
+ if (!uploaded.ok) throw uploaded.error;
75
+ }
76
+
30
77
  /**
31
78
  * feat-20260704 M3/w18: assemble + upload the per-frame View UBO payload.
32
79
  *
@@ -24,9 +24,12 @@ import type { ImageError } from '@forgeax/engine-types';
24
24
  import type { RhiBackendInstrumentation } from './assembly/backend-contract';
25
25
  import type { Antialias, BloomEnabled, Tonemap } from './components/camera';
26
26
  import type { RenderError } from './errors/render';
27
- import type { RenderFeature } from './features/types';
27
+ import type { RenderFeature, RenderFeatureDiagnostics } from './features/types';
28
28
  import type { PostProcessShaderEntry } from './fullscreen-post-process-pass';
29
29
  import type { RenderSceneInspection } from './inspection-types';
30
+
31
+ export type { PointsLinesInspection } from './points-lines/inspection';
32
+
30
33
  import type { MeshMaterialBindingObservation } from './mesh-material-bindings';
31
34
  import type { StandardProfile } from './pipeline/standard-profile';
32
35
  import type { PostProcessError } from './post-process-errors';
@@ -172,7 +175,13 @@ export type RendererEvent =
172
175
  readonly previous: RendererState;
173
176
  readonly current: RendererState;
174
177
  }
175
- | { readonly kind: 'error'; readonly error: RenderError };
178
+ | { readonly kind: 'error'; readonly error: RenderError }
179
+ /** Emitted only after the renderer's sole submit path returns a receipt. */
180
+ | {
181
+ readonly kind: 'frame-submitted';
182
+ readonly frameId: number;
183
+ readonly deviceGeneration: number;
184
+ };
176
185
 
177
186
  /** Package-local listener alias used by the renderer assembly implementation. */
178
187
  export type RendererEventListener = (event: RendererEvent) => void;
@@ -252,6 +261,8 @@ export interface RenderInspection {
252
261
  readonly deviceGeneration: number;
253
262
  };
254
263
  readonly features: readonly string[];
264
+ /** Truthful lifecycle facts for every registered declarative feature. */
265
+ readonly featureDiagnostics: readonly RenderFeatureDiagnostics[];
255
266
  readonly frustumStats: { readonly culled: number; readonly total: number };
256
267
  readonly visibilityStats: { readonly explicitlyHidden: number };
257
268
  readonly renderScene: RenderSceneInspection;
@@ -159,13 +159,17 @@ import {
159
159
  DirectionalLight,
160
160
  Instances,
161
161
  Layer,
162
+ Lines,
162
163
  MeshFilter,
163
164
  MeshRenderer,
164
165
  PointLight,
165
166
  PointLightShadow,
167
+ Points,
166
168
  PostProcessParams,
169
+ pointShapeFromU32,
167
170
  SkyboxBackground,
168
171
  Skylight,
172
+ SortKey,
169
173
  SpotLight,
170
174
  SpriteInstances,
171
175
  SpriteRegionOverride,
@@ -191,6 +195,8 @@ import {
191
195
  resolveMeshMaterialBindings,
192
196
  } from './mesh-material-bindings';
193
197
  import { isStandardPbrMaterialShader } from './pbr-pipeline';
198
+ import { expandPointsLinesBounds } from './points-lines/bounds';
199
+ import type { PointsLinesRetainedSnapshot, PointsLinesStyle } from './points-lines/snapshot';
194
200
  import { type CameraSnapshot, STANDARD_TONEMAP_FEATURE_ID } from './render-contract';
195
201
  import { getActiveCamera, selectActiveCameraIndex } from './systems/active-camera';
196
202
  import { selectPasses } from './systems/pass-selector';
@@ -598,6 +604,8 @@ export interface RenderableSnapshot {
598
604
  * deltas and the record stage only receives this frozen POD snapshot.
599
605
  */
600
606
  readonly morph?: MorphSnapshot;
607
+ /** Detached M2 authoring facts for the optional Points/Lines projection. */
608
+ readonly pointsLines?: PointsLinesRetainedSnapshot;
601
609
  }
602
610
 
603
611
  export interface MorphSnapshot {
@@ -870,7 +878,7 @@ function gpuDrivenMaterialResourceClass(material: MaterialSnapshot): string {
870
878
  //
871
879
  // Plan-strategy D-3: single dispatch list sorted by queue value,
872
880
  // replacing the old three-bucket opaque/transparent/overlay dispatch.
873
- // Each entry carries the per-pass render-state, defines, and entry-point
881
+ // Each entry carries the per-pass render-state and entry-point
874
882
  // data from the resolved MaterialPass so the record stage
875
883
  // reads them without re-resolving the material.
876
884
 
@@ -948,7 +956,9 @@ function appendMaterialDispatchEntries(
948
956
  layer,
949
957
  tags: passState.tags ?? {},
950
958
  renderState: pipelineRenderState(passState),
951
- defines: pass.program.moduleSlots,
959
+ // Material module identity is already closed in the cooked program.
960
+ // It must never become a draw-time define map.
961
+ defines: undefined,
952
962
  vertexEntry: pass.program.vertexEntry,
953
963
  fragmentEntry: pass.program.fragmentEntry,
954
964
  materialShaderId: runtimeMaterialShaderId(pass.program.module, pass.name),
@@ -1405,9 +1415,8 @@ function materialParametersToParamSchema(
1405
1415
  ? ENGINE_INJECTED_TEXTURE_FIELDS
1406
1416
  : undefined;
1407
1417
  return parameters.flatMap((parameter): ParamSchemaEntry[] => {
1408
- // Static values select a cooked specialization and boolean material
1409
- // values have no runtime UBO representation in ParamSchemaEntry.
1410
- if (parameter.static || parameter.type === 'bool') return [];
1418
+ // Boolean material values are runtime-only and have no UBO representation.
1419
+ if (parameter.type === 'bool') return [];
1411
1420
  if (parameter.type === 'texture') {
1412
1421
  if (engineInjectedTextureFields?.has(parameter.name) === true) return [];
1413
1422
  return [{ name: parameter.name, type: 'texture2d' }];
@@ -2387,7 +2396,11 @@ export function extractFrames(
2387
2396
  }
2388
2397
  hiddenEntityReports.push(...f.hiddenEntityReports);
2389
2398
  for (const r of f.renderables) {
2390
- renderables.push({ ...r, worldId: wId });
2399
+ renderables.push({
2400
+ ...r,
2401
+ worldId: wId,
2402
+ ...(r.pointsLines === undefined ? {} : { pointsLines: { ...r.pointsLines, worldId: wId } }),
2403
+ });
2391
2404
  }
2392
2405
 
2393
2406
  // D-3: dispatch — per-world renderableIndex rebased by base offset.
@@ -3236,6 +3249,9 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
3236
3249
  MorphWeights,
3237
3250
  SpriteRegionOverride,
3238
3251
  SpriteInstances,
3252
+ Points,
3253
+ Lines,
3254
+ SortKey,
3239
3255
  ],
3240
3256
  })
3241
3257
  .unwrap();
@@ -3288,6 +3304,11 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
3288
3304
  // - 'sprite-instances-count-mismatch'
3289
3305
  // (transforms.length / 16 !== regions.length / 4) — stride pair desync.
3290
3306
  const hasSpriteInstances = row.has(SpriteInstances);
3307
+ const points = row.get(Points);
3308
+ const lines = row.get(Lines);
3309
+ const sortKey = row.get(SortKey)?.value;
3310
+ const pointsLinesComponent =
3311
+ points !== undefined ? 'Points' : lines !== undefined ? 'Lines' : undefined;
3291
3312
  const isRenderable = hasTransform && hasMeshFilter;
3292
3313
 
3293
3314
  // feat-20260601 D-3: the resolved world transform is read per-entity from
@@ -4342,6 +4363,38 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
4342
4363
  }
4343
4364
  }
4344
4365
 
4366
+ const pointsLinesStyle: PointsLinesStyle | undefined =
4367
+ points !== undefined
4368
+ ? (() => {
4369
+ const shape = pointShapeFromU32(points.shape);
4370
+ return shape === undefined
4371
+ ? undefined
4372
+ : { kind: 'points' as const, sizePx: points.sizePx, shape };
4373
+ })()
4374
+ : lines === undefined
4375
+ ? undefined
4376
+ : { kind: 'lines' as const, widthPx: lines.widthPx };
4377
+ const cullingLocalAabb = expandPointsLinesBounds(localAabb ?? [], pointsLinesStyle);
4378
+ const pointsLines =
4379
+ pointsLinesComponent === undefined
4380
+ ? undefined
4381
+ : ({
4382
+ worldId: 0,
4383
+ entityKey: entity,
4384
+ component: pointsLinesComponent,
4385
+ meshHandle: assetHandleRaw,
4386
+ meshGeneration: assets?.catalogEpoch ?? 0,
4387
+ materialHandle: handleRaw,
4388
+ materialGeneration: assets?.catalogEpoch ?? 0,
4389
+ style: pointsLinesStyle,
4390
+ layer: layerVal,
4391
+ sortKey,
4392
+ visible: true,
4393
+ sourceBounds: cullingLocalAabb,
4394
+ viewport: { width: 0, height: 0, dpr: 1 },
4395
+ projection: identityProjection(),
4396
+ } satisfies PointsLinesRetainedSnapshot);
4397
+
4345
4398
  let nonIndexedFirst = 0;
4346
4399
  const gpuDrivenDraws = gpuDrivenSubmeshes.flatMap((submesh) => {
4347
4400
  const drawMaterial = materialsArr[submesh.materialSlot] ?? materialSnap;
@@ -4364,7 +4417,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
4364
4417
  const baseRenderable: RenderableSnapshot = {
4365
4418
  assetHandle: Math.round(fAssetHandle ?? 0),
4366
4419
  transform: transformSnap,
4367
- ...(localAabb !== undefined ? { localAabb } : {}),
4420
+ ...(localAabb !== undefined ? { localAabb: cullingLocalAabb } : {}),
4368
4421
  material: materialSnap,
4369
4422
  materials: materialsArr,
4370
4423
  materialBindingSources,
@@ -4375,6 +4428,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
4375
4428
  ...(skinSlice !== undefined ? { skin: skinSlice } : {}),
4376
4429
  ...(morph !== undefined ? { morph } : {}),
4377
4430
  ...(spriteInstancesSnap !== undefined ? { spriteInstances: spriteInstancesSnap } : {}),
4431
+ ...(pointsLines !== undefined ? { pointsLines } : {}),
4378
4432
  };
4379
4433
 
4380
4434
  // feat-20260528-frustum-culling M3 / w10: frustum culling check.
@@ -4388,7 +4442,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
4388
4442
  // directly (no compose) -- same source the record stage feeds
4389
4443
  // the mesh SSBO, so cull stays same-source with render (AC-05).
4390
4444
  const worldAabb = box3.create();
4391
- box3.transformBox3(worldAabb, localAabb, transformSnap.world);
4445
+ box3.transformBox3(worldAabb, cullingLocalAabb, transformSnap.world);
4392
4446
 
4393
4447
  // Test against all cameras. Entity is visible if one camera
4394
4448
  // frustum intersects the world-space AABB (or planes are empty
@@ -4520,3 +4574,12 @@ export function defaultMaterialSnapshot(materialHandle = 0): MaterialSnapshot {
4520
4574
  materialHandle,
4521
4575
  };
4522
4576
  }
4577
+
4578
+ function identityProjection(): Float32Array {
4579
+ const projection = new Float32Array(16);
4580
+ projection[0] = 1;
4581
+ projection[5] = 1;
4582
+ projection[10] = 1;
4583
+ projection[15] = 1;
4584
+ return projection;
4585
+ }