@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,33 @@
|
|
|
1
|
+
import type { WgpuModifierSnippet } from '@flighthq/types';
|
|
2
|
+
import { ModifierSlot } from '@flighthq/types';
|
|
3
|
+
|
|
4
|
+
import { getWgpuScene3DRuntime } from './wgpuScene3DRuntime';
|
|
5
|
+
import { makeWgpuScene3DState } from './wgpuScene3DTestHelper';
|
|
6
|
+
import { registerWgpuModifierSnippet, resolveWgpuModifierSnippet } from './wgpuShadedModifierSnippet';
|
|
7
|
+
|
|
8
|
+
function makeSnippet(overrides?: Partial<WgpuModifierSnippet>): WgpuModifierSnippet {
|
|
9
|
+
return {
|
|
10
|
+
contribution: () => ({ source: '// noop' }),
|
|
11
|
+
kind: 'acme.Test',
|
|
12
|
+
slot: ModifierSlot.Effect,
|
|
13
|
+
...overrides,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe('registerWgpuModifierSnippet', () => {
|
|
18
|
+
it('lazily allocates a state-scoped registry and is last-write-wins', () => {
|
|
19
|
+
const { state } = makeWgpuScene3DState();
|
|
20
|
+
expect(getWgpuScene3DRuntime(state).modifierSnippetRegistry).toBeNull();
|
|
21
|
+
registerWgpuModifierSnippet(state, makeSnippet());
|
|
22
|
+
const override = makeSnippet({ contribution: () => ({ source: '// override' }) });
|
|
23
|
+
registerWgpuModifierSnippet(state, override);
|
|
24
|
+
expect(resolveWgpuModifierSnippet(state, 'acme.Test')).toBe(override);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('resolveWgpuModifierSnippet', () => {
|
|
29
|
+
it('returns null for an unregistered kind', () => {
|
|
30
|
+
const { state } = makeWgpuScene3DState();
|
|
31
|
+
expect(resolveWgpuModifierSnippet(state, 'acme.Missing')).toBeNull();
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createAnimatedNormalModifier,
|
|
3
|
+
createDissolveModifier,
|
|
4
|
+
createEmissiveModifier,
|
|
5
|
+
createEnvReflectModifier,
|
|
6
|
+
createFogModifier,
|
|
7
|
+
createRimModifier,
|
|
8
|
+
createShadedMaterial,
|
|
9
|
+
createToonModifier,
|
|
10
|
+
createVertexDisplaceModifier,
|
|
11
|
+
} from '@flighthq/shading';
|
|
12
|
+
import { createTexture } from '@flighthq/texture';
|
|
13
|
+
import type { ImageResource, Modifier, Texture, WgpuModifierSnippet } from '@flighthq/types';
|
|
14
|
+
import { FogModifierMode, ModifierSlot, VertexDisplaceModifierSource } from '@flighthq/types';
|
|
15
|
+
|
|
16
|
+
import { getWgpuScene3DRuntime } from './wgpuScene3DRuntime';
|
|
17
|
+
import { makeWgpuScene3DState, makeWgpuSkinningAdapter } from './wgpuScene3DTestHelper';
|
|
18
|
+
import { registerWgpuModifierSnippet, resolveWgpuModifierSnippet } from './wgpuShadedModifierSnippet';
|
|
19
|
+
import {
|
|
20
|
+
animatedNormalWgpuModifierSnippet,
|
|
21
|
+
bindWgpuShadedSurface,
|
|
22
|
+
buildWgpuShadedCacheKey,
|
|
23
|
+
dissolveWgpuModifierSnippet,
|
|
24
|
+
emissiveWgpuModifierSnippet,
|
|
25
|
+
ensureWgpuShadedPipeline,
|
|
26
|
+
envReflectWgpuModifierSnippet,
|
|
27
|
+
fogWgpuModifierSnippet,
|
|
28
|
+
getWgpuShadedModuleSource,
|
|
29
|
+
registerBuiltInWgpuModifierSnippets,
|
|
30
|
+
rimWgpuModifierSnippet,
|
|
31
|
+
toonWgpuModifierSnippet,
|
|
32
|
+
vertexDisplaceWgpuModifierSnippet,
|
|
33
|
+
} from './wgpuShadedPrelude';
|
|
34
|
+
|
|
35
|
+
describe('bindWgpuShadedSurface', () => {
|
|
36
|
+
it('uploads the base and modifier uniform block and binds group resources', () => {
|
|
37
|
+
const { fake, state } = makeWgpuScene3DState();
|
|
38
|
+
registerBuiltInWgpuModifierSnippets(state);
|
|
39
|
+
const material = createShadedMaterial({ modifiers: [createRimModifier({ color: 0xffffffff })] });
|
|
40
|
+
const pipeline = ensureWgpuShadedPipeline(state, material, 'bgra8unorm');
|
|
41
|
+
bindWgpuShadedSurface(state, pipeline, material, [1, 1, 1, 1], [1, 1, 1, 1]);
|
|
42
|
+
expect(fake.calls.some((call) => call.name === 'createBindGroup')).toBe(true);
|
|
43
|
+
expect(fake.calls.some((call) => call.name === 'writeBuffer')).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('buildWgpuShadedCacheKey', () => {
|
|
48
|
+
it('canonicalizes cross-slot order and captures modifier structural variants', () => {
|
|
49
|
+
const rim = createRimModifier({ color: 0xffffffff });
|
|
50
|
+
const emissive = createEmissiveModifier({ color: 0xffffffff, strength: 1 });
|
|
51
|
+
const a = createShadedMaterial({ modifiers: [rim, emissive] });
|
|
52
|
+
const b = createShadedMaterial({ modifiers: [emissive, rim] });
|
|
53
|
+
expect(buildWgpuShadedCacheKey(a)).toBe(buildWgpuShadedCacheKey(b));
|
|
54
|
+
|
|
55
|
+
const masked = createShadedMaterial({
|
|
56
|
+
modifiers: [createEmissiveModifier({ color: 0xffffffff, mask: createTexture(), strength: 1 })],
|
|
57
|
+
});
|
|
58
|
+
expect(buildWgpuShadedCacheKey(masked)).not.toBe(buildWgpuShadedCacheKey(a));
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('ensureWgpuShadedPipeline', () => {
|
|
63
|
+
it('caches shaded opaque and blended variants separately', () => {
|
|
64
|
+
const { state } = makeWgpuScene3DState();
|
|
65
|
+
registerBuiltInWgpuModifierSnippets(state);
|
|
66
|
+
const material = createShadedMaterial({ modifiers: [createRimModifier({ color: 0xffffffff })] });
|
|
67
|
+
ensureWgpuShadedPipeline(state, material, 'bgra8unorm');
|
|
68
|
+
getWgpuScene3DRuntime(state).activeBlendedRun = true;
|
|
69
|
+
ensureWgpuShadedPipeline(state, material, 'bgra8unorm');
|
|
70
|
+
const keys = [...getWgpuScene3DRuntime(state).pipelineCache.keys()];
|
|
71
|
+
expect(keys.some((key) => key.startsWith('shaded:') && key.endsWith('|opaque|rigid'))).toBe(true);
|
|
72
|
+
expect(keys.some((key) => key.startsWith('shaded:') && key.endsWith('|blend|rigid'))).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('reserves alpha-map binding 5 and starts modifier textures at binding 6', () => {
|
|
76
|
+
const { fake, state } = makeWgpuScene3DState();
|
|
77
|
+
registerBuiltInWgpuModifierSnippets(state);
|
|
78
|
+
const mask = createTexture();
|
|
79
|
+
const material = createShadedMaterial({
|
|
80
|
+
modifiers: [createEmissiveModifier({ color: 0xffffffff, mask, strength: 1 })],
|
|
81
|
+
});
|
|
82
|
+
ensureWgpuShadedPipeline(state, material, 'bgra8unorm');
|
|
83
|
+
const layout = fake.calls
|
|
84
|
+
.filter((call) => call.name === 'createBindGroupLayout')
|
|
85
|
+
.map((call) => call.args[0] as { entries: { binding: number }[] })
|
|
86
|
+
.find(({ entries }) => entries.some(({ binding }) => binding === 6));
|
|
87
|
+
expect(layout?.entries.map(({ binding }) => binding)).toEqual([0, 1, 2, 3, 4, 5, 6]);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('recompiles after a last-write-wins snippet replacement with the same define signature', () => {
|
|
91
|
+
const { fake, state } = makeWgpuScene3DState();
|
|
92
|
+
const modifier = { kind: 'acme.Replace', slot: ModifierSlot.Effect };
|
|
93
|
+
registerWgpuModifierSnippet(state, {
|
|
94
|
+
contribution: () => ({ source: '// compiler-marker-A' }),
|
|
95
|
+
kind: modifier.kind,
|
|
96
|
+
slot: modifier.slot,
|
|
97
|
+
});
|
|
98
|
+
const material = createShadedMaterial({ modifiers: [modifier] });
|
|
99
|
+
ensureWgpuShadedPipeline(state, material, 'bgra8unorm');
|
|
100
|
+
const before = fake.calls.filter((call) => call.name === 'createShaderModule').length;
|
|
101
|
+
|
|
102
|
+
registerWgpuModifierSnippet(state, {
|
|
103
|
+
contribution: () => ({ source: '// compiler-marker-B' }),
|
|
104
|
+
kind: modifier.kind,
|
|
105
|
+
slot: modifier.slot,
|
|
106
|
+
});
|
|
107
|
+
ensureWgpuShadedPipeline(state, material, 'bgra8unorm');
|
|
108
|
+
const modules = fake.calls.filter((call) => call.name === 'createShaderModule');
|
|
109
|
+
|
|
110
|
+
expect(modules).toHaveLength(before + 1);
|
|
111
|
+
expect((modules.at(-1)!.args[0] as { code: string }).code).toContain('compiler-marker-B');
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('getWgpuShadedModuleSource', () => {
|
|
116
|
+
it('threads the HAS_SKIN palette variant through the composed classic base', () => {
|
|
117
|
+
const material = createShadedMaterial();
|
|
118
|
+
expect(getWgpuShadedModuleSource(material)).not.toContain('jointTexture');
|
|
119
|
+
expect(getWgpuShadedModuleSource(material, undefined, true, makeWgpuSkinningAdapter())).toContain(
|
|
120
|
+
'textureLoad(jointTexture',
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('composes all built-in fragment modifier families into one shader', () => {
|
|
125
|
+
const { state } = makeWgpuScene3DState();
|
|
126
|
+
registerBuiltInWgpuModifierSnippets(state);
|
|
127
|
+
const texture = createTexture();
|
|
128
|
+
const material = createShadedMaterial({
|
|
129
|
+
modifiers: [
|
|
130
|
+
createAnimatedNormalModifier({ map: texture, scroll: { x: 0.1, y: 0.2 } }),
|
|
131
|
+
createEmissiveModifier({ color: 0xff0000ff, mask: texture, strength: 2 }),
|
|
132
|
+
createRimModifier({ color: 0x00ffffff }),
|
|
133
|
+
createDissolveModifier({ threshold: 0.2 }),
|
|
134
|
+
createEnvReflectModifier(),
|
|
135
|
+
createFogModifier({ color: 0xffffffff, mode: FogModifierMode.Exponential2 }),
|
|
136
|
+
createToonModifier({ steps: 3 }),
|
|
137
|
+
],
|
|
138
|
+
});
|
|
139
|
+
const source = getWgpuShadedModuleSource(material, getWgpuScene3DRuntime(state).modifierSnippetRegistry!);
|
|
140
|
+
expect(source).toContain('animatedNormal');
|
|
141
|
+
expect(source).toContain('emissiveTerm');
|
|
142
|
+
expect(source).toContain('rimFactor');
|
|
143
|
+
expect(source).toContain('shadedValueNoise');
|
|
144
|
+
expect(source).toContain('iblPrefiltered');
|
|
145
|
+
expect(source).toContain('exp(-pow(');
|
|
146
|
+
expect(source).toContain('toonQuant');
|
|
147
|
+
expect(source).toContain('@group(2) @binding(6) var modifierTexture6');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('injects vertex displacement before the world transform', () => {
|
|
151
|
+
const { state } = makeWgpuScene3DState();
|
|
152
|
+
registerBuiltInWgpuModifierSnippets(state);
|
|
153
|
+
const material = createShadedMaterial({
|
|
154
|
+
modifiers: [
|
|
155
|
+
createVertexDisplaceModifier({
|
|
156
|
+
amplitude: 0.25,
|
|
157
|
+
source: VertexDisplaceModifierSource.Sine,
|
|
158
|
+
}),
|
|
159
|
+
],
|
|
160
|
+
});
|
|
161
|
+
const source = getWgpuShadedModuleSource(material, getWgpuScene3DRuntime(state).modifierSnippetRegistry!);
|
|
162
|
+
expect(source.indexOf('localPosition = vec4f(localPosition.xyz')).toBeLessThan(
|
|
163
|
+
source.indexOf('let world = draw.world * localPosition'),
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('composes a registered vendor kind and treats an unregistered kind as an explicit miss', () => {
|
|
168
|
+
const { state } = makeWgpuScene3DState();
|
|
169
|
+
const vendor: WgpuModifierSnippet = {
|
|
170
|
+
contribution: () => ({ source: 'radiance = radiance + vec3f(0.125);\\n' }),
|
|
171
|
+
kind: 'acme.Glow',
|
|
172
|
+
slot: ModifierSlot.Effect,
|
|
173
|
+
};
|
|
174
|
+
registerWgpuModifierSnippet(state, vendor);
|
|
175
|
+
const registered = { kind: 'acme.Glow', slot: ModifierSlot.Effect } as Modifier;
|
|
176
|
+
const missing = { kind: 'acme.Missing', slot: ModifierSlot.Effect } as Modifier;
|
|
177
|
+
const material = createShadedMaterial({ modifiers: [registered, missing] });
|
|
178
|
+
const registry = getWgpuScene3DRuntime(state).modifierSnippetRegistry!;
|
|
179
|
+
const source = getWgpuShadedModuleSource(material, registry);
|
|
180
|
+
expect(source).toContain('radiance = radiance + vec3f(0.125)');
|
|
181
|
+
expect(buildWgpuShadedCacheKey(material, registry)).toContain('acme.Missing');
|
|
182
|
+
expect(resolveWgpuModifierSnippet(state, 'acme.Glow')).toBe(vendor);
|
|
183
|
+
expect(resolveWgpuModifierSnippet(state, 'acme.Missing')).toBeNull();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('routes every open modifier slot and delimits vendor contributions', () => {
|
|
187
|
+
const { state } = makeWgpuScene3DState();
|
|
188
|
+
const slots = [
|
|
189
|
+
ModifierSlot.Normal,
|
|
190
|
+
ModifierSlot.Diffuse,
|
|
191
|
+
ModifierSlot.Specular,
|
|
192
|
+
ModifierSlot.Emissive,
|
|
193
|
+
ModifierSlot.Effect,
|
|
194
|
+
ModifierSlot.Vertex,
|
|
195
|
+
];
|
|
196
|
+
const statements = [
|
|
197
|
+
'normal = normal;',
|
|
198
|
+
'diffuse = diffuse;',
|
|
199
|
+
'specularColor = specularColor;',
|
|
200
|
+
'emissive = emissive;',
|
|
201
|
+
'radiance = radiance;',
|
|
202
|
+
'localPosition = localPosition;',
|
|
203
|
+
];
|
|
204
|
+
const modifiers: Modifier[] = [];
|
|
205
|
+
for (let i = 0; i < slots.length; i++) {
|
|
206
|
+
const kind = `acme.Slot${i}`;
|
|
207
|
+
const marker = `slot_marker_${i}`;
|
|
208
|
+
registerWgpuModifierSnippet(state, {
|
|
209
|
+
contribution: () => ({ source: `// ${marker}\n${statements[i]}` }),
|
|
210
|
+
kind,
|
|
211
|
+
slot: slots[i],
|
|
212
|
+
});
|
|
213
|
+
modifiers.push({ kind, slot: slots[i] });
|
|
214
|
+
}
|
|
215
|
+
const registry = getWgpuScene3DRuntime(state).modifierSnippetRegistry!;
|
|
216
|
+
const source = getWgpuShadedModuleSource(createShadedMaterial({ modifiers }), registry);
|
|
217
|
+
for (let i = 0; i < slots.length; i++) expect(source).toContain(`slot_marker_${i}`);
|
|
218
|
+
expect(source).toMatch(/\/\/ slot_marker_1\s+diffuse = diffuse;\s+if \(ALPHA_MASK/);
|
|
219
|
+
expect(source.indexOf('slot_marker_2')).toBeLessThan(source.indexOf('var radiance'));
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('deduplicates repeated helper declarations and separates arbitrary declaration blocks', () => {
|
|
223
|
+
const { state } = makeWgpuScene3DState();
|
|
224
|
+
registerBuiltInWgpuModifierSnippets(state);
|
|
225
|
+
registerWgpuModifierSnippet(state, {
|
|
226
|
+
contribution: () => ({ declarations: 'fn vendorA() {}', source: '' }),
|
|
227
|
+
kind: 'acme.DeclarationA',
|
|
228
|
+
slot: ModifierSlot.Effect,
|
|
229
|
+
});
|
|
230
|
+
registerWgpuModifierSnippet(state, {
|
|
231
|
+
contribution: () => ({ declarations: 'fn vendorB() {}', source: '' }),
|
|
232
|
+
kind: 'acme.DeclarationB',
|
|
233
|
+
slot: ModifierSlot.Effect,
|
|
234
|
+
});
|
|
235
|
+
const material = createShadedMaterial({
|
|
236
|
+
modifiers: [
|
|
237
|
+
createDissolveModifier({ threshold: 0.1 }),
|
|
238
|
+
createDissolveModifier({ threshold: 0.2 }),
|
|
239
|
+
{ kind: 'acme.DeclarationA', slot: ModifierSlot.Effect },
|
|
240
|
+
{ kind: 'acme.DeclarationB', slot: ModifierSlot.Effect },
|
|
241
|
+
],
|
|
242
|
+
});
|
|
243
|
+
const source = getWgpuShadedModuleSource(material, getWgpuScene3DRuntime(state).modifierSnippetRegistry!);
|
|
244
|
+
expect(source.match(/fn shadedValueNoise/g)).toHaveLength(1);
|
|
245
|
+
expect(source).toContain('fn vendorA() {}\nfn vendorB() {}');
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
describe('registerBuiltInWgpuModifierSnippets', () => {
|
|
250
|
+
it('installs the built-in compiler records explicitly', () => {
|
|
251
|
+
const { state } = makeWgpuScene3DState();
|
|
252
|
+
registerBuiltInWgpuModifierSnippets(state);
|
|
253
|
+
expect(resolveWgpuModifierSnippet(state, 'RimModifier')).not.toBeNull();
|
|
254
|
+
expect([
|
|
255
|
+
animatedNormalWgpuModifierSnippet.kind,
|
|
256
|
+
dissolveWgpuModifierSnippet.kind,
|
|
257
|
+
emissiveWgpuModifierSnippet.kind,
|
|
258
|
+
envReflectWgpuModifierSnippet.kind,
|
|
259
|
+
fogWgpuModifierSnippet.kind,
|
|
260
|
+
rimWgpuModifierSnippet.kind,
|
|
261
|
+
toonWgpuModifierSnippet.kind,
|
|
262
|
+
vertexDisplaceWgpuModifierSnippet.kind,
|
|
263
|
+
]).toEqual([
|
|
264
|
+
'AnimatedNormalModifier',
|
|
265
|
+
'DissolveModifier',
|
|
266
|
+
'EmissiveModifier',
|
|
267
|
+
'EnvReflectModifier',
|
|
268
|
+
'FogModifier',
|
|
269
|
+
'RimModifier',
|
|
270
|
+
'ToonModifier',
|
|
271
|
+
'VertexDisplaceModifier',
|
|
272
|
+
]);
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
describe('shaded binding cache', () => {
|
|
277
|
+
it('owns GPU bindings per render state when a material is shared across devices', () => {
|
|
278
|
+
const first = makeWgpuScene3DState();
|
|
279
|
+
const second = makeWgpuScene3DState();
|
|
280
|
+
registerBuiltInWgpuModifierSnippets(first.state);
|
|
281
|
+
registerBuiltInWgpuModifierSnippets(second.state);
|
|
282
|
+
const material = createShadedMaterial();
|
|
283
|
+
const firstPipeline = ensureWgpuShadedPipeline(first.state, material, 'bgra8unorm');
|
|
284
|
+
const secondPipeline = ensureWgpuShadedPipeline(second.state, material, 'bgra8unorm');
|
|
285
|
+
|
|
286
|
+
const firstGroup = bindWgpuShadedSurface(first.state, firstPipeline, material, [1, 1, 1, 1], [1, 1, 1, 1]);
|
|
287
|
+
const firstBufferCount = first.fake.calls.filter((call) => call.name === 'createBuffer').length;
|
|
288
|
+
const firstGroupCount = first.fake.calls.filter((call) => call.name === 'createBindGroup').length;
|
|
289
|
+
const secondGroup = bindWgpuShadedSurface(second.state, secondPipeline, material, [1, 1, 1, 1], [1, 1, 1, 1]);
|
|
290
|
+
const firstAgain = bindWgpuShadedSurface(first.state, firstPipeline, material, [1, 1, 1, 1], [1, 1, 1, 1]);
|
|
291
|
+
|
|
292
|
+
expect(firstGroup).not.toBe(secondGroup);
|
|
293
|
+
expect(firstAgain).toBe(firstGroup);
|
|
294
|
+
expect(first.fake.calls.filter((call) => call.name === 'createBuffer')).toHaveLength(firstBufferCount);
|
|
295
|
+
expect(first.fake.calls.filter((call) => call.name === 'createBindGroup')).toHaveLength(firstGroupCount);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('reuses its uniform allocation while rebuilding for texture identity and readiness changes', () => {
|
|
299
|
+
const { fake, state } = makeWgpuScene3DState();
|
|
300
|
+
registerBuiltInWgpuModifierSnippets(state);
|
|
301
|
+
const first = createTexture({ image: { source: {} } as ImageResource });
|
|
302
|
+
const material = createShadedMaterial({ diffuseMap: first });
|
|
303
|
+
const pipeline = ensureWgpuShadedPipeline(state, material, 'bgra8unorm');
|
|
304
|
+
bindWgpuShadedSurface(state, pipeline, material, [1, 1, 1, 1], [1, 1, 1, 1]);
|
|
305
|
+
const buffers = fake.calls.filter((call) => call.name === 'createBuffer').length;
|
|
306
|
+
const groups = fake.calls.filter((call) => call.name === 'createBindGroup').length;
|
|
307
|
+
|
|
308
|
+
bindWgpuShadedSurface(state, pipeline, material, [1, 1, 1, 1], [1, 1, 1, 1]);
|
|
309
|
+
expect(fake.calls.filter((call) => call.name === 'createBuffer')).toHaveLength(buffers);
|
|
310
|
+
expect(fake.calls.filter((call) => call.name === 'createBindGroup')).toHaveLength(groups);
|
|
311
|
+
|
|
312
|
+
material.diffuseMap = createTexture({ image: { source: {} } as ImageResource });
|
|
313
|
+
bindWgpuShadedSurface(state, pipeline, material, [1, 1, 1, 1], [1, 1, 1, 1]);
|
|
314
|
+
expect(fake.calls.filter((call) => call.name === 'createBindGroup').length).toBeGreaterThan(groups);
|
|
315
|
+
expect(fake.calls.filter((call) => call.name === 'createBuffer')).toHaveLength(buffers);
|
|
316
|
+
|
|
317
|
+
const modifierTexture = createTexture({ image: makeImageResource() });
|
|
318
|
+
const emissive = createEmissiveModifier({ color: 0xffffffff, mask: modifierTexture, strength: 1 });
|
|
319
|
+
material.modifiers = [emissive];
|
|
320
|
+
const modifierPipeline = ensureWgpuShadedPipeline(state, material, 'bgra8unorm');
|
|
321
|
+
bindWgpuShadedSurface(state, modifierPipeline, material, [1, 1, 1, 1], [1, 1, 1, 1]);
|
|
322
|
+
const beforeModifierSwap = fake.calls.filter((call) => call.name === 'createBindGroup').length;
|
|
323
|
+
emissive.mask = createTexture({ image: makeImageResource() });
|
|
324
|
+
bindWgpuShadedSurface(state, modifierPipeline, material, [1, 1, 1, 1], [1, 1, 1, 1]);
|
|
325
|
+
expect(fake.calls.filter((call) => call.name === 'createBindGroup').length).toBeGreaterThan(beforeModifierSwap);
|
|
326
|
+
|
|
327
|
+
emissive.mask = createTexture();
|
|
328
|
+
bindWgpuShadedSurface(state, modifierPipeline, material, [1, 1, 1, 1], [1, 1, 1, 1]);
|
|
329
|
+
const beforeReady = fake.calls.filter((call) => call.name === 'createBindGroup').length;
|
|
330
|
+
const readyingTexture = emissive.mask;
|
|
331
|
+
expect(readyingTexture).toBeDefined();
|
|
332
|
+
readyingTexture!.image = { source: {} } as ImageResource;
|
|
333
|
+
bindWgpuShadedSurface(state, modifierPipeline, material, [1, 1, 1, 1], [1, 1, 1, 1]);
|
|
334
|
+
expect(fake.calls.filter((call) => call.name === 'createBindGroup').length).toBeGreaterThan(beforeReady);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it('rebinds when a ready image swaps or its version changes', () => {
|
|
338
|
+
const { fake, state } = makeWgpuScene3DState();
|
|
339
|
+
registerBuiltInWgpuModifierSnippets(state);
|
|
340
|
+
const firstImage = makeImageResource();
|
|
341
|
+
const texture = createTexture({ image: firstImage });
|
|
342
|
+
const material = createShadedMaterial({ diffuseMap: texture });
|
|
343
|
+
const pipeline = ensureWgpuShadedPipeline(state, material, 'bgra8unorm');
|
|
344
|
+
bindWgpuShadedSurface(state, pipeline, material, [1, 1, 1, 1], [1, 1, 1, 1]);
|
|
345
|
+
|
|
346
|
+
const beforeSwap = fake.calls.filter((call) => call.name === 'createBindGroup').length;
|
|
347
|
+
texture.image = makeImageResource();
|
|
348
|
+
bindWgpuShadedSurface(state, pipeline, material, [1, 1, 1, 1], [1, 1, 1, 1]);
|
|
349
|
+
expect(fake.calls.filter((call) => call.name === 'createBindGroup').length).toBeGreaterThan(beforeSwap);
|
|
350
|
+
|
|
351
|
+
const beforeVersion = fake.calls.filter((call) => call.name === 'createBindGroup').length;
|
|
352
|
+
texture.image.version++;
|
|
353
|
+
bindWgpuShadedSurface(state, pipeline, material, [1, 1, 1, 1], [1, 1, 1, 1]);
|
|
354
|
+
expect(fake.calls.filter((call) => call.name === 'createBindGroup').length).toBeGreaterThan(beforeVersion);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it('reuses the compiled plan and GPU resource arrays on an unchanged draw', () => {
|
|
358
|
+
const { fake, state } = makeWgpuScene3DState();
|
|
359
|
+
let contributions = 0;
|
|
360
|
+
const modifier = { kind: 'acme.Stable', slot: ModifierSlot.Effect };
|
|
361
|
+
registerWgpuModifierSnippet(state, {
|
|
362
|
+
contribution: () => {
|
|
363
|
+
contributions++;
|
|
364
|
+
return { source: 'radiance = radiance;' };
|
|
365
|
+
},
|
|
366
|
+
kind: modifier.kind,
|
|
367
|
+
slot: modifier.slot,
|
|
368
|
+
});
|
|
369
|
+
const material = createShadedMaterial({ modifiers: [modifier] });
|
|
370
|
+
const pipeline = ensureWgpuShadedPipeline(state, material, 'bgra8unorm');
|
|
371
|
+
bindWgpuShadedSurface(state, pipeline, material, [1, 1, 1, 1], [1, 1, 1, 1]);
|
|
372
|
+
const buffers = fake.calls.filter((call) => call.name === 'createBuffer').length;
|
|
373
|
+
const groups = fake.calls.filter((call) => call.name === 'createBindGroup').length;
|
|
374
|
+
const compiled = contributions;
|
|
375
|
+
|
|
376
|
+
bindWgpuShadedSurface(state, pipeline, material, [1, 1, 1, 1], [1, 1, 1, 1]);
|
|
377
|
+
expect(contributions).toBe(compiled);
|
|
378
|
+
expect(fake.calls.filter((call) => call.name === 'createBuffer')).toHaveLength(buffers);
|
|
379
|
+
expect(fake.calls.filter((call) => call.name === 'createBindGroup')).toHaveLength(groups);
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
function makeImageResource(): ImageResource {
|
|
384
|
+
return {
|
|
385
|
+
alphaType: 'straight',
|
|
386
|
+
compressed: null,
|
|
387
|
+
data: null,
|
|
388
|
+
format: 'rgba8unorm',
|
|
389
|
+
height: 1,
|
|
390
|
+
source: {} as CanvasImageSource,
|
|
391
|
+
version: 0,
|
|
392
|
+
width: 1,
|
|
393
|
+
} as ImageResource;
|
|
394
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { configureDirectionalShadowCamera3D, createCamera3D } from '@flighthq/camera';
|
|
2
|
+
import { createAabb, createVector3 } from '@flighthq/geometry';
|
|
3
|
+
import { createAmbientLight, createDirectionalLight } from '@flighthq/lighting';
|
|
4
|
+
import { createStandardPbrMaterial } from '@flighthq/materials';
|
|
5
|
+
import { createBoxMeshGeometry } from '@flighthq/mesh';
|
|
6
|
+
import { addNodeChild } from '@flighthq/node';
|
|
7
|
+
import { getWgpuRenderStateRuntime } from '@flighthq/render-wgpu';
|
|
8
|
+
import { createMesh, createNode3D, Node3DKind } from '@flighthq/scene3d';
|
|
9
|
+
import type { Camera3D, Scene3DLightsLike, Node3D } from '@flighthq/types';
|
|
10
|
+
|
|
11
|
+
import { drawWgpuScene3D } from './drawWgpuScene3D';
|
|
12
|
+
import { registerStandardPbrWgpuMaterial } from './registerStandardPbrWgpuMaterial';
|
|
13
|
+
import { buildWgpuPbrStandardDefineKey } from './standardPbrWgpuMeshMaterialRenderer';
|
|
14
|
+
import { getWgpuClassicModuleSourceForKey } from './wgpuClassicPrelude';
|
|
15
|
+
import { getWgpuPbrModuleSourceForKey } from './wgpuPbrPrelude';
|
|
16
|
+
import { getWgpuScene3DRuntime } from './wgpuScene3DRuntime';
|
|
17
|
+
import { makeWgpuScene3DState } from './wgpuScene3DTestHelper';
|
|
18
|
+
import { destroyWgpuScene3DShadow, drawWgpuScene3DShadowMap } from './wgpuShadowMap';
|
|
19
|
+
|
|
20
|
+
const LIGHTS: Scene3DLightsLike = {
|
|
21
|
+
ambient: createAmbientLight({ color: 0xffffffff, intensity: 0.2 }),
|
|
22
|
+
directional: createDirectionalLight({ color: 0xffffffff, direction: createVector3(0, -1, -1), intensity: 1 }),
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function makeShadowCamera(): Camera3D {
|
|
26
|
+
// The projection is overwritten by configureDirectionalShadowCamera3D (to orthographic); start perspective.
|
|
27
|
+
const camera = createCamera3D({
|
|
28
|
+
far: 10,
|
|
29
|
+
near: 0.1,
|
|
30
|
+
projection: { aspect: 1, fovY: Math.PI / 3, kind: 'perspective' },
|
|
31
|
+
});
|
|
32
|
+
configureDirectionalShadowCamera3D(camera, { x: 0, y: -1, z: -1 }, createAabb(-1, -1, -1, 1, 1, 1));
|
|
33
|
+
return camera;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function makeShadowScene3D(): Node3D {
|
|
37
|
+
const scene = createNode3D(Node3DKind);
|
|
38
|
+
addNodeChild(scene, createMesh(createBoxMeshGeometry(), []));
|
|
39
|
+
return scene;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describe('destroyWgpuScene3DShadow', () => {
|
|
43
|
+
it('destroys the shadow depth texture and clears the slot', () => {
|
|
44
|
+
const { state } = makeWgpuScene3DState();
|
|
45
|
+
drawWgpuScene3DShadowMap(state, makeShadowScene3D(), makeShadowCamera());
|
|
46
|
+
|
|
47
|
+
const runtime = getWgpuScene3DRuntime(state);
|
|
48
|
+
expect(runtime.shadow).not.toBeNull();
|
|
49
|
+
let destroyed = false;
|
|
50
|
+
runtime.shadow!.depthTexture.destroy = () => {
|
|
51
|
+
destroyed = true;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
destroyWgpuScene3DShadow(state);
|
|
55
|
+
expect(destroyed).toBe(true);
|
|
56
|
+
expect(runtime.shadow).toBeNull();
|
|
57
|
+
expect(runtime.shadowDepthPipeline).toBeNull();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('is a no-op when no shadow was drawn', () => {
|
|
61
|
+
const { state } = makeWgpuScene3DState();
|
|
62
|
+
expect(() => destroyWgpuScene3DShadow(state)).not.toThrow();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('drawWgpuScene3DShadowMap', () => {
|
|
67
|
+
it('creates a sampleable depth32float shadow map and stores it on the runtime', () => {
|
|
68
|
+
const { fake, state } = makeWgpuScene3DState();
|
|
69
|
+
drawWgpuScene3DShadowMap(state, makeShadowScene3D(), makeShadowCamera());
|
|
70
|
+
|
|
71
|
+
const depthCreate = fake.calls.find(
|
|
72
|
+
(c) => c.name === 'createTexture' && (c.args[0] as GPUTextureDescriptor).format === 'depth32float',
|
|
73
|
+
);
|
|
74
|
+
expect(depthCreate).toBeDefined();
|
|
75
|
+
|
|
76
|
+
const runtime = getWgpuScene3DRuntime(state);
|
|
77
|
+
expect(runtime.shadow).not.toBeNull();
|
|
78
|
+
expect(runtime.shadow!.matrix.m.some((v) => v !== 0)).toBe(true);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('opens a depth-only pass and renders each caster mesh depth', () => {
|
|
82
|
+
const { fake, state } = makeWgpuScene3DState();
|
|
83
|
+
drawWgpuScene3DShadowMap(state, makeShadowScene3D(), makeShadowCamera());
|
|
84
|
+
|
|
85
|
+
expect(fake.calls.some((c) => c.name === 'beginRenderPass')).toBe(true);
|
|
86
|
+
expect(fake.calls.some((c) => c.name === 'setPipeline')).toBe(true);
|
|
87
|
+
expect(fake.calls.some((c) => c.name === 'drawIndexed')).toBe(true);
|
|
88
|
+
expect(fake.calls.some((c) => c.name === 'end')).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('compiles a vertex-only depth module with the GL->WebGPU depth remap', () => {
|
|
92
|
+
const { fake, state } = makeWgpuScene3DState();
|
|
93
|
+
drawWgpuScene3DShadowMap(state, makeShadowScene3D(), makeShadowCamera());
|
|
94
|
+
|
|
95
|
+
const shaderCall = fake.calls.find(
|
|
96
|
+
(c) => c.name === 'createShaderModule' && String((c.args[0] as { code: string }).code).includes('draw.world'),
|
|
97
|
+
);
|
|
98
|
+
expect(shaderCall).toBeDefined();
|
|
99
|
+
const code = (shaderCall!.args[0] as { code: string }).code;
|
|
100
|
+
expect(code).toContain('clip.z = (clip.z + clip.w) * 0.5');
|
|
101
|
+
expect(code).not.toContain('fs_main');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('reuses the shadow depth texture across frames', () => {
|
|
105
|
+
const { fake, state } = makeWgpuScene3DState();
|
|
106
|
+
const scene = makeShadowScene3D();
|
|
107
|
+
const camera = makeShadowCamera();
|
|
108
|
+
drawWgpuScene3DShadowMap(state, scene, camera);
|
|
109
|
+
drawWgpuScene3DShadowMap(state, scene, camera);
|
|
110
|
+
const depthCreates = fake.calls.filter(
|
|
111
|
+
(c) => c.name === 'createTexture' && (c.args[0] as GPUTextureDescriptor).format === 'depth32float',
|
|
112
|
+
).length;
|
|
113
|
+
expect(depthCreates).toBe(1);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('is a no-op when no command encoder is active', () => {
|
|
117
|
+
const { fake, state } = makeWgpuScene3DState();
|
|
118
|
+
getWgpuRenderStateRuntime(state).commandEncoder = null;
|
|
119
|
+
drawWgpuScene3DShadowMap(state, makeShadowScene3D(), makeShadowCamera());
|
|
120
|
+
expect(fake.calls.some((c) => c.name === 'beginRenderPass')).toBe(false);
|
|
121
|
+
expect(getWgpuScene3DRuntime(state).shadow).toBeNull();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('binds a group(3) shadow group on the lit PBR draw that follows', () => {
|
|
125
|
+
const { fake, state } = makeWgpuScene3DState();
|
|
126
|
+
registerStandardPbrWgpuMaterial(state);
|
|
127
|
+
drawWgpuScene3DShadowMap(state, makeShadowScene3D(), makeShadowCamera());
|
|
128
|
+
|
|
129
|
+
const scene = createNode3D(Node3DKind);
|
|
130
|
+
addNodeChild(scene, createMesh(createBoxMeshGeometry(), [createStandardPbrMaterial()]));
|
|
131
|
+
const camera = createCamera3D({
|
|
132
|
+
far: 100,
|
|
133
|
+
near: 0.1,
|
|
134
|
+
projection: { aspect: 1, fovY: Math.PI / 3, kind: 'perspective' },
|
|
135
|
+
});
|
|
136
|
+
drawWgpuScene3D(state, scene, camera, LIGHTS);
|
|
137
|
+
|
|
138
|
+
expect(fake.calls.some((c) => c.name === 'setBindGroup' && c.args[0] === 3)).toBe(true);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// The lit WGSL string surface: both PBR and classic declare the group(3) shadow bindings and apply the
|
|
143
|
+
// PCF comparison to their directional terms, mirroring scene-gl.
|
|
144
|
+
describe('wgpuPbrPrelude shadow sampling', () => {
|
|
145
|
+
it('declares the group(3) shadow bindings and PCF comparison', () => {
|
|
146
|
+
const code = getWgpuPbrModuleSourceForKey(buildWgpuPbrStandardDefineKey(null, null));
|
|
147
|
+
expect(code).toContain('@group(3) @binding(0) var<uniform> shadow');
|
|
148
|
+
expect(code).toContain('var shadowMap : texture_depth_2d');
|
|
149
|
+
expect(code).toContain('var shadowSampler : sampler_comparison');
|
|
150
|
+
expect(code).toContain('fn sampleDirectionalShadow');
|
|
151
|
+
expect(code).toContain('textureSampleCompareLevel(shadowMap, shadowSampler');
|
|
152
|
+
expect(code).toContain('direct * sampleDirectionalShadow(in.worldPosition)');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('classic prelude samples the shadow map on the directional term (mirrors scene-gl classic)', () => {
|
|
156
|
+
const code = getWgpuClassicModuleSourceForKey({
|
|
157
|
+
alphaMaskEnabled: false,
|
|
158
|
+
doubleSided: false,
|
|
159
|
+
hasAlphaMap: false,
|
|
160
|
+
hasDiffuseMap: false,
|
|
161
|
+
hasNormalMap: false,
|
|
162
|
+
hasSpecularMap: false,
|
|
163
|
+
lightingModel: 'lambert',
|
|
164
|
+
});
|
|
165
|
+
expect(code).toContain('@group(3) @binding(1) var shadowMap : texture_depth_2d');
|
|
166
|
+
expect(code).toContain('fn sampleDirectionalShadow');
|
|
167
|
+
expect(code).toContain('direct * sampleDirectionalShadow(in.worldPosition)');
|
|
168
|
+
});
|
|
169
|
+
});
|