diff --git a/404.html b/404.html index f5863d34..9c99fba1 100644 --- a/404.html +++ b/404.html @@ -1,4 +1,4 @@ -404: This page could not be found
\ No newline at end of file + }

404

This page could not be found.

\ No newline at end of file diff --git a/_next/data/em7S8GbBqqJHvN6FB6pa3/samples/A-buffer.json b/_next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/A-buffer.json similarity index 100% rename from _next/data/em7S8GbBqqJHvN6FB6pa3/samples/A-buffer.json rename to _next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/A-buffer.json diff --git a/_next/data/em7S8GbBqqJHvN6FB6pa3/samples/animometer.json b/_next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/animometer.json similarity index 100% rename from _next/data/em7S8GbBqqJHvN6FB6pa3/samples/animometer.json rename to _next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/animometer.json diff --git a/_next/data/em7S8GbBqqJHvN6FB6pa3/samples/bitonicSort.json b/_next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/bitonicSort.json similarity index 100% rename from _next/data/em7S8GbBqqJHvN6FB6pa3/samples/bitonicSort.json rename to _next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/bitonicSort.json diff --git a/_next/data/em7S8GbBqqJHvN6FB6pa3/samples/cameras.json b/_next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/cameras.json similarity index 100% rename from _next/data/em7S8GbBqqJHvN6FB6pa3/samples/cameras.json rename to _next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/cameras.json diff --git a/_next/data/em7S8GbBqqJHvN6FB6pa3/samples/computeBoids.json b/_next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/computeBoids.json similarity index 100% rename from _next/data/em7S8GbBqqJHvN6FB6pa3/samples/computeBoids.json rename to _next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/computeBoids.json diff --git a/_next/data/em7S8GbBqqJHvN6FB6pa3/samples/cornell.json b/_next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/cornell.json similarity index 100% rename from _next/data/em7S8GbBqqJHvN6FB6pa3/samples/cornell.json rename to _next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/cornell.json diff --git a/_next/data/em7S8GbBqqJHvN6FB6pa3/samples/cubemap.json b/_next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/cubemap.json similarity index 100% rename from _next/data/em7S8GbBqqJHvN6FB6pa3/samples/cubemap.json rename to _next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/cubemap.json diff --git a/_next/data/em7S8GbBqqJHvN6FB6pa3/samples/deferredRendering.json b/_next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/deferredRendering.json similarity index 100% rename from _next/data/em7S8GbBqqJHvN6FB6pa3/samples/deferredRendering.json rename to _next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/deferredRendering.json diff --git a/_next/data/em7S8GbBqqJHvN6FB6pa3/samples/fractalCube.json b/_next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/fractalCube.json similarity index 100% rename from _next/data/em7S8GbBqqJHvN6FB6pa3/samples/fractalCube.json rename to _next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/fractalCube.json diff --git a/_next/data/em7S8GbBqqJHvN6FB6pa3/samples/gameOfLife.json b/_next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/gameOfLife.json similarity index 100% rename from _next/data/em7S8GbBqqJHvN6FB6pa3/samples/gameOfLife.json rename to _next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/gameOfLife.json diff --git a/_next/data/em7S8GbBqqJHvN6FB6pa3/samples/helloTriangle.json b/_next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/helloTriangle.json similarity index 100% rename from _next/data/em7S8GbBqqJHvN6FB6pa3/samples/helloTriangle.json rename to _next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/helloTriangle.json diff --git a/_next/data/em7S8GbBqqJHvN6FB6pa3/samples/helloTriangleMSAA.json b/_next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/helloTriangleMSAA.json similarity index 100% rename from _next/data/em7S8GbBqqJHvN6FB6pa3/samples/helloTriangleMSAA.json rename to _next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/helloTriangleMSAA.json diff --git a/_next/data/em7S8GbBqqJHvN6FB6pa3/samples/imageBlur.json b/_next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/imageBlur.json similarity index 100% rename from _next/data/em7S8GbBqqJHvN6FB6pa3/samples/imageBlur.json rename to _next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/imageBlur.json diff --git a/_next/data/em7S8GbBqqJHvN6FB6pa3/samples/instancedCube.json b/_next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/instancedCube.json similarity index 100% rename from _next/data/em7S8GbBqqJHvN6FB6pa3/samples/instancedCube.json rename to _next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/instancedCube.json diff --git a/_next/data/em7S8GbBqqJHvN6FB6pa3/samples/normalMap.json b/_next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/normalMap.json similarity index 100% rename from _next/data/em7S8GbBqqJHvN6FB6pa3/samples/normalMap.json rename to _next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/normalMap.json diff --git a/_next/data/em7S8GbBqqJHvN6FB6pa3/samples/particles.json b/_next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/particles.json similarity index 100% rename from _next/data/em7S8GbBqqJHvN6FB6pa3/samples/particles.json rename to _next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/particles.json diff --git a/_next/data/em7S8GbBqqJHvN6FB6pa3/samples/renderBundles.json b/_next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/renderBundles.json similarity index 100% rename from _next/data/em7S8GbBqqJHvN6FB6pa3/samples/renderBundles.json rename to _next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/renderBundles.json diff --git a/_next/data/em7S8GbBqqJHvN6FB6pa3/samples/resizeCanvas.json b/_next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/resizeCanvas.json similarity index 100% rename from _next/data/em7S8GbBqqJHvN6FB6pa3/samples/resizeCanvas.json rename to _next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/resizeCanvas.json diff --git a/_next/data/em7S8GbBqqJHvN6FB6pa3/samples/reversedZ.json b/_next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/reversedZ.json similarity index 100% rename from _next/data/em7S8GbBqqJHvN6FB6pa3/samples/reversedZ.json rename to _next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/reversedZ.json diff --git a/_next/data/em7S8GbBqqJHvN6FB6pa3/samples/rotatingCube.json b/_next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/rotatingCube.json similarity index 100% rename from _next/data/em7S8GbBqqJHvN6FB6pa3/samples/rotatingCube.json rename to _next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/rotatingCube.json diff --git a/_next/data/em7S8GbBqqJHvN6FB6pa3/samples/samplerParameters.json b/_next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/samplerParameters.json similarity index 100% rename from _next/data/em7S8GbBqqJHvN6FB6pa3/samples/samplerParameters.json rename to _next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/samplerParameters.json diff --git a/_next/data/em7S8GbBqqJHvN6FB6pa3/samples/shadowMapping.json b/_next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/shadowMapping.json similarity index 100% rename from _next/data/em7S8GbBqqJHvN6FB6pa3/samples/shadowMapping.json rename to _next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/shadowMapping.json diff --git a/_next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/skinnedMesh.json b/_next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/skinnedMesh.json new file mode 100644 index 00000000..f44c055b --- /dev/null +++ b/_next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/skinnedMesh.json @@ -0,0 +1 @@ +{"pageProps":{"slug":"skinnedMesh"},"__N_SSG":true} \ No newline at end of file diff --git a/_next/data/em7S8GbBqqJHvN6FB6pa3/samples/texturedCube.json b/_next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/texturedCube.json similarity index 100% rename from _next/data/em7S8GbBqqJHvN6FB6pa3/samples/texturedCube.json rename to _next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/texturedCube.json diff --git a/_next/data/em7S8GbBqqJHvN6FB6pa3/samples/twoCubes.json b/_next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/twoCubes.json similarity index 100% rename from _next/data/em7S8GbBqqJHvN6FB6pa3/samples/twoCubes.json rename to _next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/twoCubes.json diff --git a/_next/data/em7S8GbBqqJHvN6FB6pa3/samples/videoUploading.json b/_next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/videoUploading.json similarity index 100% rename from _next/data/em7S8GbBqqJHvN6FB6pa3/samples/videoUploading.json rename to _next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/videoUploading.json diff --git a/_next/data/em7S8GbBqqJHvN6FB6pa3/samples/videoUploadingWebCodecs.json b/_next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/videoUploadingWebCodecs.json similarity index 100% rename from _next/data/em7S8GbBqqJHvN6FB6pa3/samples/videoUploadingWebCodecs.json rename to _next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/videoUploadingWebCodecs.json diff --git a/_next/data/em7S8GbBqqJHvN6FB6pa3/samples/worker.json b/_next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/worker.json similarity index 100% rename from _next/data/em7S8GbBqqJHvN6FB6pa3/samples/worker.json rename to _next/data/2DZEoOdrd-0zjg_2w8ZgH/samples/worker.json diff --git a/_next/static/em7S8GbBqqJHvN6FB6pa3/_buildManifest.js b/_next/static/2DZEoOdrd-0zjg_2w8ZgH/_buildManifest.js similarity index 100% rename from _next/static/em7S8GbBqqJHvN6FB6pa3/_buildManifest.js rename to _next/static/2DZEoOdrd-0zjg_2w8ZgH/_buildManifest.js diff --git a/_next/static/em7S8GbBqqJHvN6FB6pa3/_ssgManifest.js b/_next/static/2DZEoOdrd-0zjg_2w8ZgH/_ssgManifest.js similarity index 100% rename from _next/static/em7S8GbBqqJHvN6FB6pa3/_ssgManifest.js rename to _next/static/2DZEoOdrd-0zjg_2w8ZgH/_ssgManifest.js diff --git a/_next/static/chunks/0.6a50185fa8ff8ed2.js b/_next/static/chunks/0.6a50185fa8ff8ed2.js new file mode 100644 index 00000000..848a716b --- /dev/null +++ b/_next/static/chunks/0.6a50185fa8ff8ed2.js @@ -0,0 +1 @@ +(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[0],{5671:function(e,n,t){"use strict";t.d(n,{Tl:function(){return p},hu:function(){return d}});var o=t(5893),r=t(9008),i=t.n(r),a=t(1163),s=t(7294),l=t(9147),u=t.n(l);t(7319);let c=e=>{let n=(0,s.useRef)(null),r=(0,s.useRef)(null),l=(0,s.useMemo)(()=>e.sources.map(e=>{let{name:n,contents:r}=e;return{name:n,...function(e){let n;let r=null;{r=document.createElement("div");let i=t(4631);n=i(r,{lineNumbers:!0,lineWrapping:!0,theme:"monokai",readOnly:!0})}return{Container:function(t){return(0,o.jsx)("div",{...t,children:(0,o.jsx)("div",{ref(t){r&&t&&(t.appendChild(r),n.setOption("value",e))}})})}}}(r)}}),e.sources),c=(0,s.useRef)(null),p=(0,s.useMemo)(()=>{if(e.gui){let n=t(4376),o=new n.GUI({autoPlace:!1});return o.domElement.style.position="relative",o.domElement.style.zIndex="1000",o}},[]),d=(0,s.useRef)(null),m=(0,s.useMemo)(()=>{if(e.stats){let n=t(2792);return new n}},[]),f=(0,a.useRouter)(),g=f.asPath.match(/#([a-zA-Z0-9\.\/]+)/),[h,S]=(0,s.useState)(null),[E,_]=(0,s.useState)(null);return(0,s.useEffect)(()=>{if(g?_(g[1]):_(l[0].name),p&&c.current)for(c.current.appendChild(p.domElement);p.__controllers.length>0;)p.__controllers[0].remove();m&&d.current&&(m.dom.style.position="absolute",m.showPanel(1),d.current.appendChild(m.dom));let t={active:!0},o=()=>{t.active=!1};try{let r=n.current;if(!r)throw Error("The canvas is not available");let i=e.init({canvas:r,pageState:t,gui:p,stats:m});i instanceof Promise&&i.catch(e=>{console.error(e),S(e)})}catch(a){console.error(a),S(a)}return o},[]),(0,o.jsxs)("main",{children:[(0,o.jsxs)(i(),{children:[(0,o.jsx)("style",{dangerouslySetInnerHTML:{__html:"\n .CodeMirror {\n height: auto !important;\n margin: 1em 0;\n }\n\n .CodeMirror-scroll {\n height: auto !important;\n overflow: visible !important;\n }\n "}}),(0,o.jsx)("title",{children:"".concat(e.name," - WebGPU Samples")}),(0,o.jsx)("meta",{name:"description",content:e.description}),(0,o.jsx)("meta",{httpEquiv:"origin-trial",content:e.originTrial})]}),(0,o.jsxs)("div",{children:[(0,o.jsx)("h1",{children:e.name}),(0,o.jsx)("a",{target:"_blank",rel:"noreferrer",href:"https://github.com/".concat("webgpu/webgpu-samples","/tree/main/").concat(e.filename),children:"See it on Github!"}),(0,o.jsx)("p",{children:e.description}),h?(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)("p",{children:"Something went wrong. Do your browser and device support WebGPU?"}),(0,o.jsx)("p",{children:"".concat(h)})]}):null]}),(0,o.jsxs)("div",{className:u().canvasContainer,children:[(0,o.jsx)("div",{style:{position:"absolute",left:10},ref:d}),(0,o.jsx)("div",{style:{position:"absolute",right:10},ref:c}),(0,o.jsx)("canvas",{ref:n})]}),(0,o.jsxs)("div",{children:[(0,o.jsx)("nav",{className:u().sourceFileNav,ref:r,children:(0,o.jsx)("div",{className:u().sourceFileScrollContainer,onScroll(e){let n=e.currentTarget,t=n.scrollWidth-n.clientWidth-n.scrollLeft;n.scrollLeft>25?r.current.setAttribute("data-left","true"):r.current.setAttribute("data-left","false"),t>25?r.current.setAttribute("data-right","true"):r.current.setAttribute("data-right","false")},children:(0,o.jsx)("ul",{children:l.map((e,n)=>(0,o.jsx)("li",{children:(0,o.jsx)("a",{href:"#".concat(e.name),"data-active":E==e.name,onClick(){_(e.name)},children:e.name})},n))})})}),l.map((e,n)=>(0,o.jsx)(e.Container,{className:u().sourceFileContainer,"data-active":E==e.name},n))]})]})},p=e=>(0,o.jsx)(c,{...e});function d(e,n){if(!e)throw Error(n)}},2e3:function(e,n,t){"use strict";let o;t.r(n),t.d(n,{default:function(){return h}});var r,i,a=t(5671),s=t(7606),l="struct ComputeUniforms {\n width: f32,\n height: f32,\n algo: u32,\n blockHeight: u32,\n}\n\nstruct FragmentUniforms {\n // boolean, either 0 or 1\n highlight: u32,\n}\n\nstruct VertexOutput {\n @builtin(position) Position: vec4,\n @location(0) fragUV: vec2\n}\n\n// Uniforms from compute shader\n@group(0) @binding(0) var data: array;\n@group(0) @binding(2) var uniforms: ComputeUniforms;\n// Fragment shader uniforms\n@group(1) @binding(0) var fragment_uniforms: FragmentUniforms;\n\n@fragment\nfn frag_main(input: VertexOutput) -> @location(0) vec4 {\n var uv: vec2 = vec2(\n input.fragUV.x * uniforms.width,\n input.fragUV.y * uniforms.height\n );\n\n var pixel: vec2 = vec2(\n u32(floor(uv.x)),\n u32(floor(uv.y)),\n );\n \n var elementIndex = u32(uniforms.width) * pixel.y + pixel.x;\n var colorChanger = data[elementIndex];\n\n var subtracter = f32(colorChanger) / (uniforms.width * uniforms.height);\n\n if (fragment_uniforms.highlight == 1) {\n return select(\n //If element is above halfHeight, highlight green\n vec4(vec3(0.0, 1.0 - subtracter, 0.0).rgb, 1.0),\n //If element is below halfheight, highlight red\n vec4(vec3(1.0 - subtracter, 0.0, 0.0).rgb, 1.0),\n elementIndex % uniforms.blockHeight < uniforms.blockHeight / 2\n );\n }\n\n var color: vec3 = vec3f(\n 1.0 - subtracter\n );\n\n return vec4(color.rgb, 1.0);\n}\n";class u extends s.NJ{startRun(e,n){this.setArguments(n),super.executeRun(e,this.renderPassDescriptor,this.pipeline,[this.computeBGDescript.bindGroups[0],this.currentBindGroup])}constructor(e,n,t,o,r){super(),this.renderPassDescriptor=t,this.computeBGDescript=o;let i=e.createBuffer({size:Uint32Array.BYTES_PER_ELEMENT,usage:GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST}),a=(0,s.a1)([0],[GPUShaderStage.FRAGMENT],["buffer"],[{type:"uniform"}],[[{buffer:i}]],r,e);this.currentBindGroup=a.bindGroups[0],this.pipeline=super.create2DRenderPipeline(e,r,[this.computeBGDescript.bindGroupLayout,a.bindGroupLayout],l,n),this.setArguments=n=>{e.queue.writeBuffer(i,0,new Uint32Array([n.highlight]))}}}u.sourceInfo={name:"src/sample/bitonicSort/bitonicDisplay.ts".substring(23),contents:"import {\n BindGroupCluster,\n Base2DRendererClass,\n createBindGroupCluster,\n} from './utils';\n\nimport bitonicDisplay from './bitonicDisplay.frag.wgsl';\n\ninterface BitonicDisplayRenderArgs {\n highlight: number;\n}\n\nexport default class BitonicDisplayRenderer extends Base2DRendererClass {\n static sourceInfo = {\n name: __filename.substring(__dirname.length + 1),\n contents: __SOURCE__,\n };\n\n switchBindGroup: (name: string) => void;\n setArguments: (args: BitonicDisplayRenderArgs) => void;\n computeBGDescript: BindGroupCluster;\n\n constructor(\n device: GPUDevice,\n presentationFormat: GPUTextureFormat,\n renderPassDescriptor: GPURenderPassDescriptor,\n computeBGDescript: BindGroupCluster,\n label: string\n ) {\n super();\n this.renderPassDescriptor = renderPassDescriptor;\n this.computeBGDescript = computeBGDescript;\n\n const uniformBuffer = device.createBuffer({\n size: Uint32Array.BYTES_PER_ELEMENT,\n usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,\n });\n\n const bgCluster = createBindGroupCluster(\n [0],\n [GPUShaderStage.FRAGMENT],\n ['buffer'],\n [{ type: 'uniform' }],\n [[{ buffer: uniformBuffer }]],\n label,\n device\n );\n\n this.currentBindGroup = bgCluster.bindGroups[0];\n\n this.pipeline = super.create2DRenderPipeline(\n device,\n label,\n [this.computeBGDescript.bindGroupLayout, bgCluster.bindGroupLayout],\n bitonicDisplay,\n presentationFormat\n );\n\n this.setArguments = (args: BitonicDisplayRenderArgs) => {\n device.queue.writeBuffer(\n uniformBuffer,\n 0,\n new Uint32Array([args.highlight])\n );\n };\n }\n\n startRun(commandEncoder: GPUCommandEncoder, args: BitonicDisplayRenderArgs) {\n this.setArguments(args);\n super.executeRun(commandEncoder, this.renderPassDescriptor, this.pipeline, [\n this.computeBGDescript.bindGroups[0],\n this.currentBindGroup,\n ]);\n }\n}\n"};let c=e=>((e%2!=0||e>256)&&(e=256),"\n\nstruct Uniforms {\n width: f32,\n height: f32,\n algo: u32,\n blockHeight: u32,\n}\n\n// Create local workgroup data that can contain all elements\nvar local_data: array;\n\n// Define groups (functions refer to this data)\n@group(0) @binding(0) var input_data: array;\n@group(0) @binding(1) var output_data: array;\n@group(0) @binding(2) var uniforms: Uniforms;\n@group(0) @binding(3) var counter: atomic;\n\n// Compare and swap values in local_data\nfn local_compare_and_swap(idx_before: u32, idx_after: u32) {\n //idx_before should always be < idx_after\n if (local_data[idx_after] < local_data[idx_before]) {\n atomicAdd(&counter, 1);\n var temp: u32 = local_data[idx_before];\n local_data[idx_before] = local_data[idx_after];\n local_data[idx_after] = temp;\n }\n return;\n}\n\n// invoke_id goes from 0 to workgroupSize\nfn get_flip_indices(invoke_id: u32, block_height: u32) -> vec2 {\n // Caculate index offset (i.e move indices into correct block)\n let block_offset: u32 = ((2 * invoke_id) / block_height) * block_height;\n let half_height = block_height / 2;\n // Calculate index spacing\n var idx: vec2 = vec2(\n invoke_id % half_height, block_height - (invoke_id % half_height) - 1,\n );\n idx.x += block_offset;\n idx.y += block_offset;\n return idx;\n}\n\nfn get_disperse_indices(invoke_id: u32, block_height: u32) -> vec2 {\n var block_offset: u32 = ((2 * invoke_id) / block_height) * block_height;\n let half_height = block_height / 2;\n var idx: vec2 = vec2(\n invoke_id % half_height, (invoke_id % half_height) + half_height\n );\n idx.x += block_offset;\n idx.y += block_offset;\n return idx;\n}\n\nfn global_compare_and_swap(idx_before: u32, idx_after: u32) {\n if (input_data[idx_after] < input_data[idx_before]) {\n output_data[idx_before] = input_data[idx_after];\n output_data[idx_after] = input_data[idx_before];\n } \n}\n\n// Constants/enum\nconst ALGO_NONE = 0;\nconst ALGO_LOCAL_FLIP = 1;\nconst ALGO_LOCAL_DISPERSE = 2;\nconst ALGO_GLOBAL_FLIP = 3;\n\n// Our compute shader will execute specified # of invocations or elements / 2 invocations\n@compute @workgroup_size(").concat(e,", 1, 1)\nfn computeMain(\n @builtin(global_invocation_id) global_id: vec3,\n @builtin(local_invocation_id) local_id: vec3,\n @builtin(workgroup_id) workgroup_id: vec3,\n) {\n\n let offset = ").concat(e," * 2 * workgroup_id.x;\n // If we will perform a local swap, then populate the local data\n if (uniforms.algo <= 2) {\n // Assign range of input_data to local_data.\n // Range cannot exceed maxWorkgroupsX * 2\n // Each invocation will populate the workgroup data... (1 invocation for every 2 elements)\n local_data[local_id.x * 2] = input_data[offset + local_id.x * 2];\n local_data[local_id.x * 2 + 1] = input_data[offset + local_id.x * 2 + 1];\n }\n\n //...and wait for each other to finish their own bit of data population.\n workgroupBarrier();\n\n switch uniforms.algo {\n case 1: { // Local Flip\n let idx = get_flip_indices(local_id.x, uniforms.blockHeight);\n local_compare_and_swap(idx.x, idx.y);\n } \n case 2: { // Local Disperse\n let idx = get_disperse_indices(local_id.x, uniforms.blockHeight);\n local_compare_and_swap(idx.x, idx.y);\n } \n case 3: { // Global Flip\n let idx = get_flip_indices(global_id.x, uniforms.blockHeight);\n global_compare_and_swap(idx.x, idx.y);\n }\n case 4: { \n let idx = get_disperse_indices(global_id.x, uniforms.blockHeight);\n global_compare_and_swap(idx.x, idx.y);\n }\n default: { \n \n }\n }\n\n // Ensure that all invocations have swapped their own regions of data\n workgroupBarrier();\n\n if (uniforms.algo <= ALGO_LOCAL_DISPERSE) {\n //Repopulate global data with local data\n output_data[offset + local_id.x * 2] = local_data[local_id.x * 2];\n output_data[offset + local_id.x * 2 + 1] = local_data[local_id.x * 2 + 1];\n }\n\n}"));var p=t(134),d="@group(0) @binding(3) var counter: atomic;\n\n@compute @workgroup_size(1, 1, 1)\nfn atomicToZero() {\n let counterValue = atomicLoad(&counter);\n atomicSub(&counter, counterValue);\n}",m="src/sample/bitonicSort/main.ts";(r=i||(i={}))[r.NONE=0]="NONE",r[r.FLIP_LOCAL=1]="FLIP_LOCAL",r[r.DISPERSE_LOCAL=2]="DISPERSE_LOCAL",r[r.FLIP_GLOBAL=3]="FLIP_GLOBAL",r[r.DISPERSE_GLOBAL=4]="DISPERSE_GLOBAL";let f=e=>{let n=Math.log2(e);return n*(n+1)/2};(0,s.Ot)(async e=>{let n,t,o,{pageState:r,device:a,gui:l,presentationFormat:p,context:m,canvas:g,timestampQueryAvailable:h}=e,S=a.limits.maxComputeWorkgroupSizeX;h&&(n=a.createQuerySet({type:"timestamp",count:2}),t=a.createBuffer({size:2*BigInt64Array.BYTES_PER_ELEMENT,usage:GPUBufferUsage.QUERY_RESOLVE|GPUBufferUsage.COPY_SRC}),o=a.createBuffer({size:2*BigInt64Array.BYTES_PER_ELEMENT,usage:GPUBufferUsage.COPY_DST|GPUBufferUsage.MAP_READ}));let E=[],_=32*S;for(let v=_;v>=4;v/=2)E.push(v);let x=[];for(let y=S;y>=2;y/=2)x.push(y);let b=Math.sqrt(_)%2==0?Math.floor(Math.sqrt(_)):Math.floor(Math.sqrt(_/2)),w=_/b,C={"Total Elements":_,"Grid Width":b,"Grid Height":w,"Grid Dimensions":"".concat(b,"x").concat(w),"Workgroup Size":S,"Size Limit":S,"Workgroups Per Step":_/(2*S),"Hovered Cell":0,"Swapped Cell":1,"Step Index":0,"Total Steps":f(_),"Current Step":"0 of 91","Prev Step":"NONE","Next Step":"FLIP_LOCAL","Prev Swap Span":0,"Next Swap Span":2,executeStep:!1,"Randomize Values"(){},"Execute Sort Step"(){},"Log Elements"(){},"Auto Sort"(){},"Auto Sort Speed":50,"Display Mode":"Elements","Total Swaps":0,"Step Time":"0ms",stepTime:0,"Sort Time":"0ms",sortTime:0,"Average Sort Time":"0ms",configToCompleteSwapsMap:{"8192 256":{sorts:0,time:0}},configKey:"8192 256"},T=new Uint32Array(Array.from({length:C["Total Elements"]},(e,n)=>n)),P=Float32Array.BYTES_PER_ELEMENT*E[0],B=a.createBuffer({size:P,usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_DST}),L=a.createBuffer({size:P,usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_SRC}),A=a.createBuffer({size:P,usage:GPUBufferUsage.MAP_READ|GPUBufferUsage.COPY_DST}),G=a.createBuffer({size:Uint32Array.BYTES_PER_ELEMENT,usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_SRC}),I=a.createBuffer({size:Uint32Array.BYTES_PER_ELEMENT,usage:GPUBufferUsage.MAP_READ|GPUBufferUsage.COPY_DST}),k=a.createBuffer({size:4*Float32Array.BYTES_PER_ELEMENT,usage:GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST}),U=(0,s.a1)([0,1,2,3],[GPUShaderStage.COMPUTE|GPUShaderStage.FRAGMENT,GPUShaderStage.COMPUTE,GPUShaderStage.COMPUTE|GPUShaderStage.FRAGMENT,GPUShaderStage.COMPUTE],["buffer","buffer","buffer","buffer"],[{type:"read-only-storage"},{type:"storage"},{type:"uniform"},{type:"storage"}],[[{buffer:B},{buffer:L},{buffer:k},{buffer:G}]],"BitonicSort",a),M=a.createComputePipeline({layout:a.createPipelineLayout({bindGroupLayouts:[U.bindGroupLayout]}),compute:{module:a.createShaderModule({code:c(C["Workgroup Size"])}),entryPoint:"computeMain"}}),R=a.createComputePipeline({layout:a.createPipelineLayout({bindGroupLayouts:[U.bindGroupLayout]}),compute:{module:a.createShaderModule({code:d}),entryPoint:"atomicToZero"}}),O={colorAttachments:[{view:void 0,clearValue:{r:.1,g:.4,b:.5,a:1},loadOp:"clear",storeOp:"store"}]},N=new u(a,p,O,U,"BitonicDisplay"),D=()=>{C.stepTime=0,C.sortTime=0,ec.setValue("0ms"),ep.setValue("0ms");let e=C.configToCompleteSwapsMap[C.configKey].time/C.configToCompleteSwapsMap[C.configKey].sorts;ed.setValue("".concat((e||0).toFixed(5),"ms"))},z=()=>{Q.setValue(Math.min(C["Total Elements"]/2,C["Size Limit"]));let e=(C["Total Elements"]-1)/(2*C["Size Limit"]);$.setValue(Math.ceil(e)),C["Step Index"]=0,C["Total Steps"]=f(C["Total Elements"]),eo.setValue("".concat(C["Step Index"]," of ").concat(C["Total Steps"]));let n=Math.sqrt(C["Total Elements"])%2==0?Math.floor(Math.sqrt(C["Total Elements"])):Math.floor(Math.sqrt(C["Total Elements"]/2)),t=C["Total Elements"]/n;C["Grid Width"]=n,C["Grid Height"]=t,J.setValue("".concat(n,"x").concat(t)),er.setValue("NONE"),ei.setValue("FLIP_LOCAL"),es.setValue(0),el.setValue(2);let o=a.createCommandEncoder(),r=o.beginComputePass();r.setPipeline(R),r.setBindGroup(0,U.bindGroups[0]),r.dispatchWorkgroups(1),r.end(),a.queue.submit([o.finish()]),ea.setValue(0),eg=2},F=()=>{let e=T.length;for(;0!==e;){let n=Math.floor(Math.random()*e);e-=1,[T[e],T[n]]=[T[n],T[e]]}},H=()=>{T=new Uint32Array(Array.from({length:C["Total Elements"]},(e,n)=>n)),z(),M=a.createComputePipeline({layout:a.createPipelineLayout({bindGroupLayouts:[U.bindGroupLayout]}),compute:{module:a.createShaderModule({code:c(Math.min(C["Total Elements"]/2,C["Size Limit"]))}),entryPoint:"computeMain"}}),F(),eg=2};F();let V=()=>{let e;switch(C["Next Step"]){case"FLIP_LOCAL":case"FLIP_GLOBAL":{let n=C["Next Swap Span"],t=Math.floor(C["Hovered Cell"]/n)+1,o=C["Hovered Cell"]%n;e=n*t-o-1,en.setValue(e)}break;case"DISPERSE_LOCAL":{let r=C["Next Swap Span"],i=r/2;e=C["Hovered Cell"]%r{null!==W&&(clearInterval(W),W=null)},q=()=>{let e=C["Auto Sort Speed"];W=setInterval(()=>{"NONE"===C["Next Step"]&&(clearInterval(W),W=null,K.domElement.style.pointerEvents="auto"),C["Auto Sort Speed"]!==e&&(clearInterval(W),W=null,q()),C.executeStep=!0,V()},C["Auto Sort Speed"])},j=l.addFolder("Compute Resources");j.add(C,"Total Elements",E).onChange(()=>{Y(),H(),K.domElement.style.pointerEvents="auto";let e="".concat(C["Total Elements"]," ").concat(C["Size Limit"]);C.configToCompleteSwapsMap[e]||(C.configToCompleteSwapsMap[e]={sorts:0,time:0}),C.configKey=e,D()});let K=j.add(C,"Size Limit",x).onChange(()=>{let e=Math.min(C["Total Elements"]/2,C["Size Limit"]),n=(C["Total Elements"]-1)/(2*C["Size Limit"]);Q.setValue(e),$.setValue(Math.ceil(n)),M=M=a.createComputePipeline({layout:a.createPipelineLayout({bindGroupLayouts:[U.bindGroupLayout]}),compute:{module:a.createShaderModule({code:c(Math.min(C["Total Elements"]/2,C["Size Limit"]))}),entryPoint:"computeMain"}});let t="".concat(C["Total Elements"]," ").concat(C["Size Limit"]);C.configToCompleteSwapsMap[t]||(C.configToCompleteSwapsMap[t]={sorts:0,time:0}),C.configKey=t,D()}),Q=j.add(C,"Workgroup Size"),$=j.add(C,"Workgroups Per Step");j.open();let Z=l.addFolder("Sort Controls");Z.add(C,"Execute Sort Step").onChange(()=>{K.domElement.style.pointerEvents="none",Y(),C.executeStep=!0}),Z.add(C,"Randomize Values").onChange(()=>{Y(),F(),z(),D(),K.domElement.style.pointerEvents="auto"}),Z.add(C,"Log Elements").onChange(()=>console.log(T)),Z.add(C,"Auto Sort").onChange(()=>{K.domElement.style.pointerEvents="none",q()}),Z.add(C,"Auto Sort Speed",50,1e3).step(50),Z.open();let X=l.addFolder("Grid Information");X.add(C,"Display Mode",["Elements","Swap Highlight"]);let J=X.add(C,"Grid Dimensions"),ee=X.add(C,"Hovered Cell").onChange(V),en=X.add(C,"Swapped Cell"),et=l.addFolder("Execution Information"),eo=et.add(C,"Current Step"),er=et.add(C,"Prev Step"),ei=et.add(C,"Next Step"),ea=et.add(C,"Total Swaps"),es=et.add(C,"Prev Swap Span"),el=et.add(C,"Next Swap Span"),eu=l.addFolder("Timestamp Info (Chrome 121+)"),ec=eu.add(C,"Step Time"),ep=eu.add(C,"Sort Time"),ed=eu.add(C,"Average Sort Time"),em=document.getElementsByClassName("cr function");for(let ef=0;ef{let n=g.getBoundingClientRect().width,t=g.getBoundingClientRect().height,o=[n/C["Grid Width"],t/C["Grid Height"]],r=Math.floor(e.offsetX/o[0]),i=C["Grid Height"]-1-Math.floor(e.offsetY/o[1]);ee.setValue(i*C["Grid Width"]+r),C["Hovered Cell"]=i*C["Grid Width"]+r}),K.domElement.style.pointerEvents="none",$.domElement.style.pointerEvents="none",ee.domElement.style.pointerEvents="none",en.domElement.style.pointerEvents="none",eo.domElement.style.pointerEvents="none",er.domElement.style.pointerEvents="none",es.domElement.style.pointerEvents="none",ei.domElement.style.pointerEvents="none",el.domElement.style.pointerEvents="none",Q.domElement.style.pointerEvents="none",J.domElement.style.pointerEvents="none",ea.domElement.style.pointerEvents="none",ec.domElement.style.pointerEvents="none",ep.domElement.style.pointerEvents="none",ed.domElement.style.pointerEvents="none",l.width=325;let eg=2;async function eh(){if(!r.active)return;a.queue.writeBuffer(B,0,T.buffer,T.byteOffset,T.byteLength);let e=new Float32Array([C["Grid Width"],C["Grid Height"]]),s=new Uint32Array([i[C["Next Step"]],C["Next Swap Span"]]);a.queue.writeBuffer(k,0,e.buffer,e.byteOffset,e.byteLength),a.queue.writeBuffer(k,8,s),O.colorAttachments[0].view=m.getCurrentTexture().createView();let l=a.createCommandEncoder();if(N.startRun(l,{highlight:"Elements"===C["Display Mode"]?0:1}),C.executeStep&&eg<2*C["Total Elements"]){let u;(u=h?l.beginComputePass({timestampWrites:{querySet:n,beginningOfPassWriteIndex:0,endOfPassWriteIndex:1}}):l.beginComputePass()).setPipeline(M),u.setBindGroup(0,U.bindGroups[0]),u.dispatchWorkgroups(C["Workgroups Per Step"]),u.end(),h&&(l.resolveQuerySet(n,0,2,t,0),l.copyBufferToBuffer(t,0,o,0,2*BigInt64Array.BYTES_PER_ELEMENT)),C["Step Index"]=C["Step Index"]+1,eo.setValue("".concat(C["Step Index"]," of ").concat(C["Total Steps"])),er.setValue(C["Next Step"]),es.setValue(C["Next Swap Span"]),el.setValue(C["Next Swap Span"]/2),1===C["Next Swap Span"]?(eg*=2)==2*C["Total Elements"]?(ei.setValue("NONE"),el.setValue(0),C.configToCompleteSwapsMap[C.configKey].sorts+=1):eg>2*C["Workgroup Size"]?(ei.setValue("FLIP_GLOBAL"),el.setValue(eg)):(ei.setValue("FLIP_LOCAL"),el.setValue(eg)):C["Next Swap Span"]>2*C["Workgroup Size"]?ei.setValue("DISPERSE_GLOBAL"):ei.setValue("DISPERSE_LOCAL"),l.copyBufferToBuffer(L,0,A,0,P),l.copyBufferToBuffer(G,0,I,0,Uint32Array.BYTES_PER_ELEMENT)}if(a.queue.submit([l.finish()]),C.executeStep&&eg<4*C["Total Elements"]){await A.mapAsync(GPUMapMode.READ,0,P);let c=A.getMappedRange(0,P);await I.mapAsync(GPUMapMode.READ,0,Uint32Array.BYTES_PER_ELEMENT);let p=I.getMappedRange(0,Uint32Array.BYTES_PER_ELEMENT),d=c.slice(0,Uint32Array.BYTES_PER_ELEMENT*C["Total Elements"]),f=p.slice(0,Uint32Array.BYTES_PER_ELEMENT),g=new Uint32Array(d);if(ea.setValue(new Uint32Array(f)[0]),A.unmap(),I.unmap(),T=g,V(),h){await o.mapAsync(GPUMapMode.READ,0,2*BigInt64Array.BYTES_PER_ELEMENT);let S=new BigInt64Array(o.getMappedRange()),E=Number(S[1]-S[0])/1e6,_=C.sortTime+E;if(C.stepTime=E,C.sortTime=_,ec.setValue("".concat(E.toFixed(5),"ms")),ep.setValue("".concat(_.toFixed(5),"ms")),eg===2*C["Total Elements"]){eg*=2,C.configToCompleteSwapsMap[C.configKey].time+=_;let v=C.configToCompleteSwapsMap[C.configKey].time/C.configToCompleteSwapsMap[C.configKey].sorts;ed.setValue("".concat(v.toFixed(5),"ms"))}o.unmap()}}C.executeStep=!1,requestAnimationFrame(eh)}q(),requestAnimationFrame(eh)}).then(e=>o=e);let g=()=>(0,a.Tl)({name:"Bitonic Sort",description:"A naive bitonic sort algorithm executed on the GPU, based on tgfrerer's implementation at poniesandlight.co.uk/reflect/bitonic_merge_sort/. Each dispatch of the bitonic sort shader dispatches a workgroup containing elements/2 invocations. The GUI's Execution Information folder contains information about the sort's current state. The visualizer displays the sort's results as colored cells sorted from brightest to darkest.",init:o,gui:!0,sources:[{name:m.substring(23),contents:"import { makeSample, SampleInit } from '../../components/SampleLayout';\nimport { createBindGroupCluster, SampleInitFactoryWebGPU } from './utils';\nimport BitonicDisplayRenderer from './bitonicDisplay';\nimport bitonicDisplay from './bitonicDisplay.frag.wgsl';\nimport { NaiveBitonicCompute } from './bitonicCompute';\nimport fullscreenTexturedQuad from '../../shaders/fullscreenTexturedQuad.wgsl';\nimport atomicToZero from './atomicToZero.wgsl';\n\n// Type of step that will be executed in our shader\nenum StepEnum {\n NONE,\n FLIP_LOCAL,\n DISPERSE_LOCAL,\n FLIP_GLOBAL,\n DISPERSE_GLOBAL,\n}\n\ntype StepType =\n // NONE: No sort step has or will occur\n | 'NONE'\n // FLIP_LOCAL: A sort step that performs a flip operation over indices in a workgroup's locally addressable area\n // (i.e invocations * workgroup_index -> invocations * (workgroup_index + 1) - 1.\n | 'FLIP_LOCAL'\n // DISPERSE_LOCAL A sort step that performs a flip operation over indices in a workgroup's locally addressable area.\n | 'DISPERSE_LOCAL'\n // FLIP_GLOBAL A sort step that performs a flip step across a range of indices outside a workgroup's locally addressable area.\n | 'FLIP_GLOBAL'\n // DISPERSE_GLOBAL A sort step that performs a disperse operation across a range of indices outside a workgroup's locally addressable area.\n | 'DISPERSE_GLOBAL';\n\ntype DisplayType = 'Elements' | 'Swap Highlight';\n\ninterface ConfigInfo {\n // Number of sorts executed under a given elements + size limit config\n sorts: number;\n // Total collective time taken to execute each complete sort under this config\n time: number;\n}\n\ninterface StringKeyToNumber {\n [key: string]: ConfigInfo;\n}\n\n// Gui settings object\ninterface SettingsInterface {\n 'Total Elements': number;\n 'Grid Width': number;\n 'Grid Height': number;\n 'Grid Dimensions': string;\n 'Workgroup Size': number;\n 'Size Limit': number;\n 'Workgroups Per Step': number;\n 'Hovered Cell': number;\n 'Swapped Cell': number;\n 'Current Step': string;\n 'Step Index': number;\n 'Total Steps': number;\n 'Prev Step': StepType;\n 'Next Step': StepType;\n 'Prev Swap Span': number;\n 'Next Swap Span': number;\n executeStep: boolean;\n 'Randomize Values': () => void;\n 'Execute Sort Step': () => void;\n 'Log Elements': () => void;\n 'Auto Sort': () => void;\n 'Auto Sort Speed': number;\n 'Display Mode': DisplayType;\n 'Total Swaps': number;\n stepTime: number;\n 'Step Time': string;\n sortTime: number;\n 'Sort Time': string;\n 'Average Sort Time': string;\n configToCompleteSwapsMap: StringKeyToNumber;\n configKey: string;\n}\n\nconst getNumSteps = (numElements: number) => {\n const n = Math.log2(numElements);\n return (n * (n + 1)) / 2;\n};\n\nlet init: SampleInit;\nSampleInitFactoryWebGPU(\n async ({\n pageState,\n device,\n gui,\n presentationFormat,\n context,\n canvas,\n timestampQueryAvailable,\n }) => {\n const maxInvocationsX = device.limits.maxComputeWorkgroupSizeX;\n\n let querySet: GPUQuerySet;\n let timestampQueryResolveBuffer: GPUBuffer;\n let timestampQueryResultBuffer: GPUBuffer;\n if (timestampQueryAvailable) {\n querySet = device.createQuerySet({ type: 'timestamp', count: 2 });\n timestampQueryResolveBuffer = device.createBuffer({\n // 2 timestamps * BigInt size for nanoseconds\n size: 2 * BigInt64Array.BYTES_PER_ELEMENT,\n usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC,\n });\n timestampQueryResultBuffer = device.createBuffer({\n // 2 timestamps * BigInt size for nanoseconds\n size: 2 * BigInt64Array.BYTES_PER_ELEMENT,\n usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,\n });\n }\n\n const totalElementOptions = [];\n const maxElements = maxInvocationsX * 32;\n for (let i = maxElements; i >= 4; i /= 2) {\n totalElementOptions.push(i);\n }\n\n const sizeLimitOptions: number[] = [];\n for (let i = maxInvocationsX; i >= 2; i /= 2) {\n sizeLimitOptions.push(i);\n }\n\n const defaultGridWidth =\n Math.sqrt(maxElements) % 2 === 0\n ? Math.floor(Math.sqrt(maxElements))\n : Math.floor(Math.sqrt(maxElements / 2));\n\n const defaultGridHeight = maxElements / defaultGridWidth;\n\n const settings: SettingsInterface = {\n // TOTAL ELEMENT AND GRID SETTINGS\n // The number of elements to be sorted. Must equal gridWidth * gridHeight || Workgroup Size * Workgroups * 2.\n // When changed, all relevant values within the settings object are reset to their defaults at the beginning of a sort with n elements.\n 'Total Elements': maxElements,\n // The width of the screen in cells.\n 'Grid Width': defaultGridWidth,\n // The height of the screen in cells.\n 'Grid Height': defaultGridHeight,\n // Grid Dimensions as string\n 'Grid Dimensions': `${defaultGridWidth}x${defaultGridHeight}`,\n\n // INVOCATION, WORKGROUP SIZE, AND WORKGROUP DISPATCH SETTINGS\n // The size of a workgroup, or the number of invocations executed within each workgroup\n // Determined algorithmically based on 'Size Limit', maxInvocationsX, and the current number of elements to sort\n 'Workgroup Size': maxInvocationsX,\n // An artifical constraint on the maximum workgroup size/maximumn invocations per workgroup as specified by device.limits.maxComputeWorkgroupSizeX\n 'Size Limit': maxInvocationsX,\n // Total workgroups that are dispatched during each step of the bitonic sort\n 'Workgroups Per Step': maxElements / (maxInvocationsX * 2),\n\n // HOVER SETTINGS\n // The element/cell in the element visualizer directly beneath the mouse cursor\n 'Hovered Cell': 0,\n // The element/cell in the element visualizer that the hovered cell will swap with in the next execution step of the bitonic sort.\n 'Swapped Cell': 1,\n\n // STEP INDEX, STEP TYPE, AND STEP SWAP SPAN SETTINGS\n // The index of the current step in the bitonic sort.\n 'Step Index': 0,\n // The total number of steps required to sort the displayed elements.\n 'Total Steps': getNumSteps(maxElements),\n // A string that condenses 'Step Index' and 'Total Steps' into a single GUI Controller display element.\n 'Current Step': `0 of 91`,\n // The category of the previously executed step. Always begins the bitonic sort with a value of 'NONE' and ends with a value of 'DISPERSE_LOCAL'\n 'Prev Step': 'NONE',\n // The category of the next step that will be executed. Always begins the bitonic sort with a value of 'FLIP_LOCAL' and ends with a value of 'NONE'\n 'Next Step': 'FLIP_LOCAL',\n // The maximum span of a swap operation in the sort's previous step.\n 'Prev Swap Span': 0,\n // The maximum span of a swap operation in the sort's upcoming step.\n 'Next Swap Span': 2,\n\n // ANIMATION LOOP AND FUNCTION SETTINGS\n // A flag that designates whether we will dispatch a workload this frame.\n executeStep: false,\n // A function that randomizes the values of each element.\n // When called, all relevant values within the settings object are reset to their defaults at the beginning of a sort with n elements.\n 'Randomize Values': () => {\n return;\n },\n // A function that manually executes a single step of the bitonic sort.\n 'Execute Sort Step': () => {\n return;\n },\n // A function that logs the values of each element as an array to the browser's console.\n 'Log Elements': () => {\n return;\n },\n // A function that automatically executes each step of the bitonic sort at an interval determined by 'Auto Sort Speed'\n 'Auto Sort': () => {\n return;\n },\n // The speed at which each step of the bitonic sort will be executed after 'Auto Sort' has been called.\n 'Auto Sort Speed': 50,\n\n // MISCELLANEOUS SETTINGS\n 'Display Mode': 'Elements',\n // An atomic value representing the total number of swap operations executed over the course of the bitonic sort.\n 'Total Swaps': 0,\n\n // TIMESTAMP SETTINGS\n // NOTE: Timestep values below all are calculated in terms of milliseconds rather than the nanoseconds a timestamp query set usually outputs.\n // Time taken to execute the previous step of the bitonic sort in milliseconds\n 'Step Time': '0ms',\n stepTime: 0,\n // Total taken to colletively execute each step of the complete bitonic sort, represented in milliseconds.\n 'Sort Time': '0ms',\n sortTime: 0,\n // Average time taken to complete a bitonic sort with the current combination of n 'Total Elements' and x 'Size Limit'\n 'Average Sort Time': '0ms',\n // A string to number map that maps a string representation of the current 'Total Elements' + 'Size Limit' configuration to a number\n // representing the total number of sorts that have been executed under that same configuration.\n configToCompleteSwapsMap: {\n '8192 256': {\n sorts: 0,\n time: 0,\n },\n },\n // Current key into configToCompleteSwapsMap\n configKey: '8192 256',\n };\n\n // Initialize initial elements array\n let elements = new Uint32Array(\n Array.from({ length: settings['Total Elements'] }, (_, i) => i)\n );\n\n // Initialize elementsBuffer and elementsStagingBuffer\n const elementsBufferSize =\n Float32Array.BYTES_PER_ELEMENT * totalElementOptions[0];\n // Initialize input, output, staging buffers\n const elementsInputBuffer = device.createBuffer({\n size: elementsBufferSize,\n usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,\n });\n const elementsOutputBuffer = device.createBuffer({\n size: elementsBufferSize,\n usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,\n });\n const elementsStagingBuffer = device.createBuffer({\n size: elementsBufferSize,\n usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,\n });\n\n // Initialize atomic swap buffer on GPU and CPU. Counts number of swaps actually performed by\n // compute shader (when value at index x is greater than value at index y)\n const atomicSwapsOutputBuffer = device.createBuffer({\n size: Uint32Array.BYTES_PER_ELEMENT,\n usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,\n });\n const atomicSwapsStagingBuffer = device.createBuffer({\n size: Uint32Array.BYTES_PER_ELEMENT,\n usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,\n });\n\n // Create uniform buffer for compute shader\n const computeUniformsBuffer = device.createBuffer({\n // width, height, blockHeight, algo\n size: Float32Array.BYTES_PER_ELEMENT * 4,\n usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,\n });\n\n const computeBGCluster = createBindGroupCluster(\n [0, 1, 2, 3],\n [\n GPUShaderStage.COMPUTE | GPUShaderStage.FRAGMENT,\n GPUShaderStage.COMPUTE,\n GPUShaderStage.COMPUTE | GPUShaderStage.FRAGMENT,\n GPUShaderStage.COMPUTE,\n ],\n ['buffer', 'buffer', 'buffer', 'buffer'],\n [\n { type: 'read-only-storage' },\n { type: 'storage' },\n { type: 'uniform' },\n { type: 'storage' },\n ],\n [\n [\n { buffer: elementsInputBuffer },\n { buffer: elementsOutputBuffer },\n { buffer: computeUniformsBuffer },\n { buffer: atomicSwapsOutputBuffer },\n ],\n ],\n 'BitonicSort',\n device\n );\n\n let computePipeline = device.createComputePipeline({\n layout: device.createPipelineLayout({\n bindGroupLayouts: [computeBGCluster.bindGroupLayout],\n }),\n compute: {\n module: device.createShaderModule({\n code: NaiveBitonicCompute(settings['Workgroup Size']),\n }),\n entryPoint: 'computeMain',\n },\n });\n\n // Simple pipeline that zeros out an atomic value at group 0 binding 3\n const atomicToZeroComputePipeline = device.createComputePipeline({\n layout: device.createPipelineLayout({\n bindGroupLayouts: [computeBGCluster.bindGroupLayout],\n }),\n compute: {\n module: device.createShaderModule({\n code: atomicToZero,\n }),\n entryPoint: 'atomicToZero',\n },\n });\n\n // Create bitonic debug renderer\n const renderPassDescriptor: GPURenderPassDescriptor = {\n colorAttachments: [\n {\n view: undefined, // Assigned later\n\n clearValue: { r: 0.1, g: 0.4, b: 0.5, a: 1.0 },\n loadOp: 'clear',\n storeOp: 'store',\n },\n ],\n };\n\n const bitonicDisplayRenderer = new BitonicDisplayRenderer(\n device,\n presentationFormat,\n renderPassDescriptor,\n computeBGCluster,\n 'BitonicDisplay'\n );\n\n const resetTimeInfo = () => {\n settings.stepTime = 0;\n settings.sortTime = 0;\n stepTimeController.setValue('0ms');\n sortTimeController.setValue(`0ms`);\n const nanCheck =\n settings.configToCompleteSwapsMap[settings.configKey].time /\n settings.configToCompleteSwapsMap[settings.configKey].sorts;\n const ast = nanCheck ? nanCheck : 0;\n averageSortTimeController.setValue(`${ast.toFixed(5)}ms`);\n };\n\n const resetExecutionInformation = () => {\n // The workgroup size is either elements / 2 or Size Limit\n workgroupSizeController.setValue(\n Math.min(settings['Total Elements'] / 2, settings['Size Limit'])\n );\n\n // Dispatch a workgroup for every (Size Limit * 2) elements\n const workgroupsPerStep =\n (settings['Total Elements'] - 1) / (settings['Size Limit'] * 2);\n\n workgroupsPerStepController.setValue(Math.ceil(workgroupsPerStep));\n\n // Reset step Index and number of steps based on elements size\n settings['Step Index'] = 0;\n settings['Total Steps'] = getNumSteps(settings['Total Elements']);\n currentStepController.setValue(\n `${settings['Step Index']} of ${settings['Total Steps']}`\n );\n\n // Get new width and height of screen display in cells\n const newCellWidth =\n Math.sqrt(settings['Total Elements']) % 2 === 0\n ? Math.floor(Math.sqrt(settings['Total Elements']))\n : Math.floor(Math.sqrt(settings['Total Elements'] / 2));\n const newCellHeight = settings['Total Elements'] / newCellWidth;\n settings['Grid Width'] = newCellWidth;\n settings['Grid Height'] = newCellHeight;\n gridDimensionsController.setValue(`${newCellWidth}x${newCellHeight}`);\n\n // Set prevStep to None (restart) and next step to FLIP\n prevStepController.setValue('NONE');\n nextStepController.setValue('FLIP_LOCAL');\n\n // Reset block heights\n prevBlockHeightController.setValue(0);\n nextBlockHeightController.setValue(2);\n\n // Reset Total Swaps by setting atomic value to 0\n const commandEncoder = device.createCommandEncoder();\n const computePassEncoder = commandEncoder.beginComputePass();\n computePassEncoder.setPipeline(atomicToZeroComputePipeline);\n computePassEncoder.setBindGroup(0, computeBGCluster.bindGroups[0]);\n computePassEncoder.dispatchWorkgroups(1);\n computePassEncoder.end();\n device.queue.submit([commandEncoder.finish()]);\n totalSwapsController.setValue(0);\n\n highestBlockHeight = 2;\n };\n\n const randomizeElementArray = () => {\n let currentIndex = elements.length;\n // While there are elements to shuffle\n while (currentIndex !== 0) {\n // Pick a remaining element\n const randomIndex = Math.floor(Math.random() * currentIndex);\n currentIndex -= 1;\n [elements[currentIndex], elements[randomIndex]] = [\n elements[randomIndex],\n elements[currentIndex],\n ];\n }\n };\n\n const resizeElementArray = () => {\n // Recreate elements array with new length\n elements = new Uint32Array(\n Array.from({ length: settings['Total Elements'] }, (_, i) => i)\n );\n\n resetExecutionInformation();\n\n // Create new shader invocation with workgroupSize that reflects number of invocations\n computePipeline = device.createComputePipeline({\n layout: device.createPipelineLayout({\n bindGroupLayouts: [computeBGCluster.bindGroupLayout],\n }),\n compute: {\n module: device.createShaderModule({\n code: NaiveBitonicCompute(\n Math.min(settings['Total Elements'] / 2, settings['Size Limit'])\n ),\n }),\n entryPoint: 'computeMain',\n },\n });\n // Randomize array elements\n randomizeElementArray();\n highestBlockHeight = 2;\n };\n\n randomizeElementArray();\n\n const setSwappedCell = () => {\n let swappedIndex: number;\n switch (settings['Next Step']) {\n case 'FLIP_LOCAL':\n case 'FLIP_GLOBAL':\n {\n const blockHeight = settings['Next Swap Span'];\n const p2 = Math.floor(settings['Hovered Cell'] / blockHeight) + 1;\n const p3 = settings['Hovered Cell'] % blockHeight;\n swappedIndex = blockHeight * p2 - p3 - 1;\n swappedCellController.setValue(swappedIndex);\n }\n break;\n case 'DISPERSE_LOCAL':\n {\n const blockHeight = settings['Next Swap Span'];\n const halfHeight = blockHeight / 2;\n swappedIndex =\n settings['Hovered Cell'] % blockHeight < halfHeight\n ? settings['Hovered Cell'] + halfHeight\n : settings['Hovered Cell'] - halfHeight;\n swappedCellController.setValue(swappedIndex);\n }\n break;\n case 'NONE': {\n swappedIndex = settings['Hovered Cell'];\n swappedCellController.setValue(swappedIndex);\n }\n default:\n {\n swappedIndex = settings['Hovered Cell'];\n swappedCellController.setValue(swappedIndex);\n }\n break;\n }\n };\n\n let autoSortIntervalID: ReturnType | null = null;\n const endSortInterval = () => {\n if (autoSortIntervalID !== null) {\n clearInterval(autoSortIntervalID);\n autoSortIntervalID = null;\n }\n };\n const startSortInterval = () => {\n const currentIntervalSpeed = settings['Auto Sort Speed'];\n autoSortIntervalID = setInterval(() => {\n if (settings['Next Step'] === 'NONE') {\n clearInterval(autoSortIntervalID);\n autoSortIntervalID = null;\n sizeLimitController.domElement.style.pointerEvents = 'auto';\n }\n if (settings['Auto Sort Speed'] !== currentIntervalSpeed) {\n clearInterval(autoSortIntervalID);\n autoSortIntervalID = null;\n startSortInterval();\n }\n settings.executeStep = true;\n setSwappedCell();\n }, settings['Auto Sort Speed']);\n };\n\n // At top level, information about resources used to execute the compute shader\n // i.e elements sorted, invocations per workgroup, and workgroups dispatched\n const computeResourcesFolder = gui.addFolder('Compute Resources');\n computeResourcesFolder\n .add(settings, 'Total Elements', totalElementOptions)\n .onChange(() => {\n endSortInterval();\n resizeElementArray();\n sizeLimitController.domElement.style.pointerEvents = 'auto';\n // Create new config key for current element + size limit configuration\n const currConfigKey = `${settings['Total Elements']} ${settings['Size Limit']}`;\n // If configKey doesn't exist in the map, create it.\n if (!settings.configToCompleteSwapsMap[currConfigKey]) {\n settings.configToCompleteSwapsMap[currConfigKey] = {\n sorts: 0,\n time: 0,\n };\n }\n settings.configKey = currConfigKey;\n resetTimeInfo();\n });\n const sizeLimitController = computeResourcesFolder\n .add(settings, 'Size Limit', sizeLimitOptions)\n .onChange(() => {\n // Change total workgroups per step and size of a workgroup based on arbitrary constraint\n // imposed by size limit.\n const constraint = Math.min(\n settings['Total Elements'] / 2,\n settings['Size Limit']\n );\n const workgroupsPerStep =\n (settings['Total Elements'] - 1) / (settings['Size Limit'] * 2);\n workgroupSizeController.setValue(constraint);\n workgroupsPerStepController.setValue(Math.ceil(workgroupsPerStep));\n // Apply new compute resources values to the sort's compute pipeline\n computePipeline = computePipeline = device.createComputePipeline({\n layout: device.createPipelineLayout({\n bindGroupLayouts: [computeBGCluster.bindGroupLayout],\n }),\n compute: {\n module: device.createShaderModule({\n code: NaiveBitonicCompute(\n Math.min(settings['Total Elements'] / 2, settings['Size Limit'])\n ),\n }),\n entryPoint: 'computeMain',\n },\n });\n // Create new config key for current element + size limit configuration\n const currConfigKey = `${settings['Total Elements']} ${settings['Size Limit']}`;\n // If configKey doesn't exist in the map, create it.\n if (!settings.configToCompleteSwapsMap[currConfigKey]) {\n settings.configToCompleteSwapsMap[currConfigKey] = {\n sorts: 0,\n time: 0,\n };\n }\n settings.configKey = currConfigKey;\n resetTimeInfo();\n });\n const workgroupSizeController = computeResourcesFolder.add(\n settings,\n 'Workgroup Size'\n );\n const workgroupsPerStepController = computeResourcesFolder.add(\n settings,\n 'Workgroups Per Step'\n );\n\n computeResourcesFolder.open();\n\n // Folder with functions that control the execution of the sort\n const controlFolder = gui.addFolder('Sort Controls');\n controlFolder.add(settings, 'Execute Sort Step').onChange(() => {\n // Size Limit locked upon sort\n sizeLimitController.domElement.style.pointerEvents = 'none';\n endSortInterval();\n settings.executeStep = true;\n });\n controlFolder.add(settings, 'Randomize Values').onChange(() => {\n endSortInterval();\n randomizeElementArray();\n resetExecutionInformation();\n resetTimeInfo();\n // Unlock workgroup size limit controller since sort has stopped\n sizeLimitController.domElement.style.pointerEvents = 'auto';\n });\n controlFolder\n .add(settings, 'Log Elements')\n .onChange(() => console.log(elements));\n controlFolder.add(settings, 'Auto Sort').onChange(() => {\n // Invocation Limit locked upon sort\n sizeLimitController.domElement.style.pointerEvents = 'none';\n startSortInterval();\n });\n controlFolder.add(settings, 'Auto Sort Speed', 50, 1000).step(50);\n controlFolder.open();\n\n // Information about grid display\n const gridFolder = gui.addFolder('Grid Information');\n gridFolder.add(settings, 'Display Mode', ['Elements', 'Swap Highlight']);\n const gridDimensionsController = gridFolder.add(\n settings,\n 'Grid Dimensions'\n );\n const hoveredCellController = gridFolder\n .add(settings, 'Hovered Cell')\n .onChange(setSwappedCell);\n const swappedCellController = gridFolder.add(settings, 'Swapped Cell');\n\n // Additional Information about the execution state of the sort\n const executionInformationFolder = gui.addFolder('Execution Information');\n const currentStepController = executionInformationFolder.add(\n settings,\n 'Current Step'\n );\n const prevStepController = executionInformationFolder.add(\n settings,\n 'Prev Step'\n );\n const nextStepController = executionInformationFolder.add(\n settings,\n 'Next Step'\n );\n const totalSwapsController = executionInformationFolder.add(\n settings,\n 'Total Swaps'\n );\n const prevBlockHeightController = executionInformationFolder.add(\n settings,\n 'Prev Swap Span'\n );\n const nextBlockHeightController = executionInformationFolder.add(\n settings,\n 'Next Swap Span'\n );\n\n // Timestamp information for Chrome 121+ or other compatible browsers\n const timestampFolder = gui.addFolder('Timestamp Info (Chrome 121+)');\n const stepTimeController = timestampFolder.add(settings, 'Step Time');\n const sortTimeController = timestampFolder.add(settings, 'Sort Time');\n const averageSortTimeController = timestampFolder.add(\n settings,\n 'Average Sort Time'\n );\n\n // Adjust styles of Function List Elements within GUI\n const liFunctionElements = document.getElementsByClassName('cr function');\n for (let i = 0; i < liFunctionElements.length; i++) {\n (liFunctionElements[i].children[0] as HTMLElement).style.display = 'flex';\n (liFunctionElements[i].children[0] as HTMLElement).style.justifyContent =\n 'center';\n (\n liFunctionElements[i].children[0].children[1] as HTMLElement\n ).style.position = 'absolute';\n }\n\n // Mouse listener that determines values of hoveredCell and swappedCell\n canvas.addEventListener('mousemove', (event) => {\n const currWidth = canvas.getBoundingClientRect().width;\n const currHeight = canvas.getBoundingClientRect().height;\n const cellSize: [number, number] = [\n currWidth / settings['Grid Width'],\n currHeight / settings['Grid Height'],\n ];\n const xIndex = Math.floor(event.offsetX / cellSize[0]);\n const yIndex =\n settings['Grid Height'] - 1 - Math.floor(event.offsetY / cellSize[1]);\n hoveredCellController.setValue(yIndex * settings['Grid Width'] + xIndex);\n settings['Hovered Cell'] = yIndex * settings['Grid Width'] + xIndex;\n });\n\n // Deactivate interaction with select GUI elements\n sizeLimitController.domElement.style.pointerEvents = 'none';\n workgroupsPerStepController.domElement.style.pointerEvents = 'none';\n hoveredCellController.domElement.style.pointerEvents = 'none';\n swappedCellController.domElement.style.pointerEvents = 'none';\n currentStepController.domElement.style.pointerEvents = 'none';\n prevStepController.domElement.style.pointerEvents = 'none';\n prevBlockHeightController.domElement.style.pointerEvents = 'none';\n nextStepController.domElement.style.pointerEvents = 'none';\n nextBlockHeightController.domElement.style.pointerEvents = 'none';\n workgroupSizeController.domElement.style.pointerEvents = 'none';\n gridDimensionsController.domElement.style.pointerEvents = 'none';\n totalSwapsController.domElement.style.pointerEvents = 'none';\n stepTimeController.domElement.style.pointerEvents = 'none';\n sortTimeController.domElement.style.pointerEvents = 'none';\n averageSortTimeController.domElement.style.pointerEvents = 'none';\n gui.width = 325;\n\n let highestBlockHeight = 2;\n\n startSortInterval();\n\n async function frame() {\n if (!pageState.active) return;\n\n // Write elements buffer\n device.queue.writeBuffer(\n elementsInputBuffer,\n 0,\n elements.buffer,\n elements.byteOffset,\n elements.byteLength\n );\n\n const dims = new Float32Array([\n settings['Grid Width'],\n settings['Grid Height'],\n ]);\n const stepDetails = new Uint32Array([\n StepEnum[settings['Next Step']],\n settings['Next Swap Span'],\n ]);\n device.queue.writeBuffer(\n computeUniformsBuffer,\n 0,\n dims.buffer,\n dims.byteOffset,\n dims.byteLength\n );\n\n device.queue.writeBuffer(computeUniformsBuffer, 8, stepDetails);\n\n renderPassDescriptor.colorAttachments[0].view = context\n .getCurrentTexture()\n .createView();\n\n const commandEncoder = device.createCommandEncoder();\n bitonicDisplayRenderer.startRun(commandEncoder, {\n highlight: settings['Display Mode'] === 'Elements' ? 0 : 1,\n });\n if (\n settings.executeStep &&\n highestBlockHeight < settings['Total Elements'] * 2\n ) {\n let computePassEncoder: GPUComputePassEncoder;\n if (timestampQueryAvailable) {\n computePassEncoder = commandEncoder.beginComputePass({\n timestampWrites: {\n querySet,\n beginningOfPassWriteIndex: 0,\n endOfPassWriteIndex: 1,\n },\n });\n } else {\n computePassEncoder = commandEncoder.beginComputePass();\n }\n computePassEncoder.setPipeline(computePipeline);\n computePassEncoder.setBindGroup(0, computeBGCluster.bindGroups[0]);\n computePassEncoder.dispatchWorkgroups(settings['Workgroups Per Step']);\n computePassEncoder.end();\n // Resolve time passed in between beginning and end of computePass\n if (timestampQueryAvailable) {\n commandEncoder.resolveQuerySet(\n querySet,\n 0,\n 2,\n timestampQueryResolveBuffer,\n 0\n );\n commandEncoder.copyBufferToBuffer(\n timestampQueryResolveBuffer,\n 0,\n timestampQueryResultBuffer,\n 0,\n 2 * BigInt64Array.BYTES_PER_ELEMENT\n );\n }\n settings['Step Index'] = settings['Step Index'] + 1;\n currentStepController.setValue(\n `${settings['Step Index']} of ${settings['Total Steps']}`\n );\n prevStepController.setValue(settings['Next Step']);\n prevBlockHeightController.setValue(settings['Next Swap Span']);\n nextBlockHeightController.setValue(settings['Next Swap Span'] / 2);\n // Each cycle of a bitonic sort contains a flip operation followed by multiple disperse operations\n // Next Swap Span will equal one when the sort needs to begin a new cycle of flip and disperse operations\n if (settings['Next Swap Span'] === 1) {\n // The next cycle's flip operation will have a maximum swap span 2 times that of the previous cycle\n highestBlockHeight *= 2;\n if (highestBlockHeight === settings['Total Elements'] * 2) {\n // The next cycle's maximum swap span exceeds the total number of elements. Therefore, the sort is over.\n // Accordingly, there will be no next step.\n nextStepController.setValue('NONE');\n // And if there is no next step, then there are no swaps, and no block range within which two elements are swapped.\n nextBlockHeightController.setValue(0);\n // Finally, with our sort completed, we can increment the number of total completed sorts executed with n 'Total Elements'\n // and x 'Size Limit', which will allow us to calculate the average time of all sorts executed with this specific\n // configuration of compute resources\n settings.configToCompleteSwapsMap[settings.configKey].sorts += 1;\n } else if (highestBlockHeight > settings['Workgroup Size'] * 2) {\n // The next cycle's maximum swap span exceeds the range of a single workgroup, so our next flip will operate on global indices.\n nextStepController.setValue('FLIP_GLOBAL');\n nextBlockHeightController.setValue(highestBlockHeight);\n } else {\n // The next cycle's maximum swap span can be executed on a range of indices local to the workgroup.\n nextStepController.setValue('FLIP_LOCAL');\n nextBlockHeightController.setValue(highestBlockHeight);\n }\n } else {\n // Otherwise, execute the next disperse operation\n settings['Next Swap Span'] > settings['Workgroup Size'] * 2\n ? nextStepController.setValue('DISPERSE_GLOBAL')\n : nextStepController.setValue('DISPERSE_LOCAL');\n }\n\n // Copy GPU accessible buffers to CPU accessible buffers\n commandEncoder.copyBufferToBuffer(\n elementsOutputBuffer,\n 0,\n elementsStagingBuffer,\n 0,\n elementsBufferSize\n );\n\n commandEncoder.copyBufferToBuffer(\n atomicSwapsOutputBuffer,\n 0,\n atomicSwapsStagingBuffer,\n 0,\n Uint32Array.BYTES_PER_ELEMENT\n );\n }\n device.queue.submit([commandEncoder.finish()]);\n\n if (\n settings.executeStep &&\n highestBlockHeight < settings['Total Elements'] * 4\n ) {\n // Copy GPU element data to CPU\n await elementsStagingBuffer.mapAsync(\n GPUMapMode.READ,\n 0,\n elementsBufferSize\n );\n const copyElementsBuffer = elementsStagingBuffer.getMappedRange(\n 0,\n elementsBufferSize\n );\n // Copy atomic swaps data to CPU\n await atomicSwapsStagingBuffer.mapAsync(\n GPUMapMode.READ,\n 0,\n Uint32Array.BYTES_PER_ELEMENT\n );\n const copySwapsBuffer = atomicSwapsStagingBuffer.getMappedRange(\n 0,\n Uint32Array.BYTES_PER_ELEMENT\n );\n const elementsData = copyElementsBuffer.slice(\n 0,\n Uint32Array.BYTES_PER_ELEMENT * settings['Total Elements']\n );\n const swapsData = copySwapsBuffer.slice(\n 0,\n Uint32Array.BYTES_PER_ELEMENT\n );\n // Extract data\n const elementsOutput = new Uint32Array(elementsData);\n totalSwapsController.setValue(new Uint32Array(swapsData)[0]);\n elementsStagingBuffer.unmap();\n atomicSwapsStagingBuffer.unmap();\n // Elements output becomes elements input, swap accumulate\n elements = elementsOutput;\n setSwappedCell();\n\n // Handle timestamp query stuff\n if (timestampQueryAvailable) {\n // Copy timestamp query result buffer data to CPU\n await timestampQueryResultBuffer.mapAsync(\n GPUMapMode.READ,\n 0,\n 2 * BigInt64Array.BYTES_PER_ELEMENT\n );\n const copyTimestampResult = new BigInt64Array(\n timestampQueryResultBuffer.getMappedRange()\n );\n // Calculate new step, sort, and average sort times\n const newStepTime =\n Number(copyTimestampResult[1] - copyTimestampResult[0]) / 1000000;\n const newSortTime = settings.sortTime + newStepTime;\n // Apply calculated times to settings object as both number and 'ms' appended string\n settings.stepTime = newStepTime;\n settings.sortTime = newSortTime;\n stepTimeController.setValue(`${newStepTime.toFixed(5)}ms`);\n sortTimeController.setValue(`${newSortTime.toFixed(5)}ms`);\n // Calculate new average sort upon end of final execution step of a full bitonic sort.\n if (highestBlockHeight === settings['Total Elements'] * 2) {\n // Lock off access to this larger if block..not best architected solution but eh\n highestBlockHeight *= 2;\n settings.configToCompleteSwapsMap[settings.configKey].time +=\n newSortTime;\n const averageSortTime =\n settings.configToCompleteSwapsMap[settings.configKey].time /\n settings.configToCompleteSwapsMap[settings.configKey].sorts;\n averageSortTimeController.setValue(\n `${averageSortTime.toFixed(5)}ms`\n );\n }\n timestampQueryResultBuffer.unmap();\n // Get correct range of data from CPU copy of GPU Data\n }\n }\n settings.executeStep = false;\n requestAnimationFrame(frame);\n }\n requestAnimationFrame(frame);\n }\n).then((resultInit) => (init = resultInit));\n\nconst bitonicSortExample: () => JSX.Element = () =>\n makeSample({\n name: 'Bitonic Sort',\n description:\n \"A naive bitonic sort algorithm executed on the GPU, based on tgfrerer's implementation at poniesandlight.co.uk/reflect/bitonic_merge_sort/. Each dispatch of the bitonic sort shader dispatches a workgroup containing elements/2 invocations. The GUI's Execution Information folder contains information about the sort's current state. The visualizer displays the sort's results as colored cells sorted from brightest to darkest.\",\n init,\n gui: true,\n sources: [\n {\n name: __filename.substring(__dirname.length + 1),\n contents: __SOURCE__,\n },\n BitonicDisplayRenderer.sourceInfo,\n {\n name: '../../../shaders/fullscreenTexturedQuad.vert.wgsl',\n contents: fullscreenTexturedQuad,\n },\n {\n name: './bitonicDisplay.frag.wgsl',\n contents: bitonicDisplay,\n },\n {\n name: './bitonicCompute.ts',\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n contents: require('!!raw-loader!./bitonicCompute.ts').default,\n },\n {\n name: './atomicToZero.wgsl',\n contents: atomicToZero,\n },\n ],\n filename: __filename,\n });\n\nexport default bitonicSortExample;\n"},u.sourceInfo,{name:"../../../shaders/fullscreenTexturedQuad.vert.wgsl",contents:p.Z},{name:"./bitonicDisplay.frag.wgsl",contents:l},{name:"./bitonicCompute.ts",contents:t(6502).Z},{name:"./atomicToZero.wgsl",contents:d}],filename:m});var h=g},7606:function(e,n,t){"use strict";t.d(n,{NJ:function(){return a},Ot:function(){return i},a1:function(){return r}});var o=t(134);let r=(e,n,t,o,r,i,a)=>{let s=[];for(let l=0;l{let n=async n=>{let t,{canvas:o,pageState:r,gui:i,stats:a}=n,s=await navigator.gpu.requestAdapter(),l=s.features.has("timestamp-query");if(t=l?await s.requestDevice({requiredFeatures:["timestamp-query"]}):await s.requestDevice(),!r.active)return;let u=o.getContext("webgpu"),c=window.devicePixelRatio;o.width=o.clientWidth*c,o.height=o.clientHeight*c;let p=navigator.gpu.getPreferredCanvasFormat();u.configure({device:t,format:p,alphaMode:"premultiplied"}),e({canvas:o,pageState:r,gui:i,device:t,context:u,presentationFormat:p,stats:a,timestampQueryAvailable:l})};return n};class a{executeRun(e,n,t,o){let r=e.beginRenderPass(n);r.setPipeline(t);for(let i=0;i{let n=(0,o.useRef)(null),i=(0,o.useRef)(null),u=(0,o.useMemo)(()=>e.sources.map(e=>{let{name:n,contents:i}=e;return{name:n,...function(e){let n;let i=null;{i=document.createElement("div");let a=t(4631);n=a(i,{lineNumbers:!0,lineWrapping:!0,theme:"monokai",readOnly:!0})}return{Container:function(t){return(0,r.jsx)("div",{...t,children:(0,r.jsx)("div",{ref(t){i&&t&&(t.appendChild(i),n.setOption("value",e))}})})}}}(i)}}),e.sources),d=(0,o.useRef)(null),f=(0,o.useMemo)(()=>{if(e.gui){let n=t(4376),r=new n.GUI({autoPlace:!1});return r.domElement.style.position="relative",r.domElement.style.zIndex="1000",r}},[]),l=(0,o.useRef)(null),p=(0,o.useMemo)(()=>{if(e.stats){let n=t(2792);return new n}},[]),m=(0,s.useRouter)(),h=m.asPath.match(/#([a-zA-Z0-9\.\/]+)/),[g,b]=(0,o.useState)(null),[y,v]=(0,o.useState)(null);return(0,o.useEffect)(()=>{if(h?v(h[1]):v(u[0].name),f&&d.current)for(d.current.appendChild(f.domElement);f.__controllers.length>0;)f.__controllers[0].remove();p&&l.current&&(p.dom.style.position="absolute",p.showPanel(1),l.current.appendChild(p.dom));let t={active:!0},r=()=>{t.active=!1};try{let i=n.current;if(!i)throw Error("The canvas is not available");let a=e.init({canvas:i,pageState:t,gui:f,stats:p});a instanceof Promise&&a.catch(e=>{console.error(e),b(e)})}catch(s){console.error(s),b(s)}return r},[]),(0,r.jsxs)("main",{children:[(0,r.jsxs)(a(),{children:[(0,r.jsx)("style",{dangerouslySetInnerHTML:{__html:"\n .CodeMirror {\n height: auto !important;\n margin: 1em 0;\n }\n\n .CodeMirror-scroll {\n height: auto !important;\n overflow: visible !important;\n }\n "}}),(0,r.jsx)("title",{children:"".concat(e.name," - WebGPU Samples")}),(0,r.jsx)("meta",{name:"description",content:e.description}),(0,r.jsx)("meta",{httpEquiv:"origin-trial",content:e.originTrial})]}),(0,r.jsxs)("div",{children:[(0,r.jsx)("h1",{children:e.name}),(0,r.jsx)("a",{target:"_blank",rel:"noreferrer",href:"https://github.com/".concat("webgpu/webgpu-samples","/tree/main/").concat(e.filename),children:"See it on Github!"}),(0,r.jsx)("p",{children:e.description}),g?(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)("p",{children:"Something went wrong. Do your browser and device support WebGPU?"}),(0,r.jsx)("p",{children:"".concat(g)})]}):null]}),(0,r.jsxs)("div",{className:c().canvasContainer,children:[(0,r.jsx)("div",{style:{position:"absolute",left:10},ref:l}),(0,r.jsx)("div",{style:{position:"absolute",right:10},ref:d}),(0,r.jsx)("canvas",{ref:n})]}),(0,r.jsxs)("div",{children:[(0,r.jsx)("nav",{className:c().sourceFileNav,ref:i,children:(0,r.jsx)("div",{className:c().sourceFileScrollContainer,onScroll(e){let n=e.currentTarget,t=n.scrollWidth-n.clientWidth-n.scrollLeft;n.scrollLeft>25?i.current.setAttribute("data-left","true"):i.current.setAttribute("data-left","false"),t>25?i.current.setAttribute("data-right","true"):i.current.setAttribute("data-right","false")},children:(0,r.jsx)("ul",{children:u.map((e,n)=>(0,r.jsx)("li",{children:(0,r.jsx)("a",{href:"#".concat(e.name),"data-active":y==e.name,onClick(){v(e.name)},children:e.name})},n))})})}),u.map((e,n)=>(0,r.jsx)(e.Container,{className:c().sourceFileContainer,"data-active":y==e.name},n))]})]})},f=e=>(0,r.jsx)(d,{...e});function l(e,n){if(!e)throw Error(n)}},7606:function(e,n,t){"use strict";t.d(n,{NJ:function(){return s},Ot:function(){return a},a1:function(){return i}});var r=t(134);let i=(e,n,t,r,i,a,s)=>{let o=[];for(let u=0;u{let n=async n=>{let t,{canvas:r,pageState:i,gui:a,stats:s}=n,o=await navigator.gpu.requestAdapter(),u=o.features.has("timestamp-query");if(t=u?await o.requestDevice({requiredFeatures:["timestamp-query"]}):await o.requestDevice(),!i.active)return;let c=r.getContext("webgpu"),d=window.devicePixelRatio;r.width=r.clientWidth*d,r.height=r.clientHeight*d;let f=navigator.gpu.getPreferredCanvasFormat();c.configure({device:t,format:f,alphaMode:"premultiplied"}),e({canvas:r,pageState:i,gui:a,device:t,context:c,presentationFormat:f,stats:s,timestampQueryAvailable:u})};return n};class s{executeRun(e,n,t,r){let i=e.beginRenderPass(n);i.setPipeline(t);for(let a=0;aMath.floor((e+n-1)/n)*n,g=e=>{switch(e){case"SCALAR":return d.SCALAR;case"VEC2":return d.VEC2;case"VEC3":return d.VEC3;case"VEC4":return d.VEC4;case"MAT2":return d.MAT2;case"MAT3":return d.MAT3;case"MAT4":return d.MAT4;default:throw Error("Unhandled glTF Type ".concat(e))}},b=e=>{switch(e){case d.SCALAR:return 1;case d.VEC2:return 2;case d.VEC3:return 3;case d.VEC4:case d.MAT2:return 4;case d.MAT3:return 9;case d.MAT4:return 16;default:throw Error("Invalid glTF Type ".concat(e))}},y=(e,n)=>{let t=null;switch(e){case c.BYTE:t="sint8";break;case c.UNSIGNED_BYTE:t="uint8";break;case c.SHORT:t="sint16";break;case c.UNSIGNED_SHORT:t="uint16";break;case c.INT:t="int32";break;case c.UNSIGNED_INT:t="uint32";break;case c.FLOAT:t="float32";break;default:throw Error("Unrecognized or unsupported glTF type ".concat(e))}switch(b(n)){case 1:return t;case 2:return t+"x2";case 3:return t+"x3";case 4:return t+"x4";default:throw Error("Invalid number of components for gltfType: ".concat(n))}},v=(e,n)=>{let t=0;switch(e){case c.BYTE:case c.UNSIGNED_BYTE:t=1;break;case c.SHORT:case c.UNSIGNED_SHORT:t=2;break;case c.INT:case c.UNSIGNED_INT:case c.FLOAT:t=4;break;case c.DOUBLE:t=8;break;default:throw Error("Unrecognized GLTF Component Type?")}return b(n)*t},T=e=>{switch(e){case"float32":default:return"f32";case"float32x2":return"vec2";case"float32x3":return"vec3";case"float32x4":return"vec4";case"uint32":return"u32";case"uint32x2":case"uint8x2":case"uint16x2":return"vec2";case"uint32x3":return"vec3";case"uint32x4":case"uint8x4":case"uint16x4":return"vec4"}};class G{constructor(e,n,t){this.buffer=new Uint8Array(e,n,t)}}class w{addUsage(e){this.usage=this.usage|e}upload(e){let n=e.createBuffer({size:h(this.view.byteLength,4),usage:this.usage,mappedAtCreation:!0});new Uint8Array(n.getMappedRange()).set(this.view),n.unmap(),this.gpuBuffer=n,this.needsUpload=!1}constructor(e,n){this.byteLength=n.byteLength,this.byteStride=0,void 0!==n.byteStride&&(this.byteStride=n.byteStride);let t=0;void 0!==n.byteOffset&&(t=n.byteOffset),this.view=e.buffer.subarray(t,t+this.byteLength),this.needsUpload=!1,this.gpuBuffer=null,this.usage=0}}class x{get byteStride(){let e=v(this.componentType,this.structureType);return Math.max(e,this.view.byteStride)}get byteLength(){return this.count*this.byteStride}get vertexType(){return y(this.componentType,this.structureType)}constructor(e,n){this.count=n.count,this.componentType=n.componentType,this.structureType=g(n.type),this.view=e,this.byteOffset=0,void 0!==n.byteOffset&&(this.byteOffset=n.byteOffset)}}class S{buildRenderPipeline(e,n,t,r,i,a,s){let o="struct VertexInput {\n",c=this.attributes.map((e,n)=>{let t=this.attributeMap[e].vertexType,r=e.toLowerCase().replace(/_0$/,"");return o+=" @location(".concat(n,") ").concat(r,": ").concat(T(t),",\n"),{arrayStride:this.attributeMap[e].byteStride,attributes:[{format:this.attributeMap[e].vertexType,offset:this.attributeMap[e].byteOffset,shaderLocation:n}]}});o+="}";let d={module:e.createShaderModule({code:o+n}),entryPoint:"vertexMain",buffers:c},f={module:e.createShaderModule({code:o+t}),entryPoint:"fragmentMain",targets:[{format:r}]},l={topology:"triangle-list"};this.topology==u.TRIANGLE_STRIP&&(l.topology="triangle-strip",l.stripIndexFormat=this.attributeMap.INDICES.vertexType);let p=e.createPipelineLayout({bindGroupLayouts:a,label:"".concat(s,".pipelineLayout")});this.renderPipeline=e.createRenderPipeline({layout:p,label:"".concat(s,".pipeline"),vertex:d,fragment:f,primitive:l,depthStencil:{format:i,depthWriteEnabled:!0,depthCompare:"less"}})}render(e,n){e.setPipeline(this.renderPipeline),n.forEach((n,t)=>{e.setBindGroup(t,n)}),this.attributes.map((n,t)=>{e.setVertexBuffer(t,this.attributeMap[n].view.gpuBuffer,this.attributeMap[n].byteOffset,this.attributeMap[n].byteLength)}),this.attributeMap.INDICES?(e.setIndexBuffer(this.attributeMap.INDICES.view.gpuBuffer,this.attributeMap.INDICES.vertexType,this.attributeMap.INDICES.byteOffset,this.attributeMap.INDICES.byteLength),e.drawIndexed(this.attributeMap.INDICES.count)):e.draw(this.attributeMap.POSITION.count)}constructor(e,n,t){for(let r in this.attributes=[],this.topology=e,this.renderPipeline=null,this.attributeMap=n,this.attributes=t,this.attributeMap){if(this.attributeMap[r].view.needsUpload=!0,"INDICES"===r){this.attributeMap.INDICES.view.addUsage(GPUBufferUsage.INDEX);continue}this.attributeMap[r].view.addUsage(GPUBufferUsage.VERTEX)}}}class B{buildRenderPipeline(e,n,t,r,i,a){for(let s=0;s{if(1179937895!=e.getUint32(0,!0))throw Error("Provided file is not a glB file");if(2!=e.getUint32(4,!0))throw Error("Provided file is glTF 2.0 file")},M=e=>{if(5130562!=e[1])throw Error("Invalid glB: The second chunk of the glB file is not a binary chunk!")};class U{getMatrix(){let e=m._E.identity();m._E.scale(e,this.scale,e);let n=m._E.fromQuat(this.rotation);return m._E.multiply(n,e,e),m._E.translate(e,this.position,e),e}constructor(e=[0,0,0],n=[0,0,0,1],t=[1,1,1]){this.position=e,this.rotation=n,this.scale=t}}class L{setParent(e){this.parent&&(this.parent.removeChild(this),this.parent=null),e.addChild(this),this.parent=e}updateWorldMatrix(e,n){this.localMatrix=this.source.getMatrix(),n?m._E.multiply(n,this.localMatrix,this.worldMatrix):m._E.copy(this.localMatrix,this.worldMatrix);let t=this.worldMatrix;for(let r of(e.queue.writeBuffer(this.nodeTransformGPUBuffer,0,t.buffer,t.byteOffset,t.byteLength),this.children))r.updateWorldMatrix(e,t)}traverse(e){for(let n of(e(this),this.children))n.traverse(e)}renderDrawables(e,n){if(void 0!==this.drawables)for(let t of this.drawables)this.skin?t.render(e,[...n,this.nodeTransformBindGroup,this.skin.skinBindGroup]):t.render(e,[...n,this.nodeTransformBindGroup]);for(let r of this.children)r.renderDrawables(e,n)}addChild(e){this.children.push(e)}removeChild(e){let n=this.children.indexOf(e);this.children.splice(n,1)}constructor(e,n,t,r,i){this.test=0,this.name=r||"node_".concat(t.position," ").concat(t.rotation," ").concat(t.scale),this.source=t,this.parent=null,this.children=[],this.localMatrix=m._E.identity(),this.worldMatrix=m._E.identity(),this.drawables=[],this.nodeTransformGPUBuffer=e.createBuffer({size:16*Float32Array.BYTES_PER_ELEMENT,usage:GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST}),this.nodeTransformBindGroup=e.createBindGroup({layout:n,entries:[{binding:0,resource:{buffer:this.nodeTransformGPUBuffer}}]}),this.skin=i}}class P{constructor(e,n,t){this.nodes=t.nodes,this.name=t.name,this.root=new L(e,n,new U,t.name)}}class C{static createSharedBindGroupLayout(e){this.skinBindGroupLayout=e.createBindGroupLayout({label:"StaticGLTFSkin.bindGroupLayout",entries:[{binding:0,buffer:{type:"read-only-storage"},visibility:GPUShaderStage.VERTEX},{binding:1,buffer:{type:"read-only-storage"},visibility:GPUShaderStage.VERTEX}]})}update(e,n,t){let r=m._E.inverse(t[n].worldMatrix);for(let i=0;i matrix, or does not access the provided mat4x4 data correctly");this.inverseBindMatrices=new Float32Array(n.view.view.buffer,n.view.view.byteOffset,n.view.view.byteLength/4),this.joints=t;let r={size:16*Float32Array.BYTES_PER_ELEMENT*t.length,usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_DST};this.jointMatricesUniformBuffer=e.createBuffer(r),this.inverseBindMatricesUniformBuffer=e.createBuffer(r),e.queue.writeBuffer(this.inverseBindMatricesUniformBuffer,0,this.inverseBindMatrices),this.skinBindGroup=e.createBindGroup({layout:C.skinBindGroupLayout,label:"StaticGLTFSkin.bindGroup",entries:[{binding:0,resource:{buffer:this.jointMatricesUniformBuffer}},{binding:1,resource:{buffer:this.inverseBindMatricesUniformBuffer}}]})}}let I=async(e,n)=>{var t,r,i,a,s;let o=new DataView(e,0,20);E(o);let c=o.getUint32(12,!0),d=JSON.parse(new TextDecoder("utf-8").decode(new Uint8Array(e,20,c)));console.log(d);let f=new Uint32Array(e,20+c,2);M(f);let l=new G(e,28+c,f[0]);for(let p of d.accessors)p.byteOffset=null!==(t=p.byteOffset)&&void 0!==t?t:0,p.normalized=null!==(r=p.normalized)&&void 0!==r&&r;for(let m of d.bufferViews)m.byteOffset=null!==(i=m.byteOffset)&&void 0!==i?i:0;if(d.samplers)for(let h of d.samplers)h.wrapS=null!==(a=h.wrapS)&&void 0!==a?a:10497,h.wrapT=null!==(s=h.wrapT)&&void 0!==s?s:10947;for(let g of d.meshes)for(let b of g.primitives){if("indices"in b){let y=d.accessors[b.indices];d.accessors[b.indices].bufferViewUsage|=GPUBufferUsage.INDEX,d.bufferViews[y.bufferView].usage|=GPUBufferUsage.INDEX}for(let v of Object.values(b.attributes)){let T=d.accessors[v];d.accessors[v].bufferViewUsage|=GPUBufferUsage.VERTEX,d.bufferViews[T.bufferView].usage|=GPUBufferUsage.VERTEX}}let I=[];for(let k=0;k3)throw Error("Vertex attribute accessor accessed an unsupported data type for vertex attribute");z.push(H)}V.push(new S(W,Y,z))}F.push(new B(O.name,V))}let J=[];for(let $ of d.skins){let Q=A[$.inverseBindMatrices];Q.view.addUsage(GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST),Q.view.needsUpload=!0}for(let K=0;K{let t=d.nodes[n].children;t&&t.forEach(n=>{let t=er[n];t.setParent(e)})});let ec=[];for(let ed of d.scenes){let ef=new P(n,ei,ed),el=ef.nodes;el.forEach(e=>{let n=er[e];n.setParent(ef.root)}),ec.push(ef)}return{meshes:F,nodes:er,scenes:ec,skins:J}};var k="// Whale.glb Vertex attributes\n// Read in VertexInput from attributes\n// f32x3 f32x3 f32x2 u8x4 f32x4\nstruct VertexOutput {\n @builtin(position) Position: vec4,\n @location(0) normal: vec3,\n @location(1) joints: vec4,\n @location(2) weights: vec4,\n}\n\nstruct CameraUniforms {\n proj_matrix: mat4x4f,\n view_matrix: mat4x4f,\n model_matrix: mat4x4f,\n}\n\nstruct GeneralUniforms {\n render_mode: u32,\n skin_mode: u32,\n}\n\nstruct NodeUniforms {\n world_matrix: mat4x4f,\n}\n\n@group(0) @binding(0) var camera_uniforms: CameraUniforms;\n@group(1) @binding(0) var general_uniforms: GeneralUniforms;\n@group(2) @binding(0) var node_uniforms: NodeUniforms;\n@group(3) @binding(0) var joint_matrices: array>;\n@group(3) @binding(1) var inverse_bind_matrices: array>;\n\n@vertex\nfn vertexMain(input: VertexInput) -> VertexOutput {\n var output: VertexOutput;\n // Compute joint_matrices * inverse_bind_matrices\n let joint0 = joint_matrices[input.joints[0]] * inverse_bind_matrices[input.joints[0]];\n let joint1 = joint_matrices[input.joints[1]] * inverse_bind_matrices[input.joints[1]];\n let joint2 = joint_matrices[input.joints[2]] * inverse_bind_matrices[input.joints[2]];\n let joint3 = joint_matrices[input.joints[3]] * inverse_bind_matrices[input.joints[3]];\n // Compute influence of joint based on weight\n let skin_matrix = \n joint0 * input.weights[0] +\n joint1 * input.weights[1] +\n joint2 * input.weights[2] +\n joint3 * input.weights[3];\n // Position of the vertex relative to our world\n let world_position = vec4(input.position.x, input.position.y, input.position.z, 1.0);\n // Vertex position with model rotation, skinning, and the mesh's node transformation applied.\n let skinned_position = camera_uniforms.model_matrix * skin_matrix * node_uniforms.world_matrix * world_position;\n // Vertex position with only the model rotation applied.\n let rotated_position = camera_uniforms.model_matrix * world_position;\n // Determine which position to used based on whether skinMode is turnd on or off.\n let transformed_position = select(\n rotated_position,\n skinned_position,\n general_uniforms.skin_mode == 0\n );\n // Apply the camera and projection matrix transformations to our transformed position;\n output.Position = camera_uniforms.proj_matrix * camera_uniforms.view_matrix * transformed_position;\n output.normal = input.normal;\n // Convert u32 joint data to f32s to prevent flat interpolation error.\n output.joints = vec4(f32(input.joints[0]), f32(input.joints[1]), f32(input.joints[2]), f32(input.joints[3]));\n output.weights = input.weights;\n return output;\n}\n\n@fragment\nfn fragmentMain(input: VertexOutput) -> @location(0) vec4 {\n switch general_uniforms.render_mode {\n case 1: {\n return input.joints;\n } \n case 2: {\n return input.weights;\n }\n default: {\n return vec4(input.normal, 1.0);\n }\n }\n}",A=t(2624),N=t(7606);let j=new Float32Array([0,1,0,-1,2,1,2,-1,4,1,4,-1,6,1,6,-1,8,1,8,-1,10,1,10,-1,12,1,12,-1]),_=new Uint32Array([0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,1,0,0,0,1,0,0,0,1,2,0,0,1,2,0,0,2,0,0,0,2,0,0,0,1,2,3,0,1,2,3,0,2,3,0,0,2,3,0,0]),F=new Float32Array([1,0,0,0,1,0,0,0,.5,.5,0,0,.5,.5,0,0,1,0,0,0,1,0,0,0,.5,.5,0,0,.5,.5,0,0,1,0,0,0,1,0,0,0,.5,.5,0,0,.5,.5,0,0,1,0,0,0,1,0,0,0]),R=new Uint16Array([0,1,0,2,1,3,2,3,2,4,3,5,4,5,4,6,5,7,6,7,6,8,7,9,8,9,8,10,9,11,10,11,10,12,11,13,12,13]),O=e=>{let n=(n,t)=>{let r=e.createBuffer({size:n.byteLength,usage:GPUBufferUsage.VERTEX,mappedAtCreation:!0});return"f32"===t?new Float32Array(r.getMappedRange()).set(n):new Uint32Array(r.getMappedRange()).set(n),r.unmap(),r},t=n(j,"f32"),r=n(_,"u32"),i=n(F,"f32"),a=e.createBuffer({size:Uint16Array.BYTES_PER_ELEMENT*R.length,usage:GPUBufferUsage.INDEX,mappedAtCreation:!0});return new Uint16Array(a.getMappedRange()).set(R),a.unmap(),{positions:t,joints:r,weights:i,indices:a}},V=(e,n,t,r,i)=>{let a=e.createRenderPipeline({label:"SkinnedGridRenderer",layout:e.createPipelineLayout({label:"SkinnedGridRenderer.pipelineLayout",bindGroupLayouts:i}),vertex:{module:e.createShaderModule({label:"SkinnedGridRenderer.vertexShader",code:t}),entryPoint:"vertexMain",buffers:[{arrayStride:2*Float32Array.BYTES_PER_ELEMENT,attributes:[{format:"float32x2",offset:0,shaderLocation:0}]},{arrayStride:4*Uint32Array.BYTES_PER_ELEMENT,attributes:[{format:"uint32x4",offset:0,shaderLocation:1}]},{arrayStride:4*Float32Array.BYTES_PER_ELEMENT,attributes:[{format:"float32x4",offset:0,shaderLocation:2}]}]},fragment:{module:e.createShaderModule({label:"SkinnedGridRenderer.fragmentShader",code:r}),entryPoint:"fragmentMain",targets:[{format:n}]},primitive:{topology:"line-list"}});return a};var D="src/sample/skinnedMesh/main.ts";(s=f||(f={}))[s.NORMAL=0]="NORMAL",s[s.JOINTS=1]="JOINTS",s[s.WEIGHTS=2]="WEIGHTS",(o=l||(l={}))[o.ON=0]="ON",o[o.OFF=1]="OFF";let q=e=>{let n=[0,0,0,0],t=m._E.getScaling(e),r=1/t[0],i=1/t[1],a=1/t[2],s=e[0]*r,o=e[1]*i,u=e[2]*a,c=e[4]*r,d=e[5]*i,f=e[6]*a,l=e[8]*r,p=e[9]*i,h=e[10]*a,g=s+d+h,b=0;return g>0?(b=2*Math.sqrt(g+1),n[3]=.25*b,n[0]=(f-p)/b,n[1]=(l-u)/b,n[2]=(o-c)/b):s>d&&s>h?(b=2*Math.sqrt(1+s-d-h),n[3]=(f-p)/b,n[0]=.25*b,n[1]=(o+c)/b,n[2]=(l+u)/b):d>h?(b=2*Math.sqrt(1+d-s-h),n[3]=(l-u)/b,n[0]=(o+c)/b,n[1]=.25*b,n[2]=(f+p)/b):(b=2*Math.sqrt(1+h-s-d),n[3]=(o-c)/b,n[0]=(l+u)/b,n[1]=(f+p)/b,n[2]=.25*b),n},W=async e=>{let{canvas:n,pageState:t,gui:r}=e,i=await navigator.gpu.requestAdapter(),a=await i.requestDevice();if(!t.active)return;let s=n.getContext("webgpu"),o=window.devicePixelRatio||1;n.width=n.clientWidth*o,n.height=n.clientHeight*o;let u=navigator.gpu.getPreferredCanvasFormat();s.configure({device:a,format:u,alphaMode:"premultiplied"});let c={cameraX:0,cameraY:-5.1,cameraZ:-14.6,objectScale:1,angle:.2,speed:50,object:"Whale",renderMode:"NORMAL",skinMode:"ON"};r.add(c,"object",["Whale","Skinned Grid"]).onChange(()=>{"Skinned Grid"===c.object?(c.cameraX=-10,c.cameraY=0,c.objectScale=1.27):"OFF"===c.skinMode?(c.cameraX=0,c.cameraY=0,c.cameraZ=-11):(c.cameraX=0,c.cameraY=-5.1,c.cameraZ=-14.6)}),r.add(c,"renderMode",["NORMAL","JOINTS","WEIGHTS"]).onChange(()=>{a.queue.writeBuffer(b,0,new Uint32Array([f[c.renderMode]]))}),r.add(c,"skinMode",["ON","OFF"]).onChange(()=>{"Whale"===c.object&&("OFF"===c.skinMode?(c.cameraX=0,c.cameraY=0,c.cameraZ=-11):(c.cameraX=0,c.cameraY=-5.1,c.cameraZ=-14.6)),a.queue.writeBuffer(b,4,new Uint32Array([l[c.skinMode]]))});let d=r.addFolder("Animation Settings");d.add(c,"angle",.05,.5).step(.05),d.add(c,"speed",10,100).step(10);let p=a.createTexture({size:[n.width,n.height],format:"depth24plus",usage:GPUTextureUsage.RENDER_ATTACHMENT}),h=a.createBuffer({size:192,usage:GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST}),g=(0,N.a1)([0],[GPUShaderStage.VERTEX],["buffer"],[{type:"uniform"}],[[{buffer:h}]],"Camera",a),b=a.createBuffer({size:2*Uint32Array.BYTES_PER_ELEMENT,usage:GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST}),y=(0,N.a1)([0],[GPUShaderStage.VERTEX|GPUShaderStage.FRAGMENT],["buffer"],[{type:"uniform"}],[[{buffer:b}]],"General",a),v=a.createBindGroupLayout({label:"NodeUniforms.bindGroupLayout",entries:[{binding:0,buffer:{type:"uniform"},visibility:GPUShaderStage.VERTEX}]}),T=await fetch("../assets/gltf/whale.glb").then(e=>e.arrayBuffer()).then(e=>I(e,a));T.meshes[0].buildRenderPipeline(a,k,k,u,p.format,[g.bindGroupLayout,y.bindGroupLayout,v,C.skinBindGroupLayout]);let G=O(a),w={size:320,usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_DST},x=a.createBuffer(w),S=a.createBuffer(w),B=(0,N.a1)([0,1],[GPUShaderStage.VERTEX,GPUShaderStage.VERTEX],["buffer","buffer"],[{type:"read-only-storage"},{type:"read-only-storage"}],[[{buffer:x},{buffer:S}]],"SkinnedGridJointUniforms",a),E=V(a,u,A.Z,A.Z,[g.bindGroupLayout,y.bindGroupLayout,B.bindGroupLayout]),M=n.width/n.height,U=m._E.perspective(2*Math.PI/5,M,.1,100),L=m._E.ortho(-20,20,-10,10,-100,100),P={colorAttachments:[{view:void 0,clearValue:{r:.3,g:.3,b:.3,a:1},loadOp:"clear",storeOp:"store"}],depthStencilAttachment:{view:p.createView(),depthLoadOp:"clear",depthClearValue:1,depthStoreOp:"store"}},j={colorAttachments:[{view:void 0,clearValue:{r:.3,g:.3,b:.3,a:1},loadOp:"clear",storeOp:"store"}]},_=(e,n)=>{let t=m._E.identity();m._E.rotateZ(t,n,e[0]),m._E.translate(e[0],m.R3.create(4,0,0),t),m._E.rotateZ(t,n,e[1]),m._E.translate(e[1],m.R3.create(4,0,0),t),m._E.rotateZ(t,n,e[2])},F=(e=>{let n=[],t=[];for(let r=0;r<5;r++)n.push(m._E.identity()),t.push(m._E.identity());_(t,0);let i=t.map(e=>m._E.inverse(e));return{transforms:n,bindPoses:t,bindPosesInv:i}})(0);for(let D=0;D{for(let t=0;t(0,p.Tl)({name:"Skinned Mesh",description:"A demonstration of basic gltf loading and mesh skinning, ported from https://webgl2fundamentals.org/webgl/lessons/webgl-skinning.html. Mesh data, per vertex attributes, and skin inverseBindMatrices are taken from the json parsed from the binary output of the .glb file. Animations are generated progrmatically, with animated joint matrices updated and passed to shaders per frame via uniform buffers.",init:W,gui:!0,sources:[{name:D.substring(23),contents:"import { makeSample, SampleInit } from '../../components/SampleLayout';\nimport { convertGLBToJSONAndBinary, GLTFSkin } from './glbUtils';\nimport gltfWGSL from './gltf.wgsl';\nimport gridWGSL from './grid.wgsl';\nimport { Mat4, mat4, Quat, vec3 } from 'wgpu-matrix';\nimport { createBindGroupCluster } from '../bitonicSort/utils';\nimport {\n createSkinnedGridBuffers,\n createSkinnedGridRenderPipeline,\n} from './gridUtils';\nimport { gridIndices } from './gridData';\n\nconst MAT4X4_BYTES = 64;\n\ninterface BoneObject {\n transforms: Mat4[];\n bindPoses: Mat4[];\n bindPosesInv: Mat4[];\n}\n\nenum RenderMode {\n NORMAL,\n JOINTS,\n WEIGHTS,\n}\n\nenum SkinMode {\n ON,\n OFF,\n}\n\n// Copied from toji/gl-matrix\nconst getRotation = (mat: Mat4): Quat => {\n // Initialize our output quaternion\n const out = [0, 0, 0, 0];\n // Extract the scaling factor from the final matrix transformation\n // to normalize our rotation;\n const scaling = mat4.getScaling(mat);\n const is1 = 1 / scaling[0];\n const is2 = 1 / scaling[1];\n const is3 = 1 / scaling[2];\n\n // Scale the matrix elements by the scaling factors\n const sm11 = mat[0] * is1;\n const sm12 = mat[1] * is2;\n const sm13 = mat[2] * is3;\n const sm21 = mat[4] * is1;\n const sm22 = mat[5] * is2;\n const sm23 = mat[6] * is3;\n const sm31 = mat[8] * is1;\n const sm32 = mat[9] * is2;\n const sm33 = mat[10] * is3;\n\n // The trace of a square matrix is the sum of its diagonal entries\n // While the matrix trace has many interesting mathematical properties,\n // the primary purpose of the trace is to assess the characteristics of the rotation.\n const trace = sm11 + sm22 + sm33;\n let S = 0;\n\n // If all matrix elements contribute equally to the rotation.\n if (trace > 0) {\n S = Math.sqrt(trace + 1.0) * 2;\n out[3] = 0.25 * S;\n out[0] = (sm23 - sm32) / S;\n out[1] = (sm31 - sm13) / S;\n out[2] = (sm12 - sm21) / S;\n // If the rotation is primarily around the x-axis\n } else if (sm11 > sm22 && sm11 > sm33) {\n S = Math.sqrt(1.0 + sm11 - sm22 - sm33) * 2;\n out[3] = (sm23 - sm32) / S;\n out[0] = 0.25 * S;\n out[1] = (sm12 + sm21) / S;\n out[2] = (sm31 + sm13) / S;\n // If rotation is primarily around the y-axis\n } else if (sm22 > sm33) {\n S = Math.sqrt(1.0 + sm22 - sm11 - sm33) * 2;\n out[3] = (sm31 - sm13) / S;\n out[0] = (sm12 + sm21) / S;\n out[1] = 0.25 * S;\n out[2] = (sm23 + sm32) / S;\n // If the rotation is primarily around the z-axis\n } else {\n S = Math.sqrt(1.0 + sm33 - sm11 - sm22) * 2;\n out[3] = (sm12 - sm21) / S;\n out[0] = (sm31 + sm13) / S;\n out[1] = (sm23 + sm32) / S;\n out[2] = 0.25 * S;\n }\n\n return out;\n};\n\nconst init: SampleInit = async ({ canvas, pageState, gui }) => {\n //Normal setup\n const adapter = await navigator.gpu.requestAdapter();\n const device = await adapter.requestDevice();\n\n if (!pageState.active) return;\n const context = canvas.getContext('webgpu') as GPUCanvasContext;\n\n const devicePixelRatio = window.devicePixelRatio || 1;\n canvas.width = canvas.clientWidth * devicePixelRatio;\n canvas.height = canvas.clientHeight * devicePixelRatio;\n const presentationFormat = navigator.gpu.getPreferredCanvasFormat();\n\n context.configure({\n device,\n format: presentationFormat,\n alphaMode: 'premultiplied',\n });\n\n const settings = {\n cameraX: 0,\n cameraY: -5.1,\n cameraZ: -14.6,\n objectScale: 1,\n angle: 0.2,\n speed: 50,\n object: 'Whale',\n renderMode: 'NORMAL',\n skinMode: 'ON',\n };\n\n // Determine whether we want to render our whale or our skinned grid\n gui.add(settings, 'object', ['Whale', 'Skinned Grid']).onChange(() => {\n if (settings.object === 'Skinned Grid') {\n settings.cameraX = -10;\n settings.cameraY = 0;\n settings.objectScale = 1.27;\n } else {\n if (settings.skinMode === 'OFF') {\n settings.cameraX = 0;\n settings.cameraY = 0;\n settings.cameraZ = -11;\n } else {\n settings.cameraX = 0;\n settings.cameraY = -5.1;\n settings.cameraZ = -14.6;\n }\n }\n });\n\n // Output the mesh normals, its joints, or the weights that influence the movement of the joints\n gui\n .add(settings, 'renderMode', ['NORMAL', 'JOINTS', 'WEIGHTS'])\n .onChange(() => {\n device.queue.writeBuffer(\n generalUniformsBuffer,\n 0,\n new Uint32Array([RenderMode[settings.renderMode]])\n );\n });\n // Determine whether the mesh is static or whether skinning is activated\n gui.add(settings, 'skinMode', ['ON', 'OFF']).onChange(() => {\n if (settings.object === 'Whale') {\n if (settings.skinMode === 'OFF') {\n settings.cameraX = 0;\n settings.cameraY = 0;\n settings.cameraZ = -11;\n } else {\n settings.cameraX = 0;\n settings.cameraY = -5.1;\n settings.cameraZ = -14.6;\n }\n }\n device.queue.writeBuffer(\n generalUniformsBuffer,\n 4,\n new Uint32Array([SkinMode[settings.skinMode]])\n );\n });\n const animFolder = gui.addFolder('Animation Settings');\n animFolder.add(settings, 'angle', 0.05, 0.5).step(0.05);\n animFolder.add(settings, 'speed', 10, 100).step(10);\n\n const depthTexture = device.createTexture({\n size: [canvas.width, canvas.height],\n format: 'depth24plus',\n usage: GPUTextureUsage.RENDER_ATTACHMENT,\n });\n\n const cameraBuffer = device.createBuffer({\n size: MAT4X4_BYTES * 3,\n usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,\n });\n\n const cameraBGCluster = createBindGroupCluster(\n [0],\n [GPUShaderStage.VERTEX],\n ['buffer'],\n [{ type: 'uniform' }],\n [[{ buffer: cameraBuffer }]],\n 'Camera',\n device\n );\n\n const generalUniformsBuffer = device.createBuffer({\n size: Uint32Array.BYTES_PER_ELEMENT * 2,\n usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,\n });\n\n const generalUniformsBGCLuster = createBindGroupCluster(\n [0],\n [GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT],\n ['buffer'],\n [{ type: 'uniform' }],\n [[{ buffer: generalUniformsBuffer }]],\n 'General',\n device\n );\n\n // Same bindGroupLayout as in main file.\n const nodeUniformsBindGroupLayout = device.createBindGroupLayout({\n label: 'NodeUniforms.bindGroupLayout',\n entries: [\n {\n binding: 0,\n buffer: {\n type: 'uniform',\n },\n visibility: GPUShaderStage.VERTEX,\n },\n ],\n });\n\n // Fetch whale resources from the glb file\n const whaleScene = await fetch('../assets/gltf/whale.glb')\n .then((res) => res.arrayBuffer())\n .then((buffer) => convertGLBToJSONAndBinary(buffer, device));\n\n // Builds a render pipeline for our whale mesh\n // Since we are building a lightweight gltf parser around a gltf scene with a known\n // quantity of meshes, we only build a renderPipeline for the singular mesh present\n // within our scene. A more robust gltf parser would loop through all the meshes,\n // cache replicated pipelines, and perform other optimizations.\n whaleScene.meshes[0].buildRenderPipeline(\n device,\n gltfWGSL,\n gltfWGSL,\n presentationFormat,\n depthTexture.format,\n [\n cameraBGCluster.bindGroupLayout,\n generalUniformsBGCLuster.bindGroupLayout,\n nodeUniformsBindGroupLayout,\n GLTFSkin.skinBindGroupLayout,\n ]\n );\n\n // Create skinned grid resources\n const skinnedGridVertexBuffers = createSkinnedGridBuffers(device);\n // Buffer for our uniforms, joints, and inverse bind matrices\n const skinnedGridUniformBufferUsage: GPUBufferDescriptor = {\n // 5 4x4 matrices, one for each bone\n size: MAT4X4_BYTES * 5,\n usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,\n };\n const skinnedGridJointUniformBuffer = device.createBuffer(\n skinnedGridUniformBufferUsage\n );\n const skinnedGridInverseBindUniformBuffer = device.createBuffer(\n skinnedGridUniformBufferUsage\n );\n const skinnedGridBoneBGCluster = createBindGroupCluster(\n [0, 1],\n [GPUShaderStage.VERTEX, GPUShaderStage.VERTEX],\n ['buffer', 'buffer'],\n [{ type: 'read-only-storage' }, { type: 'read-only-storage' }],\n [\n [\n { buffer: skinnedGridJointUniformBuffer },\n { buffer: skinnedGridInverseBindUniformBuffer },\n ],\n ],\n 'SkinnedGridJointUniforms',\n device\n );\n const skinnedGridPipeline = createSkinnedGridRenderPipeline(\n device,\n presentationFormat,\n gridWGSL,\n gridWGSL,\n [\n cameraBGCluster.bindGroupLayout,\n generalUniformsBGCLuster.bindGroupLayout,\n skinnedGridBoneBGCluster.bindGroupLayout,\n ]\n );\n\n // Global Calc\n const aspect = canvas.width / canvas.height;\n const perspectiveProjection = mat4.perspective(\n (2 * Math.PI) / 5,\n aspect,\n 0.1,\n 100.0\n );\n\n const orthographicProjection = mat4.ortho(-20, 20, -10, 10, -100, 100);\n\n function getProjectionMatrix() {\n if (settings.object !== 'Skinned Grid') {\n return perspectiveProjection as Float32Array;\n }\n return orthographicProjection as Float32Array;\n }\n\n function getViewMatrix() {\n const viewMatrix = mat4.identity();\n if (settings.object === 'Skinned Grid') {\n mat4.translate(\n viewMatrix,\n vec3.fromValues(\n settings.cameraX * settings.objectScale,\n settings.cameraY * settings.objectScale,\n settings.cameraZ\n ),\n viewMatrix\n );\n } else {\n mat4.translate(\n viewMatrix,\n vec3.fromValues(settings.cameraX, settings.cameraY, settings.cameraZ),\n viewMatrix\n );\n }\n return viewMatrix as Float32Array;\n }\n\n function getModelMatrix() {\n const modelMatrix = mat4.identity();\n const scaleVector = vec3.fromValues(\n settings.objectScale,\n settings.objectScale,\n settings.objectScale\n );\n mat4.scale(modelMatrix, scaleVector, modelMatrix);\n if (settings.object === 'Whale') {\n mat4.rotateY(modelMatrix, (Date.now() / 1000) * 0.5, modelMatrix);\n }\n return modelMatrix as Float32Array;\n }\n\n // Pass Descriptor for GLTFs\n const gltfRenderPassDescriptor: GPURenderPassDescriptor = {\n colorAttachments: [\n {\n view: undefined, // Assigned later\n\n clearValue: { r: 0.3, g: 0.3, b: 0.3, a: 1.0 },\n loadOp: 'clear',\n storeOp: 'store',\n },\n ],\n depthStencilAttachment: {\n view: depthTexture.createView(),\n depthLoadOp: 'clear',\n depthClearValue: 1.0,\n depthStoreOp: 'store',\n },\n };\n\n // Pass descriptor for grid with no depth testing\n const skinnedGridRenderPassDescriptor: GPURenderPassDescriptor = {\n colorAttachments: [\n {\n view: undefined, // Assigned later\n\n clearValue: { r: 0.3, g: 0.3, b: 0.3, a: 1.0 },\n loadOp: 'clear',\n storeOp: 'store',\n },\n ],\n };\n\n const animSkinnedGrid = (boneTransforms: Mat4[], angle: number) => {\n const m = mat4.identity();\n mat4.rotateZ(m, angle, boneTransforms[0]);\n mat4.translate(boneTransforms[0], vec3.create(4, 0, 0), m);\n mat4.rotateZ(m, angle, boneTransforms[1]);\n mat4.translate(boneTransforms[1], vec3.create(4, 0, 0), m);\n mat4.rotateZ(m, angle, boneTransforms[2]);\n };\n\n // Create a group of bones\n // Each index associates an actual bone to its transforms, bindPoses, uniforms, etc\n const createBoneCollection = (numBones: number): BoneObject => {\n // Initial bone transformation\n const transforms: Mat4[] = [];\n // Bone bind poses, an extra matrix per joint/bone that represents the starting point\n // of the bone before any transformations are applied\n const bindPoses: Mat4[] = [];\n // Create a transform, bind pose, and inverse bind pose for each bone\n for (let i = 0; i < numBones; i++) {\n transforms.push(mat4.identity());\n bindPoses.push(mat4.identity());\n }\n\n // Get initial bind pose positions\n animSkinnedGrid(bindPoses, 0);\n const bindPosesInv = bindPoses.map((bindPose) => {\n return mat4.inverse(bindPose);\n });\n\n return {\n transforms,\n bindPoses,\n bindPosesInv,\n };\n };\n\n // Create bones of the skinned grid and write the inverse bind positions to\n // the skinned grid's inverse bind matrix array\n const gridBoneCollection = createBoneCollection(5);\n for (let i = 0; i < gridBoneCollection.bindPosesInv.length; i++) {\n device.queue.writeBuffer(\n skinnedGridInverseBindUniformBuffer,\n i * 64,\n gridBoneCollection.bindPosesInv[i] as Float32Array\n );\n }\n\n // A map that maps a joint index to the original matrix transformation of a bone\n const origMatrices = new Map();\n const animWhaleSkin = (skin: GLTFSkin, angle: number) => {\n for (let i = 0; i < skin.joints.length; i++) {\n // Index into the current joint\n const joint = skin.joints[i];\n // If our map does\n if (!origMatrices.has(joint)) {\n origMatrices.set(joint, whaleScene.nodes[joint].source.getMatrix());\n }\n // Get the original position, rotation, and scale of the current joint\n const origMatrix = origMatrices.get(joint);\n let m = mat4.create();\n // Depending on which bone we are accessing, apply a specific rotation to the bone's original\n // transformation to animate it\n if (joint === 1 || joint === 0) {\n m = mat4.rotateY(origMatrix, -angle);\n } else if (joint === 3 || joint === 4) {\n m = mat4.rotateX(origMatrix, joint === 3 ? angle : -angle);\n } else {\n m = mat4.rotateZ(origMatrix, angle);\n }\n // Apply the current transformation to the transform values within the relevant nodes\n // (these nodes, of course, each being nodes that represent joints/bones)\n whaleScene.nodes[joint].source.position = mat4.getTranslation(m);\n whaleScene.nodes[joint].source.scale = mat4.getScaling(m);\n whaleScene.nodes[joint].source.rotation = getRotation(m);\n }\n };\n\n function frame() {\n // Sample is no longer the active page.\n if (!pageState.active) return;\n\n // Calculate camera matrices\n const projectionMatrix = getProjectionMatrix();\n const viewMatrix = getViewMatrix();\n const modelMatrix = getModelMatrix();\n\n // Calculate bone transformation\n const t = (Date.now() / 20000) * settings.speed;\n const angle = Math.sin(t) * settings.angle;\n // Compute Transforms when angle is applied\n animSkinnedGrid(gridBoneCollection.transforms, angle);\n\n // Write to mvp to camera buffer\n device.queue.writeBuffer(\n cameraBuffer,\n 0,\n projectionMatrix.buffer,\n projectionMatrix.byteOffset,\n projectionMatrix.byteLength\n );\n\n device.queue.writeBuffer(\n cameraBuffer,\n 64,\n viewMatrix.buffer,\n viewMatrix.byteOffset,\n viewMatrix.byteLength\n );\n\n device.queue.writeBuffer(\n cameraBuffer,\n 128,\n modelMatrix.buffer,\n modelMatrix.byteOffset,\n modelMatrix.byteLength\n );\n\n // Write to skinned grid bone uniform buffer\n for (let i = 0; i < gridBoneCollection.transforms.length; i++) {\n device.queue.writeBuffer(\n skinnedGridJointUniformBuffer,\n i * 64,\n gridBoneCollection.transforms[i] as Float32Array\n );\n }\n\n // Difference between these two render passes is just the presence of depthTexture\n gltfRenderPassDescriptor.colorAttachments[0].view = context\n .getCurrentTexture()\n .createView();\n\n skinnedGridRenderPassDescriptor.colorAttachments[0].view = context\n .getCurrentTexture()\n .createView();\n\n // Update node matrixes\n for (const scene of whaleScene.scenes) {\n scene.root.updateWorldMatrix(device);\n }\n\n // Updates skins (we index into skins in the renderer, which is not the best approach but hey)\n animWhaleSkin(whaleScene.skins[0], Math.sin(t) * settings.angle);\n // Node 6 should be the only node with a drawable mesh so hopefully this works fine\n whaleScene.skins[0].update(device, 6, whaleScene.nodes);\n\n const commandEncoder = device.createCommandEncoder();\n if (settings.object === 'Whale') {\n const passEncoder = commandEncoder.beginRenderPass(\n gltfRenderPassDescriptor\n );\n for (const scene of whaleScene.scenes) {\n scene.root.renderDrawables(passEncoder, [\n cameraBGCluster.bindGroups[0],\n generalUniformsBGCLuster.bindGroups[0],\n ]);\n }\n passEncoder.end();\n } else {\n // Our skinned grid isn't checking for depth, so we pass it\n // a separate render descriptor that does not take in a depth texture\n const passEncoder = commandEncoder.beginRenderPass(\n skinnedGridRenderPassDescriptor\n );\n passEncoder.setPipeline(skinnedGridPipeline);\n passEncoder.setBindGroup(0, cameraBGCluster.bindGroups[0]);\n passEncoder.setBindGroup(1, generalUniformsBGCLuster.bindGroups[0]);\n passEncoder.setBindGroup(2, skinnedGridBoneBGCluster.bindGroups[0]);\n // Pass in vertex and index buffers generated from our static skinned grid\n // data at ./gridData.ts\n passEncoder.setVertexBuffer(0, skinnedGridVertexBuffers.positions);\n passEncoder.setVertexBuffer(1, skinnedGridVertexBuffers.joints);\n passEncoder.setVertexBuffer(2, skinnedGridVertexBuffers.weights);\n passEncoder.setIndexBuffer(skinnedGridVertexBuffers.indices, 'uint16');\n passEncoder.drawIndexed(gridIndices.length, 1);\n passEncoder.end();\n }\n\n device.queue.submit([commandEncoder.finish()]);\n\n requestAnimationFrame(frame);\n }\n requestAnimationFrame(frame);\n};\n\nconst skinnedMesh: () => JSX.Element = () =>\n makeSample({\n name: 'Skinned Mesh',\n description:\n 'A demonstration of basic gltf loading and mesh skinning, ported from https://webgl2fundamentals.org/webgl/lessons/webgl-skinning.html. Mesh data, per vertex attributes, and skin inverseBindMatrices are taken from the json parsed from the binary output of the .glb file. Animations are generated progrmatically, with animated joint matrices updated and passed to shaders per frame via uniform buffers.',\n init,\n gui: true,\n sources: [\n {\n name: __filename.substring(__dirname.length + 1),\n contents: __SOURCE__,\n },\n {\n name: './gridData.ts',\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n contents: require('!!raw-loader!./gridData.ts').default,\n },\n {\n name: './gridUtils.ts',\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n contents: require('!!raw-loader!./gridUtils.ts').default,\n },\n {\n name: './grid.wgsl',\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n contents: require('!!raw-loader!./grid.wgsl').default,\n },\n {\n name: './gltf.ts',\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n contents: require('!!raw-loader!./gltf.ts').default,\n },\n {\n name: './glbUtils.ts',\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n contents: require('!!raw-loader!./glbUtils.ts').default,\n },\n {\n name: './gltf.wgsl',\n contents: gltfWGSL,\n },\n ],\n filename: __filename,\n });\n\nexport default skinnedMesh;\n"},{name:"./gridData.ts",contents:t(6270).Z},{name:"./gridUtils.ts",contents:t(9483).Z},{name:"./grid.wgsl",contents:t(2624).Z},{name:"./gltf.ts",contents:t(6370).Z},{name:"./glbUtils.ts",contents:t(7674).Z},{name:"./gltf.wgsl",contents:k}],filename:D});var z=Y},9147:function(e){e.exports={canvasContainer:"SampleLayout_canvasContainer__zRR_l",sourceFileNav:"SampleLayout_sourceFileNav__ml48P",sourceFileScrollContainer:"SampleLayout_sourceFileScrollContainer__LsNEm",sourceFileContainer:"SampleLayout_sourceFileContainer__3s84x"}},7674:function(e,n){"use strict";n.Z="import { Quat } from 'wgpu-matrix/dist/2.x/quat';\nimport { Accessor, BufferView, GlTf, Scene } from './gltf';\nimport { Mat4, Vec3, mat4 } from 'wgpu-matrix';\n\n//NOTE: GLTF code is not generally extensible to all gltf models\n// Modified from Will Usher code found at this link https://www.willusher.io/graphics/2023/05/16/0-to-gltf-first-mesh\n\n// Associates the mode paramete of a gltf primitive object with the primitive's intended render mode\nenum GLTFRenderMode {\n POINTS = 0,\n LINE = 1,\n LINE_LOOP = 2,\n LINE_STRIP = 3,\n TRIANGLES = 4,\n TRIANGLE_STRIP = 5,\n TRIANGLE_FAN = 6,\n}\n\n// Determines how to interpret each element of the structure that is accessed from our accessor\nenum GLTFDataComponentType {\n BYTE = 5120,\n UNSIGNED_BYTE = 5121,\n SHORT = 5122,\n UNSIGNED_SHORT = 5123,\n INT = 5124,\n UNSIGNED_INT = 5125,\n FLOAT = 5126,\n DOUBLE = 5130,\n}\n\n// Determines how to interpret the structure of the values accessed by an accessor\nenum GLTFDataStructureType {\n SCALAR = 0,\n VEC2 = 1,\n VEC3 = 2,\n VEC4 = 3,\n MAT2 = 4,\n MAT3 = 5,\n MAT4 = 6,\n}\n\nexport const alignTo = (val: number, align: number): number => {\n return Math.floor((val + align - 1) / align) * align;\n};\n\nconst parseGltfDataStructureType = (type: string) => {\n switch (type) {\n case 'SCALAR':\n return GLTFDataStructureType.SCALAR;\n case 'VEC2':\n return GLTFDataStructureType.VEC2;\n case 'VEC3':\n return GLTFDataStructureType.VEC3;\n case 'VEC4':\n return GLTFDataStructureType.VEC4;\n case 'MAT2':\n return GLTFDataStructureType.MAT2;\n case 'MAT3':\n return GLTFDataStructureType.MAT3;\n case 'MAT4':\n return GLTFDataStructureType.MAT4;\n default:\n throw Error(`Unhandled glTF Type ${type}`);\n }\n};\n\nconst gltfDataStructureTypeNumComponents = (type: GLTFDataStructureType) => {\n switch (type) {\n case GLTFDataStructureType.SCALAR:\n return 1;\n case GLTFDataStructureType.VEC2:\n return 2;\n case GLTFDataStructureType.VEC3:\n return 3;\n case GLTFDataStructureType.VEC4:\n case GLTFDataStructureType.MAT2:\n return 4;\n case GLTFDataStructureType.MAT3:\n return 9;\n case GLTFDataStructureType.MAT4:\n return 16;\n default:\n throw Error(`Invalid glTF Type ${type}`);\n }\n};\n\n// Note: only returns non-normalized type names,\n// so byte/ubyte = sint8/uint8, not snorm8/unorm8, same for ushort\nconst gltfVertexType = (\n componentType: GLTFDataComponentType,\n type: GLTFDataStructureType\n) => {\n let typeStr = null;\n switch (componentType) {\n case GLTFDataComponentType.BYTE:\n typeStr = 'sint8';\n break;\n case GLTFDataComponentType.UNSIGNED_BYTE:\n typeStr = 'uint8';\n break;\n case GLTFDataComponentType.SHORT:\n typeStr = 'sint16';\n break;\n case GLTFDataComponentType.UNSIGNED_SHORT:\n typeStr = 'uint16';\n break;\n case GLTFDataComponentType.INT:\n typeStr = 'int32';\n break;\n case GLTFDataComponentType.UNSIGNED_INT:\n typeStr = 'uint32';\n break;\n case GLTFDataComponentType.FLOAT:\n typeStr = 'float32';\n break;\n default:\n throw Error(`Unrecognized or unsupported glTF type ${componentType}`);\n }\n\n switch (gltfDataStructureTypeNumComponents(type)) {\n case 1:\n return typeStr;\n case 2:\n return typeStr + 'x2';\n case 3:\n return typeStr + 'x3';\n case 4:\n return typeStr + 'x4';\n // Vertex attributes should never be a matrix type, so we should not hit this\n // unless we're passed an improperly created gltf file\n default:\n throw Error(`Invalid number of components for gltfType: ${type}`);\n }\n};\n\nconst gltfElementSize = (\n componentType: GLTFDataComponentType,\n type: GLTFDataStructureType\n) => {\n let componentSize = 0;\n switch (componentType) {\n case GLTFDataComponentType.BYTE:\n componentSize = 1;\n break;\n case GLTFDataComponentType.UNSIGNED_BYTE:\n componentSize = 1;\n break;\n case GLTFDataComponentType.SHORT:\n componentSize = 2;\n break;\n case GLTFDataComponentType.UNSIGNED_SHORT:\n componentSize = 2;\n break;\n case GLTFDataComponentType.INT:\n componentSize = 4;\n break;\n case GLTFDataComponentType.UNSIGNED_INT:\n componentSize = 4;\n break;\n case GLTFDataComponentType.FLOAT:\n componentSize = 4;\n break;\n case GLTFDataComponentType.DOUBLE:\n componentSize = 8;\n break;\n default:\n throw Error('Unrecognized GLTF Component Type?');\n }\n return gltfDataStructureTypeNumComponents(type) * componentSize;\n};\n\n// Convert differently depending on if the shader is a vertex or compute shader\nconst convertGPUVertexFormatToWGSLFormat = (vertexFormat: GPUVertexFormat) => {\n switch (vertexFormat) {\n case 'float32': {\n return 'f32';\n }\n case 'float32x2': {\n return 'vec2';\n }\n case 'float32x3': {\n return 'vec3';\n }\n case 'float32x4': {\n return 'vec4';\n }\n case 'uint32': {\n return 'u32';\n }\n case 'uint32x2': {\n return 'vec2';\n }\n case 'uint32x3': {\n return 'vec3';\n }\n case 'uint32x4': {\n return 'vec4';\n }\n case 'uint8x2': {\n return 'vec2';\n }\n case 'uint8x4': {\n return 'vec4';\n }\n case 'uint16x4': {\n return 'vec4';\n }\n case 'uint16x2': {\n return 'vec2';\n }\n default: {\n return 'f32';\n }\n }\n};\n\nexport class GLTFBuffer {\n buffer: Uint8Array;\n constructor(buffer: ArrayBuffer, offset: number, size: number) {\n this.buffer = new Uint8Array(buffer, offset, size);\n }\n}\n\nexport class GLTFBufferView {\n byteLength: number;\n byteStride: number;\n view: Uint8Array;\n needsUpload: boolean;\n gpuBuffer: GPUBuffer;\n usage: number;\n constructor(buffer: GLTFBuffer, view: BufferView) {\n this.byteLength = view['byteLength'];\n this.byteStride = 0;\n if (view['byteStride'] !== undefined) {\n this.byteStride = view['byteStride'];\n }\n // Create the buffer view. Note that subarray creates a new typed\n // view over the same array buffer, we do not make a copy here.\n let viewOffset = 0;\n if (view['byteOffset'] !== undefined) {\n viewOffset = view['byteOffset'];\n }\n // NOTE: This creates a uint8array view into the buffer!\n // When we call .buffer on this view, it will give us back the original array buffer\n // Accordingly, when converting our buffer from a uint8array to a float32array representation\n // we need to apply the byte offset of our view when creating our buffer\n // ie new Float32Array(this.view.buffer, this.view.byteOffset, this.view.byteLength)\n this.view = buffer.buffer.subarray(\n viewOffset,\n viewOffset + this.byteLength\n );\n\n this.needsUpload = false;\n this.gpuBuffer = null;\n this.usage = 0;\n }\n\n addUsage(usage: number) {\n this.usage = this.usage | usage;\n }\n\n upload(device: GPUDevice) {\n // Note: must align to 4 byte size when mapped at creation is true\n const buf: GPUBuffer = device.createBuffer({\n size: alignTo(this.view.byteLength, 4),\n usage: this.usage,\n mappedAtCreation: true,\n });\n new Uint8Array(buf.getMappedRange()).set(this.view);\n buf.unmap();\n this.gpuBuffer = buf;\n this.needsUpload = false;\n }\n}\n\nexport class GLTFAccessor {\n count: number;\n componentType: GLTFDataComponentType;\n structureType: GLTFDataStructureType;\n view: GLTFBufferView;\n byteOffset: number;\n constructor(view: GLTFBufferView, accessor: Accessor) {\n this.count = accessor['count'];\n this.componentType = accessor['componentType'];\n this.structureType = parseGltfDataStructureType(accessor['type']);\n this.view = view;\n this.byteOffset = 0;\n if (accessor['byteOffset'] !== undefined) {\n this.byteOffset = accessor['byteOffset'];\n }\n }\n\n get byteStride() {\n const elementSize = gltfElementSize(this.componentType, this.structureType);\n return Math.max(elementSize, this.view.byteStride);\n }\n\n get byteLength() {\n return this.count * this.byteStride;\n }\n\n // Get the vertex attribute type for accessors that are used as vertex attributes\n get vertexType() {\n return gltfVertexType(this.componentType, this.structureType);\n }\n}\n\ninterface AttributeMapInterface {\n [key: string]: GLTFAccessor;\n}\n\nexport class GLTFPrimitive {\n topology: GLTFRenderMode;\n renderPipeline: GPURenderPipeline;\n private attributeMap: AttributeMapInterface;\n private attributes: string[] = [];\n constructor(\n topology: GLTFRenderMode,\n attributeMap: AttributeMapInterface,\n attributes: string[]\n ) {\n this.topology = topology;\n this.renderPipeline = null;\n // Maps attribute names to accessors\n this.attributeMap = attributeMap;\n this.attributes = attributes;\n\n for (const key in this.attributeMap) {\n this.attributeMap[key].view.needsUpload = true;\n if (key === 'INDICES') {\n this.attributeMap['INDICES'].view.addUsage(GPUBufferUsage.INDEX);\n continue;\n }\n this.attributeMap[key].view.addUsage(GPUBufferUsage.VERTEX);\n }\n }\n\n buildRenderPipeline(\n device: GPUDevice,\n vertexShader: string,\n fragmentShader: string,\n colorFormat: GPUTextureFormat,\n depthFormat: GPUTextureFormat,\n bgLayouts: GPUBindGroupLayout[],\n label: string\n ) {\n // For now, just check if the attributeMap contains a given attribute using map.has(), and add it if it does\n // POSITION, NORMAL, TEXCOORD_0, JOINTS_0, WEIGHTS_0 for order\n // Vertex attribute state and shader stage\n let VertexInputShaderString = `struct VertexInput {\\n`;\n const vertexBuffers: GPUVertexBufferLayout[] = this.attributes.map(\n (attr, idx) => {\n const vertexFormat: GPUVertexFormat =\n this.attributeMap[attr].vertexType;\n const attrString = attr.toLowerCase().replace(/_0$/, '');\n VertexInputShaderString += `\\t@location(${idx}) ${attrString}: ${convertGPUVertexFormatToWGSLFormat(\n vertexFormat\n )},\\n`;\n return {\n arrayStride: this.attributeMap[attr].byteStride,\n attributes: [\n {\n format: this.attributeMap[attr].vertexType,\n offset: this.attributeMap[attr].byteOffset,\n shaderLocation: idx,\n },\n ],\n } as GPUVertexBufferLayout;\n }\n );\n VertexInputShaderString += '}';\n\n const vertexState: GPUVertexState = {\n // Shader stage info\n module: device.createShaderModule({\n code: VertexInputShaderString + vertexShader,\n }),\n entryPoint: 'vertexMain',\n buffers: vertexBuffers,\n };\n\n const fragmentState: GPUFragmentState = {\n // Shader info\n module: device.createShaderModule({\n code: VertexInputShaderString + fragmentShader,\n }),\n entryPoint: 'fragmentMain',\n // Output render target info\n targets: [{ format: colorFormat }],\n };\n\n // Our loader only supports triangle lists and strips, so by default we set\n // the primitive topology to triangle list, and check if it's instead a triangle strip\n const primitive: GPUPrimitiveState = { topology: 'triangle-list' };\n if (this.topology == GLTFRenderMode.TRIANGLE_STRIP) {\n primitive.topology = 'triangle-strip';\n primitive.stripIndexFormat = this.attributeMap['INDICES'].vertexType;\n }\n\n const layout: GPUPipelineLayout = device.createPipelineLayout({\n bindGroupLayouts: bgLayouts,\n label: `${label}.pipelineLayout`,\n });\n\n const rpDescript: GPURenderPipelineDescriptor = {\n layout: layout,\n label: `${label}.pipeline`,\n vertex: vertexState,\n fragment: fragmentState,\n primitive: primitive,\n depthStencil: {\n format: depthFormat,\n depthWriteEnabled: true,\n depthCompare: 'less',\n },\n };\n\n this.renderPipeline = device.createRenderPipeline(rpDescript);\n }\n\n render(renderPassEncoder: GPURenderPassEncoder, bindGroups: GPUBindGroup[]) {\n renderPassEncoder.setPipeline(this.renderPipeline);\n bindGroups.forEach((bg, idx) => {\n renderPassEncoder.setBindGroup(idx, bg);\n });\n\n //if skin do something with bone bind group\n this.attributes.map((attr, idx) => {\n renderPassEncoder.setVertexBuffer(\n idx,\n this.attributeMap[attr].view.gpuBuffer,\n this.attributeMap[attr].byteOffset,\n this.attributeMap[attr].byteLength\n );\n });\n\n if (this.attributeMap['INDICES']) {\n renderPassEncoder.setIndexBuffer(\n this.attributeMap['INDICES'].view.gpuBuffer,\n this.attributeMap['INDICES'].vertexType,\n this.attributeMap['INDICES'].byteOffset,\n this.attributeMap['INDICES'].byteLength\n );\n renderPassEncoder.drawIndexed(this.attributeMap['INDICES'].count);\n } else {\n renderPassEncoder.draw(this.attributeMap['POSITION'].count);\n }\n }\n}\n\nexport class GLTFMesh {\n name: string;\n primitives: GLTFPrimitive[];\n constructor(name: string, primitives: GLTFPrimitive[]) {\n this.name = name;\n this.primitives = primitives;\n }\n\n buildRenderPipeline(\n device: GPUDevice,\n vertexShader: string,\n fragmentShader: string,\n colorFormat: GPUTextureFormat,\n depthFormat: GPUTextureFormat,\n bgLayouts: GPUBindGroupLayout[]\n ) {\n // We take a pretty simple approach to start. Just loop through all the primitives and\n // build their respective render pipelines\n for (let i = 0; i < this.primitives.length; ++i) {\n this.primitives[i].buildRenderPipeline(\n device,\n vertexShader,\n fragmentShader,\n colorFormat,\n depthFormat,\n bgLayouts,\n `PrimitivePipeline${i}`\n );\n }\n }\n\n render(renderPassEncoder: GPURenderPassEncoder, bindGroups: GPUBindGroup[]) {\n // We take a pretty simple approach to start. Just loop through all the primitives and\n // call their individual draw methods\n for (let i = 0; i < this.primitives.length; ++i) {\n this.primitives[i].render(renderPassEncoder, bindGroups);\n }\n }\n}\n\nexport const validateGLBHeader = (header: DataView) => {\n if (header.getUint32(0, true) != 0x46546c67) {\n throw Error('Provided file is not a glB file');\n }\n if (header.getUint32(4, true) != 2) {\n throw Error('Provided file is glTF 2.0 file');\n }\n};\n\nexport const validateBinaryHeader = (header: Uint32Array) => {\n if (header[1] != 0x004e4942) {\n throw Error(\n 'Invalid glB: The second chunk of the glB file is not a binary chunk!'\n );\n }\n};\n\ntype TempReturn = {\n meshes: GLTFMesh[];\n nodes: GLTFNode[];\n scenes: GLTFScene[];\n skins: GLTFSkin[];\n};\n\nexport class BaseTransformation {\n position: Vec3;\n rotation: Quat;\n scale: Vec3;\n constructor(\n // Identity translation vec3\n position = [0, 0, 0],\n // Identity quaternion\n rotation = [0, 0, 0, 1],\n // Identity scale vec3\n scale = [1, 1, 1]\n ) {\n this.position = position;\n this.rotation = rotation;\n this.scale = scale;\n }\n getMatrix(): Mat4 {\n // Analagous to let transformationMatrix: mat4x4f = translation * rotation * scale;\n const dst = mat4.identity();\n // Scale the transformation Matrix\n mat4.scale(dst, this.scale, dst);\n // Calculate the rotationMatrix from the quaternion\n const rotationMatrix = mat4.fromQuat(this.rotation);\n // Apply the rotation Matrix to the scaleMatrix (rotMat * scaleMat)\n mat4.multiply(rotationMatrix, dst, dst);\n // Translate the transformationMatrix\n mat4.translate(dst, this.position, dst);\n return dst;\n }\n}\n\nexport class GLTFNode {\n name: string;\n source: BaseTransformation;\n parent: GLTFNode | null;\n children: GLTFNode[];\n // Transforms all node's children in the node's local space, with node itself acting as the origin\n localMatrix: Mat4;\n worldMatrix: Mat4;\n // List of Meshes associated with this node\n drawables: GLTFMesh[];\n test = 0;\n skin?: GLTFSkin;\n private nodeTransformGPUBuffer: GPUBuffer;\n private nodeTransformBindGroup: GPUBindGroup;\n\n constructor(\n device: GPUDevice,\n bgLayout: GPUBindGroupLayout,\n source: BaseTransformation,\n name?: string,\n skin?: GLTFSkin\n ) {\n this.name = name\n ? name\n : `node_${source.position} ${source.rotation} ${source.scale}`;\n this.source = source;\n this.parent = null;\n this.children = [];\n this.localMatrix = mat4.identity();\n this.worldMatrix = mat4.identity();\n this.drawables = [];\n this.nodeTransformGPUBuffer = device.createBuffer({\n size: Float32Array.BYTES_PER_ELEMENT * 16,\n usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,\n });\n this.nodeTransformBindGroup = device.createBindGroup({\n layout: bgLayout,\n entries: [\n {\n binding: 0,\n resource: {\n buffer: this.nodeTransformGPUBuffer,\n },\n },\n ],\n });\n this.skin = skin;\n }\n\n setParent(parent: GLTFNode) {\n if (this.parent) {\n this.parent.removeChild(this);\n this.parent = null;\n }\n parent.addChild(this);\n this.parent = parent;\n }\n\n updateWorldMatrix(device: GPUDevice, parentWorldMatrix?: Mat4) {\n // Get local transform of this particular node, and if the node has a parent,\n // multiply it against the parent's transform matrix to get transformMatrix relative to world.\n this.localMatrix = this.source.getMatrix();\n if (parentWorldMatrix) {\n mat4.multiply(parentWorldMatrix, this.localMatrix, this.worldMatrix);\n } else {\n mat4.copy(this.localMatrix, this.worldMatrix);\n }\n const worldMatrix = this.worldMatrix as Float32Array;\n device.queue.writeBuffer(\n this.nodeTransformGPUBuffer,\n 0,\n worldMatrix.buffer,\n worldMatrix.byteOffset,\n worldMatrix.byteLength\n );\n for (const child of this.children) {\n child.updateWorldMatrix(device, worldMatrix);\n }\n }\n\n traverse(fn: (n: GLTFNode, ...args) => void) {\n fn(this);\n for (const child of this.children) {\n child.traverse(fn);\n }\n }\n\n renderDrawables(\n passEncoder: GPURenderPassEncoder,\n bindGroups: GPUBindGroup[]\n ) {\n if (this.drawables !== undefined) {\n for (const drawable of this.drawables) {\n if (this.skin) {\n drawable.render(passEncoder, [\n ...bindGroups,\n this.nodeTransformBindGroup,\n this.skin.skinBindGroup,\n ]);\n } else {\n drawable.render(passEncoder, [\n ...bindGroups,\n this.nodeTransformBindGroup,\n ]);\n }\n }\n }\n // Render any of its children\n for (const child of this.children) {\n child.renderDrawables(passEncoder, bindGroups);\n }\n }\n\n private addChild(child: GLTFNode) {\n this.children.push(child);\n }\n\n private removeChild(child: GLTFNode) {\n const ndx = this.children.indexOf(child);\n this.children.splice(ndx, 1);\n }\n}\n\nexport class GLTFScene {\n nodes?: number[];\n name?: any;\n extensions?: any;\n extras?: any;\n [k: string]: any;\n root: GLTFNode;\n\n constructor(\n device: GPUDevice,\n nodeTransformBGL: GPUBindGroupLayout,\n baseScene: Scene\n ) {\n this.nodes = baseScene.nodes;\n this.name = baseScene.name;\n this.root = new GLTFNode(\n device,\n nodeTransformBGL,\n new BaseTransformation(),\n baseScene.name\n );\n }\n}\n\nexport class GLTFSkin {\n // Nodes of the skin's joints\n // [5, 2, 3] means our joint info is at nodes 5, 2, and 3\n joints: number[];\n // Bind Group for this skin's uniform buffer\n skinBindGroup: GPUBindGroup;\n // Static bindGroupLayout shared across all skins\n // In a larger shader with more properties, certain bind groups\n // would likely have to be combined due to device limitations in the number of bind groups\n // allowed within a shader\n // Inverse bind matrices parsed from the accessor\n private inverseBindMatrices: Float32Array;\n private jointMatricesUniformBuffer: GPUBuffer;\n private inverseBindMatricesUniformBuffer: GPUBuffer;\n static skinBindGroupLayout: GPUBindGroupLayout;\n\n static createSharedBindGroupLayout(device: GPUDevice) {\n this.skinBindGroupLayout = device.createBindGroupLayout({\n label: 'StaticGLTFSkin.bindGroupLayout',\n entries: [\n // Holds the initial joint matrices buffer\n {\n binding: 0,\n buffer: {\n type: 'read-only-storage',\n },\n visibility: GPUShaderStage.VERTEX,\n },\n // Holds the inverse bind matrices buffer\n {\n binding: 1,\n buffer: {\n type: 'read-only-storage',\n },\n visibility: GPUShaderStage.VERTEX,\n },\n ],\n });\n }\n\n // For the sake of simplicity and easier debugging, we're going to convert our skin gpu accessor to a\n // float32array, which should be performant enough for this example since there is only one skin (again, this)\n // is not a comprehensive gltf parser\n constructor(\n device: GPUDevice,\n inverseBindMatricesAccessor: GLTFAccessor,\n joints: number[]\n ) {\n if (\n inverseBindMatricesAccessor.componentType !==\n GLTFDataComponentType.FLOAT ||\n inverseBindMatricesAccessor.byteStride !== 64\n ) {\n throw Error(\n `This skin's provided accessor does not access a mat4x4 matrix, or does not access the provided mat4x4 data correctly`\n );\n }\n // NOTE: Come back to this uint8array to float32array conversion in case it is incorrect\n this.inverseBindMatrices = new Float32Array(\n inverseBindMatricesAccessor.view.view.buffer,\n inverseBindMatricesAccessor.view.view.byteOffset,\n inverseBindMatricesAccessor.view.view.byteLength / 4\n );\n this.joints = joints;\n const skinGPUBufferUsage: GPUBufferDescriptor = {\n size: Float32Array.BYTES_PER_ELEMENT * 16 * joints.length,\n usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,\n };\n this.jointMatricesUniformBuffer = device.createBuffer(skinGPUBufferUsage);\n this.inverseBindMatricesUniformBuffer =\n device.createBuffer(skinGPUBufferUsage);\n device.queue.writeBuffer(\n this.inverseBindMatricesUniformBuffer,\n 0,\n this.inverseBindMatrices\n );\n this.skinBindGroup = device.createBindGroup({\n layout: GLTFSkin.skinBindGroupLayout,\n label: 'StaticGLTFSkin.bindGroup',\n entries: [\n {\n binding: 0,\n resource: {\n buffer: this.jointMatricesUniformBuffer,\n },\n },\n {\n binding: 1,\n resource: {\n buffer: this.inverseBindMatricesUniformBuffer,\n },\n },\n ],\n });\n }\n\n update(device: GPUDevice, currentNodeIndex: number, nodes: GLTFNode[]) {\n const globalWorldInverse = mat4.inverse(\n nodes[currentNodeIndex].worldMatrix\n );\n for (let j = 0; j < this.joints.length; j++) {\n const joint = this.joints[j];\n const dstMatrix: Mat4 = mat4.identity();\n mat4.multiply(globalWorldInverse, nodes[joint].worldMatrix, dstMatrix);\n const toWrite = dstMatrix as Float32Array;\n device.queue.writeBuffer(\n this.jointMatricesUniformBuffer,\n j * 64,\n toWrite.buffer,\n toWrite.byteOffset,\n toWrite.byteLength\n );\n }\n }\n}\n\n// Upload a GLB model, parse its JSON and Binary components, and create the requisite GPU resources\n// to render them. NOTE: Not extensible to all GLTF contexts at this point in time\nexport const convertGLBToJSONAndBinary = async (\n buffer: ArrayBuffer,\n device: GPUDevice\n): Promise => {\n // Binary GLTF layout: https://cdn.willusher.io/webgpu-0-to-gltf/glb-layout.svg\n const jsonHeader = new DataView(buffer, 0, 20);\n validateGLBHeader(jsonHeader);\n\n // Length of the jsonChunk found at jsonHeader[12 - 15]\n const jsonChunkLength = jsonHeader.getUint32(12, true);\n\n // Parse the JSON chunk of the glB file to a JSON object\n const jsonChunk: GlTf = JSON.parse(\n new TextDecoder('utf-8').decode(new Uint8Array(buffer, 20, jsonChunkLength))\n );\n\n console.log(jsonChunk);\n // Binary data located after jsonChunk\n const binaryHeader = new Uint32Array(buffer, 20 + jsonChunkLength, 2);\n validateBinaryHeader(binaryHeader);\n\n const binaryChunk = new GLTFBuffer(\n buffer,\n 28 + jsonChunkLength,\n binaryHeader[0]\n );\n\n //Const populate missing properties of jsonChunk\n for (const accessor of jsonChunk.accessors) {\n accessor.byteOffset = accessor.byteOffset ?? 0;\n accessor.normalized = accessor.normalized ?? false;\n }\n\n for (const bufferView of jsonChunk.bufferViews) {\n bufferView.byteOffset = bufferView.byteOffset ?? 0;\n }\n\n if (jsonChunk.samplers) {\n for (const sampler of jsonChunk.samplers) {\n sampler.wrapS = sampler.wrapS ?? 10497; //GL.REPEAT\n sampler.wrapT = sampler.wrapT ?? 10947; //GL.REPEAT\n }\n }\n\n //Mark each accessor with its intended usage within the vertexShader.\n //Often necessary due to infrequencey with which the BufferView target field is populated.\n for (const mesh of jsonChunk.meshes) {\n for (const primitive of mesh.primitives) {\n if ('indices' in primitive) {\n const accessor = jsonChunk.accessors[primitive.indices];\n jsonChunk.accessors[primitive.indices].bufferViewUsage |=\n GPUBufferUsage.INDEX;\n jsonChunk.bufferViews[accessor.bufferView].usage |=\n GPUBufferUsage.INDEX;\n }\n for (const attribute of Object.values(primitive.attributes)) {\n const accessor = jsonChunk.accessors[attribute];\n jsonChunk.accessors[attribute].bufferViewUsage |= GPUBufferUsage.VERTEX;\n jsonChunk.bufferViews[accessor.bufferView].usage |=\n GPUBufferUsage.VERTEX;\n }\n }\n }\n\n // Create GLTFBufferView objects for all the buffer views in the glTF file\n const bufferViews: GLTFBufferView[] = [];\n for (let i = 0; i < jsonChunk.bufferViews.length; ++i) {\n bufferViews.push(new GLTFBufferView(binaryChunk, jsonChunk.bufferViews[i]));\n }\n\n const accessors: GLTFAccessor[] = [];\n for (let i = 0; i < jsonChunk.accessors.length; ++i) {\n const accessorInfo = jsonChunk.accessors[i];\n const viewID = accessorInfo['bufferView'];\n accessors.push(new GLTFAccessor(bufferViews[viewID], accessorInfo));\n }\n // Load the first mesh\n const meshes: GLTFMesh[] = [];\n for (let i = 0; i < jsonChunk.meshes.length; i++) {\n const mesh = jsonChunk.meshes[i];\n const meshPrimitives: GLTFPrimitive[] = [];\n for (let j = 0; j < mesh.primitives.length; ++j) {\n const prim = mesh.primitives[j];\n let topology = prim['mode'];\n // Default is triangles if mode specified\n if (topology === undefined) {\n topology = GLTFRenderMode.TRIANGLES;\n }\n if (\n topology != GLTFRenderMode.TRIANGLES &&\n topology != GLTFRenderMode.TRIANGLE_STRIP\n ) {\n throw Error(`Unsupported primitive mode ${prim['mode']}`);\n }\n\n const primitiveAttributeMap = {};\n const attributes = [];\n if (jsonChunk['accessors'][prim['indices']] !== undefined) {\n const indices = accessors[prim['indices']];\n primitiveAttributeMap['INDICES'] = indices;\n }\n\n // Loop through all the attributes and store within our attributeMap\n for (const attr in prim['attributes']) {\n const accessor = accessors[prim['attributes'][attr]];\n primitiveAttributeMap[attr] = accessor;\n if (accessor.structureType > 3) {\n throw Error(\n 'Vertex attribute accessor accessed an unsupported data type for vertex attribute'\n );\n }\n attributes.push(attr);\n }\n meshPrimitives.push(\n new GLTFPrimitive(topology, primitiveAttributeMap, attributes)\n );\n }\n meshes.push(new GLTFMesh(mesh.name, meshPrimitives));\n }\n\n const skins: GLTFSkin[] = [];\n for (const skin of jsonChunk.skins) {\n const inverseBindMatrixAccessor = accessors[skin.inverseBindMatrices];\n inverseBindMatrixAccessor.view.addUsage(\n GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST\n );\n inverseBindMatrixAccessor.view.needsUpload = true;\n }\n\n // Upload the buffer views used by mesh\n for (let i = 0; i < bufferViews.length; ++i) {\n if (bufferViews[i].needsUpload) {\n bufferViews[i].upload(device);\n }\n }\n\n GLTFSkin.createSharedBindGroupLayout(device);\n for (const skin of jsonChunk.skins) {\n const inverseBindMatrixAccessor = accessors[skin.inverseBindMatrices];\n const joints = skin.joints;\n skins.push(new GLTFSkin(device, inverseBindMatrixAccessor, joints));\n }\n\n const nodes: GLTFNode[] = [];\n\n // Access each node. If node references a mesh, add mesh to that node\n const nodeUniformsBindGroupLayout = device.createBindGroupLayout({\n label: 'NodeUniforms.bindGroupLayout',\n entries: [\n {\n binding: 0,\n buffer: {\n type: 'uniform',\n },\n visibility: GPUShaderStage.VERTEX,\n },\n ],\n });\n for (const currNode of jsonChunk.nodes) {\n const baseTransformation = new BaseTransformation(\n currNode.translation,\n currNode.rotation,\n currNode.scale\n );\n const nodeToCreate = new GLTFNode(\n device,\n nodeUniformsBindGroupLayout,\n baseTransformation,\n currNode.name,\n skins[currNode.skin]\n );\n const meshToAdd = meshes[currNode.mesh];\n if (meshToAdd) {\n nodeToCreate.drawables.push(meshToAdd);\n }\n nodes.push(nodeToCreate);\n }\n\n // Assign each node its children\n nodes.forEach((node, idx) => {\n const children = jsonChunk.nodes[idx].children;\n if (children) {\n children.forEach((childIdx) => {\n const child = nodes[childIdx];\n child.setParent(node);\n });\n }\n });\n\n const scenes: GLTFScene[] = [];\n\n for (const jsonScene of jsonChunk.scenes) {\n const scene = new GLTFScene(device, nodeUniformsBindGroupLayout, jsonScene);\n const sceneChildren = scene.nodes;\n sceneChildren.forEach((childIdx) => {\n const child = nodes[childIdx];\n child.setParent(scene.root);\n });\n scenes.push(scene);\n }\n return {\n meshes,\n nodes,\n scenes,\n skins,\n };\n};\n"},6370:function(e,n){"use strict";n.Z="import { Mat4 } from 'wgpu-matrix';\nimport { GLTFNode } from './glbUtils';\n\n/* Sourced from https://github.com/bwasty/gltf-loader-ts/blob/master/source/gltf.ts */\n/* License for use can be found here: https://github.com/bwasty/gltf-loader-ts/blob/master/LICENSE */\n/* Comments and types have been excluded from original source for sake of cleanliness and brevity */\nexport type GlTfId = number;\n\nexport interface AccessorSparseIndices {\n bufferView: GlTfId;\n byteOffset?: number;\n componentType: 5121 | 5123 | 5125 | number;\n}\n\nexport interface AccessorSparseValues {\n bufferView: GlTfId;\n byteOffset?: number;\n}\n\nexport interface AccessorSparse {\n count: number;\n indices: AccessorSparseIndices;\n values: AccessorSparseValues;\n}\n\nexport interface Accessor {\n bufferView?: GlTfId;\n bufferViewUsage?: 34962 | 34963 | number;\n byteOffset?: number;\n componentType: 5120 | 5121 | 5122 | 5123 | 5125 | 5126 | number;\n normalized?: boolean;\n count: number;\n type: 'SCALAR' | 'VEC2' | 'VEC3' | 'VEC4' | 'MAT2' | 'MAT3' | 'MAT4' | string;\n max?: number[];\n min?: number[];\n sparse?: AccessorSparse;\n name?: string;\n}\n\nexport interface AnimationChannelTarget {\n node?: GlTfId;\n path: 'translation' | 'rotation' | 'scale' | 'weights' | string;\n}\n\nexport interface AnimationChannel {\n sampler: GlTfId;\n target: AnimationChannelTarget;\n}\n\nexport interface AnimationSampler {\n input: GlTfId;\n interpolation?: 'LINEAR' | 'STEP' | 'CUBICSPLINE' | string;\n output: GlTfId;\n}\n\nexport interface Animation {\n channels: AnimationChannel[];\n samplers: AnimationSampler[];\n name?: string;\n}\n\nexport interface Asset {\n copyright?: string;\n generator?: string;\n version: string;\n minVersion?: string;\n}\n\nexport interface Buffer {\n uri?: string;\n byteLength: number;\n name?: string;\n}\n\nexport interface BufferView {\n buffer: GlTfId;\n byteOffset?: number;\n byteLength: number;\n byteStride?: number;\n target?: 34962 | 34963 | number;\n name?: string;\n usage?: number;\n}\n\nexport interface CameraOrthographic {\n xmag: number;\n ymag: number;\n zfar: number;\n znear: number;\n}\n\nexport interface CameraPerspective {\n aspectRatio?: number;\n yfov: number;\n zfar?: number;\n znear: number;\n}\n\nexport interface Camera {\n orthographic?: CameraOrthographic;\n perspective?: CameraPerspective;\n type: 'perspective' | 'orthographic' | string;\n name?: any;\n}\n\nexport interface Image {\n uri?: string;\n mimeType?: 'image/jpeg' | 'image/png' | string;\n bufferView?: GlTfId;\n name?: string;\n}\n\nexport interface TextureInfo {\n index: GlTfId;\n texCoord?: number;\n}\n\nexport interface MaterialPbrMetallicRoughness {\n baseColorFactor?: number[];\n baseColorTexture?: TextureInfo;\n metallicFactor?: number;\n roughnessFactor?: number;\n metallicRoughnessTexture?: TextureInfo;\n}\nexport interface MaterialNormalTextureInfo {\n index?: any;\n texCoord?: any;\n scale?: number;\n}\nexport interface MaterialOcclusionTextureInfo {\n index?: any;\n texCoord?: any;\n strength?: number;\n}\n\nexport interface Material {\n name?: string;\n pbrMetallicRoughness?: MaterialPbrMetallicRoughness;\n normalTexture?: MaterialNormalTextureInfo;\n occlusionTexture?: MaterialOcclusionTextureInfo;\n emissiveTexture?: TextureInfo;\n emissiveFactor?: number[];\n alphaMode?: 'OPAQUE' | 'MASK' | 'BLEND' | string;\n alphaCutoff?: number;\n doubleSided?: boolean;\n}\n\nexport interface MeshPrimitive {\n attributes: {\n [k: string]: GlTfId;\n };\n indices?: GlTfId;\n material?: GlTfId;\n mode?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | number;\n targets?: {\n [k: string]: GlTfId;\n }[];\n}\n\nexport interface Mesh {\n primitives: MeshPrimitive[];\n weights?: number[];\n name?: string;\n}\n\nexport interface Node {\n camera?: GlTfId;\n children?: GlTfId[];\n skin?: GlTfId;\n matrix?: number[];\n worldTransformationMatrix?: Mat4;\n mesh?: GlTfId;\n rotation?: number[];\n scale?: number[];\n translation?: number[];\n weights?: number[];\n name?: string;\n}\n\nexport interface Sampler {\n magFilter?: 9728 | 9729 | number;\n minFilter?: 9728 | 9729 | 9984 | 9985 | 9986 | 9987 | number;\n wrapS?: 33071 | 33648 | 10497 | number;\n wrapT?: 33071 | 33648 | 10497 | number;\n name?: string;\n}\n\nexport interface Scene {\n nodes?: GlTfId[];\n name?: any;\n root?: GLTFNode;\n}\nexport interface Skin {\n inverseBindMatrices?: GlTfId;\n skeleton?: GlTfId;\n joints: GlTfId[];\n name?: string;\n}\n\nexport interface Texture {\n sampler?: GlTfId;\n source?: GlTfId;\n name?: string;\n}\n\nexport interface GlTf {\n extensionsUsed?: string[];\n extensionsRequired?: string[];\n accessors?: Accessor[];\n animations?: Animation[];\n asset: Asset;\n buffers?: Buffer[];\n bufferViews?: BufferView[];\n cameras?: Camera[];\n images?: Image[];\n materials?: Material[];\n meshes?: Mesh[];\n nodes?: Node[];\n samplers?: Sampler[];\n scene?: GlTfId;\n scenes?: Scene[];\n skins?: Skin[];\n textures?: Texture[];\n}\n"},2624:function(e,n){"use strict";n.Z="struct VertexInput {\n @location(0) vert_pos: vec2,\n @location(1) joints: vec4,\n @location(2) weights: vec4\n}\n\nstruct VertexOutput {\n @builtin(position) Position: vec4,\n @location(0) world_pos: vec3,\n @location(1) joints: vec4,\n @location(2) weights: vec4,\n}\n\nstruct CameraUniforms {\n projMatrix: mat4x4f,\n viewMatrix: mat4x4f,\n modelMatrix: mat4x4f,\n}\n\nstruct GeneralUniforms {\n render_mode: u32,\n skin_mode: u32,\n}\n\n@group(0) @binding(0) var camera_uniforms: CameraUniforms;\n@group(1) @binding(0) var general_uniforms: GeneralUniforms;\n@group(2) @binding(0) var joint_matrices: array>;\n@group(2) @binding(1) var inverse_bind_matrices: array>;\n\n@vertex\nfn vertexMain(input: VertexInput) -> VertexOutput {\n var output: VertexOutput;\n var bones = vec4(0.0, 0.0, 0.0, 0.0);\n let position = vec4(input.vert_pos.x, input.vert_pos.y, 0.0, 1.0);\n // Get relevant 4 bone matrices\n let joint0 = joint_matrices[input.joints[0]] * inverse_bind_matrices[input.joints[0]];\n let joint1 = joint_matrices[input.joints[1]] * inverse_bind_matrices[input.joints[1]];\n let joint2 = joint_matrices[input.joints[2]] * inverse_bind_matrices[input.joints[2]];\n let joint3 = joint_matrices[input.joints[3]] * inverse_bind_matrices[input.joints[3]];\n // Compute influence of joint based on weight\n let skin_matrix = \n joint0 * input.weights[0] +\n joint1 * input.weights[1] +\n joint2 * input.weights[2] +\n joint3 * input.weights[3];\n // Bone transformed mesh\n output.Position = select(\n camera_uniforms.projMatrix * camera_uniforms.viewMatrix * camera_uniforms.modelMatrix * position,\n camera_uniforms.projMatrix * camera_uniforms.viewMatrix * camera_uniforms.modelMatrix * skin_matrix * position,\n general_uniforms.skin_mode == 0\n );\n\n //Get unadjusted world coordinates\n output.world_pos = position.xyz;\n output.joints = vec4(f32(input.joints.x), f32(input.joints.y), f32(input.joints.z), f32(input.joints.w));\n output.weights = input.weights;\n return output;\n}\n\n\n@fragment\nfn fragmentMain(input: VertexOutput) -> @location(0) vec4 {\n switch general_uniforms.render_mode {\n case 1: {\n return input.joints;\n }\n case 2: {\n return input.weights;\n }\n default: {\n return vec4(255.0, 0.0, 1.0, 1.0); \n }\n }\n}"},6270:function(e,n){"use strict";n.Z="/* eslint-disable prettier/prettier */\nexport const gridVertices = new Float32Array([\n // B0\n 0, 1, // 0 \n 0, -1, // 1\n // CONNECTOR\n 2, 1, // 2\n 2, -1, // 3\n // B1\n 4, 1, // 4\n 4, -1, // 5\n // CONNECTOR\n 6, 1, // 6\n 6, -1, // 7\n // B2\n 8, 1, // 8\n 8, -1, // 9,\n // CONNECTOR\n 10, 1, //10\n 10, -1, //11\n // B3\n 12, 1, //12\n 12, -1, //13\n]);\n\n// Representing the indice of four bones that can influence each vertex\nexport const gridJoints = new Uint32Array([\n 0, 0, 0, 0, // Vertex 0 is influenced by bone 0\n 0, 0, 0, 0, // 1\n 0, 1, 0, 0, // 2\n 0, 1, 0, 0, // 3\n 1, 0, 0, 0, // 4\n 1, 0, 0, 0, // 5\n 1, 2, 0, 0, // Vertex 6 is influenced by bone 1 and bone 2\n 1, 2, 0, 0, // 7\n 2, 0, 0, 0, // 8\n 2, 0, 0, 0, // 9\n 1, 2, 3, 0, //10\n 1, 2, 3, 0, //11\n 2, 3, 0, 0, //12\n 2, 3, 0, 0, //13\n])\n\n// The weights applied when ve\nexport const gridWeights = new Float32Array([\n // B0\n 1, 0, 0, 0, // 0\n 1, 0, 0, 0, // 1\n // CONNECTOR\n .5,.5, 0, 0, // 2\n .5,.5, 0, 0, // 3\n // B1\n 1, 0, 0, 0, // 4\n 1, 0, 0, 0, // 5\n // CONNECTOR\n .5,.5, 0, 0, // 6\n .5,.5, 0, 0, // 7\n // B2\n 1, 0, 0, 0, // 8\n 1, 0, 0, 0, // 9\n // CONNECTOR\n .5,.5, 0, 0, // 10\n .5,.5, 0, 0, // 11\n // B3\n 1, 0, 0, 0, // 12\n 1, 0, 0, 0, // 13\n]);\n\n// Using data above...\n// Vertex 0 is influenced by bone 0 with a weight of 1 \n// Vertex 1 is influenced by bone 1 with a weight of 1\n// Vertex 2 is influenced by bone 0 and 1 with a weight of 0.5 each\n// and so on..\n// Although a vertex can hypothetically be influenced by 4 bones,\n// in this example, we stick to each vertex being infleunced by only two\n// although there can be downstream effects of parent bones influencing child bones\n// that influence their own children\n\nexport const gridIndices = new Uint16Array([\n // B0\n 0, 1,\n 0, 2,\n 1, 3,\n // CONNECTOR\n 2, 3, //\n 2, 4,\n 3, 5,\n // B1\n 4, 5,\n 4, 6,\n 5, 7, \n // CONNECTOR\n 6, 7,\n 6, 8,\n 7, 9,\n // B2\n 8, 9,\n 8, 10,\n 9, 11,\n // CONNECTOR\n 10, 11,\n 10, 12,\n 11, 13,\n // B3\n 12, 13,\n]);"},9483:function(e,n){"use strict";n.Z="import { gridVertices, gridIndices, gridJoints, gridWeights } from './gridData';\n\n// Uses constant grid data to create appropriately sized GPU Buffers for our skinned grid\nexport const createSkinnedGridBuffers = (device: GPUDevice) => {\n // Utility function that creates GPUBuffers from data\n const createBuffer = (\n data: Float32Array | Uint32Array,\n type: 'f32' | 'u32'\n ) => {\n const buffer = device.createBuffer({\n size: data.byteLength,\n usage: GPUBufferUsage.VERTEX,\n mappedAtCreation: true,\n });\n if (type === 'f32') {\n new Float32Array(buffer.getMappedRange()).set(data);\n } else {\n new Uint32Array(buffer.getMappedRange()).set(data);\n }\n buffer.unmap();\n return buffer;\n };\n const positionsBuffer = createBuffer(gridVertices, 'f32');\n const jointsBuffer = createBuffer(gridJoints, 'u32');\n const weightsBuffer = createBuffer(gridWeights, 'f32');\n const indicesBuffer = device.createBuffer({\n size: Uint16Array.BYTES_PER_ELEMENT * gridIndices.length,\n usage: GPUBufferUsage.INDEX,\n mappedAtCreation: true,\n });\n new Uint16Array(indicesBuffer.getMappedRange()).set(gridIndices);\n indicesBuffer.unmap();\n\n return {\n positions: positionsBuffer,\n joints: jointsBuffer,\n weights: weightsBuffer,\n indices: indicesBuffer,\n };\n};\n\nexport const createSkinnedGridRenderPipeline = (\n device: GPUDevice,\n presentationFormat: GPUTextureFormat,\n vertexShader: string,\n fragmentShader: string,\n bgLayouts: GPUBindGroupLayout[]\n) => {\n const pipeline = device.createRenderPipeline({\n label: 'SkinnedGridRenderer',\n layout: device.createPipelineLayout({\n label: `SkinnedGridRenderer.pipelineLayout`,\n bindGroupLayouts: bgLayouts,\n }),\n vertex: {\n module: device.createShaderModule({\n label: `SkinnedGridRenderer.vertexShader`,\n code: vertexShader,\n }),\n entryPoint: 'vertexMain',\n buffers: [\n // Vertex Positions (positions)\n {\n arrayStride: Float32Array.BYTES_PER_ELEMENT * 2,\n attributes: [\n {\n format: 'float32x2',\n offset: 0,\n shaderLocation: 0,\n },\n ],\n },\n // Bone Indices (joints)\n {\n arrayStride: Uint32Array.BYTES_PER_ELEMENT * 4,\n attributes: [\n {\n format: 'uint32x4',\n offset: 0,\n shaderLocation: 1,\n },\n ],\n },\n // Bone Weights (weights)\n {\n arrayStride: Float32Array.BYTES_PER_ELEMENT * 4,\n attributes: [\n {\n format: 'float32x4',\n offset: 0,\n shaderLocation: 2,\n },\n ],\n },\n ],\n },\n fragment: {\n module: device.createShaderModule({\n label: `SkinnedGridRenderer.fragmentShader`,\n code: fragmentShader,\n }),\n entryPoint: 'fragmentMain',\n targets: [\n {\n format: presentationFormat,\n },\n ],\n },\n primitive: {\n topology: 'line-list',\n },\n });\n return pipeline;\n};\n"},134:function(e,n){"use strict";n.Z="@group(0) @binding(0) var mySampler : sampler;\n@group(0) @binding(1) var myTexture : texture_2d;\n\nstruct VertexOutput {\n @builtin(position) Position : vec4,\n @location(0) fragUV : vec2,\n}\n\n@vertex\nfn vert_main(@builtin(vertex_index) VertexIndex : u32) -> VertexOutput {\n const pos = array(\n vec2( 1.0, 1.0),\n vec2( 1.0, -1.0),\n vec2(-1.0, -1.0),\n vec2( 1.0, 1.0),\n vec2(-1.0, -1.0),\n vec2(-1.0, 1.0),\n );\n\n const uv = array(\n vec2(1.0, 0.0),\n vec2(1.0, 1.0),\n vec2(0.0, 1.0),\n vec2(1.0, 0.0),\n vec2(0.0, 1.0),\n vec2(0.0, 0.0),\n );\n\n var output : VertexOutput;\n output.Position = vec4(pos[VertexIndex], 0.0, 1.0);\n output.fragUV = uv[VertexIndex];\n return output;\n}\n\n@fragment\nfn frag_main(@location(0) fragUV : vec2) -> @location(0) vec4 {\n return textureSample(myTexture, mySampler, fragUV);\n}\n"}}]); \ No newline at end of file diff --git a/_next/static/chunks/880.ee954aed19019741.js b/_next/static/chunks/880.ee954aed19019741.js deleted file mode 100644 index 56c10ac7..00000000 --- a/_next/static/chunks/880.ee954aed19019741.js +++ /dev/null @@ -1 +0,0 @@ -(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[880],{5671:function(e,n,t){"use strict";t.d(n,{Tl:function(){return p},hu:function(){return d}});var o=t(5893),r=t(9008),i=t.n(r),a=t(1163),s=t(7294),l=t(9147),u=t.n(l);t(7319);let c=e=>{let n=(0,s.useRef)(null),r=(0,s.useRef)(null),l=(0,s.useMemo)(()=>e.sources.map(e=>{let{name:n,contents:r}=e;return{name:n,...function(e){let n;let r=null;{r=document.createElement("div");let i=t(4631);n=i(r,{lineNumbers:!0,lineWrapping:!0,theme:"monokai",readOnly:!0})}return{Container:function(t){return(0,o.jsx)("div",{...t,children:(0,o.jsx)("div",{ref(t){r&&t&&(t.appendChild(r),n.setOption("value",e))}})})}}}(r)}}),e.sources),c=(0,s.useRef)(null),p=(0,s.useMemo)(()=>{if(e.gui){let n=t(4376),o=new n.GUI({autoPlace:!1});return o.domElement.style.position="relative",o.domElement.style.zIndex="1000",o}},[]),d=(0,s.useRef)(null),m=(0,s.useMemo)(()=>{if(e.stats){let n=t(2792);return new n}},[]),f=(0,a.useRouter)(),g=f.asPath.match(/#([a-zA-Z0-9\.\/]+)/),[h,S]=(0,s.useState)(null),[E,_]=(0,s.useState)(null);return(0,s.useEffect)(()=>{if(g?_(g[1]):_(l[0].name),p&&c.current)for(c.current.appendChild(p.domElement);p.__controllers.length>0;)p.__controllers[0].remove();m&&d.current&&(m.dom.style.position="absolute",m.showPanel(1),d.current.appendChild(m.dom));let t={active:!0},o=()=>{t.active=!1};try{let r=n.current;if(!r)throw Error("The canvas is not available");let i=e.init({canvas:r,pageState:t,gui:p,stats:m});i instanceof Promise&&i.catch(e=>{console.error(e),S(e)})}catch(a){console.error(a),S(a)}return o},[]),(0,o.jsxs)("main",{children:[(0,o.jsxs)(i(),{children:[(0,o.jsx)("style",{dangerouslySetInnerHTML:{__html:"\n .CodeMirror {\n height: auto !important;\n margin: 1em 0;\n }\n\n .CodeMirror-scroll {\n height: auto !important;\n overflow: visible !important;\n }\n "}}),(0,o.jsx)("title",{children:"".concat(e.name," - WebGPU Samples")}),(0,o.jsx)("meta",{name:"description",content:e.description}),(0,o.jsx)("meta",{httpEquiv:"origin-trial",content:e.originTrial})]}),(0,o.jsxs)("div",{children:[(0,o.jsx)("h1",{children:e.name}),(0,o.jsx)("a",{target:"_blank",rel:"noreferrer",href:"https://github.com/".concat("webgpu/webgpu-samples","/tree/main/").concat(e.filename),children:"See it on Github!"}),(0,o.jsx)("p",{children:e.description}),h?(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)("p",{children:"Something went wrong. Do your browser and device support WebGPU?"}),(0,o.jsx)("p",{children:"".concat(h)})]}):null]}),(0,o.jsxs)("div",{className:u().canvasContainer,children:[(0,o.jsx)("div",{style:{position:"absolute",left:10},ref:d}),(0,o.jsx)("div",{style:{position:"absolute",right:10},ref:c}),(0,o.jsx)("canvas",{ref:n})]}),(0,o.jsxs)("div",{children:[(0,o.jsx)("nav",{className:u().sourceFileNav,ref:r,children:(0,o.jsx)("div",{className:u().sourceFileScrollContainer,onScroll(e){let n=e.currentTarget,t=n.scrollWidth-n.clientWidth-n.scrollLeft;n.scrollLeft>25?r.current.setAttribute("data-left","true"):r.current.setAttribute("data-left","false"),t>25?r.current.setAttribute("data-right","true"):r.current.setAttribute("data-right","false")},children:(0,o.jsx)("ul",{children:l.map((e,n)=>(0,o.jsx)("li",{children:(0,o.jsx)("a",{href:"#".concat(e.name),"data-active":E==e.name,onClick(){_(e.name)},children:e.name})},n))})})}),l.map((e,n)=>(0,o.jsx)(e.Container,{className:u().sourceFileContainer,"data-active":E==e.name},n))]})]})},p=e=>(0,o.jsx)(c,{...e});function d(e,n){if(!e)throw Error(n)}},9880:function(e,n,t){"use strict";let o;t.r(n),t.d(n,{default:function(){return E}});var r,i,a=t(5671),s=t(134);let l=(e,n,t,o,r,i,a)=>{let s=[];for(let l=0;l{let n=async n=>{let t,{canvas:o,pageState:r,gui:i,stats:a}=n,s=await navigator.gpu.requestAdapter(),l=s.features.has("timestamp-query");if(t=l?await s.requestDevice({requiredFeatures:["timestamp-query"]}):await s.requestDevice(),!r.active)return;let u=o.getContext("webgpu"),c=window.devicePixelRatio;o.width=o.clientWidth*c,o.height=o.clientHeight*c;let p=navigator.gpu.getPreferredCanvasFormat();u.configure({device:t,format:p,alphaMode:"premultiplied"}),e({canvas:o,pageState:r,gui:i,device:t,context:u,presentationFormat:p,stats:a,timestampQueryAvailable:l})};return n};class c{executeRun(e,n,t,o){let r=e.beginRenderPass(n);r.setPipeline(t);for(let i=0;i{e.queue.writeBuffer(i,0,new Uint32Array([n.highlight]))}}}d.sourceInfo={name:"src/sample/bitonicSort/bitonicDisplay.ts".substring(23),contents:"import {\n BindGroupCluster,\n Base2DRendererClass,\n createBindGroupCluster,\n} from './utils';\n\nimport bitonicDisplay from './bitonicDisplay.frag.wgsl';\n\ninterface BitonicDisplayRenderArgs {\n highlight: number;\n}\n\nexport default class BitonicDisplayRenderer extends Base2DRendererClass {\n static sourceInfo = {\n name: __filename.substring(__dirname.length + 1),\n contents: __SOURCE__,\n };\n\n switchBindGroup: (name: string) => void;\n setArguments: (args: BitonicDisplayRenderArgs) => void;\n computeBGDescript: BindGroupCluster;\n\n constructor(\n device: GPUDevice,\n presentationFormat: GPUTextureFormat,\n renderPassDescriptor: GPURenderPassDescriptor,\n computeBGDescript: BindGroupCluster,\n label: string\n ) {\n super();\n this.renderPassDescriptor = renderPassDescriptor;\n this.computeBGDescript = computeBGDescript;\n\n const uniformBuffer = device.createBuffer({\n size: Uint32Array.BYTES_PER_ELEMENT,\n usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,\n });\n\n const bgCluster = createBindGroupCluster(\n [0],\n [GPUShaderStage.FRAGMENT],\n ['buffer'],\n [{ type: 'uniform' }],\n [[{ buffer: uniformBuffer }]],\n label,\n device\n );\n\n this.currentBindGroup = bgCluster.bindGroups[0];\n\n this.pipeline = super.create2DRenderPipeline(\n device,\n label,\n [this.computeBGDescript.bindGroupLayout, bgCluster.bindGroupLayout],\n bitonicDisplay,\n presentationFormat\n );\n\n this.setArguments = (args: BitonicDisplayRenderArgs) => {\n device.queue.writeBuffer(\n uniformBuffer,\n 0,\n new Uint32Array([args.highlight])\n );\n };\n }\n\n startRun(commandEncoder: GPUCommandEncoder, args: BitonicDisplayRenderArgs) {\n this.setArguments(args);\n super.executeRun(commandEncoder, this.renderPassDescriptor, this.pipeline, [\n this.computeBGDescript.bindGroups[0],\n this.currentBindGroup,\n ]);\n }\n}\n"};let m=e=>((e%2!=0||e>256)&&(e=256),"\n\nstruct Uniforms {\n width: f32,\n height: f32,\n algo: u32,\n blockHeight: u32,\n}\n\n// Create local workgroup data that can contain all elements\nvar local_data: array;\n\n// Define groups (functions refer to this data)\n@group(0) @binding(0) var input_data: array;\n@group(0) @binding(1) var output_data: array;\n@group(0) @binding(2) var uniforms: Uniforms;\n@group(0) @binding(3) var counter: atomic;\n\n// Compare and swap values in local_data\nfn local_compare_and_swap(idx_before: u32, idx_after: u32) {\n //idx_before should always be < idx_after\n if (local_data[idx_after] < local_data[idx_before]) {\n atomicAdd(&counter, 1);\n var temp: u32 = local_data[idx_before];\n local_data[idx_before] = local_data[idx_after];\n local_data[idx_after] = temp;\n }\n return;\n}\n\n// invoke_id goes from 0 to workgroupSize\nfn get_flip_indices(invoke_id: u32, block_height: u32) -> vec2 {\n // Caculate index offset (i.e move indices into correct block)\n let block_offset: u32 = ((2 * invoke_id) / block_height) * block_height;\n let half_height = block_height / 2;\n // Calculate index spacing\n var idx: vec2 = vec2(\n invoke_id % half_height, block_height - (invoke_id % half_height) - 1,\n );\n idx.x += block_offset;\n idx.y += block_offset;\n return idx;\n}\n\nfn get_disperse_indices(invoke_id: u32, block_height: u32) -> vec2 {\n var block_offset: u32 = ((2 * invoke_id) / block_height) * block_height;\n let half_height = block_height / 2;\n var idx: vec2 = vec2(\n invoke_id % half_height, (invoke_id % half_height) + half_height\n );\n idx.x += block_offset;\n idx.y += block_offset;\n return idx;\n}\n\nfn global_compare_and_swap(idx_before: u32, idx_after: u32) {\n if (input_data[idx_after] < input_data[idx_before]) {\n output_data[idx_before] = input_data[idx_after];\n output_data[idx_after] = input_data[idx_before];\n } \n}\n\n// Constants/enum\nconst ALGO_NONE = 0;\nconst ALGO_LOCAL_FLIP = 1;\nconst ALGO_LOCAL_DISPERSE = 2;\nconst ALGO_GLOBAL_FLIP = 3;\n\n// Our compute shader will execute specified # of invocations or elements / 2 invocations\n@compute @workgroup_size(").concat(e,", 1, 1)\nfn computeMain(\n @builtin(global_invocation_id) global_id: vec3,\n @builtin(local_invocation_id) local_id: vec3,\n @builtin(workgroup_id) workgroup_id: vec3,\n) {\n\n let offset = ").concat(e," * 2 * workgroup_id.x;\n // If we will perform a local swap, then populate the local data\n if (uniforms.algo <= 2) {\n // Assign range of input_data to local_data.\n // Range cannot exceed maxWorkgroupsX * 2\n // Each invocation will populate the workgroup data... (1 invocation for every 2 elements)\n local_data[local_id.x * 2] = input_data[offset + local_id.x * 2];\n local_data[local_id.x * 2 + 1] = input_data[offset + local_id.x * 2 + 1];\n }\n\n //...and wait for each other to finish their own bit of data population.\n workgroupBarrier();\n\n switch uniforms.algo {\n case 1: { // Local Flip\n let idx = get_flip_indices(local_id.x, uniforms.blockHeight);\n local_compare_and_swap(idx.x, idx.y);\n } \n case 2: { // Local Disperse\n let idx = get_disperse_indices(local_id.x, uniforms.blockHeight);\n local_compare_and_swap(idx.x, idx.y);\n } \n case 3: { // Global Flip\n let idx = get_flip_indices(global_id.x, uniforms.blockHeight);\n global_compare_and_swap(idx.x, idx.y);\n }\n case 4: { \n let idx = get_disperse_indices(global_id.x, uniforms.blockHeight);\n global_compare_and_swap(idx.x, idx.y);\n }\n default: { \n \n }\n }\n\n // Ensure that all invocations have swapped their own regions of data\n workgroupBarrier();\n\n if (uniforms.algo <= ALGO_LOCAL_DISPERSE) {\n //Repopulate global data with local data\n output_data[offset + local_id.x * 2] = local_data[local_id.x * 2];\n output_data[offset + local_id.x * 2 + 1] = local_data[local_id.x * 2 + 1];\n }\n\n}"));var f="@group(0) @binding(3) var counter: atomic;\n\n@compute @workgroup_size(1, 1, 1)\nfn atomicToZero() {\n let counterValue = atomicLoad(&counter);\n atomicSub(&counter, counterValue);\n}",g="src/sample/bitonicSort/main.ts";(r=i||(i={}))[r.NONE=0]="NONE",r[r.FLIP_LOCAL=1]="FLIP_LOCAL",r[r.DISPERSE_LOCAL=2]="DISPERSE_LOCAL",r[r.FLIP_GLOBAL=3]="FLIP_GLOBAL",r[r.DISPERSE_GLOBAL=4]="DISPERSE_GLOBAL";let h=e=>{let n=Math.log2(e);return n*(n+1)/2};u(async e=>{let n,t,o,{pageState:r,device:a,gui:s,presentationFormat:u,context:c,canvas:p,timestampQueryAvailable:g}=e,S=a.limits.maxComputeWorkgroupSizeX;g&&(n=a.createQuerySet({type:"timestamp",count:2}),t=a.createBuffer({size:2*BigInt64Array.BYTES_PER_ELEMENT,usage:GPUBufferUsage.QUERY_RESOLVE|GPUBufferUsage.COPY_SRC}),o=a.createBuffer({size:2*BigInt64Array.BYTES_PER_ELEMENT,usage:GPUBufferUsage.COPY_DST|GPUBufferUsage.MAP_READ}));let E=[],_=32*S;for(let v=_;v>=4;v/=2)E.push(v);let x=[];for(let y=S;y>=2;y/=2)x.push(y);let b=Math.sqrt(_)%2==0?Math.floor(Math.sqrt(_)):Math.floor(Math.sqrt(_/2)),w=_/b,C={"Total Elements":_,"Grid Width":b,"Grid Height":w,"Grid Dimensions":"".concat(b,"x").concat(w),"Workgroup Size":S,"Size Limit":S,"Workgroups Per Step":_/(2*S),"Hovered Cell":0,"Swapped Cell":1,"Step Index":0,"Total Steps":h(_),"Current Step":"0 of 91","Prev Step":"NONE","Next Step":"FLIP_LOCAL","Prev Swap Span":0,"Next Swap Span":2,executeStep:!1,"Randomize Values"(){},"Execute Sort Step"(){},"Log Elements"(){},"Auto Sort"(){},"Auto Sort Speed":50,"Display Mode":"Elements","Total Swaps":0,"Step Time":"0ms",stepTime:0,"Sort Time":"0ms",sortTime:0,"Average Sort Time":"0ms",configToCompleteSwapsMap:{"8192 256":{sorts:0,time:0}},configKey:"8192 256"},T=new Uint32Array(Array.from({length:C["Total Elements"]},(e,n)=>n)),P=Float32Array.BYTES_PER_ELEMENT*E[0],B=a.createBuffer({size:P,usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_DST}),L=a.createBuffer({size:P,usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_SRC}),A=a.createBuffer({size:P,usage:GPUBufferUsage.MAP_READ|GPUBufferUsage.COPY_DST}),G=a.createBuffer({size:Uint32Array.BYTES_PER_ELEMENT,usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_SRC}),I=a.createBuffer({size:Uint32Array.BYTES_PER_ELEMENT,usage:GPUBufferUsage.MAP_READ|GPUBufferUsage.COPY_DST}),k=a.createBuffer({size:4*Float32Array.BYTES_PER_ELEMENT,usage:GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST}),U=l([0,1,2,3],[GPUShaderStage.COMPUTE|GPUShaderStage.FRAGMENT,GPUShaderStage.COMPUTE,GPUShaderStage.COMPUTE|GPUShaderStage.FRAGMENT,GPUShaderStage.COMPUTE],["buffer","buffer","buffer","buffer"],[{type:"read-only-storage"},{type:"storage"},{type:"uniform"},{type:"storage"}],[[{buffer:B},{buffer:L},{buffer:k},{buffer:G}]],"BitonicSort",a),M=a.createComputePipeline({layout:a.createPipelineLayout({bindGroupLayouts:[U.bindGroupLayout]}),compute:{module:a.createShaderModule({code:m(C["Workgroup Size"])}),entryPoint:"computeMain"}}),R=a.createComputePipeline({layout:a.createPipelineLayout({bindGroupLayouts:[U.bindGroupLayout]}),compute:{module:a.createShaderModule({code:f}),entryPoint:"atomicToZero"}}),O={colorAttachments:[{view:void 0,clearValue:{r:.1,g:.4,b:.5,a:1},loadOp:"clear",storeOp:"store"}]},N=new d(a,u,O,U,"BitonicDisplay"),D=()=>{C.stepTime=0,C.sortTime=0,ec.setValue("0ms"),ep.setValue("0ms");let e=C.configToCompleteSwapsMap[C.configKey].time/C.configToCompleteSwapsMap[C.configKey].sorts;ed.setValue("".concat((e||0).toFixed(5),"ms"))},z=()=>{Q.setValue(Math.min(C["Total Elements"]/2,C["Size Limit"]));let e=(C["Total Elements"]-1)/(2*C["Size Limit"]);$.setValue(Math.ceil(e)),C["Step Index"]=0,C["Total Steps"]=h(C["Total Elements"]),eo.setValue("".concat(C["Step Index"]," of ").concat(C["Total Steps"]));let n=Math.sqrt(C["Total Elements"])%2==0?Math.floor(Math.sqrt(C["Total Elements"])):Math.floor(Math.sqrt(C["Total Elements"]/2)),t=C["Total Elements"]/n;C["Grid Width"]=n,C["Grid Height"]=t,J.setValue("".concat(n,"x").concat(t)),er.setValue("NONE"),ei.setValue("FLIP_LOCAL"),es.setValue(0),el.setValue(2);let o=a.createCommandEncoder(),r=o.beginComputePass();r.setPipeline(R),r.setBindGroup(0,U.bindGroups[0]),r.dispatchWorkgroups(1),r.end(),a.queue.submit([o.finish()]),ea.setValue(0),eg=2},F=()=>{let e=T.length;for(;0!==e;){let n=Math.floor(Math.random()*e);e-=1,[T[e],T[n]]=[T[n],T[e]]}},H=()=>{T=new Uint32Array(Array.from({length:C["Total Elements"]},(e,n)=>n)),z(),M=a.createComputePipeline({layout:a.createPipelineLayout({bindGroupLayouts:[U.bindGroupLayout]}),compute:{module:a.createShaderModule({code:m(Math.min(C["Total Elements"]/2,C["Size Limit"]))}),entryPoint:"computeMain"}}),F(),eg=2};F();let V=()=>{let e;switch(C["Next Step"]){case"FLIP_LOCAL":case"FLIP_GLOBAL":{let n=C["Next Swap Span"],t=Math.floor(C["Hovered Cell"]/n)+1,o=C["Hovered Cell"]%n;e=n*t-o-1,en.setValue(e)}break;case"DISPERSE_LOCAL":{let r=C["Next Swap Span"],i=r/2;e=C["Hovered Cell"]%r{null!==W&&(clearInterval(W),W=null)},q=()=>{let e=C["Auto Sort Speed"];W=setInterval(()=>{"NONE"===C["Next Step"]&&(clearInterval(W),W=null,K.domElement.style.pointerEvents="auto"),C["Auto Sort Speed"]!==e&&(clearInterval(W),W=null,q()),C.executeStep=!0,V()},C["Auto Sort Speed"])},j=s.addFolder("Compute Resources");j.add(C,"Total Elements",E).onChange(()=>{Y(),H(),K.domElement.style.pointerEvents="auto";let e="".concat(C["Total Elements"]," ").concat(C["Size Limit"]);C.configToCompleteSwapsMap[e]||(C.configToCompleteSwapsMap[e]={sorts:0,time:0}),C.configKey=e,D()});let K=j.add(C,"Size Limit",x).onChange(()=>{let e=Math.min(C["Total Elements"]/2,C["Size Limit"]),n=(C["Total Elements"]-1)/(2*C["Size Limit"]);Q.setValue(e),$.setValue(Math.ceil(n)),M=M=a.createComputePipeline({layout:a.createPipelineLayout({bindGroupLayouts:[U.bindGroupLayout]}),compute:{module:a.createShaderModule({code:m(Math.min(C["Total Elements"]/2,C["Size Limit"]))}),entryPoint:"computeMain"}});let t="".concat(C["Total Elements"]," ").concat(C["Size Limit"]);C.configToCompleteSwapsMap[t]||(C.configToCompleteSwapsMap[t]={sorts:0,time:0}),C.configKey=t,D()}),Q=j.add(C,"Workgroup Size"),$=j.add(C,"Workgroups Per Step");j.open();let Z=s.addFolder("Sort Controls");Z.add(C,"Execute Sort Step").onChange(()=>{K.domElement.style.pointerEvents="none",Y(),C.executeStep=!0}),Z.add(C,"Randomize Values").onChange(()=>{Y(),F(),z(),D(),K.domElement.style.pointerEvents="auto"}),Z.add(C,"Log Elements").onChange(()=>console.log(T)),Z.add(C,"Auto Sort").onChange(()=>{K.domElement.style.pointerEvents="none",q()}),Z.add(C,"Auto Sort Speed",50,1e3).step(50),Z.open();let X=s.addFolder("Grid Information");X.add(C,"Display Mode",["Elements","Swap Highlight"]);let J=X.add(C,"Grid Dimensions"),ee=X.add(C,"Hovered Cell").onChange(V),en=X.add(C,"Swapped Cell"),et=s.addFolder("Execution Information"),eo=et.add(C,"Current Step"),er=et.add(C,"Prev Step"),ei=et.add(C,"Next Step"),ea=et.add(C,"Total Swaps"),es=et.add(C,"Prev Swap Span"),el=et.add(C,"Next Swap Span"),eu=s.addFolder("Timestamp Info (Chrome 121+)"),ec=eu.add(C,"Step Time"),ep=eu.add(C,"Sort Time"),ed=eu.add(C,"Average Sort Time"),em=document.getElementsByClassName("cr function");for(let ef=0;ef{let n=p.getBoundingClientRect().width,t=p.getBoundingClientRect().height,o=[n/C["Grid Width"],t/C["Grid Height"]],r=Math.floor(e.offsetX/o[0]),i=C["Grid Height"]-1-Math.floor(e.offsetY/o[1]);ee.setValue(i*C["Grid Width"]+r),C["Hovered Cell"]=i*C["Grid Width"]+r}),K.domElement.style.pointerEvents="none",$.domElement.style.pointerEvents="none",ee.domElement.style.pointerEvents="none",en.domElement.style.pointerEvents="none",eo.domElement.style.pointerEvents="none",er.domElement.style.pointerEvents="none",es.domElement.style.pointerEvents="none",ei.domElement.style.pointerEvents="none",el.domElement.style.pointerEvents="none",Q.domElement.style.pointerEvents="none",J.domElement.style.pointerEvents="none",ea.domElement.style.pointerEvents="none",ec.domElement.style.pointerEvents="none",ep.domElement.style.pointerEvents="none",ed.domElement.style.pointerEvents="none",s.width=325;let eg=2;async function eh(){if(!r.active)return;a.queue.writeBuffer(B,0,T.buffer,T.byteOffset,T.byteLength);let e=new Float32Array([C["Grid Width"],C["Grid Height"]]),s=new Uint32Array([i[C["Next Step"]],C["Next Swap Span"]]);a.queue.writeBuffer(k,0,e.buffer,e.byteOffset,e.byteLength),a.queue.writeBuffer(k,8,s),O.colorAttachments[0].view=c.getCurrentTexture().createView();let l=a.createCommandEncoder();if(N.startRun(l,{highlight:"Elements"===C["Display Mode"]?0:1}),C.executeStep&&eg<2*C["Total Elements"]){let u;(u=g?l.beginComputePass({timestampWrites:{querySet:n,beginningOfPassWriteIndex:0,endOfPassWriteIndex:1}}):l.beginComputePass()).setPipeline(M),u.setBindGroup(0,U.bindGroups[0]),u.dispatchWorkgroups(C["Workgroups Per Step"]),u.end(),g&&(l.resolveQuerySet(n,0,2,t,0),l.copyBufferToBuffer(t,0,o,0,2*BigInt64Array.BYTES_PER_ELEMENT)),C["Step Index"]=C["Step Index"]+1,eo.setValue("".concat(C["Step Index"]," of ").concat(C["Total Steps"])),er.setValue(C["Next Step"]),es.setValue(C["Next Swap Span"]),el.setValue(C["Next Swap Span"]/2),1===C["Next Swap Span"]?(eg*=2)==2*C["Total Elements"]?(ei.setValue("NONE"),el.setValue(0),C.configToCompleteSwapsMap[C.configKey].sorts+=1):eg>2*C["Workgroup Size"]?(ei.setValue("FLIP_GLOBAL"),el.setValue(eg)):(ei.setValue("FLIP_LOCAL"),el.setValue(eg)):C["Next Swap Span"]>2*C["Workgroup Size"]?ei.setValue("DISPERSE_GLOBAL"):ei.setValue("DISPERSE_LOCAL"),l.copyBufferToBuffer(L,0,A,0,P),l.copyBufferToBuffer(G,0,I,0,Uint32Array.BYTES_PER_ELEMENT)}if(a.queue.submit([l.finish()]),C.executeStep&&eg<4*C["Total Elements"]){await A.mapAsync(GPUMapMode.READ,0,P);let p=A.getMappedRange(0,P);await I.mapAsync(GPUMapMode.READ,0,Uint32Array.BYTES_PER_ELEMENT);let d=I.getMappedRange(0,Uint32Array.BYTES_PER_ELEMENT),m=p.slice(0,Uint32Array.BYTES_PER_ELEMENT*C["Total Elements"]),f=d.slice(0,Uint32Array.BYTES_PER_ELEMENT),h=new Uint32Array(m);if(ea.setValue(new Uint32Array(f)[0]),A.unmap(),I.unmap(),T=h,V(),g){await o.mapAsync(GPUMapMode.READ,0,2*BigInt64Array.BYTES_PER_ELEMENT);let S=new BigInt64Array(o.getMappedRange()),E=Number(S[1]-S[0])/1e6,_=C.sortTime+E;if(C.stepTime=E,C.sortTime=_,ec.setValue("".concat(E.toFixed(5),"ms")),ep.setValue("".concat(_.toFixed(5),"ms")),eg===2*C["Total Elements"]){eg*=2,C.configToCompleteSwapsMap[C.configKey].time+=_;let v=C.configToCompleteSwapsMap[C.configKey].time/C.configToCompleteSwapsMap[C.configKey].sorts;ed.setValue("".concat(v.toFixed(5),"ms"))}o.unmap()}}C.executeStep=!1,requestAnimationFrame(eh)}q(),requestAnimationFrame(eh)}).then(e=>o=e);let S=()=>(0,a.Tl)({name:"Bitonic Sort",description:"A naive bitonic sort algorithm executed on the GPU, based on tgfrerer's implementation at poniesandlight.co.uk/reflect/bitonic_merge_sort/. Each dispatch of the bitonic sort shader dispatches a workgroup containing elements/2 invocations. The GUI's Execution Information folder contains information about the sort's current state. The visualizer displays the sort's results as colored cells sorted from brightest to darkest.",init:o,gui:!0,sources:[{name:g.substring(23),contents:"import { makeSample, SampleInit } from '../../components/SampleLayout';\nimport { createBindGroupCluster, SampleInitFactoryWebGPU } from './utils';\nimport BitonicDisplayRenderer from './bitonicDisplay';\nimport bitonicDisplay from './bitonicDisplay.frag.wgsl';\nimport { NaiveBitonicCompute } from './bitonicCompute';\nimport fullscreenTexturedQuad from '../../shaders/fullscreenTexturedQuad.wgsl';\nimport atomicToZero from './atomicToZero.wgsl';\n\n// Type of step that will be executed in our shader\nenum StepEnum {\n NONE,\n FLIP_LOCAL,\n DISPERSE_LOCAL,\n FLIP_GLOBAL,\n DISPERSE_GLOBAL,\n}\n\ntype StepType =\n // NONE: No sort step has or will occur\n | 'NONE'\n // FLIP_LOCAL: A sort step that performs a flip operation over indices in a workgroup's locally addressable area\n // (i.e invocations * workgroup_index -> invocations * (workgroup_index + 1) - 1.\n | 'FLIP_LOCAL'\n // DISPERSE_LOCAL A sort step that performs a flip operation over indices in a workgroup's locally addressable area.\n | 'DISPERSE_LOCAL'\n // FLIP_GLOBAL A sort step that performs a flip step across a range of indices outside a workgroup's locally addressable area.\n | 'FLIP_GLOBAL'\n // DISPERSE_GLOBAL A sort step that performs a disperse operation across a range of indices outside a workgroup's locally addressable area.\n | 'DISPERSE_GLOBAL';\n\ntype DisplayType = 'Elements' | 'Swap Highlight';\n\ninterface ConfigInfo {\n // Number of sorts executed under a given elements + size limit config\n sorts: number;\n // Total collective time taken to execute each complete sort under this config\n time: number;\n}\n\ninterface StringKeyToNumber {\n [key: string]: ConfigInfo;\n}\n\n// Gui settings object\ninterface SettingsInterface {\n 'Total Elements': number;\n 'Grid Width': number;\n 'Grid Height': number;\n 'Grid Dimensions': string;\n 'Workgroup Size': number;\n 'Size Limit': number;\n 'Workgroups Per Step': number;\n 'Hovered Cell': number;\n 'Swapped Cell': number;\n 'Current Step': string;\n 'Step Index': number;\n 'Total Steps': number;\n 'Prev Step': StepType;\n 'Next Step': StepType;\n 'Prev Swap Span': number;\n 'Next Swap Span': number;\n executeStep: boolean;\n 'Randomize Values': () => void;\n 'Execute Sort Step': () => void;\n 'Log Elements': () => void;\n 'Auto Sort': () => void;\n 'Auto Sort Speed': number;\n 'Display Mode': DisplayType;\n 'Total Swaps': number;\n stepTime: number;\n 'Step Time': string;\n sortTime: number;\n 'Sort Time': string;\n 'Average Sort Time': string;\n configToCompleteSwapsMap: StringKeyToNumber;\n configKey: string;\n}\n\nconst getNumSteps = (numElements: number) => {\n const n = Math.log2(numElements);\n return (n * (n + 1)) / 2;\n};\n\nlet init: SampleInit;\nSampleInitFactoryWebGPU(\n async ({\n pageState,\n device,\n gui,\n presentationFormat,\n context,\n canvas,\n timestampQueryAvailable,\n }) => {\n const maxInvocationsX = device.limits.maxComputeWorkgroupSizeX;\n\n let querySet: GPUQuerySet;\n let timestampQueryResolveBuffer: GPUBuffer;\n let timestampQueryResultBuffer: GPUBuffer;\n if (timestampQueryAvailable) {\n querySet = device.createQuerySet({ type: 'timestamp', count: 2 });\n timestampQueryResolveBuffer = device.createBuffer({\n // 2 timestamps * BigInt size for nanoseconds\n size: 2 * BigInt64Array.BYTES_PER_ELEMENT,\n usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC,\n });\n timestampQueryResultBuffer = device.createBuffer({\n // 2 timestamps * BigInt size for nanoseconds\n size: 2 * BigInt64Array.BYTES_PER_ELEMENT,\n usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,\n });\n }\n\n const totalElementOptions = [];\n const maxElements = maxInvocationsX * 32;\n for (let i = maxElements; i >= 4; i /= 2) {\n totalElementOptions.push(i);\n }\n\n const sizeLimitOptions: number[] = [];\n for (let i = maxInvocationsX; i >= 2; i /= 2) {\n sizeLimitOptions.push(i);\n }\n\n const defaultGridWidth =\n Math.sqrt(maxElements) % 2 === 0\n ? Math.floor(Math.sqrt(maxElements))\n : Math.floor(Math.sqrt(maxElements / 2));\n\n const defaultGridHeight = maxElements / defaultGridWidth;\n\n const settings: SettingsInterface = {\n // TOTAL ELEMENT AND GRID SETTINGS\n // The number of elements to be sorted. Must equal gridWidth * gridHeight || Workgroup Size * Workgroups * 2.\n // When changed, all relevant values within the settings object are reset to their defaults at the beginning of a sort with n elements.\n 'Total Elements': maxElements,\n // The width of the screen in cells.\n 'Grid Width': defaultGridWidth,\n // The height of the screen in cells.\n 'Grid Height': defaultGridHeight,\n // Grid Dimensions as string\n 'Grid Dimensions': `${defaultGridWidth}x${defaultGridHeight}`,\n\n // INVOCATION, WORKGROUP SIZE, AND WORKGROUP DISPATCH SETTINGS\n // The size of a workgroup, or the number of invocations executed within each workgroup\n // Determined algorithmically based on 'Size Limit', maxInvocationsX, and the current number of elements to sort\n 'Workgroup Size': maxInvocationsX,\n // An artifical constraint on the maximum workgroup size/maximumn invocations per workgroup as specified by device.limits.maxComputeWorkgroupSizeX\n 'Size Limit': maxInvocationsX,\n // Total workgroups that are dispatched during each step of the bitonic sort\n 'Workgroups Per Step': maxElements / (maxInvocationsX * 2),\n\n // HOVER SETTINGS\n // The element/cell in the element visualizer directly beneath the mouse cursor\n 'Hovered Cell': 0,\n // The element/cell in the element visualizer that the hovered cell will swap with in the next execution step of the bitonic sort.\n 'Swapped Cell': 1,\n\n // STEP INDEX, STEP TYPE, AND STEP SWAP SPAN SETTINGS\n // The index of the current step in the bitonic sort.\n 'Step Index': 0,\n // The total number of steps required to sort the displayed elements.\n 'Total Steps': getNumSteps(maxElements),\n // A string that condenses 'Step Index' and 'Total Steps' into a single GUI Controller display element.\n 'Current Step': `0 of 91`,\n // The category of the previously executed step. Always begins the bitonic sort with a value of 'NONE' and ends with a value of 'DISPERSE_LOCAL'\n 'Prev Step': 'NONE',\n // The category of the next step that will be executed. Always begins the bitonic sort with a value of 'FLIP_LOCAL' and ends with a value of 'NONE'\n 'Next Step': 'FLIP_LOCAL',\n // The maximum span of a swap operation in the sort's previous step.\n 'Prev Swap Span': 0,\n // The maximum span of a swap operation in the sort's upcoming step.\n 'Next Swap Span': 2,\n\n // ANIMATION LOOP AND FUNCTION SETTINGS\n // A flag that designates whether we will dispatch a workload this frame.\n executeStep: false,\n // A function that randomizes the values of each element.\n // When called, all relevant values within the settings object are reset to their defaults at the beginning of a sort with n elements.\n 'Randomize Values': () => {\n return;\n },\n // A function that manually executes a single step of the bitonic sort.\n 'Execute Sort Step': () => {\n return;\n },\n // A function that logs the values of each element as an array to the browser's console.\n 'Log Elements': () => {\n return;\n },\n // A function that automatically executes each step of the bitonic sort at an interval determined by 'Auto Sort Speed'\n 'Auto Sort': () => {\n return;\n },\n // The speed at which each step of the bitonic sort will be executed after 'Auto Sort' has been called.\n 'Auto Sort Speed': 50,\n\n // MISCELLANEOUS SETTINGS\n 'Display Mode': 'Elements',\n // An atomic value representing the total number of swap operations executed over the course of the bitonic sort.\n 'Total Swaps': 0,\n\n // TIMESTAMP SETTINGS\n // NOTE: Timestep values below all are calculated in terms of milliseconds rather than the nanoseconds a timestamp query set usually outputs.\n // Time taken to execute the previous step of the bitonic sort in milliseconds\n 'Step Time': '0ms',\n stepTime: 0,\n // Total taken to colletively execute each step of the complete bitonic sort, represented in milliseconds.\n 'Sort Time': '0ms',\n sortTime: 0,\n // Average time taken to complete a bitonic sort with the current combination of n 'Total Elements' and x 'Size Limit'\n 'Average Sort Time': '0ms',\n // A string to number map that maps a string representation of the current 'Total Elements' + 'Size Limit' configuration to a number\n // representing the total number of sorts that have been executed under that same configuration.\n configToCompleteSwapsMap: {\n '8192 256': {\n sorts: 0,\n time: 0,\n },\n },\n // Current key into configToCompleteSwapsMap\n configKey: '8192 256',\n };\n\n // Initialize initial elements array\n let elements = new Uint32Array(\n Array.from({ length: settings['Total Elements'] }, (_, i) => i)\n );\n\n // Initialize elementsBuffer and elementsStagingBuffer\n const elementsBufferSize =\n Float32Array.BYTES_PER_ELEMENT * totalElementOptions[0];\n // Initialize input, output, staging buffers\n const elementsInputBuffer = device.createBuffer({\n size: elementsBufferSize,\n usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,\n });\n const elementsOutputBuffer = device.createBuffer({\n size: elementsBufferSize,\n usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,\n });\n const elementsStagingBuffer = device.createBuffer({\n size: elementsBufferSize,\n usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,\n });\n\n // Initialize atomic swap buffer on GPU and CPU. Counts number of swaps actually performed by\n // compute shader (when value at index x is greater than value at index y)\n const atomicSwapsOutputBuffer = device.createBuffer({\n size: Uint32Array.BYTES_PER_ELEMENT,\n usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,\n });\n const atomicSwapsStagingBuffer = device.createBuffer({\n size: Uint32Array.BYTES_PER_ELEMENT,\n usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,\n });\n\n // Create uniform buffer for compute shader\n const computeUniformsBuffer = device.createBuffer({\n // width, height, blockHeight, algo\n size: Float32Array.BYTES_PER_ELEMENT * 4,\n usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,\n });\n\n const computeBGCluster = createBindGroupCluster(\n [0, 1, 2, 3],\n [\n GPUShaderStage.COMPUTE | GPUShaderStage.FRAGMENT,\n GPUShaderStage.COMPUTE,\n GPUShaderStage.COMPUTE | GPUShaderStage.FRAGMENT,\n GPUShaderStage.COMPUTE,\n ],\n ['buffer', 'buffer', 'buffer', 'buffer'],\n [\n { type: 'read-only-storage' },\n { type: 'storage' },\n { type: 'uniform' },\n { type: 'storage' },\n ],\n [\n [\n { buffer: elementsInputBuffer },\n { buffer: elementsOutputBuffer },\n { buffer: computeUniformsBuffer },\n { buffer: atomicSwapsOutputBuffer },\n ],\n ],\n 'BitonicSort',\n device\n );\n\n let computePipeline = device.createComputePipeline({\n layout: device.createPipelineLayout({\n bindGroupLayouts: [computeBGCluster.bindGroupLayout],\n }),\n compute: {\n module: device.createShaderModule({\n code: NaiveBitonicCompute(settings['Workgroup Size']),\n }),\n entryPoint: 'computeMain',\n },\n });\n\n // Simple pipeline that zeros out an atomic value at group 0 binding 3\n const atomicToZeroComputePipeline = device.createComputePipeline({\n layout: device.createPipelineLayout({\n bindGroupLayouts: [computeBGCluster.bindGroupLayout],\n }),\n compute: {\n module: device.createShaderModule({\n code: atomicToZero,\n }),\n entryPoint: 'atomicToZero',\n },\n });\n\n // Create bitonic debug renderer\n const renderPassDescriptor: GPURenderPassDescriptor = {\n colorAttachments: [\n {\n view: undefined, // Assigned later\n\n clearValue: { r: 0.1, g: 0.4, b: 0.5, a: 1.0 },\n loadOp: 'clear',\n storeOp: 'store',\n },\n ],\n };\n\n const bitonicDisplayRenderer = new BitonicDisplayRenderer(\n device,\n presentationFormat,\n renderPassDescriptor,\n computeBGCluster,\n 'BitonicDisplay'\n );\n\n const resetTimeInfo = () => {\n settings.stepTime = 0;\n settings.sortTime = 0;\n stepTimeController.setValue('0ms');\n sortTimeController.setValue(`0ms`);\n const nanCheck =\n settings.configToCompleteSwapsMap[settings.configKey].time /\n settings.configToCompleteSwapsMap[settings.configKey].sorts;\n const ast = nanCheck ? nanCheck : 0;\n averageSortTimeController.setValue(`${ast.toFixed(5)}ms`);\n };\n\n const resetExecutionInformation = () => {\n // The workgroup size is either elements / 2 or Size Limit\n workgroupSizeController.setValue(\n Math.min(settings['Total Elements'] / 2, settings['Size Limit'])\n );\n\n // Dispatch a workgroup for every (Size Limit * 2) elements\n const workgroupsPerStep =\n (settings['Total Elements'] - 1) / (settings['Size Limit'] * 2);\n\n workgroupsPerStepController.setValue(Math.ceil(workgroupsPerStep));\n\n // Reset step Index and number of steps based on elements size\n settings['Step Index'] = 0;\n settings['Total Steps'] = getNumSteps(settings['Total Elements']);\n currentStepController.setValue(\n `${settings['Step Index']} of ${settings['Total Steps']}`\n );\n\n // Get new width and height of screen display in cells\n const newCellWidth =\n Math.sqrt(settings['Total Elements']) % 2 === 0\n ? Math.floor(Math.sqrt(settings['Total Elements']))\n : Math.floor(Math.sqrt(settings['Total Elements'] / 2));\n const newCellHeight = settings['Total Elements'] / newCellWidth;\n settings['Grid Width'] = newCellWidth;\n settings['Grid Height'] = newCellHeight;\n gridDimensionsController.setValue(`${newCellWidth}x${newCellHeight}`);\n\n // Set prevStep to None (restart) and next step to FLIP\n prevStepController.setValue('NONE');\n nextStepController.setValue('FLIP_LOCAL');\n\n // Reset block heights\n prevBlockHeightController.setValue(0);\n nextBlockHeightController.setValue(2);\n\n // Reset Total Swaps by setting atomic value to 0\n const commandEncoder = device.createCommandEncoder();\n const computePassEncoder = commandEncoder.beginComputePass();\n computePassEncoder.setPipeline(atomicToZeroComputePipeline);\n computePassEncoder.setBindGroup(0, computeBGCluster.bindGroups[0]);\n computePassEncoder.dispatchWorkgroups(1);\n computePassEncoder.end();\n device.queue.submit([commandEncoder.finish()]);\n totalSwapsController.setValue(0);\n\n highestBlockHeight = 2;\n };\n\n const randomizeElementArray = () => {\n let currentIndex = elements.length;\n // While there are elements to shuffle\n while (currentIndex !== 0) {\n // Pick a remaining element\n const randomIndex = Math.floor(Math.random() * currentIndex);\n currentIndex -= 1;\n [elements[currentIndex], elements[randomIndex]] = [\n elements[randomIndex],\n elements[currentIndex],\n ];\n }\n };\n\n const resizeElementArray = () => {\n // Recreate elements array with new length\n elements = new Uint32Array(\n Array.from({ length: settings['Total Elements'] }, (_, i) => i)\n );\n\n resetExecutionInformation();\n\n // Create new shader invocation with workgroupSize that reflects number of invocations\n computePipeline = device.createComputePipeline({\n layout: device.createPipelineLayout({\n bindGroupLayouts: [computeBGCluster.bindGroupLayout],\n }),\n compute: {\n module: device.createShaderModule({\n code: NaiveBitonicCompute(\n Math.min(settings['Total Elements'] / 2, settings['Size Limit'])\n ),\n }),\n entryPoint: 'computeMain',\n },\n });\n // Randomize array elements\n randomizeElementArray();\n highestBlockHeight = 2;\n };\n\n randomizeElementArray();\n\n const setSwappedCell = () => {\n let swappedIndex: number;\n switch (settings['Next Step']) {\n case 'FLIP_LOCAL':\n case 'FLIP_GLOBAL':\n {\n const blockHeight = settings['Next Swap Span'];\n const p2 = Math.floor(settings['Hovered Cell'] / blockHeight) + 1;\n const p3 = settings['Hovered Cell'] % blockHeight;\n swappedIndex = blockHeight * p2 - p3 - 1;\n swappedCellController.setValue(swappedIndex);\n }\n break;\n case 'DISPERSE_LOCAL':\n {\n const blockHeight = settings['Next Swap Span'];\n const halfHeight = blockHeight / 2;\n swappedIndex =\n settings['Hovered Cell'] % blockHeight < halfHeight\n ? settings['Hovered Cell'] + halfHeight\n : settings['Hovered Cell'] - halfHeight;\n swappedCellController.setValue(swappedIndex);\n }\n break;\n case 'NONE': {\n swappedIndex = settings['Hovered Cell'];\n swappedCellController.setValue(swappedIndex);\n }\n default:\n {\n swappedIndex = settings['Hovered Cell'];\n swappedCellController.setValue(swappedIndex);\n }\n break;\n }\n };\n\n let autoSortIntervalID: ReturnType | null = null;\n const endSortInterval = () => {\n if (autoSortIntervalID !== null) {\n clearInterval(autoSortIntervalID);\n autoSortIntervalID = null;\n }\n };\n const startSortInterval = () => {\n const currentIntervalSpeed = settings['Auto Sort Speed'];\n autoSortIntervalID = setInterval(() => {\n if (settings['Next Step'] === 'NONE') {\n clearInterval(autoSortIntervalID);\n autoSortIntervalID = null;\n sizeLimitController.domElement.style.pointerEvents = 'auto';\n }\n if (settings['Auto Sort Speed'] !== currentIntervalSpeed) {\n clearInterval(autoSortIntervalID);\n autoSortIntervalID = null;\n startSortInterval();\n }\n settings.executeStep = true;\n setSwappedCell();\n }, settings['Auto Sort Speed']);\n };\n\n // At top level, information about resources used to execute the compute shader\n // i.e elements sorted, invocations per workgroup, and workgroups dispatched\n const computeResourcesFolder = gui.addFolder('Compute Resources');\n computeResourcesFolder\n .add(settings, 'Total Elements', totalElementOptions)\n .onChange(() => {\n endSortInterval();\n resizeElementArray();\n sizeLimitController.domElement.style.pointerEvents = 'auto';\n // Create new config key for current element + size limit configuration\n const currConfigKey = `${settings['Total Elements']} ${settings['Size Limit']}`;\n // If configKey doesn't exist in the map, create it.\n if (!settings.configToCompleteSwapsMap[currConfigKey]) {\n settings.configToCompleteSwapsMap[currConfigKey] = {\n sorts: 0,\n time: 0,\n };\n }\n settings.configKey = currConfigKey;\n resetTimeInfo();\n });\n const sizeLimitController = computeResourcesFolder\n .add(settings, 'Size Limit', sizeLimitOptions)\n .onChange(() => {\n // Change total workgroups per step and size of a workgroup based on arbitrary constraint\n // imposed by size limit.\n const constraint = Math.min(\n settings['Total Elements'] / 2,\n settings['Size Limit']\n );\n const workgroupsPerStep =\n (settings['Total Elements'] - 1) / (settings['Size Limit'] * 2);\n workgroupSizeController.setValue(constraint);\n workgroupsPerStepController.setValue(Math.ceil(workgroupsPerStep));\n // Apply new compute resources values to the sort's compute pipeline\n computePipeline = computePipeline = device.createComputePipeline({\n layout: device.createPipelineLayout({\n bindGroupLayouts: [computeBGCluster.bindGroupLayout],\n }),\n compute: {\n module: device.createShaderModule({\n code: NaiveBitonicCompute(\n Math.min(settings['Total Elements'] / 2, settings['Size Limit'])\n ),\n }),\n entryPoint: 'computeMain',\n },\n });\n // Create new config key for current element + size limit configuration\n const currConfigKey = `${settings['Total Elements']} ${settings['Size Limit']}`;\n // If configKey doesn't exist in the map, create it.\n if (!settings.configToCompleteSwapsMap[currConfigKey]) {\n settings.configToCompleteSwapsMap[currConfigKey] = {\n sorts: 0,\n time: 0,\n };\n }\n settings.configKey = currConfigKey;\n resetTimeInfo();\n });\n const workgroupSizeController = computeResourcesFolder.add(\n settings,\n 'Workgroup Size'\n );\n const workgroupsPerStepController = computeResourcesFolder.add(\n settings,\n 'Workgroups Per Step'\n );\n\n computeResourcesFolder.open();\n\n // Folder with functions that control the execution of the sort\n const controlFolder = gui.addFolder('Sort Controls');\n controlFolder.add(settings, 'Execute Sort Step').onChange(() => {\n // Size Limit locked upon sort\n sizeLimitController.domElement.style.pointerEvents = 'none';\n endSortInterval();\n settings.executeStep = true;\n });\n controlFolder.add(settings, 'Randomize Values').onChange(() => {\n endSortInterval();\n randomizeElementArray();\n resetExecutionInformation();\n resetTimeInfo();\n // Unlock workgroup size limit controller since sort has stopped\n sizeLimitController.domElement.style.pointerEvents = 'auto';\n });\n controlFolder\n .add(settings, 'Log Elements')\n .onChange(() => console.log(elements));\n controlFolder.add(settings, 'Auto Sort').onChange(() => {\n // Invocation Limit locked upon sort\n sizeLimitController.domElement.style.pointerEvents = 'none';\n startSortInterval();\n });\n controlFolder.add(settings, 'Auto Sort Speed', 50, 1000).step(50);\n controlFolder.open();\n\n // Information about grid display\n const gridFolder = gui.addFolder('Grid Information');\n gridFolder.add(settings, 'Display Mode', ['Elements', 'Swap Highlight']);\n const gridDimensionsController = gridFolder.add(\n settings,\n 'Grid Dimensions'\n );\n const hoveredCellController = gridFolder\n .add(settings, 'Hovered Cell')\n .onChange(setSwappedCell);\n const swappedCellController = gridFolder.add(settings, 'Swapped Cell');\n\n // Additional Information about the execution state of the sort\n const executionInformationFolder = gui.addFolder('Execution Information');\n const currentStepController = executionInformationFolder.add(\n settings,\n 'Current Step'\n );\n const prevStepController = executionInformationFolder.add(\n settings,\n 'Prev Step'\n );\n const nextStepController = executionInformationFolder.add(\n settings,\n 'Next Step'\n );\n const totalSwapsController = executionInformationFolder.add(\n settings,\n 'Total Swaps'\n );\n const prevBlockHeightController = executionInformationFolder.add(\n settings,\n 'Prev Swap Span'\n );\n const nextBlockHeightController = executionInformationFolder.add(\n settings,\n 'Next Swap Span'\n );\n\n // Timestamp information for Chrome 121+ or other compatible browsers\n const timestampFolder = gui.addFolder('Timestamp Info (Chrome 121+)');\n const stepTimeController = timestampFolder.add(settings, 'Step Time');\n const sortTimeController = timestampFolder.add(settings, 'Sort Time');\n const averageSortTimeController = timestampFolder.add(\n settings,\n 'Average Sort Time'\n );\n\n // Adjust styles of Function List Elements within GUI\n const liFunctionElements = document.getElementsByClassName('cr function');\n for (let i = 0; i < liFunctionElements.length; i++) {\n (liFunctionElements[i].children[0] as HTMLElement).style.display = 'flex';\n (liFunctionElements[i].children[0] as HTMLElement).style.justifyContent =\n 'center';\n (\n liFunctionElements[i].children[0].children[1] as HTMLElement\n ).style.position = 'absolute';\n }\n\n // Mouse listener that determines values of hoveredCell and swappedCell\n canvas.addEventListener('mousemove', (event) => {\n const currWidth = canvas.getBoundingClientRect().width;\n const currHeight = canvas.getBoundingClientRect().height;\n const cellSize: [number, number] = [\n currWidth / settings['Grid Width'],\n currHeight / settings['Grid Height'],\n ];\n const xIndex = Math.floor(event.offsetX / cellSize[0]);\n const yIndex =\n settings['Grid Height'] - 1 - Math.floor(event.offsetY / cellSize[1]);\n hoveredCellController.setValue(yIndex * settings['Grid Width'] + xIndex);\n settings['Hovered Cell'] = yIndex * settings['Grid Width'] + xIndex;\n });\n\n // Deactivate interaction with select GUI elements\n sizeLimitController.domElement.style.pointerEvents = 'none';\n workgroupsPerStepController.domElement.style.pointerEvents = 'none';\n hoveredCellController.domElement.style.pointerEvents = 'none';\n swappedCellController.domElement.style.pointerEvents = 'none';\n currentStepController.domElement.style.pointerEvents = 'none';\n prevStepController.domElement.style.pointerEvents = 'none';\n prevBlockHeightController.domElement.style.pointerEvents = 'none';\n nextStepController.domElement.style.pointerEvents = 'none';\n nextBlockHeightController.domElement.style.pointerEvents = 'none';\n workgroupSizeController.domElement.style.pointerEvents = 'none';\n gridDimensionsController.domElement.style.pointerEvents = 'none';\n totalSwapsController.domElement.style.pointerEvents = 'none';\n stepTimeController.domElement.style.pointerEvents = 'none';\n sortTimeController.domElement.style.pointerEvents = 'none';\n averageSortTimeController.domElement.style.pointerEvents = 'none';\n gui.width = 325;\n\n let highestBlockHeight = 2;\n\n startSortInterval();\n\n async function frame() {\n if (!pageState.active) return;\n\n // Write elements buffer\n device.queue.writeBuffer(\n elementsInputBuffer,\n 0,\n elements.buffer,\n elements.byteOffset,\n elements.byteLength\n );\n\n const dims = new Float32Array([\n settings['Grid Width'],\n settings['Grid Height'],\n ]);\n const stepDetails = new Uint32Array([\n StepEnum[settings['Next Step']],\n settings['Next Swap Span'],\n ]);\n device.queue.writeBuffer(\n computeUniformsBuffer,\n 0,\n dims.buffer,\n dims.byteOffset,\n dims.byteLength\n );\n\n device.queue.writeBuffer(computeUniformsBuffer, 8, stepDetails);\n\n renderPassDescriptor.colorAttachments[0].view = context\n .getCurrentTexture()\n .createView();\n\n const commandEncoder = device.createCommandEncoder();\n bitonicDisplayRenderer.startRun(commandEncoder, {\n highlight: settings['Display Mode'] === 'Elements' ? 0 : 1,\n });\n if (\n settings.executeStep &&\n highestBlockHeight < settings['Total Elements'] * 2\n ) {\n let computePassEncoder: GPUComputePassEncoder;\n if (timestampQueryAvailable) {\n computePassEncoder = commandEncoder.beginComputePass({\n timestampWrites: {\n querySet,\n beginningOfPassWriteIndex: 0,\n endOfPassWriteIndex: 1,\n },\n });\n } else {\n computePassEncoder = commandEncoder.beginComputePass();\n }\n computePassEncoder.setPipeline(computePipeline);\n computePassEncoder.setBindGroup(0, computeBGCluster.bindGroups[0]);\n computePassEncoder.dispatchWorkgroups(settings['Workgroups Per Step']);\n computePassEncoder.end();\n // Resolve time passed in between beginning and end of computePass\n if (timestampQueryAvailable) {\n commandEncoder.resolveQuerySet(\n querySet,\n 0,\n 2,\n timestampQueryResolveBuffer,\n 0\n );\n commandEncoder.copyBufferToBuffer(\n timestampQueryResolveBuffer,\n 0,\n timestampQueryResultBuffer,\n 0,\n 2 * BigInt64Array.BYTES_PER_ELEMENT\n );\n }\n settings['Step Index'] = settings['Step Index'] + 1;\n currentStepController.setValue(\n `${settings['Step Index']} of ${settings['Total Steps']}`\n );\n prevStepController.setValue(settings['Next Step']);\n prevBlockHeightController.setValue(settings['Next Swap Span']);\n nextBlockHeightController.setValue(settings['Next Swap Span'] / 2);\n // Each cycle of a bitonic sort contains a flip operation followed by multiple disperse operations\n // Next Swap Span will equal one when the sort needs to begin a new cycle of flip and disperse operations\n if (settings['Next Swap Span'] === 1) {\n // The next cycle's flip operation will have a maximum swap span 2 times that of the previous cycle\n highestBlockHeight *= 2;\n if (highestBlockHeight === settings['Total Elements'] * 2) {\n // The next cycle's maximum swap span exceeds the total number of elements. Therefore, the sort is over.\n // Accordingly, there will be no next step.\n nextStepController.setValue('NONE');\n // And if there is no next step, then there are no swaps, and no block range within which two elements are swapped.\n nextBlockHeightController.setValue(0);\n // Finally, with our sort completed, we can increment the number of total completed sorts executed with n 'Total Elements'\n // and x 'Size Limit', which will allow us to calculate the average time of all sorts executed with this specific\n // configuration of compute resources\n settings.configToCompleteSwapsMap[settings.configKey].sorts += 1;\n } else if (highestBlockHeight > settings['Workgroup Size'] * 2) {\n // The next cycle's maximum swap span exceeds the range of a single workgroup, so our next flip will operate on global indices.\n nextStepController.setValue('FLIP_GLOBAL');\n nextBlockHeightController.setValue(highestBlockHeight);\n } else {\n // The next cycle's maximum swap span can be executed on a range of indices local to the workgroup.\n nextStepController.setValue('FLIP_LOCAL');\n nextBlockHeightController.setValue(highestBlockHeight);\n }\n } else {\n // Otherwise, execute the next disperse operation\n settings['Next Swap Span'] > settings['Workgroup Size'] * 2\n ? nextStepController.setValue('DISPERSE_GLOBAL')\n : nextStepController.setValue('DISPERSE_LOCAL');\n }\n\n // Copy GPU accessible buffers to CPU accessible buffers\n commandEncoder.copyBufferToBuffer(\n elementsOutputBuffer,\n 0,\n elementsStagingBuffer,\n 0,\n elementsBufferSize\n );\n\n commandEncoder.copyBufferToBuffer(\n atomicSwapsOutputBuffer,\n 0,\n atomicSwapsStagingBuffer,\n 0,\n Uint32Array.BYTES_PER_ELEMENT\n );\n }\n device.queue.submit([commandEncoder.finish()]);\n\n if (\n settings.executeStep &&\n highestBlockHeight < settings['Total Elements'] * 4\n ) {\n // Copy GPU element data to CPU\n await elementsStagingBuffer.mapAsync(\n GPUMapMode.READ,\n 0,\n elementsBufferSize\n );\n const copyElementsBuffer = elementsStagingBuffer.getMappedRange(\n 0,\n elementsBufferSize\n );\n // Copy atomic swaps data to CPU\n await atomicSwapsStagingBuffer.mapAsync(\n GPUMapMode.READ,\n 0,\n Uint32Array.BYTES_PER_ELEMENT\n );\n const copySwapsBuffer = atomicSwapsStagingBuffer.getMappedRange(\n 0,\n Uint32Array.BYTES_PER_ELEMENT\n );\n const elementsData = copyElementsBuffer.slice(\n 0,\n Uint32Array.BYTES_PER_ELEMENT * settings['Total Elements']\n );\n const swapsData = copySwapsBuffer.slice(\n 0,\n Uint32Array.BYTES_PER_ELEMENT\n );\n // Extract data\n const elementsOutput = new Uint32Array(elementsData);\n totalSwapsController.setValue(new Uint32Array(swapsData)[0]);\n elementsStagingBuffer.unmap();\n atomicSwapsStagingBuffer.unmap();\n // Elements output becomes elements input, swap accumulate\n elements = elementsOutput;\n setSwappedCell();\n\n // Handle timestamp query stuff\n if (timestampQueryAvailable) {\n // Copy timestamp query result buffer data to CPU\n await timestampQueryResultBuffer.mapAsync(\n GPUMapMode.READ,\n 0,\n 2 * BigInt64Array.BYTES_PER_ELEMENT\n );\n const copyTimestampResult = new BigInt64Array(\n timestampQueryResultBuffer.getMappedRange()\n );\n // Calculate new step, sort, and average sort times\n const newStepTime =\n Number(copyTimestampResult[1] - copyTimestampResult[0]) / 1000000;\n const newSortTime = settings.sortTime + newStepTime;\n // Apply calculated times to settings object as both number and 'ms' appended string\n settings.stepTime = newStepTime;\n settings.sortTime = newSortTime;\n stepTimeController.setValue(`${newStepTime.toFixed(5)}ms`);\n sortTimeController.setValue(`${newSortTime.toFixed(5)}ms`);\n // Calculate new average sort upon end of final execution step of a full bitonic sort.\n if (highestBlockHeight === settings['Total Elements'] * 2) {\n // Lock off access to this larger if block..not best architected solution but eh\n highestBlockHeight *= 2;\n settings.configToCompleteSwapsMap[settings.configKey].time +=\n newSortTime;\n const averageSortTime =\n settings.configToCompleteSwapsMap[settings.configKey].time /\n settings.configToCompleteSwapsMap[settings.configKey].sorts;\n averageSortTimeController.setValue(\n `${averageSortTime.toFixed(5)}ms`\n );\n }\n timestampQueryResultBuffer.unmap();\n // Get correct range of data from CPU copy of GPU Data\n }\n }\n settings.executeStep = false;\n requestAnimationFrame(frame);\n }\n requestAnimationFrame(frame);\n }\n).then((resultInit) => (init = resultInit));\n\nconst bitonicSortExample: () => JSX.Element = () =>\n makeSample({\n name: 'Bitonic Sort',\n description:\n \"A naive bitonic sort algorithm executed on the GPU, based on tgfrerer's implementation at poniesandlight.co.uk/reflect/bitonic_merge_sort/. Each dispatch of the bitonic sort shader dispatches a workgroup containing elements/2 invocations. The GUI's Execution Information folder contains information about the sort's current state. The visualizer displays the sort's results as colored cells sorted from brightest to darkest.\",\n init,\n gui: true,\n sources: [\n {\n name: __filename.substring(__dirname.length + 1),\n contents: __SOURCE__,\n },\n BitonicDisplayRenderer.sourceInfo,\n {\n name: '../../../shaders/fullscreenTexturedQuad.vert.wgsl',\n contents: fullscreenTexturedQuad,\n },\n {\n name: './bitonicDisplay.frag.wgsl',\n contents: bitonicDisplay,\n },\n {\n name: './bitonicCompute.ts',\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n contents: require('!!raw-loader!./bitonicCompute.ts').default,\n },\n {\n name: './atomicToZero.wgsl',\n contents: atomicToZero,\n },\n ],\n filename: __filename,\n });\n\nexport default bitonicSortExample;\n"},d.sourceInfo,{name:"../../../shaders/fullscreenTexturedQuad.vert.wgsl",contents:s.Z},{name:"./bitonicDisplay.frag.wgsl",contents:p},{name:"./bitonicCompute.ts",contents:t(6502).Z},{name:"./atomicToZero.wgsl",contents:f}],filename:g});var E=S},9147:function(e){e.exports={canvasContainer:"SampleLayout_canvasContainer__zRR_l",sourceFileNav:"SampleLayout_sourceFileNav__ml48P",sourceFileScrollContainer:"SampleLayout_sourceFileScrollContainer__LsNEm",sourceFileContainer:"SampleLayout_sourceFileContainer__3s84x"}},6502:function(e,n){"use strict";n.Z="export const computeArgKeys = ['width', 'height', 'algo', 'blockHeight'];\n\nexport const NaiveBitonicCompute = (workgroupSize: number) => {\n if (workgroupSize % 2 !== 0 || workgroupSize > 256) {\n workgroupSize = 256;\n }\n // Ensure that workgroupSize is half the number of elements\n return `\n\nstruct Uniforms {\n width: f32,\n height: f32,\n algo: u32,\n blockHeight: u32,\n}\n\n// Create local workgroup data that can contain all elements\nvar local_data: array;\n\n// Define groups (functions refer to this data)\n@group(0) @binding(0) var input_data: array;\n@group(0) @binding(1) var output_data: array;\n@group(0) @binding(2) var uniforms: Uniforms;\n@group(0) @binding(3) var counter: atomic;\n\n// Compare and swap values in local_data\nfn local_compare_and_swap(idx_before: u32, idx_after: u32) {\n //idx_before should always be < idx_after\n if (local_data[idx_after] < local_data[idx_before]) {\n atomicAdd(&counter, 1);\n var temp: u32 = local_data[idx_before];\n local_data[idx_before] = local_data[idx_after];\n local_data[idx_after] = temp;\n }\n return;\n}\n\n// invoke_id goes from 0 to workgroupSize\nfn get_flip_indices(invoke_id: u32, block_height: u32) -> vec2 {\n // Caculate index offset (i.e move indices into correct block)\n let block_offset: u32 = ((2 * invoke_id) / block_height) * block_height;\n let half_height = block_height / 2;\n // Calculate index spacing\n var idx: vec2 = vec2(\n invoke_id % half_height, block_height - (invoke_id % half_height) - 1,\n );\n idx.x += block_offset;\n idx.y += block_offset;\n return idx;\n}\n\nfn get_disperse_indices(invoke_id: u32, block_height: u32) -> vec2 {\n var block_offset: u32 = ((2 * invoke_id) / block_height) * block_height;\n let half_height = block_height / 2;\n var idx: vec2 = vec2(\n invoke_id % half_height, (invoke_id % half_height) + half_height\n );\n idx.x += block_offset;\n idx.y += block_offset;\n return idx;\n}\n\nfn global_compare_and_swap(idx_before: u32, idx_after: u32) {\n if (input_data[idx_after] < input_data[idx_before]) {\n output_data[idx_before] = input_data[idx_after];\n output_data[idx_after] = input_data[idx_before];\n } \n}\n\n// Constants/enum\nconst ALGO_NONE = 0;\nconst ALGO_LOCAL_FLIP = 1;\nconst ALGO_LOCAL_DISPERSE = 2;\nconst ALGO_GLOBAL_FLIP = 3;\n\n// Our compute shader will execute specified # of invocations or elements / 2 invocations\n@compute @workgroup_size(${workgroupSize}, 1, 1)\nfn computeMain(\n @builtin(global_invocation_id) global_id: vec3,\n @builtin(local_invocation_id) local_id: vec3,\n @builtin(workgroup_id) workgroup_id: vec3,\n) {\n\n let offset = ${workgroupSize} * 2 * workgroup_id.x;\n // If we will perform a local swap, then populate the local data\n if (uniforms.algo <= 2) {\n // Assign range of input_data to local_data.\n // Range cannot exceed maxWorkgroupsX * 2\n // Each invocation will populate the workgroup data... (1 invocation for every 2 elements)\n local_data[local_id.x * 2] = input_data[offset + local_id.x * 2];\n local_data[local_id.x * 2 + 1] = input_data[offset + local_id.x * 2 + 1];\n }\n\n //...and wait for each other to finish their own bit of data population.\n workgroupBarrier();\n\n switch uniforms.algo {\n case 1: { // Local Flip\n let idx = get_flip_indices(local_id.x, uniforms.blockHeight);\n local_compare_and_swap(idx.x, idx.y);\n } \n case 2: { // Local Disperse\n let idx = get_disperse_indices(local_id.x, uniforms.blockHeight);\n local_compare_and_swap(idx.x, idx.y);\n } \n case 3: { // Global Flip\n let idx = get_flip_indices(global_id.x, uniforms.blockHeight);\n global_compare_and_swap(idx.x, idx.y);\n }\n case 4: { \n let idx = get_disperse_indices(global_id.x, uniforms.blockHeight);\n global_compare_and_swap(idx.x, idx.y);\n }\n default: { \n \n }\n }\n\n // Ensure that all invocations have swapped their own regions of data\n workgroupBarrier();\n\n if (uniforms.algo <= ALGO_LOCAL_DISPERSE) {\n //Repopulate global data with local data\n output_data[offset + local_id.x * 2] = local_data[local_id.x * 2];\n output_data[offset + local_id.x * 2 + 1] = local_data[local_id.x * 2 + 1];\n }\n\n}`;\n};\n"},134:function(e,n){"use strict";n.Z="@group(0) @binding(0) var mySampler : sampler;\n@group(0) @binding(1) var myTexture : texture_2d;\n\nstruct VertexOutput {\n @builtin(position) Position : vec4,\n @location(0) fragUV : vec2,\n}\n\n@vertex\nfn vert_main(@builtin(vertex_index) VertexIndex : u32) -> VertexOutput {\n const pos = array(\n vec2( 1.0, 1.0),\n vec2( 1.0, -1.0),\n vec2(-1.0, -1.0),\n vec2( 1.0, 1.0),\n vec2(-1.0, -1.0),\n vec2(-1.0, 1.0),\n );\n\n const uv = array(\n vec2(1.0, 0.0),\n vec2(1.0, 1.0),\n vec2(0.0, 1.0),\n vec2(1.0, 0.0),\n vec2(0.0, 1.0),\n vec2(0.0, 0.0),\n );\n\n var output : VertexOutput;\n output.Position = vec4(pos[VertexIndex], 0.0, 1.0);\n output.fragUV = uv[VertexIndex];\n return output;\n}\n\n@fragment\nfn frag_main(@location(0) fragUV : vec2) -> @location(0) vec4 {\n return textureSample(myTexture, mySampler, fragUV);\n}\n"}}]); \ No newline at end of file diff --git a/_next/static/chunks/pages/_app-259e1e4386d5ed75.js b/_next/static/chunks/pages/_app-fa965a3347cc3b1e.js similarity index 83% rename from _next/static/chunks/pages/_app-259e1e4386d5ed75.js rename to _next/static/chunks/pages/_app-fa965a3347cc3b1e.js index b507fc12..b05bf671 100644 --- a/_next/static/chunks/pages/_app-259e1e4386d5ed75.js +++ b/_next/static/chunks/pages/_app-fa965a3347cc3b1e.js @@ -1 +1 @@ -(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[888],{3454:function(e,t,n){"use strict";var a,r;e.exports=(null==(a=n.g.process)?void 0:a.env)&&"object"==typeof(null==(r=n.g.process)?void 0:r.env)?n.g.process:n(7663)},6840:function(e,t,n){(window.__NEXT_P=window.__NEXT_P||[]).push(["/_app",function(){return n(6505)}])},227:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getDomainLocale=function(e,t,n,a){return!1},("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},1551:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var a=n(2648).Z,r=n(7273).Z,l=a(n(7294)),o=n(1003),i=n(4465),s=n(2692),u=n(8245),d=n(9246),c=n(227),f=n(3468);let p={};function h(e,t,n,a){if(!e||!o.isLocalURL(t))return;Promise.resolve(e.prefetch(t,n,a)).catch(e=>{});let r=a&&void 0!==a.locale?a.locale:e&&e.locale;p[t+"%"+n+(r?"%"+r:"")]=!0}let b=l.default.forwardRef(function(e,t){let n,a;let{href:b,as:m,children:_,prefetch:y,passHref:v,replace:g,shallow:x,scroll:w,locale:k,onClick:P,onMouseEnter:j,onTouchStart:C,legacyBehavior:L=!0!==Boolean(!0)}=e,T=r(e,["href","as","children","prefetch","passHref","replace","shallow","scroll","locale","onClick","onMouseEnter","onTouchStart","legacyBehavior"]);n=_,L&&("string"==typeof n||"number"==typeof n)&&(n=l.default.createElement("a",null,n));let M=!1!==y,G=l.default.useContext(s.RouterContext),O=l.default.useContext(u.AppRouterContext);O&&(G=O);let{href:E,as:R}=l.default.useMemo(()=>{let[e,t]=o.resolveHref(G,b,!0);return{href:e,as:m?o.resolveHref(G,m):t||e}},[G,b,m]),S=l.default.useRef(E),A=l.default.useRef(R);L&&(a=l.default.Children.only(n));let N=L?a&&"object"==typeof a&&a.ref:t,[D,I,U]=d.useIntersection({rootMargin:"200px"}),B=l.default.useCallback(e=>{(A.current!==R||S.current!==E)&&(U(),A.current=R,S.current=E),D(e),N&&("function"==typeof N?N(e):"object"==typeof N&&(N.current=e))},[R,N,E,U,D]);l.default.useEffect(()=>{let e=I&&M&&o.isLocalURL(E),t=void 0!==k?k:G&&G.locale,n=p[E+"%"+R+(t?"%"+t:"")];e&&!n&&h(G,E,R,{locale:t})},[R,E,I,k,M,G]);let W={ref:B,onClick(e){L||"function"!=typeof P||P(e),L&&a.props&&"function"==typeof a.props.onClick&&a.props.onClick(e),e.defaultPrevented||function(e,t,n,a,r,i,s,u,d,c){let{nodeName:f}=e.currentTarget,p="A"===f.toUpperCase();if(p&&(function(e){let{target:t}=e.currentTarget;return t&&"_self"!==t||e.metaKey||e.ctrlKey||e.shiftKey||e.altKey||e.nativeEvent&&2===e.nativeEvent.which}(e)||!o.isLocalURL(n)))return;e.preventDefault();let h=()=>{"beforePopState"in t?t[r?"replace":"push"](n,a,{shallow:i,locale:u,scroll:s}):t[r?"replace":"push"](a||n,{forceOptimisticNavigation:!c})};d?l.default.startTransition(h):h()}(e,G,E,R,g,x,w,k,Boolean(O),M)},onMouseEnter(e){L||"function"!=typeof j||j(e),L&&a.props&&"function"==typeof a.props.onMouseEnter&&a.props.onMouseEnter(e),!(!M&&O)&&o.isLocalURL(E)&&h(G,E,R,{priority:!0})},onTouchStart(e){L||"function"!=typeof C||C(e),L&&a.props&&"function"==typeof a.props.onTouchStart&&a.props.onTouchStart(e),!(!M&&O)&&o.isLocalURL(E)&&h(G,E,R,{priority:!0})}};if(!L||v||"a"===a.type&&!("href"in a.props)){let Z=void 0!==k?k:G&&G.locale,H=G&&G.isLocaleDomain&&c.getDomainLocale(R,Z,G.locales,G.domainLocales);W.href=H||f.addBasePath(i.addLocale(R,Z,G&&G.defaultLocale))}return L?l.default.cloneElement(a,W):l.default.createElement("a",Object.assign({},T,W),n)});t.default=b,("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},9246:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.useIntersection=function(e){let{rootRef:t,rootMargin:n,disabled:s}=e,u=s||!l,[d,c]=a.useState(!1),[f,p]=a.useState(null);a.useEffect(()=>{if(l){if(!u&&!d&&f&&f.tagName){let e=function(e,t,n){let{id:a,observer:r,elements:l}=function(e){let t;let n={root:e.root||null,margin:e.rootMargin||""},a=i.find(e=>e.root===n.root&&e.margin===n.margin);if(a&&(t=o.get(a)))return t;let r=new Map,l=new IntersectionObserver(e=>{e.forEach(e=>{let t=r.get(e.target),n=e.isIntersecting||e.intersectionRatio>0;t&&n&&t(n)})},e);return t={id:n,observer:l,elements:r},i.push(n),o.set(n,t),t}(n);return l.set(e,t),r.observe(e),function(){if(l.delete(e),r.unobserve(e),0===l.size){r.disconnect(),o.delete(a);let t=i.findIndex(e=>e.root===a.root&&e.margin===a.margin);t>-1&&i.splice(t,1)}}}(f,e=>e&&c(e),{root:null==t?void 0:t.current,rootMargin:n});return e}}else if(!d){let a=r.requestIdleCallback(()=>c(!0));return()=>r.cancelIdleCallback(a)}},[f,u,n,t,d]);let h=a.useCallback(()=>{c(!1)},[]);return[p,d,h]};var a=n(7294),r=n(4686);let l="function"==typeof IntersectionObserver,o=new Map,i=[];("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},8245:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.TemplateContext=t.GlobalLayoutRouterContext=t.LayoutRouterContext=t.AppRouterContext=void 0;var a=(0,n(2648).Z)(n(7294));let r=a.default.createContext(null);t.AppRouterContext=r;let l=a.default.createContext(null);t.LayoutRouterContext=l;let o=a.default.createContext(null);t.GlobalLayoutRouterContext=o;let i=a.default.createContext(null);t.TemplateContext=i},7645:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){let n=l.default,r=(null==t?void 0:t.suspense)?{}:{loading(e){let{error:t,isLoading:n,pastDelay:a}=e;return null}};if(e instanceof Promise?r.loader=()=>e:"function"==typeof e?r.loader=e:"object"==typeof e&&(r=a({},r,e)),(r=a({},r,t)).suspense&&(delete r.ssr,delete r.loading),r.loadableGenerated&&delete(r=a({},r,r.loadableGenerated)).loadableGenerated,"boolean"==typeof r.ssr&&!r.suspense){if(!r.ssr)return delete r.ssr,o(n,r);delete r.ssr}return n(r)},t.noSSR=o;var a=n(6495).Z,r=n(2648).Z,l=(r(n(7294)),r(n(4588)));function o(e,t){return delete t.webpack,delete t.modules,e(t)}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3644:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.LoadableContext=void 0;var a=(0,n(2648).Z)(n(7294));let r=a.default.createContext(null);t.LoadableContext=r},4588:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var a=n(6495).Z,r=(0,n(2648).Z)(n(7294)),l=n(3644);let{useSyncExternalStore:o}=n(7294),i=[],s=[],u=!1;function d(e){let t=e(),n={loading:!0,loaded:null,error:null};return n.promise=t.then(e=>(n.loading=!1,n.loaded=e,e)).catch(e=>{throw n.loading=!1,n.error=e,e}),n}class c{promise(){return this._res.promise}retry(){this._clearTimeouts(),this._res=this._loadFn(this._opts.loader),this._state={pastDelay:!1,timedOut:!1};let{_res:e,_opts:t}=this;e.loading&&("number"==typeof t.delay&&(0===t.delay?this._state.pastDelay=!0:this._delay=setTimeout(()=>{this._update({pastDelay:!0})},t.delay)),"number"==typeof t.timeout&&(this._timeout=setTimeout(()=>{this._update({timedOut:!0})},t.timeout))),this._res.promise.then(()=>{this._update({}),this._clearTimeouts()}).catch(e=>{this._update({}),this._clearTimeouts()}),this._update({})}_update(e){this._state=a({},this._state,{error:this._res.error,loaded:this._res.loaded,loading:this._res.loading},e),this._callbacks.forEach(e=>e())}_clearTimeouts(){clearTimeout(this._delay),clearTimeout(this._timeout)}getCurrentValue(){return this._state}subscribe(e){return this._callbacks.add(e),()=>{this._callbacks.delete(e)}}constructor(e,t){this._loadFn=e,this._opts=t,this._callbacks=new Set,this._delay=null,this._timeout=null,this.retry()}}function f(e){return function(e,t){let n=Object.assign({loader:null,loading:null,delay:200,timeout:null,webpack:null,modules:null,suspense:!1},t);n.suspense&&(n.lazy=r.default.lazy(n.loader));let i=null;function d(){if(!i){let t=new c(e,n);i={getCurrentValue:t.getCurrentValue.bind(t),subscribe:t.subscribe.bind(t),retry:t.retry.bind(t),promise:t.promise.bind(t)}}return i.promise()}if(!u){let f=n.webpack?n.webpack():n.modules;f&&s.push(e=>{for(let t of f)if(-1!==e.indexOf(t))return d()})}function p(){d();let e=r.default.useContext(l.LoadableContext);e&&Array.isArray(n.modules)&&n.modules.forEach(t=>{e(t)})}let h=n.suspense?function(e,t){return p(),r.default.createElement(n.lazy,a({},e,{ref:t}))}:function(e,t){p();let a=o(i.subscribe,i.getCurrentValue,i.getCurrentValue);return r.default.useImperativeHandle(t,()=>({retry:i.retry}),[]),r.default.useMemo(()=>{var t;return a.loading||a.error?r.default.createElement(n.loading,{isLoading:a.loading,pastDelay:a.pastDelay,timedOut:a.timedOut,error:a.error,retry:i.retry}):a.loaded?r.default.createElement((t=a.loaded)&&t.__esModule?t.default:t,e):null},[e,a])};return h.preload=()=>d(),h.displayName="LoadableComponent",r.default.forwardRef(h)}(d,e)}function p(e,t){let n=[];for(;e.length;){let a=e.pop();n.push(a(t))}return Promise.all(n).then(()=>{if(e.length)return p(e,t)})}f.preloadAll=()=>new Promise((e,t)=>{p(i).then(e,t)}),f.preloadReady=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];return new Promise(t=>{let n=()=>(u=!0,t());p(s,e).then(n,n)})},window.__NEXT_PRELOADREADY=f.preloadReady,t.default=f},6505:function(e,t,n){"use strict";n.r(t);var a=n(5893),r=n(9008),l=n.n(r),o=n(1664),i=n.n(o),s=n(1163),u=n(7294);n(4112);var d=n(6988),c=n.n(d),f=n(1958),p=n(3454);let h="WebGPU Samples",b=e=>{let{Component:t,pageProps:n}=e,r=(0,s.useRouter)(),o=Object.keys(f.pages),[d,b]=(0,u.useState)(!1),m=(0,u.useMemo)(()=>(0,u.memo)(t),[t]),_=r.asPath.match(/(\?wgsl=[01])#(\S+)/);if(_){let y=_[2];return r.replace("/samples/".concat(y)),(0,a.jsx)(a.Fragment,{})}return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsxs)(l(),{children:[(0,a.jsx)("title",{children:h}),(0,a.jsx)("meta",{name:"description",content:"The WebGPU Samples are a set of samples demonstrating the use of the WebGPU API."}),(0,a.jsx)("meta",{name:"viewport",content:"width=device-width, initial-scale=1, shrink-to-fit=no"})]}),(0,a.jsxs)("div",{className:c().wrapper,children:[(0,a.jsxs)("nav",{className:"".concat(c().panel," ").concat(c().container),"data-expanded":d,children:[(0,a.jsxs)("h1",{children:[(0,a.jsx)(i(),{href:"/",children:h}),(0,a.jsx)("div",{className:c().expand,onClick(){b(!d)}})]}),(0,a.jsxs)("div",{className:c().panelContents,children:[(0,a.jsx)("a",{href:"https://github.com/".concat("webgpu/webgpu-samples"),children:"Github"}),(0,a.jsx)("hr",{}),(0,a.jsx)("ul",{className:c().exampleList,children:o.map(e=>{let t="/samples/[slug]"===r.pathname&&r.query.slug===e?c().selected:void 0;return(0,a.jsx)("li",{className:t,onMouseOver(){f.pages[e].render.preload()},children:(0,a.jsx)(i(),{href:"/samples/".concat(e),onClick(){b(!1)},children:e})},e)})}),(0,a.jsx)("hr",{}),(0,a.jsx)("h3",{children:"Other Pages"}),(0,a.jsx)("ul",{className:c().exampleList,children:(0,a.jsx)("li",{children:(0,a.jsx)("a",{rel:"noreferrer",target:"_blank",href:"".concat(p.env.BASE_PATH||"","/workload-simulator.html"),children:"Workload Simulator ↗️"})})})]})]}),(0,a.jsx)(m,{...n})]})]})};t.default=b},1958:function(e,t,n){"use strict";n.r(t),n.d(t,{__N_SSG:function(){return o},pages:function(){return i}});var a=n(5893),r=n(5152),l=n.n(r),o=!0;let i={helloTriangle:l()(()=>Promise.all([n.e(126),n.e(82),n.e(607)]).then(n.bind(n,6607)),{loadableGenerated:{webpack:()=>[6607]}}),helloTriangleMSAA:l()(()=>Promise.all([n.e(126),n.e(82),n.e(198)]).then(n.bind(n,1198)),{loadableGenerated:{webpack:()=>[1198]}}),resizeCanvas:l()(()=>Promise.all([n.e(126),n.e(82),n.e(565)]).then(n.bind(n,8565)),{loadableGenerated:{webpack:()=>[8565]}}),rotatingCube:l()(()=>Promise.all([n.e(126),n.e(746),n.e(82),n.e(15)]).then(n.bind(n,9015)),{loadableGenerated:{webpack:()=>[9015]}}),twoCubes:l()(()=>Promise.all([n.e(126),n.e(746),n.e(82),n.e(710)]).then(n.bind(n,5710)),{loadableGenerated:{webpack:()=>[5710]}}),texturedCube:l()(()=>Promise.all([n.e(126),n.e(746),n.e(82),n.e(613)]).then(n.bind(n,613)),{loadableGenerated:{webpack:()=>[613]}}),instancedCube:l()(()=>Promise.all([n.e(126),n.e(746),n.e(82),n.e(621)]).then(n.bind(n,8621)),{loadableGenerated:{webpack:()=>[8621]}}),fractalCube:l()(()=>Promise.all([n.e(126),n.e(746),n.e(82),n.e(103)]).then(n.bind(n,5103)),{loadableGenerated:{webpack:()=>[5103]}}),cameras:l()(()=>Promise.all([n.e(126),n.e(746),n.e(82),n.e(878)]).then(n.bind(n,3878)),{loadableGenerated:{webpack:()=>[3878]}}),cubemap:l()(()=>Promise.all([n.e(126),n.e(746),n.e(82),n.e(432)]).then(n.bind(n,1432)),{loadableGenerated:{webpack:()=>[1432]}}),computeBoids:l()(()=>Promise.all([n.e(126),n.e(82),n.e(752)]).then(n.bind(n,2752)),{loadableGenerated:{webpack:()=>[2752]}}),animometer:l()(()=>Promise.all([n.e(126),n.e(82),n.e(841)]).then(n.bind(n,841)),{loadableGenerated:{webpack:()=>[841]}}),videoUploading:l()(()=>Promise.all([n.e(126),n.e(82),n.e(677)]).then(n.bind(n,6677)),{loadableGenerated:{webpack:()=>[6677]}}),videoUploadingWebCodecs:l()(()=>Promise.all([n.e(126),n.e(82),n.e(31)]).then(n.bind(n,7031)),{loadableGenerated:{webpack:()=>[7031]}}),samplerParameters:l()(()=>Promise.all([n.e(126),n.e(746),n.e(82),n.e(624)]).then(n.bind(n,8624)),{loadableGenerated:{webpack:()=>[8624]}}),imageBlur:l()(()=>Promise.all([n.e(126),n.e(82),n.e(770)]).then(n.bind(n,1770)),{loadableGenerated:{webpack:()=>[1770]}}),shadowMapping:l()(()=>Promise.all([n.e(126),n.e(746),n.e(667),n.e(82),n.e(342)]).then(n.bind(n,2342)),{loadableGenerated:{webpack:()=>[2342]}}),reversedZ:l()(()=>Promise.all([n.e(126),n.e(746),n.e(82),n.e(588)]).then(n.bind(n,7502)),{loadableGenerated:{webpack:()=>[7502]}}),deferredRendering:l()(()=>Promise.all([n.e(126),n.e(746),n.e(667),n.e(82),n.e(704)]).then(n.bind(n,9704)),{loadableGenerated:{webpack:()=>[9704]}}),particles:l()(()=>Promise.all([n.e(126),n.e(746),n.e(82),n.e(167)]).then(n.bind(n,6167)),{loadableGenerated:{webpack:()=>[6167]}}),cornell:l()(()=>Promise.all([n.e(126),n.e(746),n.e(82),n.e(874)]).then(n.bind(n,6874)),{loadableGenerated:{webpack:()=>[6874]}}),gameOfLife:l()(()=>Promise.all([n.e(126),n.e(82),n.e(391)]).then(n.bind(n,7391)),{loadableGenerated:{webpack:()=>[7391]}}),renderBundles:l()(()=>Promise.all([n.e(126),n.e(746),n.e(82),n.e(428)]).then(n.bind(n,3428)),{loadableGenerated:{webpack:()=>[3428]}}),worker:l()(()=>Promise.all([n.e(126),n.e(82),n.e(78)]).then(n.bind(n,6078)),{loadableGenerated:{webpack:()=>[6078]}}),"A-buffer":l()(()=>Promise.all([n.e(126),n.e(746),n.e(82),n.e(73)]).then(n.bind(n,3073)),{loadableGenerated:{webpack:()=>[3073]}}),bitonicSort:l()(()=>Promise.all([n.e(126),n.e(82),n.e(880)]).then(n.bind(n,9880)),{loadableGenerated:{webpack:()=>[9880]}}),normalMap:l()(()=>Promise.all([n.e(126),n.e(746),n.e(82),n.e(118)]).then(n.bind(n,7118)),{loadableGenerated:{webpack:()=>[7118]}})};t.default=function(e){let{slug:t}=e,n=i[t];return(0,a.jsx)(n,{})}},6988:function(e){e.exports={container:"MainLayout_container__l_Vkn",wrapper:"MainLayout_wrapper__eI_HE",panel:"MainLayout_panel__GdjKj",exampleList:"MainLayout_exampleList__FHCmd",selected:"MainLayout_selected__Yjoh0",expand:"MainLayout_expand__FEWWW",panelContents:"MainLayout_panelContents__w1BWs",exampleLink:"MainLayout_exampleLink__qX1DA"}},4112:function(){},7663:function(e){!function(){var t={229:function(e){var t,n,a,r=e.exports={};function l(){throw Error("setTimeout has not been defined")}function o(){throw Error("clearTimeout has not been defined")}function i(e){if(t===setTimeout)return setTimeout(e,0);if((t===l||!t)&&setTimeout)return t=setTimeout,setTimeout(e,0);try{return t(e,0)}catch(a){try{return t.call(null,e,0)}catch(n){return t.call(this,e,0)}}}!function(){try{t="function"==typeof setTimeout?setTimeout:l}catch(e){t=l}try{n="function"==typeof clearTimeout?clearTimeout:o}catch(a){n=o}}();var s=[],u=!1,d=-1;function c(){u&&a&&(u=!1,a.length?s=a.concat(s):d=-1,s.length&&f())}function f(){if(!u){var e=i(c);u=!0;for(var t=s.length;t;){for(a=s,s=[];++d1)for(var n=1;n{});let r=a&&void 0!==a.locale?a.locale:e&&e.locale;p[t+"%"+n+(r?"%"+r:"")]=!0}let b=l.default.forwardRef(function(e,t){let n,a;let{href:b,as:m,children:_,prefetch:y,passHref:v,replace:g,shallow:x,scroll:w,locale:k,onClick:P,onMouseEnter:j,onTouchStart:C,legacyBehavior:L=!0!==Boolean(!0)}=e,T=r(e,["href","as","children","prefetch","passHref","replace","shallow","scroll","locale","onClick","onMouseEnter","onTouchStart","legacyBehavior"]);n=_,L&&("string"==typeof n||"number"==typeof n)&&(n=l.default.createElement("a",null,n));let M=!1!==y,G=l.default.useContext(s.RouterContext),O=l.default.useContext(u.AppRouterContext);O&&(G=O);let{href:E,as:R}=l.default.useMemo(()=>{let[e,t]=o.resolveHref(G,b,!0);return{href:e,as:m?o.resolveHref(G,m):t||e}},[G,b,m]),S=l.default.useRef(E),A=l.default.useRef(R);L&&(a=l.default.Children.only(n));let N=L?a&&"object"==typeof a&&a.ref:t,[D,I,U]=d.useIntersection({rootMargin:"200px"}),B=l.default.useCallback(e=>{(A.current!==R||S.current!==E)&&(U(),A.current=R,S.current=E),D(e),N&&("function"==typeof N?N(e):"object"==typeof N&&(N.current=e))},[R,N,E,U,D]);l.default.useEffect(()=>{let e=I&&M&&o.isLocalURL(E),t=void 0!==k?k:G&&G.locale,n=p[E+"%"+R+(t?"%"+t:"")];e&&!n&&h(G,E,R,{locale:t})},[R,E,I,k,M,G]);let W={ref:B,onClick(e){L||"function"!=typeof P||P(e),L&&a.props&&"function"==typeof a.props.onClick&&a.props.onClick(e),e.defaultPrevented||function(e,t,n,a,r,i,s,u,d,c){let{nodeName:f}=e.currentTarget,p="A"===f.toUpperCase();if(p&&(function(e){let{target:t}=e.currentTarget;return t&&"_self"!==t||e.metaKey||e.ctrlKey||e.shiftKey||e.altKey||e.nativeEvent&&2===e.nativeEvent.which}(e)||!o.isLocalURL(n)))return;e.preventDefault();let h=()=>{"beforePopState"in t?t[r?"replace":"push"](n,a,{shallow:i,locale:u,scroll:s}):t[r?"replace":"push"](a||n,{forceOptimisticNavigation:!c})};d?l.default.startTransition(h):h()}(e,G,E,R,g,x,w,k,Boolean(O),M)},onMouseEnter(e){L||"function"!=typeof j||j(e),L&&a.props&&"function"==typeof a.props.onMouseEnter&&a.props.onMouseEnter(e),!(!M&&O)&&o.isLocalURL(E)&&h(G,E,R,{priority:!0})},onTouchStart(e){L||"function"!=typeof C||C(e),L&&a.props&&"function"==typeof a.props.onTouchStart&&a.props.onTouchStart(e),!(!M&&O)&&o.isLocalURL(E)&&h(G,E,R,{priority:!0})}};if(!L||v||"a"===a.type&&!("href"in a.props)){let Z=void 0!==k?k:G&&G.locale,H=G&&G.isLocaleDomain&&c.getDomainLocale(R,Z,G.locales,G.domainLocales);W.href=H||f.addBasePath(i.addLocale(R,Z,G&&G.defaultLocale))}return L?l.default.cloneElement(a,W):l.default.createElement("a",Object.assign({},T,W),n)});t.default=b,("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},9246:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.useIntersection=function(e){let{rootRef:t,rootMargin:n,disabled:s}=e,u=s||!l,[d,c]=a.useState(!1),[f,p]=a.useState(null);a.useEffect(()=>{if(l){if(!u&&!d&&f&&f.tagName){let e=function(e,t,n){let{id:a,observer:r,elements:l}=function(e){let t;let n={root:e.root||null,margin:e.rootMargin||""},a=i.find(e=>e.root===n.root&&e.margin===n.margin);if(a&&(t=o.get(a)))return t;let r=new Map,l=new IntersectionObserver(e=>{e.forEach(e=>{let t=r.get(e.target),n=e.isIntersecting||e.intersectionRatio>0;t&&n&&t(n)})},e);return t={id:n,observer:l,elements:r},i.push(n),o.set(n,t),t}(n);return l.set(e,t),r.observe(e),function(){if(l.delete(e),r.unobserve(e),0===l.size){r.disconnect(),o.delete(a);let t=i.findIndex(e=>e.root===a.root&&e.margin===a.margin);t>-1&&i.splice(t,1)}}}(f,e=>e&&c(e),{root:null==t?void 0:t.current,rootMargin:n});return e}}else if(!d){let a=r.requestIdleCallback(()=>c(!0));return()=>r.cancelIdleCallback(a)}},[f,u,n,t,d]);let h=a.useCallback(()=>{c(!1)},[]);return[p,d,h]};var a=n(7294),r=n(4686);let l="function"==typeof IntersectionObserver,o=new Map,i=[];("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},8245:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.TemplateContext=t.GlobalLayoutRouterContext=t.LayoutRouterContext=t.AppRouterContext=void 0;var a=(0,n(2648).Z)(n(7294));let r=a.default.createContext(null);t.AppRouterContext=r;let l=a.default.createContext(null);t.LayoutRouterContext=l;let o=a.default.createContext(null);t.GlobalLayoutRouterContext=o;let i=a.default.createContext(null);t.TemplateContext=i},7645:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){let n=l.default,r=(null==t?void 0:t.suspense)?{}:{loading(e){let{error:t,isLoading:n,pastDelay:a}=e;return null}};if(e instanceof Promise?r.loader=()=>e:"function"==typeof e?r.loader=e:"object"==typeof e&&(r=a({},r,e)),(r=a({},r,t)).suspense&&(delete r.ssr,delete r.loading),r.loadableGenerated&&delete(r=a({},r,r.loadableGenerated)).loadableGenerated,"boolean"==typeof r.ssr&&!r.suspense){if(!r.ssr)return delete r.ssr,o(n,r);delete r.ssr}return n(r)},t.noSSR=o;var a=n(6495).Z,r=n(2648).Z,l=(r(n(7294)),r(n(4588)));function o(e,t){return delete t.webpack,delete t.modules,e(t)}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3644:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.LoadableContext=void 0;var a=(0,n(2648).Z)(n(7294));let r=a.default.createContext(null);t.LoadableContext=r},4588:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var a=n(6495).Z,r=(0,n(2648).Z)(n(7294)),l=n(3644);let{useSyncExternalStore:o}=n(7294),i=[],s=[],u=!1;function d(e){let t=e(),n={loading:!0,loaded:null,error:null};return n.promise=t.then(e=>(n.loading=!1,n.loaded=e,e)).catch(e=>{throw n.loading=!1,n.error=e,e}),n}class c{promise(){return this._res.promise}retry(){this._clearTimeouts(),this._res=this._loadFn(this._opts.loader),this._state={pastDelay:!1,timedOut:!1};let{_res:e,_opts:t}=this;e.loading&&("number"==typeof t.delay&&(0===t.delay?this._state.pastDelay=!0:this._delay=setTimeout(()=>{this._update({pastDelay:!0})},t.delay)),"number"==typeof t.timeout&&(this._timeout=setTimeout(()=>{this._update({timedOut:!0})},t.timeout))),this._res.promise.then(()=>{this._update({}),this._clearTimeouts()}).catch(e=>{this._update({}),this._clearTimeouts()}),this._update({})}_update(e){this._state=a({},this._state,{error:this._res.error,loaded:this._res.loaded,loading:this._res.loading},e),this._callbacks.forEach(e=>e())}_clearTimeouts(){clearTimeout(this._delay),clearTimeout(this._timeout)}getCurrentValue(){return this._state}subscribe(e){return this._callbacks.add(e),()=>{this._callbacks.delete(e)}}constructor(e,t){this._loadFn=e,this._opts=t,this._callbacks=new Set,this._delay=null,this._timeout=null,this.retry()}}function f(e){return function(e,t){let n=Object.assign({loader:null,loading:null,delay:200,timeout:null,webpack:null,modules:null,suspense:!1},t);n.suspense&&(n.lazy=r.default.lazy(n.loader));let i=null;function d(){if(!i){let t=new c(e,n);i={getCurrentValue:t.getCurrentValue.bind(t),subscribe:t.subscribe.bind(t),retry:t.retry.bind(t),promise:t.promise.bind(t)}}return i.promise()}if(!u){let f=n.webpack?n.webpack():n.modules;f&&s.push(e=>{for(let t of f)if(-1!==e.indexOf(t))return d()})}function p(){d();let e=r.default.useContext(l.LoadableContext);e&&Array.isArray(n.modules)&&n.modules.forEach(t=>{e(t)})}let h=n.suspense?function(e,t){return p(),r.default.createElement(n.lazy,a({},e,{ref:t}))}:function(e,t){p();let a=o(i.subscribe,i.getCurrentValue,i.getCurrentValue);return r.default.useImperativeHandle(t,()=>({retry:i.retry}),[]),r.default.useMemo(()=>{var t;return a.loading||a.error?r.default.createElement(n.loading,{isLoading:a.loading,pastDelay:a.pastDelay,timedOut:a.timedOut,error:a.error,retry:i.retry}):a.loaded?r.default.createElement((t=a.loaded)&&t.__esModule?t.default:t,e):null},[e,a])};return h.preload=()=>d(),h.displayName="LoadableComponent",r.default.forwardRef(h)}(d,e)}function p(e,t){let n=[];for(;e.length;){let a=e.pop();n.push(a(t))}return Promise.all(n).then(()=>{if(e.length)return p(e,t)})}f.preloadAll=()=>new Promise((e,t)=>{p(i).then(e,t)}),f.preloadReady=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];return new Promise(t=>{let n=()=>(u=!0,t());p(s,e).then(n,n)})},window.__NEXT_PRELOADREADY=f.preloadReady,t.default=f},6505:function(e,t,n){"use strict";n.r(t);var a=n(5893),r=n(9008),l=n.n(r),o=n(1664),i=n.n(o),s=n(1163),u=n(7294);n(4112);var d=n(6988),c=n.n(d),f=n(1958),p=n(3454);let h="WebGPU Samples",b=e=>{let{Component:t,pageProps:n}=e,r=(0,s.useRouter)(),o=Object.keys(f.pages),[d,b]=(0,u.useState)(!1),m=(0,u.useMemo)(()=>(0,u.memo)(t),[t]),_=r.asPath.match(/(\?wgsl=[01])#(\S+)/);if(_){let y=_[2];return r.replace("/samples/".concat(y)),(0,a.jsx)(a.Fragment,{})}return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsxs)(l(),{children:[(0,a.jsx)("title",{children:h}),(0,a.jsx)("meta",{name:"description",content:"The WebGPU Samples are a set of samples demonstrating the use of the WebGPU API."}),(0,a.jsx)("meta",{name:"viewport",content:"width=device-width, initial-scale=1, shrink-to-fit=no"})]}),(0,a.jsxs)("div",{className:c().wrapper,children:[(0,a.jsxs)("nav",{className:"".concat(c().panel," ").concat(c().container),"data-expanded":d,children:[(0,a.jsxs)("h1",{children:[(0,a.jsx)(i(),{href:"/",children:h}),(0,a.jsx)("div",{className:c().expand,onClick(){b(!d)}})]}),(0,a.jsxs)("div",{className:c().panelContents,children:[(0,a.jsx)("a",{href:"https://github.com/".concat("webgpu/webgpu-samples"),children:"Github"}),(0,a.jsx)("hr",{}),(0,a.jsx)("ul",{className:c().exampleList,children:o.map(e=>{let t="/samples/[slug]"===r.pathname&&r.query.slug===e?c().selected:void 0;return(0,a.jsx)("li",{className:t,onMouseOver(){f.pages[e].render.preload()},children:(0,a.jsx)(i(),{href:"/samples/".concat(e),onClick(){b(!1)},children:e})},e)})}),(0,a.jsx)("hr",{}),(0,a.jsx)("h3",{children:"Other Pages"}),(0,a.jsx)("ul",{className:c().exampleList,children:(0,a.jsx)("li",{children:(0,a.jsx)("a",{rel:"noreferrer",target:"_blank",href:"".concat(p.env.BASE_PATH||"","/workload-simulator.html"),children:"Workload Simulator ↗️"})})})]})]}),(0,a.jsx)(m,{...n})]})]})};t.default=b},1958:function(e,t,n){"use strict";n.r(t),n.d(t,{__N_SSG:function(){return o},pages:function(){return i}});var a=n(5893),r=n(5152),l=n.n(r),o=!0;let i={helloTriangle:l()(()=>Promise.all([n.e(126),n.e(82),n.e(607)]).then(n.bind(n,6607)),{loadableGenerated:{webpack:()=>[6607]}}),helloTriangleMSAA:l()(()=>Promise.all([n.e(126),n.e(82),n.e(198)]).then(n.bind(n,1198)),{loadableGenerated:{webpack:()=>[1198]}}),resizeCanvas:l()(()=>Promise.all([n.e(126),n.e(82),n.e(565)]).then(n.bind(n,8565)),{loadableGenerated:{webpack:()=>[8565]}}),rotatingCube:l()(()=>Promise.all([n.e(126),n.e(746),n.e(82),n.e(15)]).then(n.bind(n,9015)),{loadableGenerated:{webpack:()=>[9015]}}),twoCubes:l()(()=>Promise.all([n.e(126),n.e(746),n.e(82),n.e(710)]).then(n.bind(n,5710)),{loadableGenerated:{webpack:()=>[5710]}}),texturedCube:l()(()=>Promise.all([n.e(126),n.e(746),n.e(82),n.e(613)]).then(n.bind(n,613)),{loadableGenerated:{webpack:()=>[613]}}),instancedCube:l()(()=>Promise.all([n.e(126),n.e(746),n.e(82),n.e(621)]).then(n.bind(n,8621)),{loadableGenerated:{webpack:()=>[8621]}}),fractalCube:l()(()=>Promise.all([n.e(126),n.e(746),n.e(82),n.e(103)]).then(n.bind(n,5103)),{loadableGenerated:{webpack:()=>[5103]}}),cameras:l()(()=>Promise.all([n.e(126),n.e(746),n.e(82),n.e(878)]).then(n.bind(n,3878)),{loadableGenerated:{webpack:()=>[3878]}}),cubemap:l()(()=>Promise.all([n.e(126),n.e(746),n.e(82),n.e(432)]).then(n.bind(n,1432)),{loadableGenerated:{webpack:()=>[1432]}}),computeBoids:l()(()=>Promise.all([n.e(126),n.e(82),n.e(752)]).then(n.bind(n,2752)),{loadableGenerated:{webpack:()=>[2752]}}),animometer:l()(()=>Promise.all([n.e(126),n.e(82),n.e(841)]).then(n.bind(n,841)),{loadableGenerated:{webpack:()=>[841]}}),videoUploading:l()(()=>Promise.all([n.e(126),n.e(82),n.e(677)]).then(n.bind(n,6677)),{loadableGenerated:{webpack:()=>[6677]}}),videoUploadingWebCodecs:l()(()=>Promise.all([n.e(126),n.e(82),n.e(31)]).then(n.bind(n,7031)),{loadableGenerated:{webpack:()=>[7031]}}),samplerParameters:l()(()=>Promise.all([n.e(126),n.e(746),n.e(82),n.e(624)]).then(n.bind(n,8624)),{loadableGenerated:{webpack:()=>[8624]}}),imageBlur:l()(()=>Promise.all([n.e(126),n.e(82),n.e(770)]).then(n.bind(n,1770)),{loadableGenerated:{webpack:()=>[1770]}}),shadowMapping:l()(()=>Promise.all([n.e(126),n.e(746),n.e(667),n.e(82),n.e(342)]).then(n.bind(n,2342)),{loadableGenerated:{webpack:()=>[2342]}}),reversedZ:l()(()=>Promise.all([n.e(126),n.e(746),n.e(82),n.e(588)]).then(n.bind(n,7502)),{loadableGenerated:{webpack:()=>[7502]}}),deferredRendering:l()(()=>Promise.all([n.e(126),n.e(746),n.e(667),n.e(82),n.e(704)]).then(n.bind(n,9704)),{loadableGenerated:{webpack:()=>[9704]}}),particles:l()(()=>Promise.all([n.e(126),n.e(746),n.e(82),n.e(167)]).then(n.bind(n,6167)),{loadableGenerated:{webpack:()=>[6167]}}),cornell:l()(()=>Promise.all([n.e(126),n.e(746),n.e(82),n.e(874)]).then(n.bind(n,6874)),{loadableGenerated:{webpack:()=>[6874]}}),gameOfLife:l()(()=>Promise.all([n.e(126),n.e(82),n.e(391)]).then(n.bind(n,7391)),{loadableGenerated:{webpack:()=>[7391]}}),renderBundles:l()(()=>Promise.all([n.e(126),n.e(746),n.e(82),n.e(428)]).then(n.bind(n,3428)),{loadableGenerated:{webpack:()=>[3428]}}),worker:l()(()=>Promise.all([n.e(126),n.e(82),n.e(78)]).then(n.bind(n,6078)),{loadableGenerated:{webpack:()=>[6078]}}),"A-buffer":l()(()=>Promise.all([n.e(126),n.e(746),n.e(82),n.e(73)]).then(n.bind(n,3073)),{loadableGenerated:{webpack:()=>[3073]}}),bitonicSort:l()(()=>Promise.all([n.e(126),n.e(82),n.e(0)]).then(n.bind(n,2e3)),{loadableGenerated:{webpack:()=>[2e3]}}),normalMap:l()(()=>Promise.all([n.e(126),n.e(746),n.e(82),n.e(118)]).then(n.bind(n,7118)),{loadableGenerated:{webpack:()=>[7118]}}),skinnedMesh:l()(()=>Promise.all([n.e(126),n.e(746),n.e(82),n.e(869)]).then(n.bind(n,6869)),{loadableGenerated:{webpack:()=>[6869]}})};t.default=function(e){let{slug:t}=e,n=i[t];return(0,a.jsx)(n,{})}},6988:function(e){e.exports={container:"MainLayout_container__l_Vkn",wrapper:"MainLayout_wrapper__eI_HE",panel:"MainLayout_panel__GdjKj",exampleList:"MainLayout_exampleList__FHCmd",selected:"MainLayout_selected__Yjoh0",expand:"MainLayout_expand__FEWWW",panelContents:"MainLayout_panelContents__w1BWs",exampleLink:"MainLayout_exampleLink__qX1DA"}},4112:function(){},7663:function(e){!function(){var t={229:function(e){var t,n,a,r=e.exports={};function l(){throw Error("setTimeout has not been defined")}function o(){throw Error("clearTimeout has not been defined")}function i(e){if(t===setTimeout)return setTimeout(e,0);if((t===l||!t)&&setTimeout)return t=setTimeout,setTimeout(e,0);try{return t(e,0)}catch(a){try{return t.call(null,e,0)}catch(n){return t.call(this,e,0)}}}!function(){try{t="function"==typeof setTimeout?setTimeout:l}catch(e){t=l}try{n="function"==typeof clearTimeout?clearTimeout:o}catch(a){n=o}}();var s=[],u=!1,d=-1;function c(){u&&a&&(u=!1,a.length?s=a.concat(s):d=-1,s.length&&f())}function f(){if(!u){var e=i(c);u=!0;for(var t=s.length;t;){for(a=s,s=[];++d1)for(var n=1;n0&&e[f-1][2]>a;f--)e[f]=e[f-1];e[f]=[r,n,a];return}for(var c=1/0,f=0;f=a&&Object.keys(l.O).every(function(e){return l.O[e](r[i])})?r.splice(i--,1):(o=!1,a0&&e[f-1][2]>a;f--)e[f]=e[f-1];e[f]=[r,n,a];return}for(var c=1/0,f=0;f=a&&Object.keys(l.O).every(function(e){return l.O[e](r[i])})?r.splice(i--,1):(o=!1,aWebGPU Samples \ No newline at end of file +WebGPU Samples \ No newline at end of file diff --git a/samples/A-buffer.html b/samples/A-buffer.html index e0f60959..39e8d246 100644 --- a/samples/A-buffer.html +++ b/samples/A-buffer.html @@ -10,6 +10,6 @@ } A-Buffer - WebGPU Samples

A-Buffer

See it on Github!

Demonstrates order independent transparency using a per-pixel + limiting memory usage (when required)."/>

\ No newline at end of file + limiting memory usage (when required).

\ No newline at end of file diff --git a/samples/animometer.html b/samples/animometer.html index 0fd7a67b..151f7e07 100644 --- a/samples/animometer.html +++ b/samples/animometer.html @@ -8,4 +8,4 @@ height: auto !important; overflow: visible !important; } - Animometer - WebGPU Samples \ No newline at end of file + Animometer - WebGPU Samples \ No newline at end of file diff --git a/samples/bitonicSort.html b/samples/bitonicSort.html index cb19f5f6..1a89ba3a 100644 --- a/samples/bitonicSort.html +++ b/samples/bitonicSort.html @@ -8,4 +8,4 @@ height: auto !important; overflow: visible !important; } - Bitonic Sort - WebGPU Samples

Bitonic Sort

See it on Github!

A naive bitonic sort algorithm executed on the GPU, based on tgfrerer's implementation at poniesandlight.co.uk/reflect/bitonic_merge_sort/. Each dispatch of the bitonic sort shader dispatches a workgroup containing elements/2 invocations. The GUI's Execution Information folder contains information about the sort's current state. The visualizer displays the sort's results as colored cells sorted from brightest to darkest.

\ No newline at end of file + Bitonic Sort - WebGPU Samples

Bitonic Sort

See it on Github!

A naive bitonic sort algorithm executed on the GPU, based on tgfrerer's implementation at poniesandlight.co.uk/reflect/bitonic_merge_sort/. Each dispatch of the bitonic sort shader dispatches a workgroup containing elements/2 invocations. The GUI's Execution Information folder contains information about the sort's current state. The visualizer displays the sort's results as colored cells sorted from brightest to darkest.

\ No newline at end of file diff --git a/samples/cameras.html b/samples/cameras.html index 8cfe59db..4b535f65 100644 --- a/samples/cameras.html +++ b/samples/cameras.html @@ -8,4 +8,4 @@ height: auto !important; overflow: visible !important; } - Cameras - WebGPU Samples \ No newline at end of file + Cameras - WebGPU Samples \ No newline at end of file diff --git a/samples/computeBoids.html b/samples/computeBoids.html index 182e638e..d3c20694 100644 --- a/samples/computeBoids.html +++ b/samples/computeBoids.html @@ -8,4 +8,4 @@ height: auto !important; overflow: visible !important; } - Compute Boids - WebGPU Samples \ No newline at end of file + Compute Boids - WebGPU Samples \ No newline at end of file diff --git a/samples/cornell.html b/samples/cornell.html index bd54e1c3..a0809669 100644 --- a/samples/cornell.html +++ b/samples/cornell.html @@ -8,4 +8,4 @@ height: auto !important; overflow: visible !important; } - Cornell box - WebGPU Samples \ No newline at end of file + Cornell box - WebGPU Samples \ No newline at end of file diff --git a/samples/cubemap.html b/samples/cubemap.html index 3f006bb8..06921ecf 100644 --- a/samples/cubemap.html +++ b/samples/cubemap.html @@ -8,4 +8,4 @@ height: auto !important; overflow: visible !important; } - Cubemap - WebGPU Samples \ No newline at end of file + Cubemap - WebGPU Samples \ No newline at end of file diff --git a/samples/deferredRendering.html b/samples/deferredRendering.html index 89be8b57..c89953a8 100644 --- a/samples/deferredRendering.html +++ b/samples/deferredRendering.html @@ -16,7 +16,7 @@ We also update light position in a compute shader, where further operations like tile/cluster culling could happen. The debug view shows the depth buffer on the left (flipped and scaled a bit to make it more visible), the normal G buffer in the middle, and the albedo G-buffer on the right side of the screen. - "/>

