www.xbdev.net
xbdev - software development
Tuesday April 29, 2025
Home | Contact | Support | WebGPU Graphics and Compute ... | WebGPU 'Compute'.. Compute, Algorithms, and Code.....
     
 

WebGPU 'Compute'..

Compute, Algorithms, and Code.....

 

Fluid Dynamics


Emulate the visual characteristics of fluids and smoke using multiple buffers on the compute shader.


Simple fluid dynamics demo using a start pattern with words.
Simple fluid dynamics demo using a start pattern with words.


Functions Used: requestAdapter(), getPreferredCanvasFormat(), createCommandEncoder(), beginRenderPass(), setPipeline(), draw(), end(), submit(), getCurrentTexture(), createView(), createShaderModule()

So we can distribute the fluid dynamic calculations across the GPU threads, we use a double buffering approach for the density and velocity arrays.

We implement the diffusion, advect and curl mechanisms.

• Diffusion - Process by which molecules in a fluid move from regions of higher concentration to regions of lower concentration, resulting in a more uniform distribution. This movement occurs due to random thermal motion, leading to the gradual mixing of substances within the fluid. Acts as a mechanism for the redistribution of mass, energy, and momentum, playing a crucial role in phenomena such as mass transport, heat conduction, and the dissipation of vortices within fluid systems.

• Advect - The advect step is a computational process where the fluid properties, such as velocity, temperature, or concentration of a substance, are transported through the flow field according to the local velocity field. This step involves calculating the movement of fluid parcels over small time intervals, typically using numerical methods like finite difference or finite volume schemes. The advect step is essential for accurately simulating the advection of quantities within the fluid domain, capturing phenomena such as fluid mixing, dispersion, and transport of properties like heat or chemical species.

• Curl - The curl is a mathematical operation that describes the rotation or vorticity of a vector field. It quantifies the tendency of the field to circulate around a point or axis, indicating the local spinning motion within the field. The curl of a vector field is a vector quantity obtained by taking the cross product of the gradient operator with the vector field itself. It's particularly useful for understanding and analyzing the presence of vortices, eddies, and rotational flow patterns within a fluid, providing insights into the dynamics of fluid motion and turbulence.




Complete Code


let div document.createElement('div');
document.body.appendChilddiv );
div.style['font-size'] = '20pt';
function 
log)
{
  
console.log);
  
let args = [...arguments].join(' ');
  
div.innerHTML += args '<br><br>';
}

log('WebGPU Compute Example');

if (!
navigator.gpu) { log("WebGPU is not supported (or is it disabled? flags/settings)"); return; }

const 
adapter await navigator.gpu.requestAdapter();
const 
device  await adapter.requestDevice();


let canvas document.createElement('canvas');
document.body.appendChildcanvas ); canvas.height canvas.width 500;

const 
context canvas.getContext('webgpu');
const 
presentationFormat navigator.gpu.getPreferredCanvasFormat(); 
console.log('presentationFormat:'presentationFormat );


context.configure({ devicedevice
                    
usageGPUTextureUsage.RENDER_ATTACHMENT GPUTextureUsage.COPY_SRC GPUTextureUsage.COPY_DST,
                    
format"rgba8unorm" /*presentationFormat*/  });

let canvasTexture context.getCurrentTexture();

let imgWidth  canvas.width;
let imgHeight imgWidth;


const 
texture1 device.createTexture({
  
size: [imgWidthimgHeight1],
  
format"rgba8unorm",
  
usageGPUTextureUsage.COPY_DST GPUTextureUsage.COPY_SRC GPUTextureUsage.TEXTURE_BINDING GPUTextureUsage.STORAGE_BINDING
});

// ----------------------------------------------------------------

const bufferSize imgWidth imgHeight 2// for vec2<f32>

const velocityBuffer0 device.createBuffer({ sizebufferSizeusageGPUBufferUsage.STORAGE GPUBufferUsage.COPY_SRC GPUBufferUsage.COPY_DST });

const 
velocityBuffer1 device.createBuffer({ sizebufferSizeusageGPUBufferUsage.STORAGE GPUBufferUsage.COPY_SRC GPUBufferUsage.COPY_DST });

const 
densityBuffer0 device.createBuffer({ sizebufferSize 2,  usageGPUBufferUsage.STORAGE GPUBufferUsage.COPY_SRC GPUBufferUsage.COPY_DST });

const 
densityBuffer1 device.createBuffer({ sizebufferSize 2,  usageGPUBufferUsage.STORAGE GPUBufferUsage.COPY_SRC GPUBufferUsage.COPY_DST });

// ----------------------------------------------------------------

