@forgeax/engine-render 0.1.24 → 0.1.26
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.
- package/README.md +118 -1
- package/dist/assembly/factory.d.ts +1 -1
- package/dist/assembly/factory.d.ts.map +1 -1
- package/dist/assembly/material/assembly.d.ts +4 -17
- package/dist/assembly/material/assembly.d.ts.map +1 -1
- package/dist/assembly/material/standard-layer-projection.d.ts.map +1 -1
- package/dist/assembly/material/surface-projection.d.ts +24 -0
- package/dist/assembly/material/surface-projection.d.ts.map +1 -0
- package/dist/assembly/material-shader-policy.d.ts +20 -0
- package/dist/assembly/material-shader-policy.d.ts.map +1 -1
- package/dist/assembly/webgpu-ready.d.ts.map +1 -1
- package/dist/assembly/webgpu-renderer.d.ts +1 -2
- package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
- package/dist/assembly/webgpu-vertex-layouts.d.ts +89 -0
- package/dist/assembly/webgpu-vertex-layouts.d.ts.map +1 -0
- package/dist/assets/asset-decoders.d.ts.map +1 -1
- package/dist/authoring.mjs +1 -1
- package/dist/{chunk-Q752SHI5.mjs → chunk-ADHHVYLW.mjs} +3 -3
- package/dist/chunk-ADHHVYLW.mjs.map +1 -0
- package/dist/{chunk-TFH5I7JJ.mjs → chunk-E34VL5VI.mjs} +3 -3
- package/dist/{chunk-TFH5I7JJ.mjs.map → chunk-E34VL5VI.mjs.map} +1 -1
- package/dist/{chunk-YKB5DQV7.mjs → chunk-GF523LHF.mjs} +89 -21
- package/dist/chunk-GF523LHF.mjs.map +1 -0
- package/dist/{chunk-KZFTK6DO.mjs → chunk-HKXTW355.mjs} +7 -4
- package/dist/chunk-HKXTW355.mjs.map +1 -0
- package/dist/{chunk-UQYS7FRM.mjs → chunk-KM2NOX2I.mjs} +4 -4
- package/dist/{chunk-UQYS7FRM.mjs.map → chunk-KM2NOX2I.mjs.map} +1 -1
- package/dist/{chunk-YRUYGA4A.mjs → chunk-TMIASV2N.mjs} +944 -577
- package/dist/chunk-TMIASV2N.mjs.map +1 -0
- package/dist/{chunk-Z3YYH2MB.mjs → chunk-YUQXSEHG.mjs} +160 -73
- package/dist/chunk-YUQXSEHG.mjs.map +1 -0
- package/dist/{chunk-RR5L4SRS.mjs → chunk-ZZ4YQ474.mjs} +2 -2
- package/dist/{chunk-RR5L4SRS.mjs.map → chunk-ZZ4YQ474.mjs.map} +1 -1
- package/dist/components/instances.d.ts +13 -31
- package/dist/components/instances.d.ts.map +1 -1
- package/dist/construct-renderer.mjs +1012 -149
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/extract/contracts.d.ts +6 -6
- package/dist/extract/contracts.d.ts.map +1 -1
- package/dist/gpu-driven/production-raster.d.ts +6 -1
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts +4 -0
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +27 -9
- package/dist/index.mjs.map +1 -1
- package/dist/instance-buffer-cache.d.ts +7 -1
- package/dist/instance-buffer-cache.d.ts.map +1 -1
- package/dist/instances.d.ts +101 -0
- package/dist/instances.d.ts.map +1 -0
- package/dist/internal.mjs +5 -5
- package/dist/materials.d.ts +63 -2
- package/dist/materials.d.ts.map +1 -1
- package/dist/pipeline-builder.d.ts.map +1 -1
- package/dist/pipeline-spec-types.d.ts +2 -0
- package/dist/pipeline-spec-types.d.ts.map +1 -1
- package/dist/pipeline-spec.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +13 -2
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts +23 -4
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +20 -2
- package/dist/record/main-pass-material.d.ts.map +1 -1
- package/dist/record/main-pass-sprite-draws.d.ts +3 -3
- package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
- package/dist/record/main-pass.d.ts.map +1 -1
- package/dist/record/material-uniforms.d.ts +4 -0
- package/dist/record/material-uniforms.d.ts.map +1 -0
- package/dist/record/render-context.d.ts +4 -3
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts +10 -1
- package/dist/record/shadow-pass.d.ts.map +1 -1
- package/dist/recovery/render-system-candidate.d.ts +1 -1
- package/dist/recovery/render-system-candidate.d.ts.map +1 -1
- package/dist/render-contract.d.ts +4 -0
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-system-extract-tail.d.ts +6 -0
- package/dist/render-system-extract-tail.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +44 -9
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts +4 -1
- package/dist/render-system.d.ts.map +1 -1
- package/dist/scene/render-scene-types.d.ts +2 -0
- package/dist/scene/render-scene-types.d.ts.map +1 -1
- package/dist/scene/render-scene.d.ts +12 -1
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/systems/skin-palette-allocator.d.ts +6 -0
- package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
- package/dist/temporal/index.mjs +5 -5
- package/package.json +20 -20
- package/src/__tests__/fixtures/ai-weapon-spirit/README.md +20 -0
- package/src/__tests__/fixtures/ai-weapon-spirit/low-poly-toon.wgsl +178 -0
- package/src/__tests__/fixtures/ai-weapon-spirit/material.json +97 -0
- package/src/__tests__/fixtures/ai-weapon-spirit/painterly-surface.wgsl +55 -0
- package/src/__tests__/gpu-driven-view-gpu-evidence.ts +3 -3
- package/src/__tests__/gpu-driven-view.browser.test.ts +11 -3
- package/src/__tests__/gpu-scene.unit.test.ts +38 -2
- package/src/__tests__/instances-store.unit.test.ts +46 -0
- package/src/__tests__/instances-world-ownership.integration.test.ts +62 -0
- package/src/__tests__/material-contract-inventory.unit.test.ts +48 -13
- package/src/__tests__/{material-static-dynamic-route.unit.test.ts → material-layer-projection.unit.test.ts} +30 -47
- package/src/__tests__/material-pass-policy.unit.test.ts +69 -0
- package/src/__tests__/material-program-dispatch.unit.test.ts +48 -0
- package/src/__tests__/material-snapshot-mutation.integration.test.ts +69 -0
- package/src/__tests__/material-surface-authoring.test-d.ts +62 -0
- package/src/__tests__/materials-transmission-validation.unit.test.ts +24 -0
- package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +0 -4
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +44 -2
- package/src/__tests__/skin-motion-regression.unit.test.ts +33 -0
- package/src/__tests__/skinned-shadow-caster.test.ts +30 -2
- package/src/__tests__/surface-adjacent-regressions.integration.test.ts +60 -0
- package/src/__tests__/surface-standard-pipeline.browser.test.ts +20 -0
- package/src/__tests__/surface-standard-pipeline.dawn.test.ts +20 -0
- package/src/__tests__/surface-standard-pipeline.fixture.ts +132 -0
- package/src/__tests__/surface-variant-provenance.integration.test.ts +33 -0
- package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +1 -1
- package/src/assembly/factory.ts +2 -3
- package/src/assembly/material/__tests__/standard-layer-plan-projection.integration.test.ts +18 -0
- package/src/assembly/material/__tests__/surface-pass-projection.integration.test.ts +62 -0
- package/src/assembly/material/assembly.ts +8 -40
- package/src/assembly/material/standard-layer-projection.ts +3 -1
- package/src/assembly/material/surface-projection.ts +91 -0
- package/src/assembly/material-shader-policy.ts +88 -10
- package/src/assembly/recovery/renderer-recover.ts +1 -1
- package/src/assembly/webgpu-ready.ts +3 -16
- package/src/assembly/webgpu-renderer.ts +106 -166
- package/src/assembly/webgpu-vertex-layouts.ts +132 -0
- package/src/assets/asset-decoders.ts +27 -4
- package/src/components/instances.ts +14 -197
- package/src/extract/contracts.ts +6 -6
- package/src/gpu-driven/production-raster.ts +34 -1
- package/src/gpu-scene.ts +84 -29
- package/src/index.ts +1 -0
- package/src/instance-buffer-cache.ts +14 -1
- package/src/instances.ts +288 -0
- package/src/materials.ts +109 -14
- package/src/pipeline-builder.ts +14 -10
- package/src/pipeline-spec-types.ts +2 -0
- package/src/pipeline-spec.ts +13 -17
- package/src/record/frame-snapshot.ts +19 -2
- package/src/record/frame.ts +38 -3
- package/src/record/main-pass-geometry.ts +837 -337
- package/src/record/main-pass-material.ts +87 -2
- package/src/record/main-pass-sprite-draws.ts +66 -169
- package/src/record/main-pass.ts +24 -246
- package/src/record/material-uniforms.ts +186 -0
- package/src/record/render-context.ts +8 -0
- package/src/record/shadow-pass.ts +120 -115
- package/src/recovery/render-system-candidate.ts +46 -5
- package/src/render-contract.ts +4 -0
- package/src/render-system-extract-tail.ts +153 -57
- package/src/render-system-extract.ts +110 -73
- package/src/render-system.ts +59 -5
- package/src/scene/render-scene-types.ts +2 -0
- package/src/scene/render-scene.ts +579 -36
- package/src/systems/skin-palette-allocator.ts +129 -7
- package/src/transmission/__tests__/surface-compatibility.integration.test.ts +32 -0
- package/dist/assembly/material/pipeline-projection.d.ts +0 -30
- package/dist/assembly/material/pipeline-projection.d.ts.map +0 -1
- package/dist/chunk-KZFTK6DO.mjs.map +0 -1
- package/dist/chunk-Q752SHI5.mjs.map +0 -1
- package/dist/chunk-YKB5DQV7.mjs.map +0 -1
- package/dist/chunk-YRUYGA4A.mjs.map +0 -1
- package/dist/chunk-Z3YYH2MB.mjs.map +0 -1
- package/src/assembly/material/pipeline-projection.ts +0 -63
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { getOrCreateIblCache } from '../ibl/IblPipelineCache';
|
|
2
|
+
import type { _InternalRenderPipelineContext } from './render-context';
|
|
1
3
|
// @forgeax/engine-runtime - RenderSystem record stage: main-pass-material.
|
|
2
4
|
// Extracted from render-system-record.ts (feat-20260704 M3/w17, pure move).
|
|
3
5
|
|
|
@@ -976,6 +978,7 @@ export function buildPerSubmeshMaterialBg(
|
|
|
976
978
|
submeshMaterial: MaterialSnapshot,
|
|
977
979
|
entityKey: number,
|
|
978
980
|
materialWorld: World = deps.world,
|
|
981
|
+
materialShaderId: string | undefined = submeshMaterial.materialShaderId,
|
|
979
982
|
): BindGroup {
|
|
980
983
|
const {
|
|
981
984
|
runtime,
|
|
@@ -996,9 +999,11 @@ export function buildPerSubmeshMaterialBg(
|
|
|
996
999
|
resolveReflectionProbeResources?.(materialWorld, entityKey) ?? skylightResources;
|
|
997
1000
|
const smMaterialHandle = submeshMaterial.materialHandle;
|
|
998
1001
|
const materialCacheKey =
|
|
999
|
-
smMaterialHandle === undefined
|
|
1002
|
+
smMaterialHandle === undefined
|
|
1003
|
+
? undefined
|
|
1004
|
+
: `${materialWorld.identity}:${smMaterialHandle}:${materialShaderId ?? ''}`;
|
|
1000
1005
|
const smHasVideoFields = (submeshMaterial.videoTextureFields?.size ?? 0) > 0;
|
|
1001
|
-
const smShaderId =
|
|
1006
|
+
const smShaderId = materialShaderId;
|
|
1002
1007
|
// The cooked shader schema owns the resource ABI. A material snapshot may
|
|
1003
1008
|
// carry only the authored values (for example a clearcoat-only root omits
|
|
1004
1009
|
// the standard emissive/occlusion maps), while the Standard template still
|
|
@@ -1293,3 +1298,83 @@ export function buildPerSubmeshMaterialBg(
|
|
|
1293
1298
|
}
|
|
1294
1299
|
return smBindGroup;
|
|
1295
1300
|
}
|
|
1301
|
+
|
|
1302
|
+
const ZERO_SKYLIGHT_PAYLOAD = new Float32Array([0, 0, 0, 0, 0, 0, 0, 1]);
|
|
1303
|
+
|
|
1304
|
+
/** The same scene resources back material bindings in depth and color passes. */
|
|
1305
|
+
export function prepareMaterialSkylight(c: _InternalRenderPipelineContext) {
|
|
1306
|
+
const { runtime, pipelineState, skylight, skylightCount } = c;
|
|
1307
|
+
const skylightFallback = pipelineState.skylightFallback;
|
|
1308
|
+
if (skylightFallback === null) {
|
|
1309
|
+
throw new RhiError({
|
|
1310
|
+
code: 'webgpu-runtime-error',
|
|
1311
|
+
expected: 'pipelineState.skylightFallback != null when PBR pipeline is active',
|
|
1312
|
+
hint: 'createRenderer must allocate skylightFallback alongside the PBR pipeline (D-5 round-4)',
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
// feat-20260520-skylight-ibl-cubemap M4 round-4 / t60 (D-5 round-4):
|
|
1316
|
+
// select active vs fallback Skylight resources by `skylightCount` from
|
|
1317
|
+
// the extract stage. Active path reaches into the per-device
|
|
1318
|
+
// `IblPipelineCache` slots (irradianceView / prefilterView / brdfLutView)
|
|
1319
|
+
// populated by the internal equirect-to-cubemap projection; fallback uses the
|
|
1320
|
+
// 1x1-zero identity bundle that converges ambient to 0 (D-4 physical
|
|
1321
|
+
// convergence -- no `if (hasSkylight)` shader branch).
|
|
1322
|
+
// The samplers are reused from `skylightFallback.sampler` for both
|
|
1323
|
+
// paths (linear / clamp-to-edge is correct for IBL cube + 2D LUT
|
|
1324
|
+
// sampling either way). The intensity uniform is rewritten per-frame
|
|
1325
|
+
// when active so `sampleIblSpecular * intensity` carries the user's
|
|
1326
|
+
// Skylight.intensity value; fallback keeps intensity=0 (createSkylightFallback
|
|
1327
|
+
// seed) so ambient = 0 even when the same buffer is shared.
|
|
1328
|
+
let activeViews: { irr: TextureView; pref: TextureView; brdf: TextureView } | undefined;
|
|
1329
|
+
// Per-frame Skylight uniform: std140 32 B = [intensity, colorR, colorG,
|
|
1330
|
+
// colorB, rotation quaternion]. Default to all-zero so a transition from "has Skylight" ->
|
|
1331
|
+
// "no Skylight" does not leak the prior frame's ambient (intensity 0
|
|
1332
|
+
// muzzles everything, including the white fallback irradiance cube).
|
|
1333
|
+
runtime.device.queue.writeBuffer(skylightFallback.intensityBuffer, 0, ZERO_SKYLIGHT_PAYLOAD);
|
|
1334
|
+
if (skylight !== undefined && skylightCount >= 1) {
|
|
1335
|
+
// A Skylight exists. Write its intensity + color regardless of whether
|
|
1336
|
+
// a cubemap is bound: with a cubemap the IBL views below light the
|
|
1337
|
+
// ambient; WITHOUT one, the white fallback irradiance cube + this color
|
|
1338
|
+
// give an instant solid-color ambient (downstream integration #4) with
|
|
1339
|
+
// no async precompute. The white fallback only contributes when a
|
|
1340
|
+
// Skylight is present because the zero-payload above sets intensity 0
|
|
1341
|
+
// when no Skylight exists.
|
|
1342
|
+
const [cr, cg, cb] = skylight.color;
|
|
1343
|
+
const [qx, qy, qz, qw] = skylight.rotation;
|
|
1344
|
+
const uniformPayload = new Float32Array([skylight.intensity, cr, cg, cb, qx, qy, qz, qw]);
|
|
1345
|
+
runtime.device.queue.writeBuffer(skylightFallback.intensityBuffer, 0, uniformPayload);
|
|
1346
|
+
const cache = getOrCreateIblCache(runtime.deviceScope);
|
|
1347
|
+
if (
|
|
1348
|
+
cache.irradianceView !== undefined &&
|
|
1349
|
+
cache.prefilterView !== undefined &&
|
|
1350
|
+
cache.brdfLutView !== undefined
|
|
1351
|
+
) {
|
|
1352
|
+
activeViews = {
|
|
1353
|
+
irr: cache.irradianceView,
|
|
1354
|
+
pref: cache.prefilterView,
|
|
1355
|
+
brdf: cache.brdfLutView,
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
const skylightResources =
|
|
1360
|
+
activeViews !== undefined
|
|
1361
|
+
? {
|
|
1362
|
+
irradianceView: activeViews.irr,
|
|
1363
|
+
irradianceSampler: skylightFallback.sampler,
|
|
1364
|
+
prefilterView: activeViews.pref,
|
|
1365
|
+
prefilterSampler: skylightFallback.sampler,
|
|
1366
|
+
brdfLutView: activeViews.brdf,
|
|
1367
|
+
brdfLutSampler: skylightFallback.sampler,
|
|
1368
|
+
intensityBuffer: skylightFallback.intensityBuffer,
|
|
1369
|
+
}
|
|
1370
|
+
: {
|
|
1371
|
+
irradianceView: skylightFallback.irradianceView,
|
|
1372
|
+
irradianceSampler: skylightFallback.sampler,
|
|
1373
|
+
prefilterView: skylightFallback.prefilterView,
|
|
1374
|
+
prefilterSampler: skylightFallback.sampler,
|
|
1375
|
+
brdfLutView: skylightFallback.brdfLutView,
|
|
1376
|
+
brdfLutSampler: skylightFallback.sampler,
|
|
1377
|
+
intensityBuffer: skylightFallback.intensityBuffer,
|
|
1378
|
+
};
|
|
1379
|
+
return { skylightResources, activeViews };
|
|
1380
|
+
}
|
|
@@ -25,24 +25,19 @@ import {
|
|
|
25
25
|
} from '../ibl/skylight-bind-group';
|
|
26
26
|
import type { InstanceBufferCacheEntry } from '../instance-buffer-cache';
|
|
27
27
|
import { SPRITE_PREMULTIPLIED_ALPHA_BLEND } from '../materials';
|
|
28
|
-
import {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
variantSetFromVertexLayoutProjection,
|
|
36
|
-
} from '../pipeline-spec';
|
|
37
|
-
import type { MaterialSnapshot, SpriteInstancesSnapshot } from '../render-system-extract';
|
|
28
|
+
import { SPRITE_PASS_PER_INSTANCE_REGION_VARIANT_SET } from '../pbr-pipeline';
|
|
29
|
+
import { buildBeginRenderPassDescriptor, standardTopologyVariantSet } from '../pipeline-spec';
|
|
30
|
+
import type {
|
|
31
|
+
DispatchEntry,
|
|
32
|
+
MaterialSnapshot,
|
|
33
|
+
SpriteInstancesSnapshot,
|
|
34
|
+
} from '../render-system-extract';
|
|
38
35
|
import { worldEntityKey } from './frame-snapshot';
|
|
39
|
-
import {
|
|
36
|
+
import { recordGeometryDraws, resolveGeometryInstancesBindGroup } from './main-pass-geometry';
|
|
40
37
|
import {
|
|
41
38
|
BUILTIN_USER_REGION_TEXTURE_FIELDS,
|
|
42
39
|
defaultViewForUserRegionField,
|
|
43
|
-
entityHasTransparentSubmesh,
|
|
44
40
|
residentTextureView,
|
|
45
|
-
selectMaterialGroup2,
|
|
46
41
|
} from './main-pass-material';
|
|
47
42
|
import {
|
|
48
43
|
extractEntryResourceHandle,
|
|
@@ -164,8 +159,14 @@ export function computeSplitLdrSprite(
|
|
|
164
159
|
| undefined
|
|
165
160
|
)[],
|
|
166
161
|
tonemapActive: boolean,
|
|
162
|
+
dispatch?: readonly DispatchEntry[],
|
|
167
163
|
): boolean {
|
|
168
164
|
if (tonemapActive) return false;
|
|
165
|
+
if (dispatch !== undefined && dispatch.length > 0) {
|
|
166
|
+
return dispatch.some(
|
|
167
|
+
(pass) => pass.tags.LightMode === 'Forward' && pass.renderState?.blend !== undefined,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
169
170
|
for (const entry of validatedOrdered) {
|
|
170
171
|
if (entry === undefined) continue;
|
|
171
172
|
const materials = entry.source.materials;
|
|
@@ -201,9 +202,11 @@ export function recordSpritePass(
|
|
|
201
202
|
submeshMaterial: MaterialSnapshot,
|
|
202
203
|
entityKey: number,
|
|
203
204
|
materialWorld: World,
|
|
205
|
+
materialShaderId?: string,
|
|
204
206
|
) => BindGroup,
|
|
205
207
|
skylightResources: SkylightBindGroupResources,
|
|
206
208
|
graphPass?: RhiRenderPassEncoder,
|
|
209
|
+
selectedDispatch?: readonly DispatchEntry[],
|
|
207
210
|
): boolean {
|
|
208
211
|
const {
|
|
209
212
|
runtime,
|
|
@@ -409,22 +412,7 @@ export function recordSpritePass(
|
|
|
409
412
|
reportSpriteUnavailable,
|
|
410
413
|
);
|
|
411
414
|
|
|
412
|
-
//
|
|
413
|
-
// draws in the LDR blend sub-pass. The sprite loop above handles only true
|
|
414
|
-
// sprite / sprite-lit entities (whole-mesh sprite PSO). EVERY non-sprite
|
|
415
|
-
// transparent material — built-in PBR, single- or multi-submesh, incl.
|
|
416
|
-
// glTF alphaMode=BLEND (the crosswalk decal submesh AND the 4.3-blending
|
|
417
|
-
// window quad) — is drawn here with its real shader + per-submesh
|
|
418
|
-
// geometry, reusing the geometry pass's PBR machinery but rendering into
|
|
419
|
-
// the resolved transparent attachment (colorFormatOverride =
|
|
420
|
-
// transparentPassColorFormat(c)).
|
|
421
|
-
// Opaque submeshes of a mixed mesh were already drawn in the geometry pass
|
|
422
|
-
// (per-submesh skip there).
|
|
423
|
-
//
|
|
424
|
-
// Non-skinned, non-instanced (glTF static meshes): group 2 = the shared
|
|
425
|
-
// frame meshBindGroup with the per-entity dynamic offset; group 3 = the
|
|
426
|
-
// identity instance BG (drawIndexed instanceCount=1). Skinned / instanced
|
|
427
|
-
// transparent submeshes are OOS here (no such content) and fall through.
|
|
415
|
+
// Keep program, geometry, bindings and dynamic offsets on the common draw path.
|
|
428
416
|
recordSpriteTransparentPbrDraws(
|
|
429
417
|
c,
|
|
430
418
|
spritePass,
|
|
@@ -433,6 +421,7 @@ export function recordSpritePass(
|
|
|
433
421
|
sampleCount,
|
|
434
422
|
resolveMaterialBindGroup,
|
|
435
423
|
meshGroup2,
|
|
424
|
+
selectedDispatch,
|
|
436
425
|
);
|
|
437
426
|
|
|
438
427
|
if (graphPass === undefined) spritePass.end();
|
|
@@ -440,159 +429,67 @@ export function recordSpritePass(
|
|
|
440
429
|
return geometryPassEnded;
|
|
441
430
|
}
|
|
442
431
|
|
|
443
|
-
/**
|
|
444
|
-
* feat-20260704 M3/w19: generic per-submesh transparent PBR draws in the LDR
|
|
445
|
-
* blend sub-pass, extracted verbatim from the sprite split pass. Every
|
|
446
|
-
* non-sprite transparent material (built-in PBR, single- or multi-submesh, incl.
|
|
447
|
-
* glTF alphaMode=BLEND) draws here with its real shader + per-submesh geometry,
|
|
448
|
-
* reusing the geometry pass's PBR machinery but rendering into the resolved
|
|
449
|
-
* transparent attachment (colorFormatOverride = transparentPassColorFormat(c)). Opaque submeshes of a
|
|
450
|
-
* mixed mesh were already drawn in the geometry pass (per-submesh skip there).
|
|
451
|
-
* Sprites are handled by the sprite loop; skinned / instanced transparent
|
|
452
|
-
* submeshes are OOS here and fall through.
|
|
453
|
-
*
|
|
454
|
-
* @internal
|
|
455
|
-
*/
|
|
432
|
+
/** Non-sprite transparent Passes use the same geometry and binding owner as opaque draws. */
|
|
456
433
|
function recordSpriteTransparentPbrDraws(
|
|
457
434
|
c: _InternalRenderPipelineContext,
|
|
458
|
-
|
|
435
|
+
pass: RhiRenderPassEncoder,
|
|
459
436
|
matchedIndices: Set<number> | null,
|
|
460
437
|
materialSlotIndices: readonly (readonly number[])[],
|
|
461
438
|
sampleCount: number,
|
|
462
439
|
resolveMaterialBindGroup: (
|
|
463
|
-
|
|
464
|
-
|
|
440
|
+
slot: number,
|
|
441
|
+
material: MaterialSnapshot,
|
|
465
442
|
entityKey: number,
|
|
466
|
-
|
|
443
|
+
world: World,
|
|
444
|
+
shader?: string,
|
|
467
445
|
) => BindGroup,
|
|
468
446
|
meshGroup2: BindGroup | null,
|
|
447
|
+
selectedDispatch?: readonly DispatchEntry[],
|
|
469
448
|
): void {
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
// Only entities that carry at least one transparent submesh reach a draw
|
|
485
|
-
// (single-submesh transparent PBR and mixed opaque+transparent meshes).
|
|
486
|
-
if (!entityHasTransparentSubmesh(entry.source)) continue;
|
|
487
|
-
// Skinned / instanced transparent submeshes are not supported in this
|
|
488
|
-
// sub-pass path (no such content today); skip to avoid mis-binding.
|
|
489
|
-
if (entry.source.skin !== undefined || entry.source.instances !== undefined) continue;
|
|
490
|
-
|
|
491
|
-
const matsForRebind = entry.source.materials;
|
|
492
|
-
if (entry.mesh.vertexBuffer !== lastPbrSubVertexBuffer) {
|
|
493
|
-
spritePass.setVertexBuffer(0, entry.mesh.vertexBuffer.handle);
|
|
494
|
-
lastPbrSubVertexBuffer = entry.mesh.vertexBuffer;
|
|
495
|
-
}
|
|
496
|
-
if (
|
|
497
|
-
entry.mesh.indexed &&
|
|
498
|
-
entry.mesh.indexBuffer !== null &&
|
|
499
|
-
entry.mesh.indexBuffer !== lastPbrSubIndexBuffer
|
|
500
|
-
) {
|
|
501
|
-
spritePass.setIndexBuffer(entry.mesh.indexBuffer.handle, entry.mesh.indexFormat);
|
|
502
|
-
lastPbrSubIndexBuffer = entry.mesh.indexBuffer;
|
|
449
|
+
const isSprite = (shader: string | undefined) =>
|
|
450
|
+
shader === 'forgeax::sprite' || shader === 'forgeax::sprite-lit';
|
|
451
|
+
const passes = selectedDispatch?.filter(
|
|
452
|
+
(entry) =>
|
|
453
|
+
entry.renderState?.blend !== undefined &&
|
|
454
|
+
!isSprite(entry.materialShaderId) &&
|
|
455
|
+
(matchedIndices === null || matchedIndices.has(entry.renderableIndex)),
|
|
456
|
+
);
|
|
457
|
+
const matched = new Map<number, Set<number>>();
|
|
458
|
+
const add = (index: number, handle: number) => {
|
|
459
|
+
let handles = matched.get(index);
|
|
460
|
+
if (handles === undefined) {
|
|
461
|
+
handles = new Set();
|
|
462
|
+
matched.set(index, handles);
|
|
503
463
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
if (
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
const probeBlendAvailable =
|
|
515
|
-
runtime.device.caps.storageBuffer &&
|
|
516
|
-
entry.source.probeBlendRecord !== undefined &&
|
|
517
|
-
isStandardPbrMaterialShader(smShaderId);
|
|
518
|
-
const probeBinding = probeBlendAvailable
|
|
519
|
-
? resolveProbeBlendBuffer(
|
|
520
|
-
c,
|
|
521
|
-
entry.source.probeBlendRecord,
|
|
522
|
-
worldEntityKey(entry.source.worldId, entry.source.entityKey),
|
|
523
|
-
)
|
|
524
|
-
: undefined;
|
|
525
|
-
const identityInstBg = resolveGeometryInstancesBindGroup(
|
|
526
|
-
c,
|
|
527
|
-
pipelineState.identityInstanceBuffer,
|
|
528
|
-
probeBinding?.buffer,
|
|
529
|
-
);
|
|
530
|
-
if (probeBinding === undefined) spritePass.setBindGroup(3, identityInstBg);
|
|
531
|
-
else spritePass.setBindGroup(3, identityInstBg, [probeBinding.offset]);
|
|
532
|
-
const subVariantSet = standardTopologyVariantSet(
|
|
533
|
-
c.standardLighting,
|
|
534
|
-
runtime.device.caps.storageBuffer,
|
|
535
|
-
entry.mesh.layoutProjection.attributes.some((attribute) => attribute.key === 'color'),
|
|
536
|
-
probeBlendAvailable,
|
|
537
|
-
);
|
|
538
|
-
const projectionVariantSetResult = variantSetFromVertexLayoutProjection(
|
|
539
|
-
entry.mesh.layoutProjection,
|
|
540
|
-
subVariantSet,
|
|
541
|
-
);
|
|
542
|
-
if (!projectionVariantSetResult.ok) {
|
|
543
|
-
runtime.errorRegistry.fire(projectionVariantSetResult.error);
|
|
544
|
-
continue;
|
|
545
|
-
}
|
|
546
|
-
const projectionVariantSet = projectionVariantSetResult.value;
|
|
547
|
-
const subPipelineEntry =
|
|
548
|
-
c.runtime.getMaterialShaderPipelineEntry?.(
|
|
549
|
-
smShaderId,
|
|
550
|
-
isHdrTarget,
|
|
551
|
-
submeshMaterial.renderState,
|
|
552
|
-
sm.topology,
|
|
553
|
-
entry.mesh.indexFormat,
|
|
554
|
-
projectionVariantSet,
|
|
555
|
-
'forward',
|
|
556
|
-
undefined,
|
|
557
|
-
sampleCount,
|
|
558
|
-
// Non-sRGB blend view (same override the sprite path uses).
|
|
559
|
-
transparentPassColorFormat(c) as GPUTextureFormat,
|
|
560
|
-
undefined,
|
|
561
|
-
undefined,
|
|
562
|
-
undefined,
|
|
563
|
-
entry.mesh.layoutProjection,
|
|
564
|
-
) ?? null;
|
|
565
|
-
if (subPipelineEntry === null) continue;
|
|
566
|
-
const submeshContract = subPipelineEntry.group2Contract;
|
|
567
|
-
const submeshGroup2 = selectMaterialGroup2(meshGroup2, c.meshBindGroup, submeshContract);
|
|
568
|
-
if (submeshGroup2 === null) continue;
|
|
569
|
-
spritePass.setBindGroup(2, submeshGroup2, [i * MESH_PER_ENTITY_STRIDE]);
|
|
570
|
-
|
|
571
|
-
// Bind the per-submesh material BG first (mirrors the geometry pass
|
|
572
|
-
// order), then resolve the PSO; a first-frame async-compile miss skips
|
|
573
|
-
// only the draw, not the BG (one transient frame, PSO flows in next).
|
|
574
|
-
const materialSlot = materialSlotIndices[i]?.[matSlotIdx] ?? materialSlotIndices[i]?.[0] ?? 0;
|
|
575
|
-
const subBg = resolveMaterialBindGroup(
|
|
576
|
-
materialSlot,
|
|
577
|
-
submeshMaterial,
|
|
578
|
-
entry.source.entityKey,
|
|
579
|
-
entry.world ?? c.world,
|
|
580
|
-
);
|
|
581
|
-
spritePass.setBindGroup(1, subBg, [materialSlot * MATERIAL_PER_ENTITY_STRIDE]);
|
|
582
|
-
|
|
583
|
-
const subPipeline = subPipelineEntry.pipeline;
|
|
584
|
-
|
|
585
|
-
if (lastPbrSubPipelineHandle !== subPipeline) {
|
|
586
|
-
spritePass.setPipeline(subPipeline);
|
|
587
|
-
lastPbrSubPipelineHandle = subPipeline;
|
|
588
|
-
}
|
|
589
|
-
if (entry.mesh.indexed) {
|
|
590
|
-
spritePass.drawIndexed(sm.indexCount, 1, sm.indexOffset, 0, 0);
|
|
591
|
-
} else {
|
|
592
|
-
spritePass.draw(sm.vertexCount, 1, 0, 0);
|
|
464
|
+
handles.add(handle);
|
|
465
|
+
};
|
|
466
|
+
if (passes !== undefined) {
|
|
467
|
+
for (const entry of passes) add(entry.renderableIndex, entry.materialHandle);
|
|
468
|
+
} else {
|
|
469
|
+
for (const entry of c.validatedOrdered) {
|
|
470
|
+
if (matchedIndices !== null && !matchedIndices.has(entry.renderableIndex)) continue;
|
|
471
|
+
if (isSprite(entry.source.material.materialShaderId)) continue;
|
|
472
|
+
for (const material of entry.source.materials) {
|
|
473
|
+
if (material.transparent === true) add(entry.renderableIndex, material.materialHandle ?? 0);
|
|
593
474
|
}
|
|
594
475
|
}
|
|
595
476
|
}
|
|
477
|
+
recordGeometryDraws(
|
|
478
|
+
{
|
|
479
|
+
...c,
|
|
480
|
+
splitLdrSprite: false,
|
|
481
|
+
transparentColorFormat: transparentPassColorFormat(c) as GPUTextureFormat,
|
|
482
|
+
},
|
|
483
|
+
pass,
|
|
484
|
+
matched,
|
|
485
|
+
materialSlotIndices,
|
|
486
|
+
sampleCount,
|
|
487
|
+
meshGroup2,
|
|
488
|
+
c.meshBindGroup,
|
|
489
|
+
resolveMaterialBindGroup,
|
|
490
|
+
'forward',
|
|
491
|
+
passes,
|
|
492
|
+
);
|
|
596
493
|
}
|
|
597
494
|
|
|
598
495
|
/**
|
|
@@ -856,7 +753,7 @@ function recordSpriteEntityDraws(
|
|
|
856
753
|
new RhiError({
|
|
857
754
|
code: 'limit-exceeded',
|
|
858
755
|
expected: `requestedBytes (${requestedBytes}) <= maxStorageBufferBindingSize (${cap})`,
|
|
859
|
-
hint: 'reduce
|
|
756
|
+
hint: 'reduce the sprite batch to fit within device.limits.maxStorageBufferBindingSize or use a storage-capable backend',
|
|
860
757
|
detail: {
|
|
861
758
|
maxStorageBufferBindingSize: cap,
|
|
862
759
|
requestedBytes,
|