|
|
@@ -7,16 +7,10 @@
|
|
|
</head>
|
|
|
<body>
|
|
|
|
|
|
- <div id="info">
|
|
|
- <a href="https://threejs.org" target="_blank" rel="noopener">three.js</a>
|
|
|
- <br /> This example demonstrates a bitonic sort running step by step in a compute shader.
|
|
|
- <br /> The left canvas swaps values within workgroup local arrays. The right swaps values within storage buffers.
|
|
|
- <br /> Reference implementation by <a href="https://poniesandlight.co.uk/reflect/bitonic_merge_sort/">Tim Gfrerer</a>
|
|
|
- <br />
|
|
|
- <div id="local_swap" style="
|
|
|
+ <style>
|
|
|
+ .swap_area {
|
|
|
position: absolute;
|
|
|
top: 150px;
|
|
|
- left: 0;
|
|
|
padding: 10px;
|
|
|
background: rgba( 0, 0, 0, 0.5 );
|
|
|
color: #fff;
|
|
|
@@ -25,20 +19,18 @@
|
|
|
line-height: 1.5;
|
|
|
pointer-events: none;
|
|
|
text-align: left;
|
|
|
- "></div>
|
|
|
- <div id="global_swap" style="
|
|
|
- position: absolute;
|
|
|
- top: 150px;
|
|
|
- right: 0;
|
|
|
- padding: 10px;
|
|
|
- background: rgba( 0, 0, 0, 0.5 );
|
|
|
- color: #fff;
|
|
|
- font-family: monospace;
|
|
|
- font-size: 12px;
|
|
|
- line-height: 1.5;
|
|
|
- pointer-events: none;
|
|
|
- text-align: left;
|
|
|
- "></div>
|
|
|
+ }
|
|
|
+
|
|
|
+ </style>
|
|
|
+
|
|
|
+ <div id="info">
|
|
|
+ <a href="https://threejs.org" target="_blank" rel="noopener">three.js</a>
|
|
|
+ <br /> This example demonstrates a bitonic sort running step by step in a compute shader.
|
|
|
+ <br /> The left canvas swaps values within workgroup local arrays. The right swaps values within storage buffers.
|
|
|
+ <br /> Reference implementation by <a href="https://poniesandlight.co.uk/reflect/bitonic_merge_sort/">Tim Gfrerer</a>
|
|
|
+ <br />
|
|
|
+ <div id="local_swap" class="swap_area" style="left: 0;"></div>
|
|
|
+ <div id="global_swap" class="swap_area" style="right: 0;"></div>
|
|
|
</div>
|
|
|
|
|
|
<script type="importmap">
|
|
|
@@ -55,7 +47,9 @@
|
|
|
<script type="module">
|
|
|
|
|
|
import * as THREE from 'three/webgpu';
|
|
|
- import { storage, If, vec3, not, uniform, uv, uint, float, Fn, vec2, abs, int, invocationLocalIndex, workgroupArray, uvec2, floor, instanceIndex, workgroupBarrier, atomicAdd, atomicStore, workgroupId } from 'three/tsl';
|
|
|
+ import { storage, If, vec3, not, uniform, uv, uint, Fn, vec2, abs, int, uvec2, floor, instanceIndex } from 'three/tsl';
|
|
|
+
|
|
|
+ import { BitonicSort, getBitonicDisperseIndices, getBitonicFlipIndices } from 'three/addons/gpgpu/BitonicSort.js';
|
|
|
|
|
|
import WebGPU from 'three/addons/capabilities/WebGPU.js';
|
|
|
|
|
|
@@ -64,8 +58,8 @@
|
|
|
const StepType = {
|
|
|
|
|
|
NONE: 0,
|
|
|
- // Swap values within workgroup local buffer.
|
|
|
- FLIP_LOCAL: 1,
|
|
|
+ // Swap values within workgroup local values
|
|
|
+ SWAP_LOCAL: 1,
|
|
|
DISPERSE_LOCAL: 2,
|
|
|
// Swap values within global data buffer.
|
|
|
FLIP_GLOBAL: 3,
|
|
|
@@ -78,7 +72,6 @@
|
|
|
global_swap: document.getElementById( 'global_swap' )
|
|
|
};
|
|
|
|
|
|
-
|
|
|
const localColors = [ 'rgb(203, 64, 203)', 'rgb(0, 215, 215)' ];
|
|
|
const globalColors = [ 'rgb(1, 150, 1)', 'red' ];
|
|
|
|
|
|
@@ -95,13 +88,13 @@
|
|
|
|
|
|
// Total number of steps in a bitonic sort with 'size' elements.
|
|
|
const MAX_STEPS = getNumSteps();
|
|
|
- const WORKGROUP_SIZE = [ 64 ];
|
|
|
|
|
|
const effectController = {
|
|
|
// Sqr root of 16834
|
|
|
gridWidth: uniform( gridDim ),
|
|
|
gridHeight: uniform( gridDim ),
|
|
|
highlight: uniform( 1 ),
|
|
|
+ stepBitonic: true,
|
|
|
'Display Mode': 'Swap Zone Highlight'
|
|
|
};
|
|
|
|
|
|
@@ -119,7 +112,6 @@
|
|
|
|
|
|
}
|
|
|
|
|
|
-
|
|
|
} );
|
|
|
|
|
|
if ( WebGPU.isAvailable() === false ) {
|
|
|
@@ -130,125 +122,333 @@
|
|
|
|
|
|
}
|
|
|
|
|
|
- // Allow Workgroup Array Swaps
|
|
|
- init();
|
|
|
+ // Display utilities
|
|
|
|
|
|
- // Global Swaps Only
|
|
|
- init( true );
|
|
|
+ const getElementIndex = Fn( ( [ uvNode, gridWidth, gridHeight ] ) => {
|
|
|
|
|
|
+ const newUV = uvNode.mul( vec2( gridWidth, gridHeight ) );
|
|
|
+ const pixel = uvec2( uint( floor( newUV.x ) ), uint( floor( newUV.y ) ) );
|
|
|
+ const elementIndex = uint( gridWidth ).mul( pixel.y ).add( pixel.x );
|
|
|
|
|
|
- // When forceGlobalSwap is true, force all valid local swaps to be global swaps.
|
|
|
- async function init( forceGlobalSwap = false ) {
|
|
|
+ return elementIndex;
|
|
|
|
|
|
- let currentStep = 0;
|
|
|
- let nextStepGlobal = false;
|
|
|
+ }, {
|
|
|
+ uvNode: 'vec2',
|
|
|
+ gridWidth: 'uint',
|
|
|
+ gridHeight: 'uint',
|
|
|
+ return: 'uint'
|
|
|
+ } );
|
|
|
+
|
|
|
+ const getColor = Fn( ( [ colorChanger, gridWidth, gridHeight ] ) => {
|
|
|
+
|
|
|
+ const subtracter = colorChanger.div( gridWidth.mul( gridHeight ) );
|
|
|
+ return vec3( subtracter.oneMinus() ).toVar();
|
|
|
+
|
|
|
+ }, {
|
|
|
+ colorChanger: 'float',
|
|
|
+ gridWidth: 'float',
|
|
|
+ gridHeight: 'float',
|
|
|
+ return: 'vec3'
|
|
|
+ } );
|
|
|
+
|
|
|
+ const randomizeDataArray = ( array ) => {
|
|
|
+
|
|
|
+ let currentIndex = array.length;
|
|
|
+ while ( currentIndex !== 0 ) {
|
|
|
|
|
|
+ const randomIndex = Math.floor( Math.random() * currentIndex );
|
|
|
+ currentIndex -= 1;
|
|
|
+ [ array[ currentIndex ], array[ randomIndex ] ] = [
|
|
|
+ array[ randomIndex ],
|
|
|
+ array[ currentIndex ],
|
|
|
+ ];
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ };
|
|
|
+
|
|
|
+ const windowResizeCallback = ( renderer, scene, camera ) => {
|
|
|
+
|
|
|
+ renderer.setSize( window.innerWidth / 2, window.innerHeight );
|
|
|
const aspect = ( window.innerWidth / 2 ) / window.innerHeight;
|
|
|
- const camera = new THREE.OrthographicCamera( - aspect, aspect, 1, - 1, 0, 2 );
|
|
|
- camera.position.z = 1;
|
|
|
+ const frustumHeight = camera.top - camera.bottom;
|
|
|
+ camera.left = - frustumHeight * aspect / 2;
|
|
|
+ camera.right = frustumHeight * aspect / 2;
|
|
|
+ camera.updateProjectionMatrix();
|
|
|
+ renderer.render( scene, camera );
|
|
|
|
|
|
- const scene = new THREE.Scene();
|
|
|
+ };
|
|
|
|
|
|
- const nextAlgoBuffer = new THREE.StorageInstancedBufferAttribute( new Uint32Array( 1 ).fill( forceGlobalSwap ? StepType.FLIP_GLOBAL : StepType.FLIP_LOCAL ), 1 );
|
|
|
+ const constructInnerHTML = ( isGlobal, colorsArr ) => {
|
|
|
|
|
|
- const nextAlgoStorage = storage( nextAlgoBuffer, 'uint', nextAlgoBuffer.count ).setPBO( true ).setName( 'NextAlgo' );
|
|
|
+ return `
|
|
|
|
|
|
- const nextBlockHeightBuffer = new THREE.StorageInstancedBufferAttribute( new Uint32Array( 1 ).fill( 2 ), 1 );
|
|
|
- const nextBlockHeightStorage = storage( nextBlockHeightBuffer, 'uint', nextBlockHeightBuffer.count ).setPBO( true ).setName( 'NextBlockHeight' );
|
|
|
- const nextBlockHeightRead = storage( nextBlockHeightBuffer, 'uint', nextBlockHeightBuffer.count ).setPBO( true ).setName( 'NextBlockHeight' ).toReadOnly();
|
|
|
+ Compute ${isGlobal ? 'Global' : 'Local'}:
|
|
|
+ <div style="display: flex; flex-direction:row; justify-content: center; align-items: center;">
|
|
|
+ ${isGlobal ? 'Global Swaps' : 'Local Swaps'} Compare Region
|
|
|
+ <div style="background-color: ${ colorsArr[ 0 ]}; width:12.5px; height: 1em; border-radius: 20%;"></div>
|
|
|
+ to Region
|
|
|
+ <div style="background-color: ${ colorsArr[ 1 ] }; width:12.5px; height: 1em; border-radius: 20%;"></div>
|
|
|
+ </div>`;
|
|
|
|
|
|
- const highestBlockHeightBuffer = new THREE.StorageInstancedBufferAttribute( new Uint32Array( 1 ).fill( 2 ), 1 );
|
|
|
- const highestBlockHeightStorage = storage( highestBlockHeightBuffer, 'uint', highestBlockHeightBuffer.count ).setPBO( true ).setName( 'HighestBlockHeight' );
|
|
|
+ };
|
|
|
|
|
|
- const counterBuffer = new THREE.StorageBufferAttribute( 1, 1 );
|
|
|
- const counterStorage = storage( counterBuffer, 'uint', counterBuffer.count ).setPBO( true ).toAtomic().setName( 'Counter' );
|
|
|
+ const createDisplayMesh = ( elementsStorage, algoStorage = null, blockHeightStorage = null ) => {
|
|
|
|
|
|
- const array = new Uint32Array( Array.from( { length: size }, ( _, i ) => {
|
|
|
+ const material = new THREE.MeshBasicNodeMaterial( { color: 0x00ff00 } );
|
|
|
|
|
|
- return i;
|
|
|
+ const display = Fn( () => {
|
|
|
|
|
|
- } ) );
|
|
|
+ const { gridWidth, gridHeight, highlight } = effectController;
|
|
|
|
|
|
+ const elementIndex = getElementIndex( uv(), gridWidth, gridHeight );
|
|
|
+ const color = getColor( elementsStorage.element( elementIndex ), gridWidth, gridHeight ).toVar();
|
|
|
|
|
|
- const randomizeDataArray = () => {
|
|
|
+ if ( algoStorage !== null && blockHeightStorage !== null ) {
|
|
|
|
|
|
- let currentIndex = array.length;
|
|
|
- while ( currentIndex !== 0 ) {
|
|
|
+ If( highlight.equal( 1 ).and( not( algoStorage.element( 0 ).equal( StepType.NONE ) ) ), () => {
|
|
|
|
|
|
- const randomIndex = Math.floor( Math.random() * currentIndex );
|
|
|
- currentIndex -= 1;
|
|
|
- [ array[ currentIndex ], array[ randomIndex ] ] = [
|
|
|
- array[ randomIndex ],
|
|
|
- array[ currentIndex ],
|
|
|
- ];
|
|
|
+ const boolCheck = int( elementIndex.mod( blockHeightStorage.element( 0 ) ).lessThan( blockHeightStorage.element( 0 ).div( 2 ) ) );
|
|
|
+ color.z.assign( algoStorage.element( 0 ).lessThanEqual( StepType.DISPERSE_LOCAL ) );
|
|
|
+ color.x.mulAssign( boolCheck );
|
|
|
+ color.y.mulAssign( abs( boolCheck.sub( 1 ) ) );
|
|
|
+
|
|
|
+ } );
|
|
|
|
|
|
}
|
|
|
|
|
|
- };
|
|
|
+ return color;
|
|
|
+
|
|
|
+ } );
|
|
|
+
|
|
|
+ material.colorNode = display();
|
|
|
+ const plane = new THREE.Mesh( new THREE.PlaneGeometry( 1, 1 ), material );
|
|
|
+ return plane;
|
|
|
+
|
|
|
+ };
|
|
|
+
|
|
|
+ const createDisplayMesh2 = ( elementsStorage, infoStorage ) => {
|
|
|
+
|
|
|
+ const material = new THREE.MeshBasicNodeMaterial( { color: 0x00ff00 } );
|
|
|
+
|
|
|
+ const display = Fn( () => {
|
|
|
+
|
|
|
+ const { gridWidth, gridHeight, highlight } = effectController;
|
|
|
+
|
|
|
+ const elementIndex = getElementIndex( uv(), gridWidth, gridHeight );
|
|
|
+ const color = getColor( elementsStorage.element( elementIndex ), gridWidth, gridHeight ).toVar();
|
|
|
+
|
|
|
|
|
|
- randomizeDataArray();
|
|
|
+ If( highlight.equal( 1 ).and( not( infoStorage.element( 0 ).equal( StepType.SWAP_LOCAL ) ) ), () => {
|
|
|
+
|
|
|
+ const boolCheck = int( elementIndex.mod( infoStorage.element( 1 ) ).lessThan( infoStorage.element( 1 ).div( 2 ) ) );
|
|
|
+ color.z.assign( infoStorage.element( 0 ).lessThanEqual( StepType.DISPERSE_LOCAL ) );
|
|
|
+ color.x.mulAssign( boolCheck );
|
|
|
+ color.y.mulAssign( abs( boolCheck.sub( 1 ) ) );
|
|
|
+
|
|
|
+ } );
|
|
|
+
|
|
|
+
|
|
|
+ return color;
|
|
|
+
|
|
|
+ } );
|
|
|
+
|
|
|
+ material.colorNode = display();
|
|
|
+ const plane = new THREE.Mesh( new THREE.PlaneGeometry( 1, 1 ), material );
|
|
|
+ return plane;
|
|
|
+
|
|
|
+ };
|
|
|
+
|
|
|
+ const setupDomElement = ( renderer ) => {
|
|
|
+
|
|
|
+ document.body.appendChild( renderer.domElement );
|
|
|
+ renderer.domElement.style.position = 'absolute';
|
|
|
+ renderer.domElement.style.top = '0';
|
|
|
+ renderer.domElement.style.left = '0';
|
|
|
+ renderer.domElement.style.width = '50%';
|
|
|
+ renderer.domElement.style.height = '100%';
|
|
|
+
|
|
|
+ };
|
|
|
+
|
|
|
+ async function initBitonicSort() {
|
|
|
+
|
|
|
+ let currentStep = 0;
|
|
|
+
|
|
|
+ const aspect = ( window.innerWidth / 2 ) / window.innerHeight;
|
|
|
+ const camera = new THREE.OrthographicCamera( - aspect, aspect, 1, - 1, 0, 2 );
|
|
|
+ camera.position.z = 1;
|
|
|
+
|
|
|
+ const scene = new THREE.Scene();
|
|
|
+
|
|
|
+ const array = new Uint32Array( Array.from( { length: size }, ( _, i ) => {
|
|
|
+
|
|
|
+ return i;
|
|
|
+
|
|
|
+ } ) );
|
|
|
+
|
|
|
+
|
|
|
+ randomizeDataArray( array );
|
|
|
|
|
|
const currentElementsBuffer = new THREE.StorageInstancedBufferAttribute( array, 1 );
|
|
|
const currentElementsStorage = storage( currentElementsBuffer, 'uint', size ).setPBO( true ).setName( 'Elements' );
|
|
|
- const tempBuffer = new THREE.StorageInstancedBufferAttribute( array, 1 );
|
|
|
- const tempStorage = storage( tempBuffer, 'uint', size ).setPBO( true ).setName( 'Temp' );
|
|
|
const randomizedElementsBuffer = new THREE.StorageInstancedBufferAttribute( size, 1 );
|
|
|
const randomizedElementsStorage = storage( randomizedElementsBuffer, 'uint', size ).setPBO( true ).setName( 'RandomizedElements' );
|
|
|
|
|
|
- const getFlipIndices = ( index, blockHeight ) => {
|
|
|
+ const computeInitFn = Fn( () => {
|
|
|
|
|
|
- const blockOffset = ( index.mul( 2 ).div( blockHeight ) ).mul( blockHeight );
|
|
|
- const halfHeight = blockHeight.div( 2 );
|
|
|
- const idx = uvec2(
|
|
|
- index.mod( halfHeight ),
|
|
|
- blockHeight.sub( index.mod( halfHeight ) ).sub( 1 )
|
|
|
- );
|
|
|
- idx.x.addAssign( blockOffset );
|
|
|
- idx.y.addAssign( blockOffset );
|
|
|
+ randomizedElementsStorage.element( instanceIndex ).assign( currentElementsStorage.element( instanceIndex ) );
|
|
|
|
|
|
- return idx;
|
|
|
+ } );
|
|
|
|
|
|
- };
|
|
|
+ const computeResetBuffersFn = Fn( () => {
|
|
|
|
|
|
- const getDisperseIndices = ( index, blockHeight ) => {
|
|
|
+ currentElementsStorage.element( instanceIndex ).assign( randomizedElementsStorage.element( instanceIndex ) );
|
|
|
|
|
|
- const blockOffset = ( ( index.mul( 2 ) ).div( blockHeight ) ).mul( blockHeight );
|
|
|
- const halfHeight = blockHeight.div( 2 );
|
|
|
- const idx = uvec2(
|
|
|
- index.mod( halfHeight ),
|
|
|
- ( index.mod( halfHeight ) ).add( halfHeight )
|
|
|
- );
|
|
|
+ } );
|
|
|
+
|
|
|
+ const renderer = new THREE.WebGPURenderer( { antialias: false } );
|
|
|
+ renderer.setPixelRatio( window.devicePixelRatio );
|
|
|
+ renderer.setSize( window.innerWidth / 2, window.innerHeight );
|
|
|
|
|
|
- idx.x.addAssign( blockOffset );
|
|
|
- idx.y.addAssign( blockOffset );
|
|
|
+ const animate = () => {
|
|
|
|
|
|
- return idx;
|
|
|
+ renderer.render( scene, camera );
|
|
|
|
|
|
};
|
|
|
|
|
|
- const localStorage = workgroupArray( 'uint', 64 * 2 );
|
|
|
+ renderer.setAnimationLoop( animate );
|
|
|
+ setupDomElement( renderer );
|
|
|
+ scene.background = new THREE.Color( 0x313131 );
|
|
|
+
|
|
|
+ const bitonicSortModule = new BitonicSort(
|
|
|
+ renderer,
|
|
|
+ currentElementsStorage,
|
|
|
+ {
|
|
|
+ workgroupSize: 64,
|
|
|
+ sideEffectBuffers: [ 2, 3 ]
|
|
|
+ }
|
|
|
+ );
|
|
|
|
|
|
- // Swap the elements in local storage
|
|
|
- const localCompareAndSwap = ( idxBefore, idxAfter ) => {
|
|
|
+ scene.add( createDisplayMesh2( currentElementsStorage, bitonicSortModule.infoStorage ) );
|
|
|
|
|
|
- If( localStorage.element( idxAfter ).lessThan( localStorage.element( idxBefore ) ), () => {
|
|
|
+ // Initialize each value in the elements buffer.
|
|
|
+ const computeInit = computeInitFn().compute( size );
|
|
|
+ const computeReset = computeResetBuffersFn().compute( size );
|
|
|
|
|
|
- atomicAdd( counterStorage.element( 0 ), 1 );
|
|
|
- const temp = localStorage.element( idxBefore ).toVar();
|
|
|
- localStorage.element( idxBefore ).assign( localStorage.element( idxAfter ) );
|
|
|
- localStorage.element( idxAfter ).assign( temp );
|
|
|
+ await renderer.computeAsync( computeInit );
|
|
|
|
|
|
- } );
|
|
|
+ gui.add( effectController, 'stepBitonic' ).onChange( async () => {
|
|
|
+
|
|
|
+ if ( currentStep < bitonicSortModule.stepCount ) {
|
|
|
+
|
|
|
+ await bitonicSortModule.computeStep( renderer );
|
|
|
+
|
|
|
+ currentStep ++;
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ await renderer.computeAsync( computeReset );
|
|
|
+
|
|
|
+ currentStep = 0;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ timestamps[ 'local_swap' ].innerHTML = constructInnerHTML( false, localColors );
|
|
|
+
|
|
|
+ } );
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ const stepAnimation = async function () {
|
|
|
+
|
|
|
+ renderer.info.reset();
|
|
|
+
|
|
|
+ if ( currentStep < bitonicSortModule.stepCount ) {
|
|
|
+
|
|
|
+ bitonicSortModule.computeStep( renderer );
|
|
|
+
|
|
|
+ currentStep ++;
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ renderer.compute( computeReset );
|
|
|
+
|
|
|
+ currentStep = 0;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ timestamps[ 'local_swap' ].innerHTML = constructInnerHTML( false, localColors );
|
|
|
+
|
|
|
+ if ( currentStep === bitonicSortModule.stepCount ) {
|
|
|
+
|
|
|
+ setTimeout( stepAnimation, 1000 );
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ setTimeout( stepAnimation, 100 );
|
|
|
+
|
|
|
+ }
|
|
|
|
|
|
};
|
|
|
|
|
|
+ stepAnimation();
|
|
|
+
|
|
|
+ window.addEventListener( 'resize', onWindowResize );
|
|
|
+
|
|
|
+ function onWindowResize() {
|
|
|
+
|
|
|
+ windowResizeCallback( renderer, scene, camera );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ initBitonicSort();
|
|
|
+
|
|
|
+ // Global Swaps Only
|
|
|
+ initGlobalSwapOnly();
|
|
|
+
|
|
|
+ // When forceGlobalSwap is true, force all valid local swaps to be global swaps.
|
|
|
+ async function initGlobalSwapOnly() {
|
|
|
+
|
|
|
+ let currentStep = 0;
|
|
|
+
|
|
|
+ const aspect = ( window.innerWidth / 2 ) / window.innerHeight;
|
|
|
+ const camera = new THREE.OrthographicCamera( - aspect, aspect, 1, - 1, 0, 2 );
|
|
|
+ camera.position.z = 1;
|
|
|
+
|
|
|
+ const scene = new THREE.Scene();
|
|
|
+
|
|
|
+ const infoArray = new Uint32Array( 3, 2, 2 );
|
|
|
+ const infoBuffer = new THREE.StorageInstancedBufferAttribute( infoArray, 1 );
|
|
|
+ const infoStorage = storage( infoBuffer, 'uint', infoBuffer.count ).setPBO( true ).setName( 'TheInfo' );
|
|
|
+
|
|
|
+ const nextBlockHeightBuffer = new THREE.StorageInstancedBufferAttribute( new Uint32Array( 1 ).fill( 2 ), 1 );
|
|
|
+ const nextBlockHeightStorage = storage( nextBlockHeightBuffer, 'uint', nextBlockHeightBuffer.count ).setPBO( true ).setName( 'NextBlockHeight' );
|
|
|
+ const nextBlockHeightRead = storage( nextBlockHeightBuffer, 'uint', nextBlockHeightBuffer.count ).setPBO( true ).setName( 'NextBlockHeight' ).toReadOnly();
|
|
|
+
|
|
|
+ const array = new Uint32Array( Array.from( { length: size }, ( _, i ) => {
|
|
|
+
|
|
|
+ return i;
|
|
|
+
|
|
|
+ } ) );
|
|
|
+
|
|
|
+ randomizeDataArray( array );
|
|
|
+
|
|
|
+ const currentElementsBuffer = new THREE.StorageInstancedBufferAttribute( array, 1 );
|
|
|
+ const currentElementsStorage = storage( currentElementsBuffer, 'uint', size ).setPBO( true ).setName( 'Elements' );
|
|
|
+ const tempBuffer = new THREE.StorageInstancedBufferAttribute( array, 1 );
|
|
|
+ const tempStorage = storage( tempBuffer, 'uint', size ).setPBO( true ).setName( 'Temp' );
|
|
|
+ const randomizedElementsBuffer = new THREE.StorageInstancedBufferAttribute( size, 1 );
|
|
|
+ const randomizedElementsStorage = storage( randomizedElementsBuffer, 'uint', size ).setPBO( true ).setName( 'RandomizedElements' );
|
|
|
+
|
|
|
+ // Swap the elements in local storage
|
|
|
const globalCompareAndSwap = ( idxBefore, idxAfter ) => {
|
|
|
|
|
|
// If the later element is less than the current element
|
|
|
If( currentElementsStorage.element( idxAfter ).lessThan( currentElementsStorage.element( idxBefore ) ), () => {
|
|
|
|
|
|
// Apply the swapped values to temporary storage.
|
|
|
- atomicAdd( counterStorage.element( 0 ), 1 );
|
|
|
tempStorage.element( idxBefore ).assign( currentElementsStorage.element( idxAfter ) );
|
|
|
tempStorage.element( idxAfter ).assign( currentElementsStorage.element( idxBefore ) );
|
|
|
|
|
|
@@ -271,68 +471,30 @@
|
|
|
const computeBitonicStepFn = Fn( () => {
|
|
|
|
|
|
const nextBlockHeight = nextBlockHeightStorage.element( 0 ).toVar();
|
|
|
- const nextAlgo = nextAlgoStorage.element( 0 ).toVar();
|
|
|
-
|
|
|
- // Get ids of indices needed to populate workgroup local buffer.
|
|
|
- // Use .toVar() to prevent these values from being recalculated multiple times.
|
|
|
- const localOffset = uint( WORKGROUP_SIZE[ 0 ] ).mul( 2 ).mul( workgroupId.x ).toVar();
|
|
|
-
|
|
|
- const localID1 = invocationLocalIndex.mul( 2 );
|
|
|
- const localID2 = invocationLocalIndex.mul( 2 ).add( 1 );
|
|
|
-
|
|
|
- // If we will perform a local swap, then populate the local data
|
|
|
- If( nextAlgo.lessThanEqual( uint( StepType.DISPERSE_LOCAL ) ), () => {
|
|
|
-
|
|
|
- localStorage.element( localID1 ).assign( currentElementsStorage.element( localOffset.add( localID1 ) ) );
|
|
|
- localStorage.element( localID2 ).assign( currentElementsStorage.element( localOffset.add( localID2 ) ) );
|
|
|
-
|
|
|
- } );
|
|
|
-
|
|
|
- workgroupBarrier();
|
|
|
+ const nextAlgo = infoStorage.element( 0 ).toVar();
|
|
|
|
|
|
// TODO: Convert to switch block.
|
|
|
- If( nextAlgo.equal( uint( StepType.FLIP_LOCAL ) ), () => {
|
|
|
-
|
|
|
- const idx = getFlipIndices( invocationLocalIndex, nextBlockHeight );
|
|
|
- localCompareAndSwap( idx.x, idx.y );
|
|
|
-
|
|
|
- } ).ElseIf( nextAlgo.equal( uint( StepType.DISPERSE_LOCAL ) ), () => {
|
|
|
+ If( nextAlgo.equal( uint( StepType.FLIP_GLOBAL ) ), () => {
|
|
|
|
|
|
- const idx = getDisperseIndices( invocationLocalIndex, nextBlockHeight );
|
|
|
- localCompareAndSwap( idx.x, idx.y );
|
|
|
-
|
|
|
- } ).ElseIf( nextAlgo.equal( uint( StepType.FLIP_GLOBAL ) ), () => {
|
|
|
-
|
|
|
- const idx = getFlipIndices( instanceIndex, nextBlockHeight );
|
|
|
+ const idx = getBitonicFlipIndices( instanceIndex, nextBlockHeight );
|
|
|
globalCompareAndSwap( idx.x, idx.y );
|
|
|
|
|
|
} ).ElseIf( nextAlgo.equal( uint( StepType.DISPERSE_GLOBAL ) ), () => {
|
|
|
|
|
|
- const idx = getDisperseIndices( instanceIndex, nextBlockHeight );
|
|
|
+ const idx = getBitonicDisperseIndices( instanceIndex, nextBlockHeight );
|
|
|
globalCompareAndSwap( idx.x, idx.y );
|
|
|
|
|
|
} );
|
|
|
|
|
|
- // Ensure that all invocations have swapped their own regions of data
|
|
|
- workgroupBarrier();
|
|
|
-
|
|
|
- // Populate output data with the results from our swaps
|
|
|
- If( nextAlgo.lessThanEqual( uint( StepType.DISPERSE_LOCAL ) ), () => {
|
|
|
-
|
|
|
- currentElementsStorage.element( localOffset.add( localID1 ) ).assign( localStorage.element( localID1 ) );
|
|
|
- currentElementsStorage.element( localOffset.add( localID2 ) ).assign( localStorage.element( localID2 ) );
|
|
|
-
|
|
|
- } );
|
|
|
-
|
|
|
- // If the previous algorithm was global, we execute an additional compute step to sync the current buffer with the output buffer.
|
|
|
+ // Since this algorithm is global only, we execute an additional compute step to sync the current buffer with the output buffer.
|
|
|
|
|
|
} );
|
|
|
|
|
|
const computeSetAlgoFn = Fn( () => {
|
|
|
|
|
|
const nextBlockHeight = nextBlockHeightStorage.element( 0 ).toVar();
|
|
|
- const nextAlgo = nextAlgoStorage.element( 0 );
|
|
|
- const highestBlockHeight = highestBlockHeightStorage.element( 0 ).toVar();
|
|
|
+ const nextAlgo = infoStorage.element( 0 );
|
|
|
+ const highestBlockHeight = infoStorage.element( 2 ).toVar();
|
|
|
|
|
|
nextBlockHeight.divAssign( 2 );
|
|
|
|
|
|
@@ -340,57 +502,27 @@
|
|
|
|
|
|
highestBlockHeight.mulAssign( 2 );
|
|
|
|
|
|
- if ( forceGlobalSwap ) {
|
|
|
-
|
|
|
- If( highestBlockHeight.equal( size * 2 ), () => {
|
|
|
-
|
|
|
- nextAlgo.assign( StepType.NONE );
|
|
|
- nextBlockHeight.assign( 0 );
|
|
|
-
|
|
|
- } ).Else( () => {
|
|
|
-
|
|
|
- nextAlgo.assign( StepType.FLIP_GLOBAL );
|
|
|
- nextBlockHeight.assign( highestBlockHeight );
|
|
|
-
|
|
|
- } );
|
|
|
-
|
|
|
- } else {
|
|
|
-
|
|
|
- If( highestBlockHeight.equal( size * 2 ), () => {
|
|
|
+ If( highestBlockHeight.equal( size * 2 ), () => {
|
|
|
|
|
|
- nextAlgo.assign( StepType.NONE );
|
|
|
- nextBlockHeight.assign( 0 );
|
|
|
+ nextAlgo.assign( StepType.NONE );
|
|
|
+ nextBlockHeight.assign( 0 );
|
|
|
|
|
|
- } ).ElseIf( highestBlockHeight.greaterThan( WORKGROUP_SIZE[ 0 ] * 2 ), () => {
|
|
|
+ } ).Else( () => {
|
|
|
|
|
|
- nextAlgo.assign( StepType.FLIP_GLOBAL );
|
|
|
- nextBlockHeight.assign( highestBlockHeight );
|
|
|
+ nextAlgo.assign( StepType.FLIP_GLOBAL );
|
|
|
+ nextBlockHeight.assign( highestBlockHeight );
|
|
|
|
|
|
- } ).Else( () => {
|
|
|
+ } );
|
|
|
|
|
|
- nextAlgo.assign( forceGlobalSwap ? StepType.FLIP_GLOBAL : StepType.FLIP_LOCAL );
|
|
|
- nextBlockHeight.assign( highestBlockHeight );
|
|
|
-
|
|
|
- } );
|
|
|
-
|
|
|
- }
|
|
|
|
|
|
} ).Else( () => {
|
|
|
|
|
|
- if ( forceGlobalSwap ) {
|
|
|
-
|
|
|
- nextAlgo.assign( StepType.DISPERSE_GLOBAL );
|
|
|
-
|
|
|
- } else {
|
|
|
-
|
|
|
- nextAlgo.assign( nextBlockHeight.greaterThan( WORKGROUP_SIZE[ 0 ] * 2 ).select( StepType.DISPERSE_GLOBAL, StepType.DISPERSE_LOCAL ).uniformFlow() );
|
|
|
-
|
|
|
- }
|
|
|
+ nextAlgo.assign( StepType.DISPERSE_GLOBAL );
|
|
|
|
|
|
} );
|
|
|
|
|
|
nextBlockHeightStorage.element( 0 ).assign( nextBlockHeight );
|
|
|
- highestBlockHeightStorage.element( 0 ).assign( highestBlockHeight );
|
|
|
+ infoStorage.element( 2 ).assign( highestBlockHeight );
|
|
|
|
|
|
} );
|
|
|
|
|
|
@@ -408,10 +540,9 @@
|
|
|
|
|
|
const computeResetAlgoFn = Fn( () => {
|
|
|
|
|
|
- nextAlgoStorage.element( 0 ).assign( forceGlobalSwap ? StepType.FLIP_GLOBAL : StepType.FLIP_LOCAL );
|
|
|
+ infoStorage.element( 0 ).assign( StepType.FLIP_GLOBAL );
|
|
|
nextBlockHeightStorage.element( 0 ).assign( 2 );
|
|
|
- highestBlockHeightStorage.element( 0 ).assign( 2 );
|
|
|
- atomicStore( counterStorage.element( 0 ), 0 );
|
|
|
+ infoStorage.element( 2 ).assign( 2 );
|
|
|
|
|
|
} );
|
|
|
|
|
|
@@ -427,43 +558,9 @@
|
|
|
const computeResetBuffers = computeResetBuffersFn().compute( size );
|
|
|
const computeResetAlgo = computeResetAlgoFn().compute( 1 );
|
|
|
|
|
|
- const material = new THREE.MeshBasicNodeMaterial( { color: 0x00ff00 } );
|
|
|
-
|
|
|
- const display = Fn( () => {
|
|
|
-
|
|
|
- const { gridWidth, gridHeight, highlight } = effectController;
|
|
|
-
|
|
|
- const newUV = uv().mul( vec2( gridWidth, gridHeight ) );
|
|
|
-
|
|
|
- const pixel = uvec2( uint( floor( newUV.x ) ), uint( floor( newUV.y ) ) );
|
|
|
-
|
|
|
- const elementIndex = uint( gridWidth ).mul( pixel.y ).add( pixel.x );
|
|
|
+ scene.add( createDisplayMesh( currentElementsStorage, infoStorage, nextBlockHeightRead ) );
|
|
|
|
|
|
- const colorChanger = currentElementsStorage.element( elementIndex );
|
|
|
-
|
|
|
- const subtracter = float( colorChanger ).div( gridWidth.mul( gridHeight ) );
|
|
|
-
|
|
|
- const color = vec3( subtracter.oneMinus() ).toVar();
|
|
|
-
|
|
|
- If( highlight.equal( 1 ).and( not( nextAlgoStorage.element( 0 ).equal( StepType.NONE ) ) ), () => {
|
|
|
-
|
|
|
- const boolCheck = int( elementIndex.mod( nextBlockHeightRead.element( 0 ) ).lessThan( nextBlockHeightRead.element( 0 ).div( 2 ) ) );
|
|
|
- color.z.assign( nextAlgoStorage.element( 0 ).lessThanEqual( StepType.DISPERSE_LOCAL ) );
|
|
|
- color.x.mulAssign( boolCheck );
|
|
|
- color.y.mulAssign( abs( boolCheck.sub( 1 ) ) );
|
|
|
-
|
|
|
- } );
|
|
|
-
|
|
|
- return color;
|
|
|
-
|
|
|
- } );
|
|
|
-
|
|
|
- material.colorNode = display();
|
|
|
-
|
|
|
- const plane = new THREE.Mesh( new THREE.PlaneGeometry( 1, 1 ), material );
|
|
|
- scene.add( plane );
|
|
|
-
|
|
|
- const renderer = new THREE.WebGPURenderer( { antialias: false, trackTimestamp: true } );
|
|
|
+ const renderer = new THREE.WebGPURenderer( { antialias: false } );
|
|
|
renderer.setPixelRatio( window.devicePixelRatio );
|
|
|
renderer.setSize( window.innerWidth / 2, window.innerHeight );
|
|
|
|
|
|
@@ -474,43 +571,19 @@
|
|
|
};
|
|
|
|
|
|
renderer.setAnimationLoop( animate );
|
|
|
-
|
|
|
- document.body.appendChild( renderer.domElement );
|
|
|
- renderer.domElement.style.position = 'absolute';
|
|
|
- renderer.domElement.style.top = '0';
|
|
|
- renderer.domElement.style.left = '0';
|
|
|
- renderer.domElement.style.width = '50%';
|
|
|
- renderer.domElement.style.height = '100%';
|
|
|
-
|
|
|
- if ( forceGlobalSwap ) {
|
|
|
-
|
|
|
- renderer.domElement.style.left = '50%';
|
|
|
-
|
|
|
- scene.background = new THREE.Color( 0x212121 );
|
|
|
-
|
|
|
- } else {
|
|
|
-
|
|
|
- scene.background = new THREE.Color( 0x313131 );
|
|
|
-
|
|
|
- }
|
|
|
+ setupDomElement( renderer );
|
|
|
+ renderer.domElement.style.left = '50%';
|
|
|
+ scene.background = new THREE.Color( 0x212121 );
|
|
|
|
|
|
await renderer.computeAsync( computeInit );
|
|
|
|
|
|
- renderer.info.autoReset = false;
|
|
|
-
|
|
|
const stepAnimation = async function () {
|
|
|
|
|
|
- renderer.info.reset();
|
|
|
-
|
|
|
if ( currentStep !== MAX_STEPS ) {
|
|
|
|
|
|
renderer.compute( computeBitonicStep );
|
|
|
|
|
|
- if ( nextStepGlobal ) {
|
|
|
-
|
|
|
- renderer.compute( computeAlignCurrent );
|
|
|
-
|
|
|
- }
|
|
|
+ renderer.compute( computeAlignCurrent );
|
|
|
|
|
|
renderer.compute( computeSetAlgo );
|
|
|
|
|
|
@@ -525,36 +598,15 @@
|
|
|
|
|
|
}
|
|
|
|
|
|
- renderer.resolveTimestampsAsync( THREE.TimestampQuery.COMPUTE );
|
|
|
-
|
|
|
-
|
|
|
- const algo = new Uint32Array( await renderer.getArrayBufferAsync( nextAlgoBuffer ) );
|
|
|
- nextStepGlobal = algo[ 0 ] > StepType.DISPERSE_LOCAL;
|
|
|
- const totalSwaps = new Uint32Array( await renderer.getArrayBufferAsync( counterBuffer ) );
|
|
|
-
|
|
|
- renderer.render( scene, camera );
|
|
|
- renderer.resolveTimestampsAsync( THREE.TimestampQuery.RENDER );
|
|
|
-
|
|
|
- timestamps[ forceGlobalSwap ? 'global_swap' : 'local_swap' ].innerHTML = `
|
|
|
-
|
|
|
- Compute ${forceGlobalSwap ? 'Global' : 'Local'}: ${renderer.info.compute.frameCalls} pass in ${renderer.info.compute.timestamp.toFixed( 6 )}ms<br>
|
|
|
- Total Swaps: ${totalSwaps}<br>
|
|
|
- <div style="display: flex; flex-direction:row; justify-content: center; align-items: center;">
|
|
|
- ${forceGlobalSwap ? 'Global Swaps' : 'Local Swaps'} Compare Region
|
|
|
- <div style="background-color: ${ forceGlobalSwap ? globalColors[ 0 ] : localColors[ 0 ]}; width:12.5px; height: 1em; border-radius: 20%;"></div>
|
|
|
- to Region
|
|
|
- <div style="background-color: ${ forceGlobalSwap ? globalColors[ 1 ] : localColors[ 1 ]}; width:12.5px; height: 1em; border-radius: 20%;"></div>
|
|
|
- </div>`;
|
|
|
-
|
|
|
+ timestamps[ 'global_swap' ].innerHTML = constructInnerHTML( true, globalColors );
|
|
|
|
|
|
if ( currentStep === MAX_STEPS ) {
|
|
|
|
|
|
setTimeout( stepAnimation, 1000 );
|
|
|
|
|
|
-
|
|
|
} else {
|
|
|
|
|
|
- setTimeout( stepAnimation, 50 );
|
|
|
+ setTimeout( stepAnimation, 100 );
|
|
|
|
|
|
}
|
|
|
|
|
|
@@ -566,18 +618,7 @@
|
|
|
|
|
|
function onWindowResize() {
|
|
|
|
|
|
- renderer.setSize( window.innerWidth / 2, window.innerHeight );
|
|
|
-
|
|
|
- const aspect = ( window.innerWidth / 2 ) / window.innerHeight;
|
|
|
-
|
|
|
- const frustumHeight = camera.top - camera.bottom;
|
|
|
-
|
|
|
- camera.left = - frustumHeight * aspect / 2;
|
|
|
- camera.right = frustumHeight * aspect / 2;
|
|
|
-
|
|
|
- camera.updateProjectionMatrix();
|
|
|
-
|
|
|
- renderer.render( scene, camera );
|
|
|
+ windowResizeCallback( renderer, scene, camera );
|
|
|
|
|
|
}
|
|
|
|