/*
// Circle density test pattern
function createDensityPattern() {
  // Create density data with test pattern
  const density = new Float32Array(imgWidth * imgWidth);
  const size = imgWidth;
  const densityData = new Float32Array(size * size);
  for (let y = 0; y < size; y++) {
    for (let x = 0; x < size; x++) {
      // Example test pattern (e.g., a circular density)
      const centerX = size / 2;
      const centerY = size / 2;
      const distance = Math.sqrt((x - centerX) ** 2 + (y - centerY) ** 2);
      const densityValue = distance < 50 ? 1.0 : 0.0; // Adjust threshold and shape as needed
      density[y * size + x] = densityValue * Math.random() * 100.0;
    }
  }
  return density;
}
*/

function createDensityPattern() {
  
// Create temporary canvas to draw the text
  
const canvas document.createElement('canvas');
  
canvas.width imgWidth;
  
canvas.height imgWidth;
  const 
ctx canvas.getContext('2d');

  
// Set font and text properties
  
ctx.font 'bold 150px Arial';
  
ctx.fillStyle 'white';

  
// Draw the text 'cat' onto the canvas
  
ctx.fillText('xbdev'30250);

  
// Extract pixel data from the canvas
  
const imageData ctx.getImageData(00imgWidthimgWidth);
  const 
pixels imageData.data;

  
// Convert pixel data to density array
  
const density = new Float32Array(imgWidth imgWidth);
  for (
let i 0pixels.length+= 4) {
    
// Convert RGBA pixel values to grayscale
    
const grayscale = (pixels[i] + pixels[1] + pixels[2]) / 3;
    
// Normalize grayscale value to range [0, 1]
    
const densityValue grayscale 255;
    
density[4] = densityValue;
  }

  return 
density;
}

const 
initialDensityData createDensityPattern();

device.queue.writeBuffer(densityBuffer00initialDensityData);
device.queue.writeBuffer(densityBuffer10initialDensityData);

// ----------------------------------------------------------------

function createVortexPattern() {
  const 
data = new Float32Array(imgWidth imgWidth 2);
  const 
centerX imgWidth 2;
  const 
centerY imgWidth 2;
  const 
strength 10.0;
  for (
let y 0imgWidthy++) {
    for (
let x 0imgWidthx++) {
      const 
dx centerX;
      const 
dy centerY;
      const 
distance Math.sqrt(dx dx dy dy);
      const 
angle Math.atan2(dydx);
      const 
index * (imgWidth x);
     
// data[index + 0] = -strength * Math.sin(angle) * Math.exp(-distance / 100);
     // data[index + 1] =  strength * Math.cos(angle) * Math.exp(-distance / 100);
      
data[index ] = -10.0 Math.random() * 20.0;
      
data[index ] = -10.0 Math.random() * 20.0;
    }
  }
  return 
data;
}

const 
initialVelocityData createVortexPattern();

device.queue.writeBuffer(velocityBuffer00initialVelocityData);
device.queue.writeBuffer(velocityBuffer10initialVelocityData);

// ----------------------------------------------------------------

const timerUniformBuffer device.createBuffer({
  
size4
  
usageGPUBufferUsage.UNIFORM GPUBufferUsage.COPY_DST
});

const 
timestep  = new Float32Array( [0.0] );

device.queue.writeBuffer(timerUniformBuffer,   0timestep             );

// ----------------------------------------------------------


// Bind group layout and bind group
const bindGroupLayout device.createBindGroupLayout({
  
entries: [  
    {
binding0visibilityGPUShaderStage.COMPUTE,  buffer: { type"uniform" }   },
    {
binding1visibilityGPUShaderStage.COMPUTE,  buffer: { type"storage"  }  },
    {
binding2visibilityGPUShaderStage.COMPUTE,  buffer: { type"storage"  }  },
    {
binding3visibilityGPUShaderStage.COMPUTE,  buffer: { type"storage"  }  },
    {
binding4visibilityGPUShaderStage.COMPUTE,  buffer: { type"storage"  }  },
    {
binding5visibilityGPUShaderStage.COMPUTE,  storageTexture: {format:"rgba8unorm"access:"write-only"viewDimension:"2d"}   }
           ]
});

const 
bindGroup0 device.createBindGroup({ layoutbindGroupLayout,
                                            
entries: [  {  binding0,  resource: { buffertimerUniformBuffer} },
                                                       {  
binding1,  resource: { buffervelocityBuffer0 } },
                                                       {  
binding2,  resource: { buffervelocityBuffer1 } },
                                                       {  
binding3,  resource: { bufferdensityBuffer0 } },
                                                       {  
binding4,  resource: { bufferdensityBuffer1 } },
                                                       {  
binding5,  resourcetexture1.createView()  },
                                                    ] });