Deferred Rendering

See it on Github!

This example shows how to do deferred rendering with webgpu. + "/>

Deferred Rendering

See it on Github!

This example shows how to do deferred rendering with webgpu. Render geometry info to multiple targets in the gBuffers in the first pass. In this sample we have 2 gBuffers for normals and albedo, along with a depth texture. And then do the lighting in a second pass with per fragment data read from gBuffers so it's independent of scene complexity. @@ -24,4 +24,4 @@ We also update light position in a compute shader, where further operations like tile/cluster culling could happen. The debug view shows the depth buffer on the left (flipped and scaled a bit to make it more visible), the normal G buffer in the middle, and the albedo G-buffer on the right side of the screen. -

\ No newline at end of file +

\ No newline at end of file diff --git a/samples/fractalCube.html b/samples/fractalCube.html index a4b13a7a..b0481d1c 100644 --- a/samples/fractalCube.html +++ b/samples/fractalCube.html @@ -8,4 +8,4 @@ height: auto !important; overflow: visible !important; } - Fractal Cube - WebGPU Samples \ No newline at end of file + Fractal Cube - WebGPU Samples \ No newline at end of file diff --git a/samples/gameOfLife.html b/samples/gameOfLife.html index 2c701f0a..ba87d331 100644 --- a/samples/gameOfLife.html +++ b/samples/gameOfLife.html @@ -8,4 +8,4 @@ height: auto !important; overflow: visible !important; } - Conway's Game of Life - WebGPU Samples \ No newline at end of file + Conway's Game of Life - WebGPU Samples \ No newline at end of file diff --git a/samples/helloTriangle.html b/samples/helloTriangle.html index 8f1b2ad0..dc8ba578 100644 --- a/samples/helloTriangle.html +++ b/samples/helloTriangle.html @@ -8,4 +8,4 @@ height: auto !important; overflow: visible !important; } - Hello Triangle - WebGPU Samples \ No newline at end of file + Hello Triangle - WebGPU Samples \ No newline at end of file diff --git a/samples/helloTriangleMSAA.html b/samples/helloTriangleMSAA.html index 1613b9d3..cea29c61 100644 --- a/samples/helloTriangleMSAA.html +++ b/samples/helloTriangleMSAA.html @@ -8,4 +8,4 @@ height: auto !important; overflow: visible !important; } - Hello Triangle MSAA - WebGPU Samples \ No newline at end of file + Hello Triangle MSAA - WebGPU Samples \ No newline at end of file diff --git a/samples/imageBlur.html b/samples/imageBlur.html index b5e76dc3..9cacf243 100644 --- a/samples/imageBlur.html +++ b/samples/imageBlur.html @@ -8,4 +8,4 @@ height: auto !important; overflow: visible !important; } - Image Blur - WebGPU Samples \ No newline at end of file + Image Blur - WebGPU Samples \ No newline at end of file diff --git a/samples/instancedCube.html b/samples/instancedCube.html index 2b021c0d..9b784a23 100644 --- a/samples/instancedCube.html +++ b/samples/instancedCube.html @@ -8,4 +8,4 @@ height: auto !important; overflow: visible !important; } - Instanced Cube - WebGPU Samples \ No newline at end of file + Instanced Cube - WebGPU Samples \ No newline at end of file diff --git a/samples/normalMap.html b/samples/normalMap.html index 19df8c30..49cb2c8e 100644 --- a/samples/normalMap.html +++ b/samples/normalMap.html @@ -8,4 +8,4 @@ height: auto !important; overflow: visible !important; } - Normal Mapping - WebGPU Samples \ No newline at end of file + Normal Mapping - WebGPU Samples \ No newline at end of file diff --git a/samples/particles.html b/samples/particles.html index 31310154..95327cdc 100644 --- a/samples/particles.html +++ b/samples/particles.html @@ -8,4 +8,4 @@ height: auto !important; overflow: visible !important; } - Particles - WebGPU Samples \ No newline at end of file + Particles - WebGPU Samples \ No newline at end of file diff --git a/samples/renderBundles.html b/samples/renderBundles.html index 134a8c0c..08174a68 100644 --- a/samples/renderBundles.html +++ b/samples/renderBundles.html @@ -11,7 +11,7 @@ Render Bundles - WebGPU Samples

