Finished up most documentation
This commit is contained in:
127
spikes/card-harness/scripts/shader-pack-preparation.mjs
Normal file
127
spikes/card-harness/scripts/shader-pack-preparation.mjs
Normal file
@@ -0,0 +1,127 @@
|
||||
import * as THREE from 'three'
|
||||
import { PackOpening, packContents } from '../src/packOpening.ts'
|
||||
import { createStudioCubeTexture } from '../src/cardMaterial.ts'
|
||||
|
||||
export async function verifyPackPreparation() {
|
||||
const startedAt = performance.now()
|
||||
const loader = new THREE.TextureLoader()
|
||||
const paths = ['david-front', 'david-finish-mask', 'timothy-front', 'timothy-finish-mask', 'card-back', 'linen-normal']
|
||||
const textures = await Promise.all(paths.map((path) => loader.loadAsync(`/reference/${path}.png`)))
|
||||
for (const index of [0, 2, 4]) textures[index].colorSpace = THREE.SRGBColorSpace
|
||||
textures[5].wrapS = textures[5].wrapT = THREE.RepeatWrapping
|
||||
const environment = createStudioCubeTexture()
|
||||
const results = []
|
||||
try {
|
||||
for (const lightType of ['Point', 'Directional', 'Spot']) {
|
||||
// A fresh context prevents the preceding pixel test from accidentally warming pack shaders.
|
||||
const renderer = new THREE.WebGLRenderer({ antialias: false })
|
||||
renderer.setSize(192, 264)
|
||||
renderer.outputColorSpace = THREE.SRGBColorSpace
|
||||
renderer.toneMapping = THREE.ACESFilmicToneMapping
|
||||
const shaderErrors = []
|
||||
renderer.debug.onShaderError = (gl, program, vertex, fragment) => {
|
||||
shaderErrors.push([gl.getProgramInfoLog(program), gl.getShaderInfoLog(vertex),
|
||||
gl.getShaderInfoLog(fragment)].join('\n'))
|
||||
}
|
||||
const scene = new THREE.Scene()
|
||||
scene.background = new THREE.Color('#0b0e14')
|
||||
scene.environment = environment
|
||||
const lightPosition = new THREE.Vector3(2.2, 2.5, 4.4)
|
||||
const light = lightType === 'Point' ? new THREE.PointLight('#fff0d0', 34, 20, 1.7)
|
||||
: lightType === 'Directional' ? new THREE.DirectionalLight('#fff0d0', 5.4)
|
||||
: new THREE.SpotLight('#fff0d0', 54, 20, THREE.MathUtils.degToRad(28), 0.55, 1.7)
|
||||
light.position.copy(lightPosition)
|
||||
scene.add(light)
|
||||
if (light.target) scene.add(light.target)
|
||||
const backMaterial = new THREE.MeshPhysicalMaterial({
|
||||
map: textures[4], roughness: 0.42, metalness: 0, clearcoat: 0.2,
|
||||
clearcoatRoughness: 0.24, normalMap: null, side: THREE.DoubleSide,
|
||||
})
|
||||
const pack = new PackOpening({
|
||||
canvas: renderer.domElement,
|
||||
textures: {
|
||||
David: { artwork: textures[0], mask: textures[1] },
|
||||
Timothy: { artwork: textures[2], mask: textures[3] },
|
||||
},
|
||||
backMaterial, normalMap: textures[5], environment, lightPosition, onChange() {},
|
||||
})
|
||||
pack.resize(192, 264)
|
||||
const revealed = []
|
||||
renderer.domElement.addEventListener('packreveal', (event) => revealed.push(event.detail))
|
||||
const originalNow = performance.now
|
||||
try {
|
||||
const programsBeforePreparation = renderer.info.programs.length
|
||||
await pack.prepareGPU(renderer, scene)
|
||||
if (pack.root.visible || pack.state !== 'sealed' || revealed.length) {
|
||||
throw new Error('Preparation changed visibility or reveal state')
|
||||
}
|
||||
const preparedPrograms = new Set(renderer.info.programs.map((program) => program.id))
|
||||
if (!preparedPrograms.size) throw new Error('Preparation compiled no programs')
|
||||
const texturesAfterPreparation = renderer.info.memory.textures
|
||||
const unexpectedPrograms = new Map()
|
||||
const visitedStates = new Set()
|
||||
let renderedFrames = 0
|
||||
let maximumTextureCount = texturesAfterPreparation
|
||||
const textureGrowth = []
|
||||
let now = originalNow.call(performance)
|
||||
performance.now = () => now
|
||||
function frame() {
|
||||
pack.tick(now)
|
||||
renderer.render(scene, pack.camera)
|
||||
renderedFrames++
|
||||
visitedStates.add(pack.state)
|
||||
if (renderer.info.memory.textures > maximumTextureCount) {
|
||||
textureGrowth.push({ state: pack.state, count: renderer.info.memory.textures })
|
||||
}
|
||||
maximumTextureCount = Math.max(maximumTextureCount, renderer.info.memory.textures)
|
||||
for (const program of renderer.info.programs) {
|
||||
if (!preparedPrograms.has(program.id) && !unexpectedPrograms.has(program.id)) {
|
||||
unexpectedPrograms.set(program.id, { id: program.id, name: program.name, firstState: pack.state })
|
||||
}
|
||||
}
|
||||
const gl = renderer.getContext()
|
||||
if (shaderErrors.length || gl.isContextLost() || gl.getError() !== gl.NO_ERROR) {
|
||||
throw new Error(`Pack WebGL rendering failed: ${shaderErrors.join('\n')}`)
|
||||
}
|
||||
}
|
||||
scene.add(pack.root)
|
||||
pack.setActive(true)
|
||||
frame()
|
||||
const durations = { opening: 3200, lifting: 620, revealing: 820, advancing: 650 }
|
||||
for (let action = 0; action < 10 && pack.state !== 'complete'; action++) {
|
||||
pack.primary()
|
||||
const duration = durations[pack.state]
|
||||
if (!duration) throw new Error(`Unexpected animated state: ${pack.state}`)
|
||||
const started = now
|
||||
for (const progress of [0, 0.1, 0.25, 0.4, 0.5, 0.65, 0.8, 0.9, 1.001]) {
|
||||
now = started + duration * progress
|
||||
frame()
|
||||
}
|
||||
}
|
||||
if (pack.state !== 'complete' || JSON.stringify(revealed.map(({ fixture, finish, substrate }) =>
|
||||
({ fixture, finish, substrate }))) !== JSON.stringify(packContents)) {
|
||||
throw new Error('First reveal cycle did not complete in authored order')
|
||||
}
|
||||
pack.restart()
|
||||
frame()
|
||||
results.push({
|
||||
lightType, passed: unexpectedPrograms.size === 0, programsBeforePreparation,
|
||||
programsAfterPreparation: preparedPrograms.size,
|
||||
programsAfterRevealAndRestart: renderer.info.programs.length,
|
||||
unexpectedPrograms: [...unexpectedPrograms.values()], texturesAfterPreparation,
|
||||
maximumTextureCount, textureGrowth, renderedFrames, visitedStates: [...visitedStates], revealed,
|
||||
})
|
||||
} finally {
|
||||
performance.now = originalNow
|
||||
pack.dispose()
|
||||
backMaterial.dispose()
|
||||
renderer.dispose()
|
||||
renderer.forceContextLoss()
|
||||
}
|
||||
}
|
||||
return { passed: results.every((result) => result.passed),
|
||||
durationMs: Math.round(performance.now() - startedAt), results }
|
||||
} finally {
|
||||
for (const texture of [...textures, environment]) texture.dispose()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user