312 lines
6.2 KiB
JavaScript
312 lines
6.2 KiB
JavaScript
|
|
export class OceanTests {
|
||
|
|
|
||
|
|
constructor( getPrimaryPipeline ) {
|
||
|
|
|
||
|
|
this.getPrimaryPipeline = getPrimaryPipeline;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
async testFft( ) {
|
||
|
|
|
||
|
|
try {
|
||
|
|
|
||
|
|
const pipeline = this.getPrimaryPipeline( );
|
||
|
|
|
||
|
|
if ( !pipeline ) {
|
||
|
|
|
||
|
|
console.warn( "No primary pipeline for testFft()" );
|
||
|
|
return;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
const N = pipeline.gridSize;
|
||
|
|
|
||
|
|
const block = pipeline.getBlockByName( "ocean" );
|
||
|
|
|
||
|
|
const rowPass = block.getPass( "RowFFT" );
|
||
|
|
|
||
|
|
const colPass = block.getPass( "ColFFT" );
|
||
|
|
|
||
|
|
const inputReal = new Float32Array( N * N );
|
||
|
|
const inputImag = new Float32Array( N * N );
|
||
|
|
|
||
|
|
for ( let y = 0; y < N; y++ ) {
|
||
|
|
|
||
|
|
for ( let x = 0; x < N; x++ ) {
|
||
|
|
|
||
|
|
const idx = y * N + x;
|
||
|
|
|
||
|
|
inputReal[ idx ] = Math.sin( 2 * Math.PI * x / N ) + 0.5 * Math.cos( 2 * Math.PI * y / N );
|
||
|
|
inputImag[ idx ] = 0;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
pipeline.memory.spectrumReal.set( inputReal );
|
||
|
|
pipeline.memory.spectrumImag.set( inputImag );
|
||
|
|
|
||
|
|
const spectrumPass = block.getPass( "Spectrum" );
|
||
|
|
|
||
|
|
spectrumPass.shader.setVariable( "spectrumReal", pipeline.memory.spectrumReal );
|
||
|
|
spectrumPass.shader.setVariable( "spectrumImag", pipeline.memory.spectrumImag );
|
||
|
|
|
||
|
|
await rowPass.execute( );
|
||
|
|
await colPass.execute( );
|
||
|
|
|
||
|
|
const gpuHeight = await colPass.shader.debugBuffer( "heightField" );
|
||
|
|
|
||
|
|
const cpuResult = this.cpuFft2D( inputReal, inputImag, N );
|
||
|
|
|
||
|
|
let maxDiff = 0;
|
||
|
|
let rms = 0;
|
||
|
|
|
||
|
|
for ( let i = 0; i < gpuHeight.length; i++ ) {
|
||
|
|
|
||
|
|
const diff = gpuHeight[ i ] - cpuResult.real[ i ];
|
||
|
|
|
||
|
|
maxDiff = Math.max( maxDiff, Math.abs( diff ) );
|
||
|
|
rms += diff * diff;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
rms = Math.sqrt( rms / gpuHeight.length );
|
||
|
|
|
||
|
|
console.log( "FFT test completed. maxDiff:", maxDiff, "rms:", rms );
|
||
|
|
|
||
|
|
} catch ( e ) {
|
||
|
|
|
||
|
|
console.error( "FFT test failed:", e );
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
async testSpectrum( ) {
|
||
|
|
|
||
|
|
try {
|
||
|
|
|
||
|
|
const pipeline = this.getPrimaryPipeline( );
|
||
|
|
|
||
|
|
if ( !pipeline ) {
|
||
|
|
|
||
|
|
console.warn( "No primary pipeline for testSpectrum()" );
|
||
|
|
return;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
const N = pipeline.gridSize;
|
||
|
|
|
||
|
|
const block = pipeline.getBlockByName( "ocean" );
|
||
|
|
|
||
|
|
const spectrumPass = block.getPass( "Spectrum" );
|
||
|
|
|
||
|
|
const h0Real = pipeline.memory.h0Real;
|
||
|
|
const h0Imag = pipeline.memory.h0Imag;
|
||
|
|
|
||
|
|
const time = 0;
|
||
|
|
const heightScale = pipeline.memory.computeParams[ 2 ];
|
||
|
|
|
||
|
|
const cpuSpec = this.cpuSpectrum2D( h0Real, h0Imag, N, time, heightScale );
|
||
|
|
|
||
|
|
pipeline.memory.computeParams[ 0 ] = time;
|
||
|
|
pipeline.memory.computeParams[ 1 ] = N;
|
||
|
|
pipeline.memory.computeParams[ 2 ] = heightScale;
|
||
|
|
|
||
|
|
spectrumPass.shader.setVariable( "params", pipeline.memory.computeParams );
|
||
|
|
|
||
|
|
await spectrumPass.execute( );
|
||
|
|
|
||
|
|
const gpuReal = await spectrumPass.shader.debugBuffer( "spectrumReal" );
|
||
|
|
const gpuImag = await spectrumPass.shader.debugBuffer( "spectrumImag" );
|
||
|
|
|
||
|
|
let maxDiff = 0;
|
||
|
|
let rms = 0;
|
||
|
|
|
||
|
|
for ( let i = 0; i < gpuReal.length; i++ ) {
|
||
|
|
|
||
|
|
const dr = gpuReal[ i ] - cpuSpec.real[ i ];
|
||
|
|
const di = gpuImag[ i ] - cpuSpec.imag[ i ];
|
||
|
|
|
||
|
|
const magDiff = Math.sqrt( dr * dr + di * di );
|
||
|
|
|
||
|
|
if ( magDiff > maxDiff ) {
|
||
|
|
|
||
|
|
maxDiff = magDiff;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
rms += dr * dr + di * di;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
rms = Math.sqrt( rms / gpuReal.length );
|
||
|
|
|
||
|
|
console.log( "Spectrum test completed. maxDiff:", maxDiff, "rms:", rms );
|
||
|
|
|
||
|
|
} catch ( e ) {
|
||
|
|
|
||
|
|
console.error( "Spectrum test failed:", e );
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
cpuFft1D( realIn, imagIn, N ) {
|
||
|
|
|
||
|
|
const outR = new Float32Array( N );
|
||
|
|
const outI = new Float32Array( N );
|
||
|
|
|
||
|
|
for ( let k = 0; k < N; k++ ) {
|
||
|
|
|
||
|
|
let sumR = 0;
|
||
|
|
let sumI = 0;
|
||
|
|
|
||
|
|
for ( let n = 0; n < N; n++ ) {
|
||
|
|
|
||
|
|
const angle = -2 * Math.PI * k * n / N;
|
||
|
|
|
||
|
|
const c = Math.cos( angle );
|
||
|
|
const s = Math.sin( angle );
|
||
|
|
|
||
|
|
const xr = realIn[ n ];
|
||
|
|
const xi = imagIn[ n ];
|
||
|
|
|
||
|
|
sumR += xr * c - xi * s;
|
||
|
|
sumI += xr * s + xi * c;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
const invScale = 1 / N;
|
||
|
|
|
||
|
|
outR[ k ] = sumR * invScale;
|
||
|
|
outI[ k ] = sumI * invScale;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
return { real: outR, imag: outI };
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
cpuFft2D( realIn, imagIn, N ) {
|
||
|
|
|
||
|
|
const realTmp = new Float32Array( realIn );
|
||
|
|
const imagTmp = new Float32Array( imagIn );
|
||
|
|
|
||
|
|
for ( let row = 0; row < N; row++ ) {
|
||
|
|
|
||
|
|
const rowOffset = row * N;
|
||
|
|
|
||
|
|
const rSlice = realTmp.subarray( rowOffset, rowOffset + N );
|
||
|
|
const iSlice = imagTmp.subarray( rowOffset, rowOffset + N );
|
||
|
|
|
||
|
|
const res = this.cpuFft1D( rSlice, iSlice, N );
|
||
|
|
|
||
|
|
for ( let x = 0; x < N; x++ ) {
|
||
|
|
|
||
|
|
realTmp[ rowOffset + x ] = res.real[ x ];
|
||
|
|
imagTmp[ rowOffset + x ] = res.imag[ x ];
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
const realOut = new Float32Array( N * N );
|
||
|
|
const imagOut = new Float32Array( N * N );
|
||
|
|
|
||
|
|
for ( let col = 0; col < N; col++ ) {
|
||
|
|
|
||
|
|
const rCol = new Float32Array( N );
|
||
|
|
const iCol = new Float32Array( N );
|
||
|
|
|
||
|
|
for ( let row = 0; row < N; row++ ) {
|
||
|
|
|
||
|
|
const idx = row * N + col;
|
||
|
|
|
||
|
|
rCol[ row ] = realTmp[ idx ];
|
||
|
|
iCol[ row ] = imagTmp[ idx ];
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
const res = this.cpuFft1D( rCol, iCol, N );
|
||
|
|
|
||
|
|
for ( let row = 0; row < N; row++ ) {
|
||
|
|
|
||
|
|
const idx = row * N + col;
|
||
|
|
|
||
|
|
realOut[ idx ] = res.real[ row ];
|
||
|
|
imagOut[ idx ] = res.imag[ row ];
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
return { real: realOut, imag: imagOut };
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
cpuSpectrum2D( h0Real, h0Imag, size, time, heightScale ) {
|
||
|
|
|
||
|
|
const GRAVITY = 9.81;
|
||
|
|
|
||
|
|
const realOut = new Float32Array( size * size );
|
||
|
|
const imagOut = new Float32Array( size * size );
|
||
|
|
|
||
|
|
for ( let ky = 0; ky < size; ky++ ) {
|
||
|
|
|
||
|
|
for ( let kx = 0; kx < size; kx++ ) {
|
||
|
|
|
||
|
|
const kxShift = kx - size / 2;
|
||
|
|
const kyShift = ky - size / 2;
|
||
|
|
|
||
|
|
const kLength = Math.sqrt( kxShift * kxShift + kyShift * kyShift );
|
||
|
|
|
||
|
|
const idx = ky * size + kx;
|
||
|
|
|
||
|
|
if ( kLength === 0 ) {
|
||
|
|
|
||
|
|
continue;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
const mx = ( size - kx ) % size;
|
||
|
|
const my = ( size - ky ) % size;
|
||
|
|
|
||
|
|
const mirrorIdx = my * size + mx;
|
||
|
|
|
||
|
|
const h0R = h0Real[ idx ];
|
||
|
|
const h0I = h0Imag[ idx ];
|
||
|
|
|
||
|
|
const h0mR = h0Real[ mirrorIdx ];
|
||
|
|
const h0mI = h0Imag[ mirrorIdx ];
|
||
|
|
|
||
|
|
const omega = Math.sqrt( GRAVITY * kLength );
|
||
|
|
|
||
|
|
const theta = omega * time;
|
||
|
|
|
||
|
|
const cosT = Math.cos( theta );
|
||
|
|
const sinT = Math.sin( theta );
|
||
|
|
|
||
|
|
const hPosR = h0R * cosT - h0I * sinT;
|
||
|
|
const hPosI = h0R * sinT + h0I * cosT;
|
||
|
|
|
||
|
|
const hNegR = h0mR * cosT + h0mI * sinT;
|
||
|
|
const hNegI = h0mI * cosT - h0mR * sinT;
|
||
|
|
|
||
|
|
realOut[ idx ] = ( hPosR + hNegR ) * heightScale;
|
||
|
|
imagOut[ idx ] = ( hPosI + hNegI ) * heightScale;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
return { real: realOut, imag: imagOut };
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|