Render Bundles

See it on Github!

This example shows how to use render bundles. It renders a large number of + of instancing to reduce draw overhead.)"/>

Render Bundles

See it on Github!

This example shows how to use render bundles. It renders a large number of meshes individually as a proxy for a more complex scene in order to demonstrate the reduction in JavaScript time spent to issue render commands. (Typically a scene like this would make use - of instancing to reduce draw overhead.)

\ No newline at end of file + of instancing to reduce draw overhead.)

\ No newline at end of file diff --git a/samples/resizeCanvas.html b/samples/resizeCanvas.html index 9045e2ae..30cc12b5 100644 --- a/samples/resizeCanvas.html +++ b/samples/resizeCanvas.html @@ -8,4 +8,4 @@ height: auto !important; overflow: visible !important; } - Resize Canvas - WebGPU Samples \ No newline at end of file + Resize Canvas - WebGPU Samples \ No newline at end of file diff --git a/samples/reversedZ.html b/samples/reversedZ.html index 26aa15ae..a84a56dd 100644 --- a/samples/reversedZ.html +++ b/samples/reversedZ.html @@ -17,7 +17,7 @@ Related reading: https://developer.nvidia.com/content/depth-precision-visualized https://web.archive.org/web/20220724174000/https://thxforthefish.com/posts/reverse_z/ - "/>

