Optimize viewer shaders and camera controls; sample GPU profiles asynchronously
Amp-Thread-ID: https://ampcode.com/threads/T-01a0b382-1e79-712b-abd1-d29201c9bb5e Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
This commit is contained in:
@@ -4,6 +4,11 @@ import { Mesh } from "../src/Mesh";
|
||||
import { FXAA } from "../src/PostProcesses/FXAA";
|
||||
import { PBRMaterial } from "../src/materials/PBRMaterial";
|
||||
import { Texture } from "../src/materials/Texture";
|
||||
import { ShaderMaterial } from "../src/materials/ShaderMaterial";
|
||||
import { PointLight } from "../src/lights/PointLight";
|
||||
import { SpotLight } from "../src/lights/SpotLight";
|
||||
import { DirectionalLight } from "../src/lights/DirectionalLight";
|
||||
import { ComputePass } from "../src/ComputePass";
|
||||
import {
|
||||
bucketDraws,
|
||||
createScene,
|
||||
@@ -34,6 +39,11 @@ test("empty scenes use neutral authored defaults without placeholder geometry",
|
||||
|
||||
test("opaque PBR graph has no implicit environment and compacts sparse lights", async () => {
|
||||
const scene = await createScene();
|
||||
await Promise.all([
|
||||
new PointLight(scene).ready,
|
||||
new SpotLight(scene).ready,
|
||||
new DirectionalLight(scene).ready,
|
||||
]);
|
||||
const material = new PBRMaterial(scene);
|
||||
await material.ready;
|
||||
const mesh = detailedMesh(scene, material);
|
||||
@@ -451,3 +461,151 @@ test("serialized render graphs compile into explicitly selectable loadouts", asy
|
||||
assert.equal(loadoutId, `loadout-${before + 1}`);
|
||||
assert.equal(core.loadouts.at(-1), loadoutId);
|
||||
});
|
||||
|
||||
test("packed ORM reads are shared only for the same texture and UV transform", async () => {
|
||||
const scene = await createScene();
|
||||
const orm = new Texture(scene, { size: [1, 1, 1] });
|
||||
const other = new Texture(scene, { size: [1, 1, 1] });
|
||||
await Promise.all([orm.ready, other.ready]);
|
||||
for (const [occlusion, info, shared] of [
|
||||
[orm, {}, true],
|
||||
[other, {}, false],
|
||||
[orm, { texCoord: 1 }, false],
|
||||
[orm, { offset: [0.25, -0.1] }, false],
|
||||
[orm, { scale: [2, 1] }, false],
|
||||
[orm, { rotation: 0.4 }, false],
|
||||
] as const) {
|
||||
const material = new PBRMaterial(scene, {
|
||||
metallicRoughnessTexture: orm,
|
||||
occlusionTexture: occlusion,
|
||||
textureInfos: [{}, {}, {}, {}, info],
|
||||
});
|
||||
await material.ready;
|
||||
const mesh = await detailedMesh(scene, material).ready;
|
||||
const graph = latestGraph(scene);
|
||||
const pass = forwardPasses(graph).at(-1);
|
||||
const code = renderPipeline(graph, pass.pipeline).code;
|
||||
assert.equal(code.includes("mix(1.0, packedMaterial.r"), shared);
|
||||
assert.equal(code.includes("textureSample(occlusionTexture"), !shared);
|
||||
assert.equal(
|
||||
pass.bindings.some((b: any) => b.group === 1 && b.binding === 5),
|
||||
!shared,
|
||||
);
|
||||
await mesh.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
test("light variants follow first/last light lifetimes and keep hot edits rebuild-free", async () => {
|
||||
const scene = await createScene();
|
||||
const material = await new PBRMaterial(scene).ready;
|
||||
await detailedMesh(scene, material).ready;
|
||||
const code = () => pbrPipelines(latestGraph(scene))[0].code;
|
||||
assert.doesNotMatch(code(), /fn (point|spot|directional)Contribution/);
|
||||
const first = await new PointLight(scene, { intensity: 0 }).ready;
|
||||
assert.match(code(), /fn pointContribution/);
|
||||
assert.doesNotMatch(code(), /fn (spot|directional)Contribution/);
|
||||
assert.equal(code().match(/let nDotV =/g)?.length, 1);
|
||||
assert.match(code(), /let diffuseColor = base.rgb \* \(1.0 - metallic\)/);
|
||||
const before = testCore(scene).graphs.length;
|
||||
first.intensity = 3;
|
||||
first.radius = 0.4;
|
||||
const second = await new PointLight(scene).ready;
|
||||
await first.dispose();
|
||||
assert.equal(testCore(scene).graphs.length, before);
|
||||
assert.match(code(), /fn pointContribution/);
|
||||
const spot = await new SpotLight(scene).ready;
|
||||
const directional = await new DirectionalLight(scene).ready;
|
||||
assert.match(code(), /fn spotContribution/);
|
||||
assert.match(code(), /fn directionalContribution/);
|
||||
await second.dispose();
|
||||
assert.doesNotMatch(code(), /fn pointContribution/);
|
||||
await spot.dispose();
|
||||
await directional.dispose();
|
||||
assert.doesNotMatch(code(), /fn (point|spot|directional)Contribution/);
|
||||
});
|
||||
|
||||
test("direct canvas loadout preserves clear/depth and falls back for linear-color consumers", async () => {
|
||||
const scene = await createScene({
|
||||
directToCanvas: true,
|
||||
clearColor: [0.02, 0.09, 0.3, 0.5],
|
||||
});
|
||||
const material = await new PBRMaterial(scene).ready;
|
||||
const mesh = await detailedMesh(scene, material).ready;
|
||||
const assertDirect = () => {
|
||||
const graph = latestGraph(scene);
|
||||
assert.equal(
|
||||
graph.resources.textures.some((t: any) => t.id === "hdr"),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
graph.passes.some((p: any) => p.id === "present"),
|
||||
false,
|
||||
);
|
||||
const background = graph.passes.find(
|
||||
(p: any) => p.id === "canvas-background",
|
||||
);
|
||||
assert.deepEqual(background.depth, { resource: "depth", clear: 1 });
|
||||
assert.match(
|
||||
renderPipeline(graph, "canvas-background").code,
|
||||
/canvasOutput\(vec4<f32>\(0.02, 0.09, 0.3, 0.5\)\)/,
|
||||
);
|
||||
const forward = forwardPasses(graph)[0];
|
||||
assert.deepEqual(forward.color, [{ resource: "canvas", load: "load" }]);
|
||||
assert.deepEqual(forward.depth, { resource: "depth", load: "load" });
|
||||
assert.equal(pbrPipelines(graph)[0].fragment.targets[0].format, "canvas");
|
||||
assert.match(pbrPipelines(graph)[0].code, /unpack2x16float\(pack2x16float/);
|
||||
};
|
||||
const assertHdr = () =>
|
||||
assert.ok(renderPipeline(latestGraph(scene), "present"));
|
||||
assertDirect();
|
||||
const effect = new FXAA(scene);
|
||||
await effect.ready;
|
||||
assertHdr();
|
||||
await effect.dispose();
|
||||
assertDirect();
|
||||
for (const options of [{ flags: 2 }, { transmission: 0.5 }]) {
|
||||
const special = await new PBRMaterial(scene, options).ready;
|
||||
const extra = await detailedMesh(scene, special).ready;
|
||||
assertHdr();
|
||||
await extra.dispose();
|
||||
assertDirect();
|
||||
}
|
||||
const compute = new ComputePass({
|
||||
code: "@compute @workgroup_size(1) fn main() {}",
|
||||
});
|
||||
await scene.addComputePass(compute);
|
||||
assertHdr();
|
||||
assert.match(
|
||||
pbrPipelines(latestGraph(scene))[0].code,
|
||||
/fn directionalContribution/,
|
||||
);
|
||||
await scene.removeComputePass(compute.id);
|
||||
assertDirect();
|
||||
const custom = new ShaderMaterial(scene, { code: "custom WGSL" });
|
||||
await custom.ready;
|
||||
assertHdr();
|
||||
await custom.dispose();
|
||||
assertDirect();
|
||||
const basic = await new Mesh(scene, {
|
||||
vertexData: { positions: triangle.positions, indices: triangle.indices },
|
||||
}).ready;
|
||||
assertHdr();
|
||||
await basic.dispose();
|
||||
assertDirect();
|
||||
const mask = await new PBRMaterial(scene, { flags: 1 }).ready;
|
||||
const masked = await detailedMesh(scene, mask).ready;
|
||||
assertDirect();
|
||||
assert.match(
|
||||
pbrPipelines(latestGraph(scene)).find((p: any) =>
|
||||
p.code.includes("discard"),
|
||||
).code,
|
||||
/canvasOutput/,
|
||||
);
|
||||
await masked.dispose();
|
||||
await mesh.dispose();
|
||||
assertHdr();
|
||||
const ldr = await createScene({ directToCanvas: true, hdr: false });
|
||||
const ldrMaterial = await new PBRMaterial(ldr).ready;
|
||||
await detailedMesh(ldr, ldrMaterial).ready;
|
||||
assert.ok(renderPipeline(latestGraph(ldr), "present"));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user