diff --git a/package-lock.json b/package-lock.json index 7f829213..75588b63 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,7 @@ "@types/stats.js": "^0.17.0", "@typescript-eslint/eslint-plugin": "^5.41.0", "@typescript-eslint/parser": "^5.41.0", - "@webgpu/types": "^0.1.21", + "@webgpu/types": "^0.1.38", "eslint": "^8.26.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-prettier": "^4.2.1", @@ -1098,9 +1098,9 @@ } }, "node_modules/@webgpu/types": { - "version": "0.1.21", - "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.21.tgz", - "integrity": "sha512-pUrWq3V5PiSGFLeLxoGqReTZmiiXwY3jRkIG5sLLKjyqNxrwm/04b4nw7LSmGWJcKk59XOM/YRTUwOzo4MMlow==", + "version": "0.1.38", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.38.tgz", + "integrity": "sha512-7LrhVKz2PRh+DD7+S+PVaFd5HxaWQvoMqBbsV9fNJO1pjUs1P8bM2vQVNfk+3URTqbuTI7gkXi0rfsN0IadoBA==", "dev": true }, "node_modules/@xtuc/ieee754": { diff --git a/package.json b/package.json index 6069dd6e..c9a76881 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "@types/stats.js": "^0.17.0", "@typescript-eslint/eslint-plugin": "^5.41.0", "@typescript-eslint/parser": "^5.41.0", - "@webgpu/types": "^0.1.21", + "@webgpu/types": "^0.1.38", "eslint": "^8.26.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-prettier": "^4.2.1", diff --git a/src/sample/computeBoids/main.ts b/src/sample/computeBoids/main.ts index 53c27fe6..31b92816 100644 --- a/src/sample/computeBoids/main.ts +++ b/src/sample/computeBoids/main.ts @@ -6,7 +6,27 @@ import updateSpritesWGSL from './updateSprites.wgsl'; const init: SampleInit = async ({ canvas, pageState, gui }) => { const adapter = await navigator.gpu.requestAdapter(); assert(adapter, 'requestAdapter returned null'); - const device = await adapter.requestDevice(); + + const hasTimestampQuery = adapter.features.has('timestamp-query'); + const device = await adapter.requestDevice({ + requiredFeatures: hasTimestampQuery ? ['timestamp-query'] : [], + }); + + const perfDisplayContainer = document.createElement('div'); + perfDisplayContainer.style.color = 'white'; + perfDisplayContainer.style.background = 'black'; + perfDisplayContainer.style.position = 'absolute'; + perfDisplayContainer.style.top = '10px'; + perfDisplayContainer.style.left = '10px'; + perfDisplayContainer.style.textAlign = 'left'; + + const perfDisplay = document.createElement('pre'); + perfDisplayContainer.appendChild(perfDisplay); + if (canvas.parentNode) { + canvas.parentNode.appendChild(perfDisplayContainer); + } else { + console.error('canvas.parentNode is null'); + } if (!pageState.active) return; const context = canvas.getContext('webgpu') as GPUCanvasContext; @@ -97,6 +117,36 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { ], }; + const computePassDescriptor = {}; + + let querySet: GPUQuerySet | undefined = undefined; + let resolveBuffer: GPUBuffer | undefined = undefined; + let resultBuffer: GPUBuffer | undefined = undefined; + if (hasTimestampQuery) { + querySet = device.createQuerySet({ + type: 'timestamp', + count: 4, + }); + resolveBuffer = device.createBuffer({ + size: 4 * BigInt64Array.BYTES_PER_ELEMENT, + usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC, + }); + resultBuffer = device.createBuffer({ + size: 4 * BigInt64Array.BYTES_PER_ELEMENT, + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ, + }); + renderPassDescriptor.timestampWrites = { + querySet, + beginningOfPassWriteIndex: 0, + endOfPassWriteIndex: 1, + }; + computePassDescriptor.timestampWrites = { + querySet, + beginningOfPassWriteIndex: 2, + endOfPassWriteIndex: 3, + }; + } + // prettier-ignore const vertexBufferData = new Float32Array([ -0.01, -0.02, 0.01, @@ -217,7 +267,9 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { const commandEncoder = device.createCommandEncoder(); { - const passEncoder = commandEncoder.beginComputePass(); + const passEncoder = commandEncoder.beginComputePass( + computePassDescriptor + ); passEncoder.setPipeline(computePipeline); passEncoder.setBindGroup(0, particleBindGroups[t % 2]); passEncoder.dispatchWorkgroups(Math.ceil(numParticles / 64)); @@ -231,8 +283,30 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { passEncoder.draw(3, numParticles, 0, 0); passEncoder.end(); } + + if (hasTimestampQuery && resultBuffer.mapState == 'unmapped') { + commandEncoder.resolveQuerySet(querySet, 0, 4, resolveBuffer, 0); + commandEncoder.copyBufferToBuffer( + resolveBuffer, + 0, + resultBuffer, + 0, + resultBuffer.size + ); + } + device.queue.submit([commandEncoder.finish()]); + if (hasTimestampQuery && resultBuffer.mapState == 'unmapped') { + resultBuffer.mapAsync(GPUMapMode.READ).then(() => { + const times = new BigInt64Array(resultBuffer.getMappedRange()); + const renderPassDuration = Number(times[1] - times[0]); + const computePassDuration = Number(times[3] - times[2]); + perfDisplay.textContent = ` render pass duration: ${renderPassDuration}ns\ncompute pass duration: ${computePassDuration}ns\n`; + resultBuffer.unmap(); + }); + } + ++t; requestAnimationFrame(frame); }