Initial commit
This commit is contained in:
544
Demos/Graphics/demo.js
Normal file
544
Demos/Graphics/demo.js
Normal file
@@ -0,0 +1,544 @@
|
||||
|
||||
import Shader from "../../framework/WebGpu.js"
|
||||
|
||||
import Matrix4 from "../../framework/Matrix4.js"
|
||||
|
||||
import Vector3 from "../../framework/Vector3.js"
|
||||
|
||||
import Camera from "../../framework/Camera.js";
|
||||
|
||||
import EventManager from "../../framework/eventManager.js";
|
||||
|
||||
import ShaderInpector from "../../framework/ShaderInpector.js";
|
||||
|
||||
|
||||
export class ParticleSimulation {
|
||||
|
||||
canvas;
|
||||
|
||||
device;
|
||||
|
||||
camera;
|
||||
|
||||
useLocalSort = true;
|
||||
|
||||
eventManager = new EventManager();
|
||||
|
||||
frameCount = 0;
|
||||
|
||||
setCanvas( canvas ) {
|
||||
|
||||
this.canvas = canvas;
|
||||
|
||||
this.eventManager.setCanvas( canvas );
|
||||
|
||||
}
|
||||
|
||||
async setup( offscreenCanvas, width, height ) {
|
||||
|
||||
offscreenCanvas.width = width;
|
||||
|
||||
offscreenCanvas.height = height;
|
||||
|
||||
this.canvas = offscreenCanvas;
|
||||
|
||||
//console.log( this.canvas.width, this.canvas.height );
|
||||
|
||||
const context = offscreenCanvas.getContext("webgpu");
|
||||
|
||||
|
||||
|
||||
|
||||
this.camera = new Camera( [0, 0, 5], [0, -.3, 0], [0, 1, 0] );
|
||||
|
||||
this.eventManager.setup( offscreenCanvas, this.camera );
|
||||
|
||||
//this.eventManager.registerEventListenersNode();
|
||||
|
||||
const adapter = await self.navigator.gpu.requestAdapter();
|
||||
|
||||
if ( !adapter ) {
|
||||
|
||||
throw new Error("Failed to get GPU adapter");
|
||||
|
||||
}
|
||||
|
||||
this.device = await adapter.requestDevice();
|
||||
|
||||
this.particleCount = 8192 * 2;
|
||||
|
||||
const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
|
||||
|
||||
context.configure({
|
||||
device: this.device,
|
||||
format: presentationFormat,
|
||||
alphaMode: "opaque"
|
||||
});
|
||||
|
||||
let boundsMinimum = new Vector3( -2, -2, -2 );
|
||||
|
||||
let boundsMaximum = new Vector3( 2, 2, 2 );
|
||||
|
||||
let cellsPerDimension = 12;
|
||||
|
||||
let gravity = -2.3;
|
||||
|
||||
this.workgroupSize = 64;
|
||||
|
||||
this.numWorkgroups = Math.ceil( this.particleCount / this.workgroupSize );
|
||||
|
||||
|
||||
var jArray = new Array();
|
||||
|
||||
var kArray = new Array();
|
||||
|
||||
if( this.useLocalSort ) {
|
||||
|
||||
var startK = this.workgroupSize * 2; // 2 * 256; 256 = this.workgroupSize
|
||||
|
||||
} else {
|
||||
|
||||
var startK = 2;
|
||||
|
||||
}
|
||||
|
||||
for (let k = startK; k <= this.particleCount; k <<= 1 ) {
|
||||
|
||||
for (let j = k >> 1; j > 0; j >>= 1 ) {
|
||||
|
||||
jArray.push( j );
|
||||
|
||||
kArray.push( k );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
this.gravityShader = new Shader( this.device );
|
||||
|
||||
await this.gravityShader.setup( "../../shaders/gravity.wgsl" );
|
||||
|
||||
this.gravityShader.setVariable("gravity", gravity );
|
||||
|
||||
this.gravityShader.setVariable("updateDistancesAndIndices", 1);
|
||||
|
||||
|
||||
this.resetParticlePositions();
|
||||
|
||||
|
||||
this.copyIndicesShader = new Shader( this.device );
|
||||
|
||||
await this.copyIndicesShader.setup( "../../shaders/copyBuffer.wgsl" );
|
||||
|
||||
|
||||
this.renderShader = new Shader( this.device );
|
||||
|
||||
|
||||
this.renderShader.setCanvas( this.canvas );
|
||||
|
||||
await this.renderShader.setup("../../shaders/points.wgsl");
|
||||
|
||||
|
||||
|
||||
const quadOffsets = new Float32Array( [
|
||||
// Triangle 1
|
||||
-1, -1, // bottom-left
|
||||
1, -1, // bottom-right
|
||||
1, 1, // top-right
|
||||
|
||||
// Triangle 2
|
||||
-1, -1, // bottom-left
|
||||
1, 1, // top-right
|
||||
-1, 1 // top-left
|
||||
]);
|
||||
|
||||
this.renderShader.setAttribute( "quadOffset", quadOffsets );
|
||||
|
||||
|
||||
this.findGridHashShader = new Shader( this.device );
|
||||
|
||||
await this.findGridHashShader.setup( "../../shaders/findGridHash.wgsl" );
|
||||
|
||||
//console.log("this.gravityShader.getBuffer(\"positions\")", this.gravityShader.getBuffer("positions"));
|
||||
|
||||
this.findGridHashShader.setBuffer( "positions", this.gravityShader.getBuffer("positions") );
|
||||
|
||||
this.findGridHashShader.setVariable( "cellCount", cellsPerDimension );
|
||||
|
||||
this.findGridHashShader.setVariable( "gridMin", boundsMinimum );
|
||||
|
||||
this.findGridHashShader.setVariable( "gridMax", boundsMaximum );
|
||||
|
||||
|
||||
if( this.useLocalSort ) {
|
||||
|
||||
this.localSortShader = new Shader( this.device );
|
||||
|
||||
await this.localSortShader.setup( "../../shaders/localSort.wgsl" );
|
||||
|
||||
this.localSortShader.setBuffer( "gridHashes", this.findGridHashShader.getBuffer("gridHashes" ) );
|
||||
|
||||
this.localSortShader.setBuffer( "indices", this.findGridHashShader.getBuffer("indices" ) );
|
||||
|
||||
this.localSortShader.setVariable( "totalCount", this.particleCount );
|
||||
|
||||
}
|
||||
|
||||
this.bitonicSortGridHashShader = new Shader( this.device );
|
||||
|
||||
await this.bitonicSortGridHashShader.setup( "../../shaders/bitonicSortUIntMultiPass.wgsl" );
|
||||
|
||||
if( this.useLocalSort ) {
|
||||
|
||||
this.bitonicSortGridHashShader.setBuffer( "gridHashes", this.localSortShader.getBuffer("gridHashes") );
|
||||
|
||||
this.bitonicSortGridHashShader.setBuffer( "indices", this.localSortShader.getBuffer("indices") );
|
||||
|
||||
} else {
|
||||
|
||||
this.bitonicSortGridHashShader.setBuffer( "gridHashes", this.findGridHashShader.getBuffer("gridHashes" ) );
|
||||
|
||||
this.bitonicSortGridHashShader.setBuffer( "indices", this.findGridHashShader.getBuffer("indices") );
|
||||
|
||||
}
|
||||
|
||||
this.bitonicSortGridHashShader.setVariable( "totalCount", this.particleCount );
|
||||
|
||||
this.bitonicSortGridHashShader.setVariable( "jArray", jArray );
|
||||
|
||||
this.bitonicSortGridHashShader.setVariable( "kArray", kArray );
|
||||
|
||||
|
||||
this.findGridHashRangeShader = new Shader( this.device );
|
||||
|
||||
await this.findGridHashRangeShader.setup( "../../shaders/findGridHashRanges.wgsl" );
|
||||
|
||||
|
||||
this.collisionDetectionShader = new Shader( this.device );
|
||||
|
||||
await this.collisionDetectionShader.setup( "../../shaders/collisionDetection.wgsl" );
|
||||
|
||||
this.collisionDetectionShader.setBuffer( "positions", this.gravityShader.getBuffer("positions" ) );
|
||||
|
||||
this.collisionDetectionShader.setBuffer( "velocities", this.gravityShader.getBuffer("velocities") );
|
||||
|
||||
this.collisionDetectionShader.setBuffer( "positions", this.gravityShader.getBuffer("positions" ) );
|
||||
|
||||
this.collisionDetectionShader.setBuffer( "velocities", this.gravityShader.getBuffer("velocities") );
|
||||
|
||||
this.collisionDetectionShader.setBuffer( "gridHashes", this.findGridHashShader.getBuffer("gridHashes") );
|
||||
|
||||
|
||||
if( this.useLocalSort ) {
|
||||
|
||||
this.collisionDetectionShader.setBuffer( "hashSortedIndices", this.localSortShader.getBuffer("indices") );
|
||||
|
||||
} else {
|
||||
|
||||
this.collisionDetectionShader.setBuffer( "hashSortedIndices", this.bitonicSortGridHashShader.getBuffer("indices") );
|
||||
|
||||
}
|
||||
|
||||
|
||||
this.collisionDetectionShader.setVariable( "cellCount", cellsPerDimension );
|
||||
|
||||
this.collisionDetectionShader.setVariable( "gridMin", boundsMinimum );
|
||||
|
||||
this.collisionDetectionShader.setVariable( "gridMax", boundsMaximum );
|
||||
|
||||
this.collisionDetectionShader.setBuffer( "startIndices", this.findGridHashRangeShader.getBuffer("startIndices") );
|
||||
|
||||
this.collisionDetectionShader.setBuffer( "endIndices", this.findGridHashRangeShader.getBuffer("endIndices") );
|
||||
|
||||
this.collisionDetectionShader.setVariable( "collisionRadius", 0.06 );
|
||||
|
||||
|
||||
this.bitonicSortShader = new Shader(this.device, "../../shaders/bitonicSort.wgsl");
|
||||
|
||||
await this.bitonicSortShader.addStage("main", GPUShaderStage.COMPUTE );
|
||||
|
||||
|
||||
this.bitonicSortShader.setBuffer( "compare", this.gravityShader.getBuffer("distances") );
|
||||
|
||||
this.bitonicSortShader.setBuffer( "indices", this.gravityShader.getBuffer("indices") );
|
||||
|
||||
this.bitonicSortShader.setVariable( "totalCount", this.particleCount );
|
||||
|
||||
|
||||
this.findGridHashRangeShader.setBuffer( "gridHashes", this.bitonicSortGridHashShader.getBuffer("gridHashes") );
|
||||
|
||||
this.findGridHashRangeShader.setBuffer( "indices", this.bitonicSortGridHashShader.getBuffer("indices") );
|
||||
|
||||
this.findGridHashRangeShader.setVariable( "totalCount", this.particleCount );
|
||||
|
||||
|
||||
this.copyIndicesShader.setBuffer( "indices", this.bitonicSortShader.getBuffer("indices") );
|
||||
|
||||
|
||||
this.renderShader.setBuffer( "positions", this.gravityShader.getBuffer("positions") );
|
||||
|
||||
this.renderShader.setBuffer( "sortedIndices", this.copyIndicesShader.getBuffer("sortedIndices") );
|
||||
|
||||
this.renderShader.setCanvas( this.canvas );
|
||||
|
||||
//var simulation = new particleSimulation();
|
||||
|
||||
this.render();
|
||||
|
||||
|
||||
return;
|
||||
|
||||
//document.getElementById("resetParticlesButton").addEventListener("click", resetParticlePositions);
|
||||
|
||||
|
||||
}
|
||||
|
||||
resetParticlePositions() {
|
||||
|
||||
const positions = new Float32Array( this.particleCount * 3 );
|
||||
|
||||
const velocities = new Float32Array( this.particleCount * 3 );
|
||||
|
||||
|
||||
for (var i = 0; i < this.particleCount; i++) {
|
||||
|
||||
positions[ i * 3 + 0 ] = Math.random() * 2.0 - 1.0;
|
||||
|
||||
positions[ i * 3 + 1 ] = Math.random() * 2.0 - 1.0;
|
||||
|
||||
positions[ i * 3 + 2 ] = Math.random() * 2.0 - 1.0;
|
||||
|
||||
velocities[ i * 3 + 0 ] = 0;
|
||||
|
||||
velocities[ i * 3 + 1 ] = 0;
|
||||
|
||||
velocities[ i * 3 + 2 ] = 0;
|
||||
|
||||
}
|
||||
|
||||
this.gravityShader.setVariable("positions", positions);
|
||||
|
||||
this.gravityShader.setVariable("velocities", velocities);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
updateTimeDelta() {
|
||||
|
||||
const now = performance.now();
|
||||
|
||||
this.deltaTimeValue = ( now - this.lastFrameTime ) / 1000;
|
||||
|
||||
this.lastFrameTime = now;
|
||||
|
||||
}
|
||||
|
||||
async sortGridHash() {
|
||||
|
||||
const commandEncoder = this.device.createCommandEncoder();
|
||||
|
||||
// Local sort 256 items
|
||||
if( this.useLocalSort ) {
|
||||
|
||||
const passEncoder = commandEncoder.beginComputePass();
|
||||
|
||||
passEncoder.setPipeline( this.localSortShader.pipeline );
|
||||
|
||||
for ( const [ index, bindGroup ] of this.localSortShader.bindGroups.entries() ) {
|
||||
|
||||
passEncoder.setBindGroup( index, bindGroup );
|
||||
|
||||
}
|
||||
|
||||
passEncoder.dispatchWorkgroups( this.numWorkgroups );
|
||||
|
||||
passEncoder.end();
|
||||
|
||||
}
|
||||
|
||||
// Bitonic global merge
|
||||
{
|
||||
const threadPassIndices = new Uint32Array( this.particleCount );
|
||||
|
||||
for (var i = 0; i < this.particleCount; i++) {
|
||||
|
||||
threadPassIndices[0] = 0;
|
||||
|
||||
}
|
||||
|
||||
this.bitonicSortGridHashShader.setVariable("threadPassIndices", threadPassIndices);
|
||||
|
||||
const passEncoder = commandEncoder.beginComputePass();
|
||||
|
||||
passEncoder.setPipeline( this.bitonicSortGridHashShader.pipeline );
|
||||
|
||||
for ( const [ index, bindGroup ] of this.bitonicSortGridHashShader.bindGroups.entries() ) {
|
||||
|
||||
passEncoder.setBindGroup( index, bindGroup );
|
||||
|
||||
}
|
||||
|
||||
if( this.useLocalSort ) {
|
||||
|
||||
var startK = this.workgroupSize * 2;
|
||||
|
||||
} else {
|
||||
|
||||
var startK = 2;
|
||||
|
||||
}
|
||||
|
||||
for (let k = startK; k <= this.particleCount; k <<= 1 ) {
|
||||
|
||||
for (let j = k >> 1; j > 0; j >>= 1 ) {
|
||||
|
||||
passEncoder.dispatchWorkgroups( this.numWorkgroups );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
passEncoder.end();
|
||||
|
||||
}
|
||||
|
||||
const commandBuffer = commandEncoder.finish();
|
||||
|
||||
await this.device.queue.submit( [ commandBuffer ] );
|
||||
|
||||
await this.device.queue.onSubmittedWorkDone();
|
||||
|
||||
}
|
||||
|
||||
async sortDistance() {
|
||||
|
||||
await this.gravityShader.execute( this.numWorkgroups );
|
||||
|
||||
this.gravityShader.setVariable("updateDistancesAndIndices", 0);
|
||||
|
||||
for (let k = 2; k <= this.particleCount; k <<= 1) {
|
||||
|
||||
for (let j = k >> 1; j > 0; j >>= 1) {
|
||||
|
||||
const commandBuffers = new Array();
|
||||
|
||||
await this.bitonicSortShader.setVariable( "j", j ); // Must await uniform update
|
||||
await this.bitonicSortShader.setVariable( "k", k );
|
||||
|
||||
const commandEncoder = this.device.createCommandEncoder();
|
||||
|
||||
const passEncoder = commandEncoder.beginComputePass();
|
||||
|
||||
passEncoder.setPipeline( this.bitonicSortShader.pipeline );
|
||||
|
||||
for ( const [ index, bindGroup ] of this.bitonicSortShader.bindGroups.entries() ) {
|
||||
|
||||
passEncoder.setBindGroup( index, bindGroup );
|
||||
|
||||
}
|
||||
|
||||
passEncoder.dispatchWorkgroups( this.numWorkgroups );
|
||||
|
||||
passEncoder.end();
|
||||
|
||||
commandBuffers.push( commandEncoder.finish() );
|
||||
|
||||
this.device.queue.submit( commandBuffers );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
this.gravityShader.setVariable( "updateDistancesAndIndices", 1 );
|
||||
|
||||
await this.copyIndicesShader.execute( this.numWorkgroups );
|
||||
|
||||
}
|
||||
|
||||
async findStartEndIndices( logBuffers ) {
|
||||
|
||||
await this.gravityShader.execute( this.numWorkgroups );
|
||||
|
||||
await this.gravityShader.setVariable("updateDistancesAndIndices", 0);
|
||||
|
||||
await this.findGridHashShader.execute( this.numWorkgroups );
|
||||
|
||||
await this.sortGridHash();
|
||||
|
||||
await this.findGridHashRangeShader.execute( this.workgroupSize );
|
||||
|
||||
if( logBuffers ) {
|
||||
|
||||
await this.bitonicSortGridHashShader.debugBuffer("indices");
|
||||
|
||||
await this.bitonicSortGridHashShader.debugBuffer("gridHashes");
|
||||
|
||||
await this.findGridHashRangeShader.debugBuffer("startIndices");
|
||||
|
||||
await this.findGridHashRangeShader.debugBuffer("endIndices");
|
||||
}
|
||||
|
||||
await this.gravityShader.setVariable("updateDistancesAndIndices", 1);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
async render() {
|
||||
|
||||
this.updateTimeDelta();
|
||||
|
||||
const viewMatrixData = this.camera.getViewMatrix();
|
||||
|
||||
const projectionMatrixData = Matrix4.createProjectionMatrix( this.camera, this.canvas )
|
||||
|
||||
const viewProjectionMatrix = Matrix4.multiply( projectionMatrixData, viewMatrixData );
|
||||
|
||||
const cameraWorldMatrix = Matrix4.invert( viewMatrixData );
|
||||
|
||||
const cameraRight = Matrix4.getColumn( cameraWorldMatrix, 0 );
|
||||
|
||||
const cameraUp = Matrix4.getColumn( cameraWorldMatrix, 1 );
|
||||
|
||||
const cameraPosition = Matrix4.getColumn( cameraWorldMatrix, 3 );
|
||||
|
||||
|
||||
this.gravityShader.execute( this.numWorkgroups );
|
||||
|
||||
this.gravityShader.setVariable( "cameraPosition", cameraPosition );
|
||||
|
||||
this.gravityShader.setVariable( "deltaTimeSeconds", this.deltaTimeValue );
|
||||
|
||||
if ( this.frameCount % 20 === 0 ) {
|
||||
|
||||
this.sortDistance();
|
||||
|
||||
}
|
||||
|
||||
this.renderShader.setVariable( "viewProjectionMatrix", viewProjectionMatrix );
|
||||
|
||||
this.renderShader.setVariable( "cameraRight", cameraRight );
|
||||
|
||||
this.renderShader.setVariable( "cameraUp", cameraUp );
|
||||
|
||||
this.renderShader.renderToCanvas( 6, this.particleCount, 0 );
|
||||
|
||||
|
||||
this.frameCount++;
|
||||
|
||||
if ( this.frameCount % 6 === 0 ) {
|
||||
|
||||
this.findStartEndIndices();
|
||||
|
||||
}
|
||||
|
||||
this.collisionDetectionShader.setVariable( "deltaTimeSeconds", this.deltaTimeValue );
|
||||
|
||||
this.collisionDetectionShader.execute( this.numWorkgroups );
|
||||
|
||||
requestAnimationFrame( this.render.bind( this ) );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user