Reversed Z

See it on Github!

This example shows the use of reversed z technique for better utilization of depth buffer precision. + "/>

Reversed Z

See it on Github!

This example shows the use of reversed z technique for better utilization of depth buffer precision. The left column uses regular method, while the right one uses reversed z technique. Both are using depth32float as their depth buffer format. A set of red and green planes are positioned very close to each other. Higher sets are placed further from camera (and are scaled for better visual purpose). @@ -26,4 +26,4 @@ Related reading: https://developer.nvidia.com/content/depth-precision-visualized https://web.archive.org/web/20220724174000/https://thxforthefish.com/posts/reverse_z/ -

\ No newline at end of file +

\ No newline at end of file diff --git a/samples/rotatingCube.html b/samples/rotatingCube.html index 130fe3ac..677cc7fd 100644 --- a/samples/rotatingCube.html +++ b/samples/rotatingCube.html @@ -8,4 +8,4 @@ height: auto !important; overflow: visible !important; } - Rotating Cube - WebGPU Samples \ No newline at end of file + Rotating Cube - WebGPU Samples \ No newline at end of file diff --git a/samples/samplerParameters.html b/samples/samplerParameters.html index 2810bb1d..90e7aa8a 100644 --- a/samples/samplerParameters.html +++ b/samples/samplerParameters.html @@ -8,4 +8,4 @@ height: auto !important; overflow: visible !important; } - Sampler Parameters - WebGPU Samples