const 
bindGroup1 device.createBindGroup({ layoutbindGroupLayout,
                                            
entries: [  {  binding0,  resource: { buffertimerUniformBuffer} },
                                                       {  
binding1,  resource: { buffervelocityBuffer1 } },
                                                       {  
binding2,  resource: { buffervelocityBuffer0 } },
                                                       {  
binding3,  resource: { bufferdensityBuffer1 } },
                                                       {  
binding4,  resource: { bufferdensityBuffer0 } },
                                                       {  
binding5,  resourcetexture1.createView()  },
                                                    ] });


// Compute shader code
const computeShader = ` 
@binding(0) @group(0) var<uniform> mytimer : f32;
@binding(1) @group(0) var<storage, read_write> velocity0: array<vec2<f32>>;
@binding(2) @group(0) var<storage, read_write> velocity1: array<vec2<f32>>;
@binding(3) @group(0) var<storage, read_write> density0: array<f32>;
@binding(4) @group(0) var<storage, read_write> density1: array<f32>;
@binding(5) @group(0) var myTexture1: texture_storage_2d<rgba8unorm, write>;

// Scene parameters
const imgWidth:  u32 = 
${canvas.width};
const imgHeight: u32 = 
${canvas.width};
const gridSize:  i32 = 
${canvas.width};

fn random(uv: vec2<f32>) -> f32 {
    return fract(sin(dot(uv, vec2<f32>(12.9898, 78.233))) * 43758.5453);
}

const dt: f32 = 0.1; // Time step
const diffusion: f32 = 0.1; // Diffusion coefficient
const vdiff : f32 = 0.1;
const curlCoefficient = 0.4;
const size: u32 = 
${canvas.width}; // Simulation grid size

fn index(x: u32, y: u32) -> u32 {
    return y * size + x;
}

fn diffuseDensity( factor:f32, x:u32, y:u32 )
{
    let center = density0[index(x, y)];
    let left   = density0[index(max(x, 1u) - 1u, y)];
    let right  = density0[index(min(x + 1u, size - 1u), y)];
    let up     = density0[index(x, max(y, 1u) - 1u)];
    let down   = density0[index(x, min(y + 1u, size - 1u))];
    density1[index(x,y)] = ( center + factor * (left + right + up + down - 4.0 * center) );
}

fn diffuseVelocity( factor:f32, x:u32, y:u32 )
{
    let center = velocity0[index(x, y)];
    let left   = velocity0[index(max(x, 1u) - 1u, y)];
    let right  = velocity0[index(min(x + 1u, size - 1u), y)];
    let up     = velocity0[index(x, max(y, 1u) - 1u)];
    let down   = velocity0[index(x, min(y + 1u, size - 1u))];
    velocity1[index(x,y)] = ( center + factor * (left + right + up + down - 4.0 * center) );
}

@compute @workgroup_size(8, 8)
fn diffuse_step(@builtin(global_invocation_id) id: vec3<u32>) {
    let x = id.x;
    let y = id.y;
    if (x >= size || y >= size) {
        return;
    }

    diffuseDensity( diffusion, x, y );
    
    diffuseVelocity( vdiff, x, y );
}

@compute @workgroup_size(8, 8)
fn advect_step(@builtin(global_invocation_id) id: vec3<u32>) {
    let x = id.x;
    let y = id.y;
    if (x >= size || y >= size) {
        return;
    }

    // Simple advection
    let uv = velocity0[index(x, y)];
    let pos = vec2<f32>(f32(x), f32(y)) - dt * uv;
    let x0 = clamp(floor(pos.x), 0.0, f32(size - 1));
    let x1 = clamp(ceil(pos.x), 0.0, f32(size - 1));
    let y0 = clamp(floor(pos.y), 0.0, f32(size - 1));
    let y1 = clamp(ceil(pos.y), 0.0, f32(size - 1));
    let s1 = pos.x - x0;
    let s0 = 1.0 - s1;
    let t1 = pos.y - y0;
    let t0 = 1.0 - t1;
    let advectedDensity = s0 * (t0 * density0[index(u32(x0), u32(y0))] + t1 * density0[index(u32(x0), u32(y1))]) +
                          s1 * (t0 * density0[index(u32(x1), u32(y0))] + t1 * density0[index(u32(x1), u32(y1))]);
    density1[index(x, y)] = advectedDensity;
}


@compute @workgroup_size(8, 8)
fn curl_step(@builtin(global_invocation_id) id: vec3<u32>) {
    let x = id.x;
    let y = id.y;
    if (x >= size || y >= size) {
        return;
    }

    
    let du_dy = ( velocity0[index(x    ,y + 1)].x  - velocity0[index(x    ,y - 1)].x ) / (2.0);
    let dv_dx = ( velocity0[index(x + 1,y    )].y  - velocity0[index(x - 1,y    )].y ) / (2.0);
    let curl = dv_dx - du_dy;
    
    velocity1[index(x,y)].x += -curl * dt * curlCoefficient;
    velocity1[index(x,y)].y +=  curl * dt * curlCoefficient;

}

@compute @workgroup_size(8, 8)
fn display(@builtin(global_invocation_id) globalId: vec3<u32>) {
    let x = globalId.x;
    let y = globalId.y;
    if (x >= imgWidth || y >= imgHeight) {
        return;
    }

    let d = density0[index(x, y)];
    var finalColor = vec4<f32>(d, d, d, 1.0);
    textureStore(myTexture1, vec2<i32>(globalId.xy), finalColor);
}


`;
  

