@flighthq/scene3d-wgpu 0.2.1-next.840.857425c
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/dist/anisotropyPbrWgpuMeshMaterialRenderer.d.ts +4 -0
- package/dist/anisotropyPbrWgpuMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/anisotropyPbrWgpuMeshMaterialRenderer.js +52 -0
- package/dist/anisotropyPbrWgpuMeshMaterialRenderer.js.map +1 -0
- package/dist/blinnPhongWgpuMeshMaterialRenderer.d.ts +4 -0
- package/dist/blinnPhongWgpuMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/blinnPhongWgpuMeshMaterialRenderer.js +66 -0
- package/dist/blinnPhongWgpuMeshMaterialRenderer.js.map +1 -0
- package/dist/clearcoatPbrWgpuMeshMaterialRenderer.d.ts +4 -0
- package/dist/clearcoatPbrWgpuMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/clearcoatPbrWgpuMeshMaterialRenderer.js +52 -0
- package/dist/clearcoatPbrWgpuMeshMaterialRenderer.js.map +1 -0
- package/dist/customShaderWgpuMeshMaterialRenderer.d.ts +7 -0
- package/dist/customShaderWgpuMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/customShaderWgpuMeshMaterialRenderer.js +225 -0
- package/dist/customShaderWgpuMeshMaterialRenderer.js.map +1 -0
- package/dist/depthWgpuMeshMaterialRenderer.d.ts +4 -0
- package/dist/depthWgpuMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/depthWgpuMeshMaterialRenderer.js +42 -0
- package/dist/depthWgpuMeshMaterialRenderer.js.map +1 -0
- package/dist/drawWgpuScene3D.d.ts +4 -0
- package/dist/drawWgpuScene3D.d.ts.map +1 -0
- package/dist/drawWgpuScene3D.js +171 -0
- package/dist/drawWgpuScene3D.js.map +1 -0
- package/dist/emissiveWgpuMeshMaterialRenderer.d.ts +4 -0
- package/dist/emissiveWgpuMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/emissiveWgpuMeshMaterialRenderer.js +51 -0
- package/dist/emissiveWgpuMeshMaterialRenderer.js.map +1 -0
- package/dist/enableWgpuScene3DCustomShaderGuards.d.ts +5 -0
- package/dist/enableWgpuScene3DCustomShaderGuards.d.ts.map +1 -0
- package/dist/enableWgpuScene3DCustomShaderGuards.js +56 -0
- package/dist/enableWgpuScene3DCustomShaderGuards.js.map +1 -0
- package/dist/enableWgpuScene3DForwardLightSelectionGuards.d.ts +4 -0
- package/dist/enableWgpuScene3DForwardLightSelectionGuards.d.ts.map +1 -0
- package/dist/enableWgpuScene3DForwardLightSelectionGuards.js +17 -0
- package/dist/enableWgpuScene3DForwardLightSelectionGuards.js.map +1 -0
- package/dist/explainWgpuScene3DForwardLightSelection.d.ts +3 -0
- package/dist/explainWgpuScene3DForwardLightSelection.d.ts.map +1 -0
- package/dist/explainWgpuScene3DForwardLightSelection.js +18 -0
- package/dist/explainWgpuScene3DForwardLightSelection.js.map +1 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -0
- package/dist/iridescencePbrWgpuMeshMaterialRenderer.d.ts +4 -0
- package/dist/iridescencePbrWgpuMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/iridescencePbrWgpuMeshMaterialRenderer.js +60 -0
- package/dist/iridescencePbrWgpuMeshMaterialRenderer.js.map +1 -0
- package/dist/lambertWgpuMeshMaterialRenderer.d.ts +4 -0
- package/dist/lambertWgpuMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/lambertWgpuMeshMaterialRenderer.js +61 -0
- package/dist/lambertWgpuMeshMaterialRenderer.js.map +1 -0
- package/dist/matcapWgpuMeshMaterialRenderer.d.ts +4 -0
- package/dist/matcapWgpuMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/matcapWgpuMeshMaterialRenderer.js +54 -0
- package/dist/matcapWgpuMeshMaterialRenderer.js.map +1 -0
- package/dist/normalWgpuMeshMaterialRenderer.d.ts +4 -0
- package/dist/normalWgpuMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/normalWgpuMeshMaterialRenderer.js +47 -0
- package/dist/normalWgpuMeshMaterialRenderer.js.map +1 -0
- package/dist/phongWgpuMeshMaterialRenderer.d.ts +4 -0
- package/dist/phongWgpuMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/phongWgpuMeshMaterialRenderer.js +63 -0
- package/dist/phongWgpuMeshMaterialRenderer.js.map +1 -0
- package/dist/prepareWgpuScene3DForwardLights.d.ts +3 -0
- package/dist/prepareWgpuScene3DForwardLights.d.ts.map +1 -0
- package/dist/prepareWgpuScene3DForwardLights.js +110 -0
- package/dist/prepareWgpuScene3DForwardLights.js.map +1 -0
- package/dist/registerStandardPbrWgpuMaterial.d.ts +3 -0
- package/dist/registerStandardPbrWgpuMaterial.d.ts.map +1 -0
- package/dist/registerStandardPbrWgpuMaterial.js +11 -0
- package/dist/registerStandardPbrWgpuMaterial.js.map +1 -0
- package/dist/shadedWgpuMeshMaterialRenderer.d.ts +11 -0
- package/dist/shadedWgpuMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/shadedWgpuMeshMaterialRenderer.js +47 -0
- package/dist/shadedWgpuMeshMaterialRenderer.js.map +1 -0
- package/dist/sheenPbrWgpuMeshMaterialRenderer.d.ts +4 -0
- package/dist/sheenPbrWgpuMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/sheenPbrWgpuMeshMaterialRenderer.js +58 -0
- package/dist/sheenPbrWgpuMeshMaterialRenderer.js.map +1 -0
- package/dist/specularGlossinessPbrWgpuMeshMaterialRenderer.d.ts +4 -0
- package/dist/specularGlossinessPbrWgpuMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/specularGlossinessPbrWgpuMeshMaterialRenderer.js +126 -0
- package/dist/specularGlossinessPbrWgpuMeshMaterialRenderer.js.map +1 -0
- package/dist/specularPbrWgpuMeshMaterialRenderer.d.ts +4 -0
- package/dist/specularPbrWgpuMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/specularPbrWgpuMeshMaterialRenderer.js +65 -0
- package/dist/specularPbrWgpuMeshMaterialRenderer.js.map +1 -0
- package/dist/standardPbrWgpuMeshMaterialRenderer.d.ts +9 -0
- package/dist/standardPbrWgpuMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/standardPbrWgpuMeshMaterialRenderer.js +174 -0
- package/dist/standardPbrWgpuMeshMaterialRenderer.js.map +1 -0
- package/dist/subsurfacePbrWgpuMeshMaterialRenderer.d.ts +4 -0
- package/dist/subsurfacePbrWgpuMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/subsurfacePbrWgpuMeshMaterialRenderer.js +68 -0
- package/dist/subsurfacePbrWgpuMeshMaterialRenderer.js.map +1 -0
- package/dist/toonWgpuMeshMaterialRenderer.d.ts +4 -0
- package/dist/toonWgpuMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/toonWgpuMeshMaterialRenderer.js +71 -0
- package/dist/toonWgpuMeshMaterialRenderer.js.map +1 -0
- package/dist/transmissionVolumePbrWgpuMeshMaterialRenderer.d.ts +4 -0
- package/dist/transmissionVolumePbrWgpuMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/transmissionVolumePbrWgpuMeshMaterialRenderer.js +74 -0
- package/dist/transmissionVolumePbrWgpuMeshMaterialRenderer.js.map +1 -0
- package/dist/unlitWgpuMeshMaterialRenderer.d.ts +4 -0
- package/dist/unlitWgpuMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/unlitWgpuMeshMaterialRenderer.js +57 -0
- package/dist/unlitWgpuMeshMaterialRenderer.js.map +1 -0
- package/dist/vertexColorWgpuMeshMaterialRenderer.d.ts +4 -0
- package/dist/vertexColorWgpuMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/vertexColorWgpuMeshMaterialRenderer.js +55 -0
- package/dist/vertexColorWgpuMeshMaterialRenderer.js.map +1 -0
- package/dist/wgpuClassicPrelude.d.ts +8 -0
- package/dist/wgpuClassicPrelude.d.ts.map +1 -0
- package/dist/wgpuClassicPrelude.js +319 -0
- package/dist/wgpuClassicPrelude.js.map +1 -0
- package/dist/wgpuCustomMaterialAbi.d.ts +3 -0
- package/dist/wgpuCustomMaterialAbi.d.ts.map +1 -0
- package/dist/wgpuCustomMaterialAbi.js +5 -0
- package/dist/wgpuCustomMaterialAbi.js.map +1 -0
- package/dist/wgpuDebugPrelude.d.ts +7 -0
- package/dist/wgpuDebugPrelude.d.ts.map +1 -0
- package/dist/wgpuDebugPrelude.js +133 -0
- package/dist/wgpuDebugPrelude.js.map +1 -0
- package/dist/wgpuEnvironmentCube.d.ts +4 -0
- package/dist/wgpuEnvironmentCube.d.ts.map +1 -0
- package/dist/wgpuEnvironmentCube.js +66 -0
- package/dist/wgpuEnvironmentCube.js.map +1 -0
- package/dist/wgpuEnvironmentIblBake.d.ts +4 -0
- package/dist/wgpuEnvironmentIblBake.d.ts.map +1 -0
- package/dist/wgpuEnvironmentIblBake.js +458 -0
- package/dist/wgpuEnvironmentIblBake.js.map +1 -0
- package/dist/wgpuEnvironmentSkybox.d.ts +3 -0
- package/dist/wgpuEnvironmentSkybox.d.ts.map +1 -0
- package/dist/wgpuEnvironmentSkybox.js +159 -0
- package/dist/wgpuEnvironmentSkybox.js.map +1 -0
- package/dist/wgpuMatcapPrelude.d.ts +7 -0
- package/dist/wgpuMatcapPrelude.d.ts.map +1 -0
- package/dist/wgpuMatcapPrelude.js +134 -0
- package/dist/wgpuMatcapPrelude.js.map +1 -0
- package/dist/wgpuMeshMaterialRegistry.d.ts +5 -0
- package/dist/wgpuMeshMaterialRegistry.d.ts.map +1 -0
- package/dist/wgpuMeshMaterialRegistry.js +29 -0
- package/dist/wgpuMeshMaterialRegistry.js.map +1 -0
- package/dist/wgpuMeshPipeline.d.ts +43 -0
- package/dist/wgpuMeshPipeline.d.ts.map +1 -0
- package/dist/wgpuMeshPipeline.js +1007 -0
- package/dist/wgpuMeshPipeline.js.map +1 -0
- package/dist/wgpuMeshUpload.d.ts +3 -0
- package/dist/wgpuMeshUpload.d.ts.map +1 -0
- package/dist/wgpuMeshUpload.js +58 -0
- package/dist/wgpuMeshUpload.js.map +1 -0
- package/dist/wgpuParticleEmitter3D.d.ts +4 -0
- package/dist/wgpuParticleEmitter3D.d.ts.map +1 -0
- package/dist/wgpuParticleEmitter3D.js +403 -0
- package/dist/wgpuParticleEmitter3D.js.map +1 -0
- package/dist/wgpuPbrPipelineCache.d.ts +4 -0
- package/dist/wgpuPbrPipelineCache.d.ts.map +1 -0
- package/dist/wgpuPbrPipelineCache.js +52 -0
- package/dist/wgpuPbrPipelineCache.js.map +1 -0
- package/dist/wgpuPbrPrelude.d.ts +7 -0
- package/dist/wgpuPbrPrelude.d.ts.map +1 -0
- package/dist/wgpuPbrPrelude.js +551 -0
- package/dist/wgpuPbrPrelude.js.map +1 -0
- package/dist/wgpuScene3DRuntime.d.ts +5 -0
- package/dist/wgpuScene3DRuntime.d.ts.map +1 -0
- package/dist/wgpuScene3DRuntime.js +81 -0
- package/dist/wgpuScene3DRuntime.js.map +1 -0
- package/dist/wgpuScene3DTestHelper.d.ts +14 -0
- package/dist/wgpuScene3DTestHelper.d.ts.map +1 -0
- package/dist/wgpuScene3DTestHelper.js +121 -0
- package/dist/wgpuScene3DTestHelper.js.map +1 -0
- package/dist/wgpuScene3DTime.d.ts +4 -0
- package/dist/wgpuScene3DTime.d.ts.map +1 -0
- package/dist/wgpuScene3DTime.js +11 -0
- package/dist/wgpuScene3DTime.js.map +1 -0
- package/dist/wgpuShadedModifierSnippet.d.ts +4 -0
- package/dist/wgpuShadedModifierSnippet.d.ts.map +1 -0
- package/dist/wgpuShadedModifierSnippet.js +18 -0
- package/dist/wgpuShadedModifierSnippet.js.map +1 -0
- package/dist/wgpuShadedPrelude.d.ts +16 -0
- package/dist/wgpuShadedPrelude.d.ts.map +1 -0
- package/dist/wgpuShadedPrelude.js +625 -0
- package/dist/wgpuShadedPrelude.js.map +1 -0
- package/dist/wgpuShadowMap.d.ts +4 -0
- package/dist/wgpuShadowMap.d.ts.map +1 -0
- package/dist/wgpuShadowMap.js +155 -0
- package/dist/wgpuShadowMap.js.map +1 -0
- package/dist/wgpuSkinPalette.d.ts +7 -0
- package/dist/wgpuSkinPalette.d.ts.map +1 -0
- package/dist/wgpuSkinPalette.js +173 -0
- package/dist/wgpuSkinPalette.js.map +1 -0
- package/dist/wgpuToonPrelude.d.ts +8 -0
- package/dist/wgpuToonPrelude.d.ts.map +1 -0
- package/dist/wgpuToonPrelude.js +192 -0
- package/dist/wgpuToonPrelude.js.map +1 -0
- package/dist/wgpuUnlitPrelude.d.ts +9 -0
- package/dist/wgpuUnlitPrelude.d.ts.map +1 -0
- package/dist/wgpuUnlitPrelude.js +142 -0
- package/dist/wgpuUnlitPrelude.js.map +1 -0
- package/dist/wgpuWireframePrelude.d.ts +6 -0
- package/dist/wgpuWireframePrelude.d.ts.map +1 -0
- package/dist/wgpuWireframePrelude.js +77 -0
- package/dist/wgpuWireframePrelude.js.map +1 -0
- package/dist/wgpuWireframeUpload.d.ts +3 -0
- package/dist/wgpuWireframeUpload.d.ts.map +1 -0
- package/dist/wgpuWireframeUpload.js +68 -0
- package/dist/wgpuWireframeUpload.js.map +1 -0
- package/dist/wireframeWgpuMeshMaterialRenderer.d.ts +4 -0
- package/dist/wireframeWgpuMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/wireframeWgpuMeshMaterialRenderer.js +69 -0
- package/dist/wireframeWgpuMeshMaterialRenderer.js.map +1 -0
- package/package.json +57 -0
- package/src/anisotropyPbrWgpuMeshMaterialRenderer.test.ts +90 -0
- package/src/blinnPhongWgpuMeshMaterialRenderer.test.ts +82 -0
- package/src/clearcoatPbrWgpuMeshMaterialRenderer.test.ts +90 -0
- package/src/customShaderWgpuMeshMaterialRenderer.test.ts +207 -0
- package/src/depthWgpuMeshMaterialRenderer.test.ts +69 -0
- package/src/drawWgpuScene3D.test.ts +247 -0
- package/src/emissiveWgpuMeshMaterialRenderer.test.ts +64 -0
- package/src/enableWgpuScene3DCustomShaderGuards.test.ts +73 -0
- package/src/enableWgpuScene3DForwardLightSelectionGuards.test.ts +39 -0
- package/src/explainWgpuScene3DForwardLightSelection.test.ts +15 -0
- package/src/iridescencePbrWgpuMeshMaterialRenderer.test.ts +90 -0
- package/src/lambertWgpuMeshMaterialRenderer.test.ts +77 -0
- package/src/matcapWgpuMeshMaterialRenderer.test.ts +69 -0
- package/src/normalWgpuMeshMaterialRenderer.test.ts +69 -0
- package/src/phongWgpuMeshMaterialRenderer.test.ts +79 -0
- package/src/prepareWgpuScene3DForwardLights.test.ts +84 -0
- package/src/registerStandardPbrWgpuMaterial.test.ts +15 -0
- package/src/shadedWgpuMeshMaterialRenderer.test.ts +63 -0
- package/src/sheenPbrWgpuMeshMaterialRenderer.test.ts +87 -0
- package/src/specularGlossinessPbrWgpuMeshMaterialRenderer.test.ts +100 -0
- package/src/specularPbrWgpuMeshMaterialRenderer.test.ts +90 -0
- package/src/standardPbrWgpuMeshMaterialRenderer.test.ts +172 -0
- package/src/subsurfacePbrWgpuMeshMaterialRenderer.test.ts +90 -0
- package/src/toonWgpuMeshMaterialRenderer.test.ts +89 -0
- package/src/transmissionVolumePbrWgpuMeshMaterialRenderer.test.ts +97 -0
- package/src/unlitWgpuMeshMaterialRenderer.test.ts +94 -0
- package/src/vertexColorWgpuMeshMaterialRenderer.test.ts +62 -0
- package/src/wgpuClassicPrelude.test.ts +176 -0
- package/src/wgpuDebugPrelude.test.ts +79 -0
- package/src/wgpuEnvironmentCube.test.ts +86 -0
- package/src/wgpuEnvironmentIblBake.test.ts +79 -0
- package/src/wgpuEnvironmentSkybox.test.ts +56 -0
- package/src/wgpuMatcapPrelude.test.ts +70 -0
- package/src/wgpuMeshMaterialRegistry.test.ts +55 -0
- package/src/wgpuMeshPipeline.test.ts +844 -0
- package/src/wgpuMeshUpload.test.ts +177 -0
- package/src/wgpuParticleEmitter3D.test.ts +200 -0
- package/src/wgpuPbrPipelineCache.test.ts +67 -0
- package/src/wgpuPbrPrelude.test.ts +162 -0
- package/src/wgpuScene3DRuntime.test.ts +35 -0
- package/src/wgpuScene3DTime.test.ts +20 -0
- package/src/wgpuShadedModifierSnippet.test.ts +33 -0
- package/src/wgpuShadedPrelude.test.ts +394 -0
- package/src/wgpuShadowMap.test.ts +169 -0
- package/src/wgpuSkinPalette.test.ts +88 -0
- package/src/wgpuToonPrelude.test.ts +93 -0
- package/src/wgpuUnlitPrelude.test.ts +102 -0
- package/src/wgpuWireframePrelude.test.ts +54 -0
- package/src/wgpuWireframeUpload.test.ts +36 -0
- package/src/wireframeWgpuMeshMaterialRenderer.test.ts +69 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { createTexture } from '@flighthq/texture';
|
|
2
|
+
import type { ImageResource, WgpuClassicDefineKey, WgpuClassicLightingModel } from '@flighthq/types';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
bindWgpuClassicSurface,
|
|
6
|
+
buildWgpuClassicDefineKey,
|
|
7
|
+
compileWgpuClassicPipeline,
|
|
8
|
+
ensureWgpuClassicPipeline,
|
|
9
|
+
getWgpuClassicModuleSourceForKey,
|
|
10
|
+
getWgpuClassicSharedSamplerModuleSourceForKey,
|
|
11
|
+
} from './wgpuClassicPrelude';
|
|
12
|
+
import { getWgpuScene3DRuntime } from './wgpuScene3DRuntime';
|
|
13
|
+
import { makeWgpuScene3DState, makeWgpuSkinningAdapter } from './wgpuScene3DTestHelper';
|
|
14
|
+
|
|
15
|
+
function makeKey(lightingModel: WgpuClassicLightingModel): WgpuClassicDefineKey {
|
|
16
|
+
return {
|
|
17
|
+
alphaMaskEnabled: false,
|
|
18
|
+
doubleSided: false,
|
|
19
|
+
hasAlphaMap: false,
|
|
20
|
+
hasDiffuseMap: false,
|
|
21
|
+
hasNormalMap: false,
|
|
22
|
+
hasSpecularMap: false,
|
|
23
|
+
lightingModel,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe('bindWgpuClassicSurface', () => {
|
|
28
|
+
it('creates a material bind group + buffer once per key and rewrites the uniform each call', () => {
|
|
29
|
+
const { fake, state } = makeWgpuScene3DState();
|
|
30
|
+
const pipeline = ensureWgpuClassicPipeline(state, makeKey('phong'), 'bgra8unorm');
|
|
31
|
+
const key = {};
|
|
32
|
+
bindWgpuClassicSurface(state, pipeline, key, [1, 0, 0, 1], [1, 1, 1, 1], 32, 0.5, null, null, null, null);
|
|
33
|
+
bindWgpuClassicSurface(state, pipeline, key, [0, 1, 0, 1], [1, 1, 1, 1], 64, 0.5, null, null, null, null);
|
|
34
|
+
|
|
35
|
+
expect(fake.calls.filter((c) => c.name === 'createBindGroup').length).toBe(1);
|
|
36
|
+
expect(fake.calls.filter((c) => c.name === 'writeBuffer').length).toBe(2);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// The per-map rebuild conditions (swap / unready→ready / ready→ready / version bump / sampler change)
|
|
40
|
+
// are proven directly against isWgpuMaterialBindGroupRebuildNeeded in wgpuMeshPipeline.test.ts; the binder
|
|
41
|
+
// wires that predicate around buildWgpuMaterialBindGroup. The unchanged-binds-no-rebuild case is the
|
|
42
|
+
// "creates a material bind group … once per key" test above.
|
|
43
|
+
|
|
44
|
+
it('exercises the real sampler path with a non-null primary texture and builds nothing new in steady state', () => {
|
|
45
|
+
const { fake, state } = makeWgpuScene3DState();
|
|
46
|
+
const pipeline = ensureWgpuClassicPipeline(state, makeKey('phong'), 'bgra8unorm');
|
|
47
|
+
const key = {};
|
|
48
|
+
// A ready diffuse (primary) map so bind() actually runs getWgpuMaterialSampler → getWgpuSampler (the
|
|
49
|
+
// path an already-resolved-sampler helper test bypasses). Same shared binder that PBR uses.
|
|
50
|
+
const diffuseMap = createTexture({ image: { source: {} } as unknown as ImageResource });
|
|
51
|
+
bindWgpuClassicSurface(state, pipeline, key, [1, 0, 0, 1], [1, 1, 1, 1], 32, 0.5, diffuseMap, null, null, null);
|
|
52
|
+
const count = (name: string) => fake.calls.filter((c) => c.name === name).length;
|
|
53
|
+
const [samplers, bindGroups, buffers] = [count('createSampler'), count('createBindGroup'), count('createBuffer')];
|
|
54
|
+
expect(samplers).toBeGreaterThanOrEqual(1); // the primary sampler was built once on first bind
|
|
55
|
+
|
|
56
|
+
// Re-bind the unchanged textured material: the sampler + view resolution run again (getWgpuSampler
|
|
57
|
+
// cache hit via its packed-number key), but no new GPUSampler / bind group / buffer is constructed —
|
|
58
|
+
// steady state builds nothing beyond the per-bind uniform write.
|
|
59
|
+
bindWgpuClassicSurface(state, pipeline, key, [1, 0, 0, 1], [1, 1, 1, 1], 32, 0.5, diffuseMap, null, null, null);
|
|
60
|
+
bindWgpuClassicSurface(state, pipeline, key, [1, 0, 0, 1], [1, 1, 1, 1], 32, 0.5, diffuseMap, null, null, null);
|
|
61
|
+
expect(count('createSampler')).toBe(samplers);
|
|
62
|
+
expect(count('createBindGroup')).toBe(bindGroups);
|
|
63
|
+
expect(count('createBuffer')).toBe(buffers);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('buildWgpuClassicDefineKey', () => {
|
|
68
|
+
it('encodes the lighting model first so the three models never collide', () => {
|
|
69
|
+
expect(buildWgpuClassicDefineKey(makeKey('lambert'))[0]).toBe('l');
|
|
70
|
+
expect(buildWgpuClassicDefineKey(makeKey('phong'))[0]).toBe('p');
|
|
71
|
+
expect(buildWgpuClassicDefineKey(makeKey('blinnphong'))[0]).toBe('b');
|
|
72
|
+
|
|
73
|
+
const keys = new Set([
|
|
74
|
+
buildWgpuClassicDefineKey(makeKey('lambert')),
|
|
75
|
+
buildWgpuClassicDefineKey(makeKey('phong')),
|
|
76
|
+
buildWgpuClassicDefineKey(makeKey('blinnphong')),
|
|
77
|
+
]);
|
|
78
|
+
expect(keys.size).toBe(3);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('encodes the alpha map as a distinct variant', () => {
|
|
82
|
+
const withAlpha = { ...makeKey('blinnphong'), hasAlphaMap: true };
|
|
83
|
+
expect(buildWgpuClassicDefineKey(withAlpha)).not.toBe(buildWgpuClassicDefineKey(makeKey('blinnphong')));
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('compileWgpuClassicPipeline', () => {
|
|
88
|
+
it('builds a render pipeline with a material bind-group layout', () => {
|
|
89
|
+
const { fake, state } = makeWgpuScene3DState();
|
|
90
|
+
const pipeline = compileWgpuClassicPipeline(state, makeKey('lambert'), 'bgra8unorm');
|
|
91
|
+
|
|
92
|
+
expect(pipeline.pipeline).toBeDefined();
|
|
93
|
+
expect(pipeline.materialBindGroupLayout).toBeDefined();
|
|
94
|
+
expect(fake.calls.some((c) => c.name === 'createShaderModule')).toBe(true);
|
|
95
|
+
expect(fake.calls.some((c) => c.name === 'createRenderPipeline')).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe('ensureWgpuClassicPipeline', () => {
|
|
100
|
+
it('caches under the classic: namespace with three distinct entries for the three models', () => {
|
|
101
|
+
const { state } = makeWgpuScene3DState();
|
|
102
|
+
ensureWgpuClassicPipeline(state, makeKey('lambert'), 'bgra8unorm');
|
|
103
|
+
ensureWgpuClassicPipeline(state, makeKey('phong'), 'bgra8unorm');
|
|
104
|
+
ensureWgpuClassicPipeline(state, makeKey('blinnphong'), 'bgra8unorm');
|
|
105
|
+
// Re-ensuring an existing model must not add a new cache entry.
|
|
106
|
+
ensureWgpuClassicPipeline(state, makeKey('phong'), 'bgra8unorm');
|
|
107
|
+
|
|
108
|
+
const cache = getWgpuScene3DRuntime(state).pipelineCache;
|
|
109
|
+
const classicKeys = [...cache.keys()].filter((k) => k.startsWith('classic:'));
|
|
110
|
+
expect(classicKeys.length).toBe(3);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('getWgpuClassicModuleSourceForKey', () => {
|
|
115
|
+
it('gates the alpha-map const + coverage multiply off the alpha-map flag', () => {
|
|
116
|
+
const none = getWgpuClassicModuleSourceForKey(makeKey('blinnphong'));
|
|
117
|
+
expect(none).toContain('const HAS_ALPHA_MAP : bool = false;');
|
|
118
|
+
const withAlpha = getWgpuClassicModuleSourceForKey({ ...makeKey('blinnphong'), hasAlphaMap: true });
|
|
119
|
+
expect(withAlpha).toContain('const HAS_ALPHA_MAP : bool = true;');
|
|
120
|
+
expect(withAlpha).toContain('alphaTexture');
|
|
121
|
+
expect(withAlpha).toContain('textureSample(alphaTexture, alphaSampler, in.uv).g');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('emits the lighting-model const matching the model and folds the others off', () => {
|
|
125
|
+
const phong = getWgpuClassicModuleSourceForKey(makeKey('phong'));
|
|
126
|
+
expect(phong).toContain('const LIGHTING_PHONG : bool = true;');
|
|
127
|
+
expect(phong).toContain('const LIGHTING_BLINNPHONG : bool = false;');
|
|
128
|
+
|
|
129
|
+
const blinn = getWgpuClassicModuleSourceForKey(makeKey('blinnphong'));
|
|
130
|
+
expect(blinn).toContain('const LIGHTING_BLINNPHONG : bool = true;');
|
|
131
|
+
expect(blinn).toContain('const LIGHTING_PHONG : bool = false;');
|
|
132
|
+
|
|
133
|
+
const lambert = getWgpuClassicModuleSourceForKey(makeKey('lambert'));
|
|
134
|
+
expect(lambert).toContain('const LIGHTING_PHONG : bool = false;');
|
|
135
|
+
expect(lambert).toContain('const LIGHTING_BLINNPHONG : bool = false;');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('emits the palette and skin attributes only for the HAS_SKIN variant', () => {
|
|
139
|
+
expect(getWgpuClassicModuleSourceForKey(makeKey('blinnphong'))).not.toContain('jointTexture');
|
|
140
|
+
expect(getWgpuClassicModuleSourceForKey(makeKey('blinnphong'), true, makeWgpuSkinningAdapter())).toContain(
|
|
141
|
+
'@group(1) @binding(1) var jointTexture',
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('declares the group(3) shadow bindings and shadow-maps the directional term', () => {
|
|
146
|
+
const source = getWgpuClassicModuleSourceForKey(makeKey('blinnphong'));
|
|
147
|
+
expect(source).toContain('@group(3) @binding(0) var<uniform> shadow : Shadow;');
|
|
148
|
+
expect(source).toContain('@group(3) @binding(1) var shadowMap : texture_depth_2d;');
|
|
149
|
+
expect(source).toContain('@group(3) @binding(2) var shadowSampler : sampler_comparison;');
|
|
150
|
+
// The whole directional contribution is scaled by the PCF factor; ambient stays unshadowed.
|
|
151
|
+
expect(source).toContain('direct * sampleDirectionalShadow(in.worldPosition)');
|
|
152
|
+
expect(source.match(/direct \* sampleDirectionalShadow\(in\.worldPosition\)/g)).toHaveLength(1);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('consumes every packed punctual-light family through the shared classic BRDF', () => {
|
|
156
|
+
const source = getWgpuClassicModuleSourceForKey(makeKey('blinnphong'));
|
|
157
|
+
expect(source).toContain('let pointCount = u32(frame.punctualCounts.x);');
|
|
158
|
+
expect(source).toContain('frame.pointLights[point * 2u + 1u].xyz * atten');
|
|
159
|
+
expect(source).toContain('let spotCount = u32(frame.punctualCounts.y);');
|
|
160
|
+
expect(source).toContain('frame.spotLights[spot * 4u + 1u].xyz * atten * cone');
|
|
161
|
+
expect(source).toContain('let hemisphereCount = u32(frame.punctualCounts.z);');
|
|
162
|
+
expect(source).toContain('frame.hemisphereLights[hemisphere * 3u + 1u].xyz');
|
|
163
|
+
expect(source.match(/shadeClassicLight\(/g)).toHaveLength(4);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe('getWgpuClassicSharedSamplerModuleSourceForKey', () => {
|
|
168
|
+
it('retains the legacy shared-sampler binding contract for shaded composition', () => {
|
|
169
|
+
const source = getWgpuClassicSharedSamplerModuleSourceForKey(makeKey('blinnphong'));
|
|
170
|
+
expect(source).toContain('@group(2) @binding(1) var materialSampler : sampler;');
|
|
171
|
+
expect(source).toContain('@group(2) @binding(2) var diffuseTexture : texture_2d<f32>;');
|
|
172
|
+
expect(source).toContain('@group(2) @binding(5) var alphaTexture : texture_2d<f32>;');
|
|
173
|
+
expect(source).toContain('textureSample(normalTexture, materialSampler, in.uv)');
|
|
174
|
+
expect(source).not.toContain('diffuseSampler');
|
|
175
|
+
});
|
|
176
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { WgpuDebugDefineKey } from '@flighthq/types';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
bindWgpuDebugSurface,
|
|
5
|
+
buildWgpuDebugDefineKey,
|
|
6
|
+
compileWgpuDebugPipeline,
|
|
7
|
+
ensureWgpuDebugPipeline,
|
|
8
|
+
getWgpuDebugModuleSourceForKey,
|
|
9
|
+
} from './wgpuDebugPrelude';
|
|
10
|
+
import { getWgpuScene3DRuntime } from './wgpuScene3DRuntime';
|
|
11
|
+
import { makeWgpuScene3DState } from './wgpuScene3DTestHelper';
|
|
12
|
+
|
|
13
|
+
const DEPTH: WgpuDebugDefineKey = { hasNormalMap: false, mode: 'depth' };
|
|
14
|
+
const NORMAL: WgpuDebugDefineKey = { hasNormalMap: false, mode: 'normal' };
|
|
15
|
+
const NORMAL_MAP: WgpuDebugDefineKey = { hasNormalMap: true, mode: 'normal' };
|
|
16
|
+
|
|
17
|
+
describe('bindWgpuDebugSurface', () => {
|
|
18
|
+
it('creates a material bind group once per key and writes its uniform', () => {
|
|
19
|
+
const { fake, state } = makeWgpuScene3DState();
|
|
20
|
+
const pipeline = compileWgpuDebugPipeline(state, DEPTH, 'bgra8unorm');
|
|
21
|
+
const key = {};
|
|
22
|
+
bindWgpuDebugSurface(state, pipeline, key, 0.1, 100, 1);
|
|
23
|
+
const groups = fake.calls.filter((c) => c.name === 'createBindGroup').length;
|
|
24
|
+
bindWgpuDebugSurface(state, pipeline, key, 0.1, 100, 1);
|
|
25
|
+
expect(fake.calls.filter((c) => c.name === 'createBindGroup').length).toBe(groups);
|
|
26
|
+
expect(fake.calls.some((c) => c.name === 'writeBuffer')).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('buildWgpuDebugDefineKey', () => {
|
|
31
|
+
it('produces distinct stable strings per mode and normal-map flag', () => {
|
|
32
|
+
expect(buildWgpuDebugDefineKey(DEPTH)).toBe('d-');
|
|
33
|
+
expect(buildWgpuDebugDefineKey(NORMAL)).toBe('n-');
|
|
34
|
+
expect(buildWgpuDebugDefineKey(NORMAL_MAP)).toBe('nm');
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('compileWgpuDebugPipeline', () => {
|
|
39
|
+
it('compiles a module and builds the pipeline with a 3-entry material layout', () => {
|
|
40
|
+
const { fake, state } = makeWgpuScene3DState();
|
|
41
|
+
const pipeline = compileWgpuDebugPipeline(state, NORMAL, 'rgba16float');
|
|
42
|
+
expect(pipeline.pipeline).toBeDefined();
|
|
43
|
+
expect(fake.calls.some((c) => c.name === 'createShaderModule')).toBe(true);
|
|
44
|
+
const matLayout = fake.calls
|
|
45
|
+
.filter((c) => c.name === 'createBindGroupLayout')
|
|
46
|
+
.map((c) => c.args[0] as { entries: unknown[] })
|
|
47
|
+
.find((d) => d.entries.length === 3);
|
|
48
|
+
expect(matLayout).toBeDefined();
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('ensureWgpuDebugPipeline', () => {
|
|
53
|
+
it('caches variants under the debug namespace with distinct depth and normal entries', () => {
|
|
54
|
+
const { state } = makeWgpuScene3DState();
|
|
55
|
+
const depthFirst = ensureWgpuDebugPipeline(state, DEPTH, 'bgra8unorm');
|
|
56
|
+
const depthSecond = ensureWgpuDebugPipeline(state, DEPTH, 'bgra8unorm');
|
|
57
|
+
expect(depthSecond).toBe(depthFirst);
|
|
58
|
+
|
|
59
|
+
const normalPipeline = ensureWgpuDebugPipeline(state, NORMAL, 'bgra8unorm');
|
|
60
|
+
expect(normalPipeline).not.toBe(depthFirst);
|
|
61
|
+
|
|
62
|
+
const keys = [...getWgpuScene3DRuntime(state).pipelineCache.keys()];
|
|
63
|
+
expect(keys.some((k) => k.startsWith('debug:'))).toBe(true);
|
|
64
|
+
expect(keys).toContain('debug:bgra8unorm|d-|opaque|rigid');
|
|
65
|
+
expect(keys).toContain('debug:bgra8unorm|n-|opaque|rigid');
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('getWgpuDebugModuleSourceForKey', () => {
|
|
70
|
+
it('emits the right mode const and includes the shared prelude + fs_main', () => {
|
|
71
|
+
const depthSource = getWgpuDebugModuleSourceForKey(DEPTH);
|
|
72
|
+
expect(depthSource).toContain('const MODE : i32 = DEPTH_MODE');
|
|
73
|
+
expect(getWgpuDebugModuleSourceForKey(NORMAL)).toContain('const MODE : i32 = NORMAL_MODE');
|
|
74
|
+
expect(depthSource).toContain('const HAS_NORMAL_MAP : bool = false');
|
|
75
|
+
expect(getWgpuDebugModuleSourceForKey(NORMAL_MAP)).toContain('const HAS_NORMAL_MAP : bool = true');
|
|
76
|
+
expect(depthSource).toContain('struct Frame');
|
|
77
|
+
expect(depthSource).toContain('fn fs_main');
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { CubeTexture, Environment, ImageResource } from '@flighthq/types';
|
|
2
|
+
|
|
3
|
+
import { ensureWgpuEnvironmentSourceCube, updateWgpuEnvironmentCubeFace } from './wgpuEnvironmentCube';
|
|
4
|
+
import { getWgpuScene3DRuntime } from './wgpuScene3DRuntime';
|
|
5
|
+
import { makeWgpuScene3DState } from './wgpuScene3DTestHelper';
|
|
6
|
+
|
|
7
|
+
// The GPU upload + sampling is validated by the functional `env-skybox` / `env-ibl` captures (jsdom has no
|
|
8
|
+
// real WebGPU cube texture). These cover the CPU-side wiring: the six-face upload, the caching identity,
|
|
9
|
+
// and the "no complete cube" sentinel path callers depend on to no-op — mirroring scene-gl's cube test.
|
|
10
|
+
|
|
11
|
+
function completeEnvironment(): Environment {
|
|
12
|
+
const face = { source: {} as CanvasImageSource, data: null, width: 4, height: 4 } as ImageResource;
|
|
13
|
+
const cube = {
|
|
14
|
+
colorSpace: 'srgb',
|
|
15
|
+
faces: [face, face, face, face, face, face],
|
|
16
|
+
sampler: {},
|
|
17
|
+
} as unknown as CubeTexture;
|
|
18
|
+
return { environment: cube, intensity: 1 } as Environment;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function dataFace(): ImageResource {
|
|
22
|
+
return { source: null, data: new Uint8ClampedArray(4 * 4 * 4), width: 4, height: 4 } as ImageResource;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function dataOnlyEnvironment(): Environment {
|
|
26
|
+
const face = dataFace();
|
|
27
|
+
const cube = {
|
|
28
|
+
colorSpace: 'srgb',
|
|
29
|
+
faces: [face, face, face, face, face, face],
|
|
30
|
+
sampler: {},
|
|
31
|
+
} as unknown as CubeTexture;
|
|
32
|
+
return { environment: cube, intensity: 1 } as Environment;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
describe('ensureWgpuEnvironmentSourceCube', () => {
|
|
36
|
+
it('returns null when the environment has no complete source cube', () => {
|
|
37
|
+
const { state } = makeWgpuScene3DState();
|
|
38
|
+
expect(ensureWgpuEnvironmentSourceCube(state, { environment: null, intensity: 1 } as Environment)).toBe(null);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('uploads six faces into a cube texture and caches the view', () => {
|
|
42
|
+
const { fake, state } = makeWgpuScene3DState();
|
|
43
|
+
const view = ensureWgpuEnvironmentSourceCube(state, completeEnvironment());
|
|
44
|
+
expect(view).not.toBe(null);
|
|
45
|
+
expect(fake.calls.filter((c) => c.name === 'copyExternalImageToTexture').length).toBe(6);
|
|
46
|
+
expect(getWgpuScene3DRuntime(state).environmentSourceCube).not.toBe(null);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('uploads a data-only cube through queue.writeTexture', () => {
|
|
50
|
+
const { fake, state } = makeWgpuScene3DState();
|
|
51
|
+
const view = ensureWgpuEnvironmentSourceCube(state, dataOnlyEnvironment());
|
|
52
|
+
expect(view).not.toBe(null);
|
|
53
|
+
const writes = fake.calls.filter((c) => c.name === 'writeTexture');
|
|
54
|
+
expect(writes.length).toBe(6);
|
|
55
|
+
expect(fake.calls.filter((c) => c.name === 'copyExternalImageToTexture').length).toBe(0);
|
|
56
|
+
// rgba8unorm data face: tightly-packed rows of width*4 bytes.
|
|
57
|
+
expect((writes[0].args[2] as { bytesPerRow: number }).bytesPerRow).toBe(16);
|
|
58
|
+
expect(writes[0].args[1]).toBeInstanceOf(Uint8ClampedArray);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('re-uses the cached cube view without re-uploading', () => {
|
|
62
|
+
const { fake, state } = makeWgpuScene3DState();
|
|
63
|
+
const view = ensureWgpuEnvironmentSourceCube(state, completeEnvironment());
|
|
64
|
+
const uploads = fake.calls.filter((c) => c.name === 'copyExternalImageToTexture').length;
|
|
65
|
+
const again = ensureWgpuEnvironmentSourceCube(state, completeEnvironment());
|
|
66
|
+
expect(again).toBe(view);
|
|
67
|
+
expect(fake.calls.filter((c) => c.name === 'copyExternalImageToTexture').length).toBe(uploads);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('updateWgpuEnvironmentCubeFace', () => {
|
|
72
|
+
it('returns false when no source cube has been built yet', () => {
|
|
73
|
+
const { fake, state } = makeWgpuScene3DState();
|
|
74
|
+
expect(updateWgpuEnvironmentCubeFace(state, 2, dataFace())).toBe(false);
|
|
75
|
+
expect(fake.calls.some((c) => c.name === 'writeTexture')).toBe(false);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('restamps a single face of the built cube without rebuilding the other five', () => {
|
|
79
|
+
const { fake, state } = makeWgpuScene3DState();
|
|
80
|
+
ensureWgpuEnvironmentSourceCube(state, dataOnlyEnvironment());
|
|
81
|
+
const afterBuild = fake.calls.filter((c) => c.name === 'writeTexture').length;
|
|
82
|
+
expect(afterBuild).toBe(6);
|
|
83
|
+
expect(updateWgpuEnvironmentCubeFace(state, 2, dataFace())).toBe(true);
|
|
84
|
+
expect(fake.calls.filter((c) => c.name === 'writeTexture').length).toBe(afterBuild + 1);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { CubeTexture, Environment, ImageResource } from '@flighthq/types';
|
|
2
|
+
|
|
3
|
+
import { bakeWgpuEnvironmentIbl, destroyWgpuScene3DIbl } from './wgpuEnvironmentIblBake';
|
|
4
|
+
import { getWgpuScene3DRuntime } from './wgpuScene3DRuntime';
|
|
5
|
+
import { makeWgpuScene3DState } from './wgpuScene3DTestHelper';
|
|
6
|
+
|
|
7
|
+
// The GPU bake (irradiance / prefiltered specular / BRDF LUT) is validated by the functional `env-ibl`
|
|
8
|
+
// capture — jsdom cannot run WGSL. These cover the CPU-side wiring: the no-op sentinel, the split-sum set's
|
|
9
|
+
// formats + mip chain stored on the runtime, the bake WGSL compile, and the destroy teardown — mirroring
|
|
10
|
+
// scene-gl's bake test.
|
|
11
|
+
|
|
12
|
+
function completeEnvironment(): Environment {
|
|
13
|
+
const face = { source: {} as CanvasImageSource, width: 4, height: 4 } as ImageResource;
|
|
14
|
+
const cube = {
|
|
15
|
+
colorSpace: 'srgb',
|
|
16
|
+
faces: [face, face, face, face, face, face],
|
|
17
|
+
sampler: {},
|
|
18
|
+
} as unknown as CubeTexture;
|
|
19
|
+
return { environment: cube, intensity: 1 } as Environment;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe('bakeWgpuEnvironmentIbl', () => {
|
|
23
|
+
it('is a no-op leaving runtime.ibl null when the environment has no source cube', () => {
|
|
24
|
+
const { state } = makeWgpuScene3DState();
|
|
25
|
+
bakeWgpuEnvironmentIbl(state, { environment: null, intensity: 1 } as Environment);
|
|
26
|
+
expect(getWgpuScene3DRuntime(state).ibl).toBe(null);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('bakes the split-sum set: rgba16float irradiance + prefiltered cubes + BRDF LUT with the mip chain', () => {
|
|
30
|
+
const { fake, state } = makeWgpuScene3DState();
|
|
31
|
+
bakeWgpuEnvironmentIbl(state, completeEnvironment());
|
|
32
|
+
|
|
33
|
+
const runtime = getWgpuScene3DRuntime(state);
|
|
34
|
+
expect(runtime.ibl).not.toBe(null);
|
|
35
|
+
expect(runtime.ibl!.prefilteredMipCount).toBe(5);
|
|
36
|
+
expect(runtime.ibl!.intensity).toBe(1);
|
|
37
|
+
|
|
38
|
+
const bakeTextures = fake.calls.filter(
|
|
39
|
+
(c) => c.name === 'createTexture' && (c.args[0] as GPUTextureDescriptor).format === 'rgba16float',
|
|
40
|
+
);
|
|
41
|
+
// irradiance cube + prefiltered cube + BRDF LUT.
|
|
42
|
+
expect(bakeTextures.length).toBe(3);
|
|
43
|
+
// The prefiltered cube declares the full roughness mip chain up front.
|
|
44
|
+
expect(bakeTextures.some((c) => (c.args[0] as GPUTextureDescriptor).mipLevelCount === 5)).toBe(true);
|
|
45
|
+
// The bake WGSL is compiled (GGX importance sampling for the prefiltered/BRDF passes).
|
|
46
|
+
expect(
|
|
47
|
+
fake.calls.some(
|
|
48
|
+
(c) =>
|
|
49
|
+
c.name === 'createShaderModule' &&
|
|
50
|
+
String((c.args[0] as { code: string }).code).includes('importanceSampleGGX'),
|
|
51
|
+
),
|
|
52
|
+
).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('destroyWgpuScene3DIbl', () => {
|
|
57
|
+
it('frees the baked textures + source cube and clears the runtime slots', () => {
|
|
58
|
+
const { state } = makeWgpuScene3DState();
|
|
59
|
+
bakeWgpuEnvironmentIbl(state, completeEnvironment());
|
|
60
|
+
|
|
61
|
+
const runtime = getWgpuScene3DRuntime(state);
|
|
62
|
+
let destroyed = 0;
|
|
63
|
+
runtime.ibl!.brdfLut.destroy = () => void destroyed++;
|
|
64
|
+
runtime.ibl!.irradianceCube.destroy = () => void destroyed++;
|
|
65
|
+
runtime.ibl!.prefilteredCube.destroy = () => void destroyed++;
|
|
66
|
+
|
|
67
|
+
destroyWgpuScene3DIbl(state);
|
|
68
|
+
expect(destroyed).toBe(3);
|
|
69
|
+
expect(runtime.ibl).toBe(null);
|
|
70
|
+
expect(runtime.environmentSourceCube).toBe(null);
|
|
71
|
+
expect(runtime.environmentSourceCubeView).toBe(null);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('is a safe no-op when no bake ran for the state', () => {
|
|
75
|
+
const { state } = makeWgpuScene3DState();
|
|
76
|
+
expect(() => destroyWgpuScene3DIbl(state)).not.toThrow();
|
|
77
|
+
expect(() => destroyWgpuScene3DIbl(state)).not.toThrow();
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { createCamera3D } from '@flighthq/camera';
|
|
2
|
+
import type { Camera3D, CubeTexture, Environment, ImageResource } from '@flighthq/types';
|
|
3
|
+
|
|
4
|
+
import { drawWgpuEnvironmentSkybox } from './wgpuEnvironmentSkybox';
|
|
5
|
+
import { makeWgpuScene3DState } from './wgpuScene3DTestHelper';
|
|
6
|
+
|
|
7
|
+
// The skybox draw itself is validated by the functional `env-skybox` capture (jsdom cannot run WGSL). These
|
|
8
|
+
// cover the CPU-side wiring: the no-complete-cube sentinel no-op, and the pipeline/bind/draw call shape of
|
|
9
|
+
// the backdrop pass — mirroring scene-gl's skybox test.
|
|
10
|
+
|
|
11
|
+
function makeCamera(): Camera3D {
|
|
12
|
+
return createCamera3D({ far: 100, near: 0.1, projection: { aspect: 1, fovY: Math.PI / 3, kind: 'perspective' } });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function completeEnvironment(): Environment {
|
|
16
|
+
const face = { source: {} as CanvasImageSource, width: 4, height: 4 } as ImageResource;
|
|
17
|
+
const cube = {
|
|
18
|
+
colorSpace: 'srgb',
|
|
19
|
+
faces: [face, face, face, face, face, face],
|
|
20
|
+
sampler: {},
|
|
21
|
+
} as unknown as CubeTexture;
|
|
22
|
+
return { environment: cube, intensity: 1 } as Environment;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe('drawWgpuEnvironmentSkybox', () => {
|
|
26
|
+
it('is a no-op when the environment has no complete source cube', () => {
|
|
27
|
+
const { fake, state } = makeWgpuScene3DState();
|
|
28
|
+
const before = fake.calls.length;
|
|
29
|
+
expect(() =>
|
|
30
|
+
drawWgpuEnvironmentSkybox(state, { environment: null, intensity: 1 } as Environment, makeCamera(), 1),
|
|
31
|
+
).not.toThrow();
|
|
32
|
+
expect(fake.calls.length).toBe(before);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('binds the cube + uniform and draws the fullscreen backdrop', () => {
|
|
36
|
+
const { fake, state } = makeWgpuScene3DState();
|
|
37
|
+
drawWgpuEnvironmentSkybox(state, completeEnvironment(), makeCamera(), 1);
|
|
38
|
+
|
|
39
|
+
expect(fake.calls.some((c) => c.name === 'setPipeline')).toBe(true);
|
|
40
|
+
expect(fake.calls.some((c) => c.name === 'setBindGroup' && c.args[0] === 1)).toBe(true);
|
|
41
|
+
expect(fake.calls.some((c) => c.name === 'draw' && c.args[0] === 3)).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('compiles skybox WGSL that reconstructs the ray from the inverse view-projection', () => {
|
|
45
|
+
const { fake, state } = makeWgpuScene3DState();
|
|
46
|
+
drawWgpuEnvironmentSkybox(state, completeEnvironment(), makeCamera(), 1);
|
|
47
|
+
|
|
48
|
+
const shader = fake.calls.find(
|
|
49
|
+
(c) =>
|
|
50
|
+
c.name === 'createShaderModule' &&
|
|
51
|
+
String((c.args[0] as { code: string }).code).includes('inverseViewProjection'),
|
|
52
|
+
);
|
|
53
|
+
expect(shader).toBeDefined();
|
|
54
|
+
expect(String((shader!.args[0] as { code: string }).code)).toContain('texture_cube<f32>');
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { LinearColor, WgpuMatcapDefineKey } from '@flighthq/types';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
bindWgpuMatcapSurface,
|
|
5
|
+
buildWgpuMatcapDefineKey,
|
|
6
|
+
compileWgpuMatcapPipeline,
|
|
7
|
+
ensureWgpuMatcapPipeline,
|
|
8
|
+
getWgpuMatcapModuleSourceForKey,
|
|
9
|
+
} from './wgpuMatcapPrelude';
|
|
10
|
+
import { getWgpuScene3DRuntime } from './wgpuScene3DRuntime';
|
|
11
|
+
import { makeWgpuScene3DState } from './wgpuScene3DTestHelper';
|
|
12
|
+
|
|
13
|
+
const FLAT: WgpuMatcapDefineKey = { alphaMaskEnabled: false, doubleSided: false, hasMatcap: false };
|
|
14
|
+
const TINT: LinearColor = [0.5, 0.25, 0.1, 1];
|
|
15
|
+
|
|
16
|
+
describe('bindWgpuMatcapSurface', () => {
|
|
17
|
+
it('creates a material bind group once per key and writes its uniform', () => {
|
|
18
|
+
const { fake, state } = makeWgpuScene3DState();
|
|
19
|
+
const pipeline = compileWgpuMatcapPipeline(state, FLAT, 'bgra8unorm');
|
|
20
|
+
const key = {};
|
|
21
|
+
bindWgpuMatcapSurface(state, pipeline, key, TINT, 0.5);
|
|
22
|
+
const groups = fake.calls.filter((c) => c.name === 'createBindGroup').length;
|
|
23
|
+
bindWgpuMatcapSurface(state, pipeline, key, TINT, 0.5);
|
|
24
|
+
expect(fake.calls.filter((c) => c.name === 'createBindGroup').length).toBe(groups);
|
|
25
|
+
expect(fake.calls.some((c) => c.name === 'writeBuffer')).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('buildWgpuMatcapDefineKey', () => {
|
|
30
|
+
it('produces distinct stable strings per flag set', () => {
|
|
31
|
+
expect(buildWgpuMatcapDefineKey(FLAT)).toBe('---');
|
|
32
|
+
expect(buildWgpuMatcapDefineKey({ alphaMaskEnabled: true, doubleSided: true, hasMatcap: true })).toBe('mdt');
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe('compileWgpuMatcapPipeline', () => {
|
|
37
|
+
it('compiles a module and builds the pipeline with a 3-entry material layout', () => {
|
|
38
|
+
const { fake, state } = makeWgpuScene3DState();
|
|
39
|
+
const pipeline = compileWgpuMatcapPipeline(state, FLAT, 'rgba16float');
|
|
40
|
+
expect(pipeline.pipeline).toBeDefined();
|
|
41
|
+
expect(fake.calls.some((c) => c.name === 'createShaderModule')).toBe(true);
|
|
42
|
+
const matLayout = fake.calls
|
|
43
|
+
.filter((c) => c.name === 'createBindGroupLayout')
|
|
44
|
+
.map((c) => c.args[0] as { entries: unknown[] })
|
|
45
|
+
.find((d) => d.entries.length === 3);
|
|
46
|
+
expect(matLayout).toBeDefined();
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('ensureWgpuMatcapPipeline', () => {
|
|
51
|
+
it('caches one pipeline per define key + format under the matcap namespace', () => {
|
|
52
|
+
const { state } = makeWgpuScene3DState();
|
|
53
|
+
const a = ensureWgpuMatcapPipeline(state, FLAT, 'bgra8unorm');
|
|
54
|
+
const b = ensureWgpuMatcapPipeline(state, FLAT, 'bgra8unorm');
|
|
55
|
+
expect(a).toBe(b);
|
|
56
|
+
expect([...getWgpuScene3DRuntime(state).pipelineCache.keys()].some((k) => k.startsWith('matcap:'))).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('getWgpuMatcapModuleSourceForKey', () => {
|
|
61
|
+
it('emits the feature consts and includes the shared prelude + fs_main', () => {
|
|
62
|
+
expect(getWgpuMatcapModuleSourceForKey(FLAT)).toContain('const HAS_MATCAP : bool = false');
|
|
63
|
+
expect(getWgpuMatcapModuleSourceForKey({ ...FLAT, alphaMaskEnabled: true })).toContain(
|
|
64
|
+
'const ALPHA_MASK : bool = true',
|
|
65
|
+
);
|
|
66
|
+
const source = getWgpuMatcapModuleSourceForKey(FLAT);
|
|
67
|
+
expect(source).toContain('struct Frame');
|
|
68
|
+
expect(source).toContain('fn fs_main');
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { createStandardPbrMaterial } from '@flighthq/materials';
|
|
2
|
+
import type { WgpuMeshMaterialRenderer } from '@flighthq/types';
|
|
3
|
+
import { DefaultMaterialKind, StandardPbrMaterialKind } from '@flighthq/types';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
getWgpuMeshMaterialRenderer,
|
|
7
|
+
registerWgpuMeshMaterialRenderer,
|
|
8
|
+
resolveWgpuMeshMaterialRenderer,
|
|
9
|
+
} from './wgpuMeshMaterialRegistry';
|
|
10
|
+
import { makeWgpuScene3DState } from './wgpuScene3DTestHelper';
|
|
11
|
+
|
|
12
|
+
function makeRenderer(): WgpuMeshMaterialRenderer {
|
|
13
|
+
return { bind: () => {}, draw: () => {} };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe('getWgpuMeshMaterialRenderer', () => {
|
|
17
|
+
it('returns a registered renderer by kind, else null', () => {
|
|
18
|
+
const { state } = makeWgpuScene3DState();
|
|
19
|
+
const renderer = makeRenderer();
|
|
20
|
+
expect(getWgpuMeshMaterialRenderer(state, StandardPbrMaterialKind)).toBeNull();
|
|
21
|
+
registerWgpuMeshMaterialRenderer(state, StandardPbrMaterialKind, renderer);
|
|
22
|
+
expect(getWgpuMeshMaterialRenderer(state, StandardPbrMaterialKind)).toBe(renderer);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('registerWgpuMeshMaterialRenderer', () => {
|
|
27
|
+
it('registers a renderer in the scene-wgpu 3D registry', () => {
|
|
28
|
+
const { state } = makeWgpuScene3DState();
|
|
29
|
+
const renderer = makeRenderer();
|
|
30
|
+
registerWgpuMeshMaterialRenderer(state, StandardPbrMaterialKind, renderer);
|
|
31
|
+
expect(getWgpuMeshMaterialRenderer(state, StandardPbrMaterialKind)).toBe(renderer);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('resolveWgpuMeshMaterialRenderer', () => {
|
|
36
|
+
it('resolves by the material kind', () => {
|
|
37
|
+
const { state } = makeWgpuScene3DState();
|
|
38
|
+
const renderer = makeRenderer();
|
|
39
|
+
registerWgpuMeshMaterialRenderer(state, StandardPbrMaterialKind, renderer);
|
|
40
|
+
expect(resolveWgpuMeshMaterialRenderer(state, createStandardPbrMaterial())).toBe(renderer);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('falls back to DefaultMaterialKind for an unregistered kind or null material', () => {
|
|
44
|
+
const { state } = makeWgpuScene3DState();
|
|
45
|
+
const fallback = makeRenderer();
|
|
46
|
+
registerWgpuMeshMaterialRenderer(state, DefaultMaterialKind, fallback);
|
|
47
|
+
expect(resolveWgpuMeshMaterialRenderer(state, createStandardPbrMaterial())).toBe(fallback);
|
|
48
|
+
expect(resolveWgpuMeshMaterialRenderer(state, null)).toBe(fallback);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('returns null when neither the kind nor the default is registered', () => {
|
|
52
|
+
const { state } = makeWgpuScene3DState();
|
|
53
|
+
expect(resolveWgpuMeshMaterialRenderer(state, createStandardPbrMaterial())).toBeNull();
|
|
54
|
+
});
|
|
55
|
+
});
|