Sampler Parameters

See it on Github!

Visualizes what all the sampler parameters do. Shows a textured plane at various scales (rotated, head-on, in perspective, and in vanishing perspective). The bottom-right view shows the raw contents of the 4 mipmap levels of the test texture (16x16, 8x8, 4x4, and 2x2).

\ No newline at end of file + Sampler Parameters - WebGPU Samples

Sampler Parameters

See it on Github!

Visualizes what all the sampler parameters do. Shows a textured plane at various scales (rotated, head-on, in perspective, and in vanishing perspective). The bottom-right view shows the raw contents of the 4 mipmap levels of the test texture (16x16, 8x8, 4x4, and 2x2).

\ No newline at end of file diff --git a/samples/shadowMapping.html b/samples/shadowMapping.html index 345d07cb..d823a5ae 100644 --- a/samples/shadowMapping.html +++ b/samples/shadowMapping.html @@ -8,4 +8,4 @@ height: auto !important; overflow: visible !important; } - Shadow Mapping - WebGPU Samples \ No newline at end of file + Shadow Mapping - WebGPU Samples \ No newline at end of file diff --git a/samples/skinnedMesh.html b/samples/skinnedMesh.html new file mode 100644 index 00000000..a47bdfce --- /dev/null +++ b/samples/skinnedMesh.html @@ -0,0 +1,11 @@ +Skinned Mesh - WebGPU Samples