let shaderModule device.createShaderModule({code:computeShader});
// Pipeline setup

var pipelines = {};
function 
addPipelinepipelineName )
{
    
let p device.createComputePipeline({
              
layout :   device.createPipelineLayout({bindGroupLayouts: [bindGroupLayout]}),
              
compute: { moduleshaderModule,
                         
entryPointpipelineName }
    });
    
pipelinespipelineName ] = p;
  
}

addPipeline'diffuse_step' );
addPipeline'advect_step' );
addPipeline'curl_step' );
addPipeline'display' );

async function frame()
{
  
// Commands submission
  
const commandEncoder device.createCommandEncoder();

  
/*
  {
  const passEncoder = commandEncoder.beginComputePass();
  passEncoder.setPipeline(computePipeline);
  passEncoder.setBindGroup(0, bindGroup0);
  // workgroup size defined on the wgsl shader
  passEncoder.dispatchWorkgroups( imgWidth/8, imgWidth/8 );
  await passEncoder.end();
  }
  */
  
async function computePasstheCommandEncoderthePipelinetheBindGroup )
  {
    const 
passEncoder theCommandEncoder.beginComputePass();
    
passEncoder.setPipeline(thePipeline);
    
passEncoder.setBindGroup(0theBindGroup);
    
// workgroup size defined on the wgsl shader
    
passEncoder.dispatchWorkgroupsimgWidth/8imgWidth/);
    
await passEncoder.end();
  }
  
  
await computePasscommandEncoderpipelines['diffuse_step'], bindGroup0 );
  
await computePasscommandEncoderpipelines['diffuse_step'], bindGroup1 );
  
  
await computePasscommandEncoderpipelines['advect_step'], bindGroup0 );
  
await computePasscommandEncoderpipelines['advect_step'], bindGroup1 );
  
  
await computePasscommandEncoderpipelines['curl_step'], bindGroup0 );
  
await computePasscommandEncoderpipelines['curl_step'], bindGroup1 );
  


  
await computePasscommandEncoderpipelines['display'], bindGroup0 );
  
  
  
canvasTexture context.getCurrentTexture();
  
  
await
  commandEncoder
.copyTextureToTexture( { texture:texture1 },
                                       { 
texturecanvasTexture },
                                       { 
width:imgWidthheight:imgHeightdepthOrArrayLayers:1} );
                                        
  
// Submit GPU commands.
  
const gpuCommands commandEncoder.finish();
  
await device.queue.submit([gpuCommands]);
  
  
timestep[0] = timestep[0] + 0.01;
  
device.queue.writeBuffer(timerUniformBuffer,   0timestep             );
  
  
requestAnimationFrame(frame);
}


frame();




Things to Try


• Try other text and test patterns for the initial density
• Try different initial 'velocity' patterns
• Visualize the velocities information
• Use other colors for the density visualization (instead of black and white - use a color gradient such as blue to red)
• Add in other fluid characteristics (e.g., boyancy)



Resources and Links


• WebGPU Lab Demo [LINK]

• Smoke/Gas Page [LINK]

• Notebook Page [LINK]








WebGPU by Example: Fractals, Image Effects, Ray-Tracing, Procedural Geometry, 2D/3D, Particles, Simulations WebGPU Compute graphics and animations using the webgpu api 12 week course kenwright learn webgpu api kenwright programming compute and graphics applications with html5 and webgpu api kenwright real-time 3d graphics with webgpu kenwright webgpu api develompent a quick start guide kenwright webgpu by example 2022 kenwright webgpu gems kenwright webgpu interactive compute and graphics visualization cookbook kenwright wgsl webgpu shading language cookbook kenwright wgsl webgpugems shading language cookbook kenwright



 
Advert (Support Website)

 
 Visitor:
Copyright (c) 2002-2025 xbdev.net - All rights reserved.
Designated articles, tutorials and software are the property of their respective owners.