Skinned Mesh

See it on Github!

A demonstration of basic gltf loading and mesh skinning, ported from https://webgl2fundamentals.org/webgl/lessons/webgl-skinning.html. Mesh data, per vertex attributes, and skin inverseBindMatrices are taken from the json parsed from the binary output of the .glb file. Animations are generated progrmatically, with animated joint matrices updated and passed to shaders per frame via uniform buffers.

\ No newline at end of file diff --git a/samples/texturedCube.html b/samples/texturedCube.html index 66dbbcfe..ee1f7961 100644 --- a/samples/texturedCube.html +++ b/samples/texturedCube.html @@ -8,4 +8,4 @@ height: auto !important; overflow: visible !important; } - Textured Cube - WebGPU Samples \ No newline at end of file + Textured Cube - WebGPU Samples \ No newline at end of file diff --git a/samples/twoCubes.html b/samples/twoCubes.html index 98caffea..8182edfa 100644 --- a/samples/twoCubes.html +++ b/samples/twoCubes.html @@ -8,4 +8,4 @@ height: auto !important; overflow: visible !important; } - Two Cubes - WebGPU Samples \ No newline at end of file + Two Cubes - WebGPU Samples \ No newline at end of file diff --git a/samples/videoUploading.html b/samples/videoUploading.html index 5f742a0a..d5051fb6 100644 --- a/samples/videoUploading.html +++ b/samples/videoUploading.html @@ -8,4 +8,4 @@ height: auto !important; overflow: visible !important; } - Video Uploading - WebGPU Samples \ No newline at end of file + Video Uploading - WebGPU Samples \ No newline at end of file diff --git a/samples/videoUploadingWebCodecs.html b/samples/videoUploadingWebCodecs.html index d1127427..fec9db58 100644 --- a/samples/videoUploadingWebCodecs.html +++ b/samples/videoUploadingWebCodecs.html @@ -8,4 +8,4 @@ height: auto !important; overflow: visible !important; } - Video Uploading with WebCodecs - WebGPU Samples \ No newline at end of file + Video Uploading with WebCodecs - WebGPU Samples \ No newline at end of file diff --git a/samples/worker.html b/samples/worker.html index 0b892e57..e3073258 100644 --- a/samples/worker.html +++ b/samples/worker.html @@ -10,6 +10,6 @@ } WebGPU in a Worker - WebGPU Samples

WebGPU in a Worker

See it on Github!

This example shows one method of using WebGPU in a web worker and presenting to + which is then transferred to the worker where all the WebGPU calls are made."/>

\ No newline at end of file + which is then transferred to the worker where all the WebGPU calls are made.

\ No newline at end of file