diff --git a/src/pages/samples/[slug].tsx b/src/pages/samples/[slug].tsx index b323c430..6bd08c0b 100644 --- a/src/pages/samples/[slug].tsx +++ b/src/pages/samples/[slug].tsx @@ -44,7 +44,7 @@ export const pages: PageComponentType = { cornell: dynamic(() => import('../../sample/cornell/main')), gameOfLife: dynamic(() => import('../../sample/gameOfLife/main')), renderBundles: dynamic(() => import('../../sample/renderBundles/main')), - worker: dynamic(() => import('../../sample/worker/main')), + //worker: dynamic(() => import('../../sample/worker/main')), }; function Page({ slug }: Props): JSX.Element { diff --git a/src/sample/animometer/main.ts b/src/sample/animometer/main.ts index 8b6074af..088d4750 100644 --- a/src/sample/animometer/main.ts +++ b/src/sample/animometer/main.ts @@ -283,7 +283,7 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { const renderPassDescriptor = { colorAttachments: [ { - view: undefined as any, // Assigned later + view: undefined as unknown as GPUTextureView, // Assigned later clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }, loadOp: 'clear' as const, storeOp: 'store' as const, diff --git a/src/sample/cornell/main.ts b/src/sample/cornell/main.ts index 506951e9..0d15f1a8 100644 --- a/src/sample/cornell/main.ts +++ b/src/sample/cornell/main.ts @@ -11,12 +11,17 @@ import Radiosity from './radiosity'; import Rasterizer from './rasterizer'; import Tonemapper from './tonemapper'; import Raytracer from './raytracer'; +import { assert } from '../../components/SampleLayout'; const init: SampleInit = async ({ canvas, pageState, gui }) => { const presentationFormat = navigator.gpu.getPreferredCanvasFormat(); const requiredFeatures: GPUFeatureName[] = presentationFormat === 'bgra8unorm' ? ['bgra8unorm-storage'] : []; const adapter = await navigator.gpu.requestAdapter(); + if (!adapter) { + throw new Error('Adapter is null or not supported'); + } + for (const feature of requiredFeatures) { if (!adapter.features.has(feature)) { throw new Error( @@ -24,7 +29,7 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { ); } } - const device = await adapter.requestDevice({ requiredFeatures }); + const device = await adapter?.requestDevice({ requiredFeatures }); if (!pageState.active) return; @@ -36,6 +41,7 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { rotateCamera: true, }; + assert(gui, 'gui is null'); gui.add(params, 'renderer', ['rasterizer', 'raytracer']); gui.add(params, 'rotateCamera', true); @@ -80,7 +86,7 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { } const canvasTexture = context.getCurrentTexture(); - const commandEncoder = device.createCommandEncoder(); + const commandEncoder = device?.createCommandEncoder(); common.update({ rotateCamera: params.rotateCamera, diff --git a/src/sample/cubemap/main.ts b/src/sample/cubemap/main.ts index 1fae471f..e18975f0 100644 --- a/src/sample/cubemap/main.ts +++ b/src/sample/cubemap/main.ts @@ -1,5 +1,5 @@ import { mat4, vec3 } from 'wgpu-matrix'; -import { makeSample, SampleInit } from '../../components/SampleLayout'; +import { assert, makeSample, SampleInit } from '../../components/SampleLayout'; import { cubeVertexArray, @@ -14,7 +14,8 @@ import sampleCubemapWGSL from './sampleCubemap.frag.wgsl'; const init: SampleInit = async ({ canvas, pageState }) => { const adapter = await navigator.gpu.requestAdapter(); - const device = await adapter.requestDevice(); + const device = await adapter?.requestDevice(); + assert(device, 'device is null'); if (!pageState.active) return; const context = canvas.getContext('webgpu') as GPUCanvasContext; @@ -198,7 +199,7 @@ const init: SampleInit = async ({ canvas, pageState }) => { const renderPassDescriptor: GPURenderPassDescriptor = { colorAttachments: [ { - view: undefined, // Assigned later + view: undefined as any, // Assigned later loadOp: 'clear', storeOp: 'store', }, @@ -245,6 +246,7 @@ const init: SampleInit = async ({ canvas, pageState }) => { function frame() { // Sample is no longer the active page. if (!pageState.active) return; + assert(device, 'device is null'); updateTransformationMatrix(); device.queue.writeBuffer( @@ -255,9 +257,16 @@ const init: SampleInit = async ({ canvas, pageState }) => { modelViewProjectionMatrix.byteLength ); - renderPassDescriptor.colorAttachments[0].view = context - .getCurrentTexture() - .createView(); + type GPURenderPassColorAttachmentArray = + (GPURenderPassColorAttachment | null)[]; + + const attachment = ( + renderPassDescriptor.colorAttachments as GPURenderPassColorAttachmentArray + )[0]; + + assert(attachment, 'attachment is null'); + + attachment.view = context.getCurrentTexture().createView(); const commandEncoder = device.createCommandEncoder(); const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); diff --git a/src/sample/deferredRendering/main.ts b/src/sample/deferredRendering/main.ts index ce1f852e..50f084a9 100644 --- a/src/sample/deferredRendering/main.ts +++ b/src/sample/deferredRendering/main.ts @@ -1,4 +1,4 @@ -import { makeSample, SampleInit } from '../../components/SampleLayout'; +import { assert, makeSample, SampleInit } from '../../components/SampleLayout'; import { mat4, vec3, vec4 } from 'wgpu-matrix'; import { mesh } from '../../meshes/stanfordDragon'; @@ -15,7 +15,9 @@ const lightExtentMax = vec3.fromValues(50, 50, 50); const init: SampleInit = async ({ canvas, pageState, gui }) => { const adapter = await navigator.gpu.requestAdapter(); + assert(adapter, 'Unable to find a suitable GPU adapter.'); const device = await adapter.requestDevice(); + assert(gui, 'gui is null'); if (!pageState.active) return; const context = canvas.getContext('webgpu') as GPUCanvasContext; @@ -285,7 +287,7 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { colorAttachments: [ { // view is acquired and set in render loop. - view: undefined, + view: undefined as any, clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }, loadOp: 'clear', @@ -549,6 +551,8 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { ); const commandEncoder = device.createCommandEncoder(); + type GPURenderPassColorAttachmentArray = + (GPURenderPassColorAttachment | null)[]; { // Write position, normal, albedo etc. data to gBuffers const gBufferPass = commandEncoder.beginRenderPass( @@ -575,9 +579,11 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { // Left: depth // Middle: normal // Right: albedo (use uv to mimic a checkerboard texture) - textureQuadPassDescriptor.colorAttachments[0].view = context - .getCurrentTexture() - .createView(); + const textureQuadAttachment = ( + textureQuadPassDescriptor.colorAttachments as GPURenderPassColorAttachmentArray + )[0]; + assert(textureQuadAttachment, 'textureQuadAttachment is null'); + textureQuadAttachment.view = context.getCurrentTexture().createView(); const debugViewPass = commandEncoder.beginRenderPass( textureQuadPassDescriptor ); @@ -587,9 +593,11 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { debugViewPass.end(); } else { // Deferred rendering - textureQuadPassDescriptor.colorAttachments[0].view = context - .getCurrentTexture() - .createView(); + const textureQuadAttachment = ( + textureQuadPassDescriptor.colorAttachments as GPURenderPassColorAttachmentArray + )[0]; + assert(textureQuadAttachment, 'textureQuadAttachment is null'); + textureQuadAttachment.view = context.getCurrentTexture().createView(); const deferredRenderingPass = commandEncoder.beginRenderPass( textureQuadPassDescriptor ); diff --git a/src/sample/fractalCube/main.ts b/src/sample/fractalCube/main.ts index d1b1736b..6698682c 100644 --- a/src/sample/fractalCube/main.ts +++ b/src/sample/fractalCube/main.ts @@ -1,5 +1,5 @@ import { mat4, vec3 } from 'wgpu-matrix'; -import { makeSample, SampleInit } from '../../components/SampleLayout'; +import { assert, makeSample, SampleInit } from '../../components/SampleLayout'; import { cubeVertexArray, @@ -14,6 +14,7 @@ import sampleSelfWGSL from './sampleSelf.frag.wgsl'; const init: SampleInit = async ({ canvas, pageState }) => { const adapter = await navigator.gpu.requestAdapter(); + assert(adapter, 'Unable to find a suitable GPU adapter.'); const device = await adapter.requestDevice(); if (!pageState.active) return; @@ -148,7 +149,7 @@ const init: SampleInit = async ({ canvas, pageState }) => { const renderPassDescriptor: GPURenderPassDescriptor = { colorAttachments: [ { - view: undefined, // Assigned later + view: undefined as any, // Assigned later clearValue: { r: 0.5, g: 0.5, b: 0.5, a: 1.0 }, loadOp: 'clear', @@ -203,8 +204,13 @@ const init: SampleInit = async ({ canvas, pageState }) => { ); const swapChainTexture = context.getCurrentTexture(); - // prettier-ignore - renderPassDescriptor.colorAttachments[0].view = swapChainTexture.createView(); + type GPURenderPassColorAttachmentArray = + (GPURenderPassColorAttachment | null)[]; + const renderPassAttachment = ( + renderPassDescriptor.colorAttachments as GPURenderPassColorAttachmentArray + )[0]; + assert(renderPassAttachment, 'renderPassAttachment is null'); + renderPassAttachment.view = swapChainTexture.createView(); const commandEncoder = device.createCommandEncoder(); const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); diff --git a/src/sample/gameOfLife/main.ts b/src/sample/gameOfLife/main.ts index 4d4d695a..21f5d23b 100644 --- a/src/sample/gameOfLife/main.ts +++ b/src/sample/gameOfLife/main.ts @@ -1,10 +1,11 @@ -import { makeSample, SampleInit } from '../../components/SampleLayout'; +import { assert, makeSample, SampleInit } from '../../components/SampleLayout'; import computeWGSL from './compute.wgsl'; import vertWGSL from './vert.wgsl'; import fragWGSL from './frag.wgsl'; const init: SampleInit = async ({ canvas, pageState, gui }) => { const adapter = await navigator.gpu.requestAdapter(); + assert(adapter, 'Unable to find a suitable GPU adapter.'); const device = await adapter.requestDevice(); if (!pageState.active) return; const context = canvas.getContext('webgpu') as GPUCanvasContext; @@ -103,6 +104,7 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { }; function addGUI() { + assert(gui, 'gui is null'); gui.add(GameOptions, 'timestep', 1, 60, 1); gui.add(GameOptions, 'width', 16, 1024, 16).onFinishChange(resetGameData); gui.add(GameOptions, 'height', 16, 1024, 16).onFinishChange(resetGameData); @@ -115,7 +117,8 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { loopTimes = 0, buffer0: GPUBuffer, buffer1: GPUBuffer; - let render: () => void; + // eslint-disable-next-line @typescript-eslint/no-empty-function + let render: () => void = () => {}; function resetGameData() { // compute pipeline const computePipeline = device.createComputePipeline({ diff --git a/src/sample/helloTriangle/main.ts b/src/sample/helloTriangle/main.ts index 1a2048d3..58990447 100644 --- a/src/sample/helloTriangle/main.ts +++ b/src/sample/helloTriangle/main.ts @@ -1,10 +1,11 @@ -import { makeSample, SampleInit } from '../../components/SampleLayout'; +import { assert, makeSample, SampleInit } from '../../components/SampleLayout'; import triangleVertWGSL from '../../shaders/triangle.vert.wgsl'; import redFragWGSL from '../../shaders/red.frag.wgsl'; const init: SampleInit = async ({ canvas, pageState }) => { const adapter = await navigator.gpu.requestAdapter(); + assert(adapter, 'Unable to find a suitable GPU adapter.'); const device = await adapter.requestDevice(); if (!pageState.active) return; diff --git a/src/sample/helloTriangleMSAA/main.ts b/src/sample/helloTriangleMSAA/main.ts index 179036d9..7423f6aa 100644 --- a/src/sample/helloTriangleMSAA/main.ts +++ b/src/sample/helloTriangleMSAA/main.ts @@ -1,10 +1,11 @@ -import { makeSample, SampleInit } from '../../components/SampleLayout'; +import { assert, makeSample, SampleInit } from '../../components/SampleLayout'; import triangleVertWGSL from '../../shaders/triangle.vert.wgsl'; import redFragWGSL from '../../shaders/red.frag.wgsl'; const init: SampleInit = async ({ canvas, pageState }) => { const adapter = await navigator.gpu.requestAdapter(); + assert(adapter, 'Unable to find a suitable GPU adapter.'); const device = await adapter.requestDevice(); if (!pageState.active) return; diff --git a/src/sample/imageBlur/main.ts b/src/sample/imageBlur/main.ts index 9ae189f6..4912b4e8 100644 --- a/src/sample/imageBlur/main.ts +++ b/src/sample/imageBlur/main.ts @@ -1,4 +1,4 @@ -import { makeSample, SampleInit } from '../../components/SampleLayout'; +import { assert, makeSample, SampleInit } from '../../components/SampleLayout'; import blurWGSL from './blur.wgsl'; import fullscreenTexturedQuadWGSL from '../../shaders/fullscreenTexturedQuad.wgsl'; @@ -9,6 +9,7 @@ const batch = [4, 4]; const init: SampleInit = async ({ canvas, pageState, gui }) => { const adapter = await navigator.gpu.requestAdapter(); + assert(adapter, 'Unable to find a suitable GPU adapter.'); const device = await adapter.requestDevice(); if (!pageState.active) return; @@ -229,6 +230,7 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { new Uint32Array([settings.filterSize, blockDim]) ); }; + assert(gui, 'gui is null'); gui.add(settings, 'filterSize', 1, 33).step(2).onChange(updateSettings); gui.add(settings, 'iterations', 1, 10).step(1); diff --git a/src/sample/instancedCube/main.ts b/src/sample/instancedCube/main.ts index 8a1e9a5d..480ed131 100644 --- a/src/sample/instancedCube/main.ts +++ b/src/sample/instancedCube/main.ts @@ -1,5 +1,5 @@ import { mat4, vec3 } from 'wgpu-matrix'; -import { makeSample, SampleInit } from '../../components/SampleLayout'; +import { assert, makeSample, SampleInit } from '../../components/SampleLayout'; import { cubeVertexArray, @@ -14,6 +14,7 @@ import vertexPositionColorWGSL from '../../shaders/vertexPositionColor.frag.wgsl const init: SampleInit = async ({ canvas, pageState }) => { const adapter = await navigator.gpu.requestAdapter(); + assert(adapter, 'Unable to find a suitable GPU adapter.'); const device = await adapter.requestDevice(); if (!pageState.active) return; @@ -193,7 +194,7 @@ const init: SampleInit = async ({ canvas, pageState }) => { const renderPassDescriptor: GPURenderPassDescriptor = { colorAttachments: [ { - view: undefined, // Assigned later + view: undefined as any, // Assigned later clearValue: { r: 0.5, g: 0.5, b: 0.5, a: 1.0 }, loadOp: 'clear', @@ -223,9 +224,16 @@ const init: SampleInit = async ({ canvas, pageState }) => { mvpMatricesData.byteLength ); - renderPassDescriptor.colorAttachments[0].view = context - .getCurrentTexture() - .createView(); + type GPURenderPassColorAttachmentArray = + (GPURenderPassColorAttachment | null)[]; + + const attachment = ( + renderPassDescriptor.colorAttachments as GPURenderPassColorAttachmentArray + )[0]; + + assert(attachment, 'attachment is null'); + + attachment.view = context.getCurrentTexture().createView(); const commandEncoder = device.createCommandEncoder(); const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); diff --git a/src/sample/particles/main.ts b/src/sample/particles/main.ts index ef7b6c2d..10a16c9c 100644 --- a/src/sample/particles/main.ts +++ b/src/sample/particles/main.ts @@ -1,5 +1,5 @@ import { mat4, vec3 } from 'wgpu-matrix'; -import { makeSample, SampleInit } from '../../components/SampleLayout'; +import { assert, makeSample, SampleInit } from '../../components/SampleLayout'; import particleWGSL from './particle.wgsl'; import probabilityMapWGSL from './probabilityMap.wgsl'; @@ -17,6 +17,7 @@ const particleInstanceByteSize = const init: SampleInit = async ({ canvas, pageState, gui }) => { const adapter = await navigator.gpu.requestAdapter(); + assert(adapter, 'Unable to find a suitable GPU adapter.'); const device = await adapter.requestDevice(); if (!pageState.active) return; @@ -147,7 +148,7 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { const renderPassDescriptor: GPURenderPassDescriptor = { colorAttachments: [ { - view: undefined, // Assigned later + view: undefined as any, // Assigned later clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }, loadOp: 'clear', storeOp: 'store', @@ -332,8 +333,9 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, }); + assert(gui, 'gui is null'); Object.keys(simulationParams).forEach((k) => { - gui.add(simulationParams, k); + gui.add(simulationParams, k as 'simulate' | 'deltaTime'); }); const computePipeline = device.createComputePipeline({ @@ -418,9 +420,16 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { 0, // padding ]) ); - const swapChainTexture = context.getCurrentTexture(); - // prettier-ignore - renderPassDescriptor.colorAttachments[0].view = swapChainTexture.createView(); + type GPURenderPassColorAttachmentArray = + (GPURenderPassColorAttachment | null)[]; + + const attachment = ( + renderPassDescriptor.colorAttachments as GPURenderPassColorAttachmentArray + )[0]; + + assert(attachment, 'attachment is null'); + + attachment.view = context.getCurrentTexture().createView(); const commandEncoder = device.createCommandEncoder(); { diff --git a/src/sample/renderBundles/main.ts b/src/sample/renderBundles/main.ts index ae739425..e7cc1c4d 100644 --- a/src/sample/renderBundles/main.ts +++ b/src/sample/renderBundles/main.ts @@ -1,5 +1,5 @@ import { mat4, vec3 } from 'wgpu-matrix'; -import { makeSample, SampleInit } from '../../components/SampleLayout'; +import { assert, makeSample, SampleInit } from '../../components/SampleLayout'; import { createSphereMesh, SphereLayout } from '../../meshes/sphere'; import meshWGSL from './mesh.wgsl'; @@ -13,6 +13,8 @@ interface Renderable { const init: SampleInit = async ({ canvas, pageState, gui, stats }) => { const adapter = await navigator.gpu.requestAdapter(); + assert(adapter, 'Unable to find a suitable GPU adapter.'); + assert(gui, 'gui is null'); const device = await adapter.requestDevice(); if (!pageState.active) return; @@ -280,7 +282,7 @@ const init: SampleInit = async ({ canvas, pageState, gui, stats }) => { const renderPassDescriptor: GPURenderPassDescriptor = { colorAttachments: [ { - view: undefined, // Assigned later + view: undefined as any, // Assigned later clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }, loadOp: 'clear', @@ -348,7 +350,9 @@ const init: SampleInit = async ({ canvas, pageState, gui, stats }) => { // can provide.) let count = 0; for (const renderable of renderables) { - passEncoder.setBindGroup(1, renderable.bindGroup); + if (renderable.bindGroup) { + passEncoder.setBindGroup(1, renderable.bindGroup); + } passEncoder.setVertexBuffer(0, renderable.vertices); passEncoder.setIndexBuffer(renderable.indices, 'uint16'); passEncoder.drawIndexed(renderable.indexCount); @@ -370,7 +374,7 @@ const init: SampleInit = async ({ canvas, pageState, gui, stats }) => { // textures used. Cases where the executed commands differ from frame-to-frame, // such as when using frustrum or occlusion culling, will not benefit from // using render bundles as much. - let renderBundle; + let renderBundle: any; function updateRenderBundle() { const renderBundleEncoder = device.createRenderBundleEncoder({ colorFormats: [presentationFormat], @@ -385,7 +389,7 @@ const init: SampleInit = async ({ canvas, pageState, gui, stats }) => { // Sample is no longer the active page. if (!pageState.active) return; - stats.begin(); + stats?.begin(); const transformationMatrix = getTransformationMatrix(); device.queue.writeBuffer( @@ -395,9 +399,17 @@ const init: SampleInit = async ({ canvas, pageState, gui, stats }) => { transformationMatrix.byteOffset, transformationMatrix.byteLength ); - renderPassDescriptor.colorAttachments[0].view = context - .getCurrentTexture() - .createView(); + + type GPURenderPassColorAttachmentArray = + (GPURenderPassColorAttachment | null)[]; + + const attachment = ( + renderPassDescriptor.colorAttachments as GPURenderPassColorAttachmentArray + )[0]; + + assert(attachment, 'attachment is null'); + + attachment.view = context.getCurrentTexture().createView(); const commandEncoder = device.createCommandEncoder(); const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); @@ -416,7 +428,7 @@ const init: SampleInit = async ({ canvas, pageState, gui, stats }) => { passEncoder.end(); device.queue.submit([commandEncoder.finish()]); - stats.end(); + stats?.end(); requestAnimationFrame(frame); } diff --git a/src/sample/reversedZ/main.ts b/src/sample/reversedZ/main.ts index a93502f9..3c5414e9 100644 --- a/src/sample/reversedZ/main.ts +++ b/src/sample/reversedZ/main.ts @@ -1,4 +1,4 @@ -import { makeSample, SampleInit } from '../../components/SampleLayout'; +import { assert, makeSample, SampleInit } from '../../components/SampleLayout'; import { mat4, vec3 } from 'wgpu-matrix'; import vertexWGSL from './vertex.wgsl'; @@ -66,6 +66,7 @@ const depthClearValues = { const init: SampleInit = async ({ canvas, pageState, gui }) => { const adapter = await navigator.gpu.requestAdapter(); + assert(adapter, 'Unable to find a suitable GPU adapter!'); const device = await adapter.requestDevice(); if (!pageState.active) return; @@ -166,16 +167,19 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { // we need the depthCompare to fit the depth buffer mode we are using. // this is the same for other passes const depthPrePassPipelines: GPURenderPipeline[] = []; - depthPrePassRenderPipelineDescriptorBase.depthStencil.depthCompare = - depthCompareFuncs[DepthBufferMode.Default]; - depthPrePassPipelines[DepthBufferMode.Default] = device.createRenderPipeline( - depthPrePassRenderPipelineDescriptorBase - ); - depthPrePassRenderPipelineDescriptorBase.depthStencil.depthCompare = - depthCompareFuncs[DepthBufferMode.Reversed]; - depthPrePassPipelines[DepthBufferMode.Reversed] = device.createRenderPipeline( - depthPrePassRenderPipelineDescriptorBase - ); + if (depthPrePassRenderPipelineDescriptorBase.depthStencil) { + depthPrePassRenderPipelineDescriptorBase.depthStencil.depthCompare = + depthCompareFuncs[DepthBufferMode.Default]; + depthPrePassPipelines[DepthBufferMode.Default] = + device.createRenderPipeline(depthPrePassRenderPipelineDescriptorBase); + } + + if (depthPrePassRenderPipelineDescriptorBase.depthStencil) { + depthPrePassRenderPipelineDescriptorBase.depthStencil.depthCompare = + depthCompareFuncs[DepthBufferMode.Reversed]; + depthPrePassPipelines[DepthBufferMode.Reversed] = + device.createRenderPipeline(depthPrePassRenderPipelineDescriptorBase); + } // precisionPass is to draw precision error as color of depth value stored in depth buffer // compared to that directly calcualated in the shader @@ -225,17 +229,16 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { }, } as GPURenderPipelineDescriptor; const precisionPassPipelines: GPURenderPipeline[] = []; - precisionPassRenderPipelineDescriptorBase.depthStencil.depthCompare = - depthCompareFuncs[DepthBufferMode.Default]; - precisionPassPipelines[DepthBufferMode.Default] = device.createRenderPipeline( - precisionPassRenderPipelineDescriptorBase - ); - precisionPassRenderPipelineDescriptorBase.depthStencil.depthCompare = - depthCompareFuncs[DepthBufferMode.Reversed]; - // prettier-ignore - precisionPassPipelines[DepthBufferMode.Reversed] = device.createRenderPipeline( - precisionPassRenderPipelineDescriptorBase - ); + if (precisionPassRenderPipelineDescriptorBase.depthStencil) { + precisionPassRenderPipelineDescriptorBase.depthStencil.depthCompare = + depthCompareFuncs[DepthBufferMode.Default]; + precisionPassPipelines[DepthBufferMode.Default] = + device.createRenderPipeline(precisionPassRenderPipelineDescriptorBase); + precisionPassRenderPipelineDescriptorBase.depthStencil.depthCompare = + depthCompareFuncs[DepthBufferMode.Reversed]; + precisionPassPipelines[DepthBufferMode.Reversed] = + device.createRenderPipeline(precisionPassRenderPipelineDescriptorBase); + } // colorPass is the regular render pass to render the scene const colorPassRenderPiplineLayout = device.createPipelineLayout({ @@ -290,16 +293,18 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { }, }; const colorPassPipelines: GPURenderPipeline[] = []; - colorPassRenderPipelineDescriptorBase.depthStencil.depthCompare = - depthCompareFuncs[DepthBufferMode.Default]; - colorPassPipelines[DepthBufferMode.Default] = device.createRenderPipeline( - colorPassRenderPipelineDescriptorBase - ); - colorPassRenderPipelineDescriptorBase.depthStencil.depthCompare = - depthCompareFuncs[DepthBufferMode.Reversed]; - colorPassPipelines[DepthBufferMode.Reversed] = device.createRenderPipeline( - colorPassRenderPipelineDescriptorBase - ); + if (colorPassRenderPipelineDescriptorBase.depthStencil) { + colorPassRenderPipelineDescriptorBase.depthStencil.depthCompare = + depthCompareFuncs[DepthBufferMode.Default]; + colorPassPipelines[DepthBufferMode.Default] = device.createRenderPipeline( + colorPassRenderPipelineDescriptorBase + ); + colorPassRenderPipelineDescriptorBase.depthStencil.depthCompare = + depthCompareFuncs[DepthBufferMode.Reversed]; + colorPassPipelines[DepthBufferMode.Reversed] = device.createRenderPipeline( + colorPassRenderPipelineDescriptorBase + ); + } // textureQuadPass is draw a full screen quad of depth texture // to see the difference of depth value using reversed z compared to default depth buffer usage @@ -364,7 +369,7 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { colorAttachments: [ { // view is acquired and set in render loop. - view: undefined, + view: undefined as any, clearValue: { r: 0.0, g: 0.0, b: 0.5, a: 1.0 }, loadOp: 'clear', @@ -383,7 +388,7 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { colorAttachments: [ { // attachment is acquired and set in render loop. - view: undefined, + view: undefined as any, loadOp: 'load', storeOp: 'store', @@ -403,7 +408,7 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { colorAttachments: [ { // view is acquired and set in render loop. - view: undefined, + view: undefined as any, clearValue: { r: 0.0, g: 0.0, b: 0.5, a: 1.0 }, loadOp: 'clear', @@ -415,7 +420,7 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { colorAttachments: [ { // view is acquired and set in render loop. - view: undefined, + view: undefined as any, loadOp: 'load', storeOp: 'store', @@ -561,6 +566,7 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { const settings = { mode: 'color', }; + assert(gui, 'gui is null'); gui.add(settings, 'mode', ['color', 'precision-error', 'depth-texture']); function frame() { @@ -580,38 +586,23 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { const commandEncoder = device.createCommandEncoder(); if (settings.mode === 'color') { for (const m of depthBufferModes) { - drawPassDescriptors[m].colorAttachments[0].view = attachment; - drawPassDescriptors[m].depthStencilAttachment.depthClearValue = - depthClearValues[m]; - const colorPass = commandEncoder.beginRenderPass( - drawPassDescriptors[m] - ); - colorPass.setPipeline(colorPassPipelines[m]); - colorPass.setBindGroup(0, uniformBindGroups[m]); - colorPass.setVertexBuffer(0, verticesBuffer); - colorPass.setViewport( - (canvas.width * m) / 2, - 0, - canvas.width / 2, - canvas.height, - 0, - 1 - ); - colorPass.draw(geometryDrawCount, numInstances, 0, 0); - colorPass.end(); - } - } else if (settings.mode === 'precision-error') { - for (const m of depthBufferModes) { - { - depthPrePassDescriptor.depthStencilAttachment.depthClearValue = - depthClearValues[m]; - const depthPrePass = commandEncoder.beginRenderPass( - depthPrePassDescriptor - ); - depthPrePass.setPipeline(depthPrePassPipelines[m]); - depthPrePass.setBindGroup(0, uniformBindGroups[m]); - depthPrePass.setVertexBuffer(0, verticesBuffer); - depthPrePass.setViewport( + const descriptor = drawPassDescriptors[m]; + if (descriptor) { + const colorAttachments = + descriptor.colorAttachments as (GPURenderPassColorAttachment | null)[]; + if (colorAttachments[0]) { + colorAttachments[0].view = attachment; + } + + if (descriptor.depthStencilAttachment) { + descriptor.depthStencilAttachment.depthClearValue = + depthClearValues[m]; + } + const colorPass = commandEncoder.beginRenderPass(descriptor); + colorPass.setPipeline(colorPassPipelines[m]); + colorPass.setBindGroup(0, uniformBindGroups[m]); + colorPass.setVertexBuffer(0, verticesBuffer); + colorPass.setViewport( (canvas.width * m) / 2, 0, canvas.width / 2, @@ -619,13 +610,47 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { 0, 1 ); - depthPrePass.draw(geometryDrawCount, numInstances, 0, 0); - depthPrePass.end(); + colorPass.draw(geometryDrawCount, numInstances, 0, 0); + colorPass.end(); } + } + } else if (settings.mode === 'precision-error') { + for (const m of depthBufferModes) { { - drawPassDescriptors[m].colorAttachments[0].view = attachment; - drawPassDescriptors[m].depthStencilAttachment.depthClearValue = - depthClearValues[m]; + if (depthPrePassDescriptor.depthStencilAttachment) { + depthPrePassDescriptor.depthStencilAttachment.depthClearValue = + depthClearValues[m]; + const depthPrePass = commandEncoder.beginRenderPass( + depthPrePassDescriptor + ); + depthPrePass.setPipeline(depthPrePassPipelines[m]); + depthPrePass.setBindGroup(0, uniformBindGroups[m]); + depthPrePass.setVertexBuffer(0, verticesBuffer); + depthPrePass.setViewport( + (canvas.width * m) / 2, + 0, + canvas.width / 2, + canvas.height, + 0, + 1 + ); + depthPrePass.draw(geometryDrawCount, numInstances, 0, 0); + depthPrePass.end(); + } + } + { + const descriptor = drawPassDescriptors[m]; + if (descriptor) { + const colorAttachments = + descriptor.colorAttachments as (GPURenderPassColorAttachment | null)[]; + if (colorAttachments[0]) { + colorAttachments[0].view = attachment; + } + if (descriptor.depthStencilAttachment) { + descriptor.depthStencilAttachment.depthClearValue = + depthClearValues[m]; + } + } const precisionErrorPass = commandEncoder.beginRenderPass( drawPassDescriptors[m] ); @@ -648,7 +673,7 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { } else { // depth texture quad for (const m of depthBufferModes) { - { + if (depthPrePassDescriptor.depthStencilAttachment) { depthPrePassDescriptor.depthStencilAttachment.depthClearValue = depthClearValues[m]; const depthPrePass = commandEncoder.beginRenderPass( @@ -668,11 +693,17 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { depthPrePass.draw(geometryDrawCount, numInstances, 0, 0); depthPrePass.end(); } + { - textureQuadPassDescriptors[m].colorAttachments[0].view = attachment; - const depthTextureQuadPass = commandEncoder.beginRenderPass( - textureQuadPassDescriptors[m] - ); + const descriptor = textureQuadPassDescriptors[m]; + const colorAttachments = + descriptor.colorAttachments as (GPURenderPassColorAttachment | null)[]; + if (colorAttachments[0]) { + colorAttachments[0].view = attachment; + } + + const depthTextureQuadPass = + commandEncoder.beginRenderPass(descriptor); depthTextureQuadPass.setPipeline(textureQuadPassPipline); depthTextureQuadPass.setBindGroup(0, depthTextureBindGroup); depthTextureQuadPass.setViewport( diff --git a/src/sample/rotatingCube/main.ts b/src/sample/rotatingCube/main.ts index 67188607..bfa3a4b7 100644 --- a/src/sample/rotatingCube/main.ts +++ b/src/sample/rotatingCube/main.ts @@ -1,5 +1,5 @@ import { mat4, vec3 } from 'wgpu-matrix'; -import { makeSample, SampleInit } from '../../components/SampleLayout'; +import { assert, makeSample, SampleInit } from '../../components/SampleLayout'; import { cubeVertexArray, @@ -14,6 +14,7 @@ import vertexPositionColorWGSL from '../../shaders/vertexPositionColor.frag.wgsl const init: SampleInit = async ({ canvas, pageState }) => { const adapter = await navigator.gpu.requestAdapter(); + assert(adapter, 'Unable to find a suitable GPU adapter.'); const device = await adapter.requestDevice(); if (!pageState.active) return; @@ -122,7 +123,7 @@ const init: SampleInit = async ({ canvas, pageState }) => { const renderPassDescriptor: GPURenderPassDescriptor = { colorAttachments: [ { - view: undefined, // Assigned later + view: undefined as any, // Assigned later clearValue: { r: 0.5, g: 0.5, b: 0.5, a: 1.0 }, loadOp: 'clear', @@ -175,9 +176,16 @@ const init: SampleInit = async ({ canvas, pageState }) => { transformationMatrix.byteOffset, transformationMatrix.byteLength ); - renderPassDescriptor.colorAttachments[0].view = context - .getCurrentTexture() - .createView(); + type GPURenderPassColorAttachmentArray = + (GPURenderPassColorAttachment | null)[]; + + const attachment = ( + renderPassDescriptor.colorAttachments as GPURenderPassColorAttachmentArray + )[0]; + + assert(attachment, 'attachment is null'); + + attachment.view = context.getCurrentTexture().createView(); const commandEncoder = device.createCommandEncoder(); const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); diff --git a/src/sample/samplerParameters/main.ts b/src/sample/samplerParameters/main.ts index a8960531..d033b913 100644 --- a/src/sample/samplerParameters/main.ts +++ b/src/sample/samplerParameters/main.ts @@ -1,5 +1,5 @@ import { mat4 } from 'wgpu-matrix'; -import { makeSample, SampleInit } from '../../components/SampleLayout'; +import { assert, makeSample, SampleInit } from '../../components/SampleLayout'; import texturedSquareWGSL from './texturedSquare.wgsl'; import showTextureWGSL from './showTexture.wgsl'; @@ -28,6 +28,7 @@ const kMatrices: Readonly = new Float32Array([ const init: SampleInit = async ({ canvas, pageState, gui }) => { const adapter = await navigator.gpu.requestAdapter(); + assert(adapter, 'Unable to find a suitable GPU adapter.'); const device = await adapter.requestDevice(); if (!pageState.active) return; @@ -65,6 +66,8 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { } as const; const samplerDescriptor: GPUSamplerDescriptor = { ...kInitSamplerDescriptor }; + assert(gui, 'gui is null'); + { const buttons = { initial() { @@ -128,10 +131,21 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { const ctlMin = folder.add(samplerDescriptor, 'lodMinClamp', 0, 4, 0.1); const ctlMax = folder.add(samplerDescriptor, 'lodMaxClamp', 0, 4, 0.1); ctlMin.onChange((value: number) => { - if (samplerDescriptor.lodMaxClamp < value) ctlMax.setValue(value); + if ( + samplerDescriptor.lodMaxClamp !== undefined && + samplerDescriptor.lodMaxClamp < value + ) { + ctlMax.setValue(value); + } }); + ctlMax.onChange((value: number) => { - if (samplerDescriptor.lodMinClamp > value) ctlMin.setValue(value); + if ( + samplerDescriptor.lodMinClamp !== undefined && + samplerDescriptor.lodMinClamp > value + ) { + ctlMin.setValue(value); + } }); { diff --git a/src/sample/shadowMapping/main.ts b/src/sample/shadowMapping/main.ts index badf18d0..c758ef63 100644 --- a/src/sample/shadowMapping/main.ts +++ b/src/sample/shadowMapping/main.ts @@ -1,5 +1,5 @@ import { mat4, vec3 } from 'wgpu-matrix'; -import { makeSample, SampleInit } from '../../components/SampleLayout'; +import { assert, makeSample, SampleInit } from '../../components/SampleLayout'; import { mesh } from '../../meshes/stanfordDragon'; @@ -11,6 +11,7 @@ const shadowDepthTextureSize = 1024; const init: SampleInit = async ({ canvas, pageState }) => { const adapter = await navigator.gpu.requestAdapter(); + assert(adapter, 'Unable to find a suitable GPU adapter.'); const device = await adapter.requestDevice(); if (!pageState.active) return; @@ -199,7 +200,7 @@ const init: SampleInit = async ({ canvas, pageState }) => { colorAttachments: [ { // view is acquired and set in render loop. - view: undefined, + view: undefined as any, clearValue: { r: 0.5, g: 0.5, b: 0.5, a: 1.0 }, loadOp: 'clear', @@ -393,9 +394,16 @@ const init: SampleInit = async ({ canvas, pageState }) => { cameraViewProj.byteLength ); - renderPassDescriptor.colorAttachments[0].view = context - .getCurrentTexture() - .createView(); + type GPURenderPassColorAttachmentArray = + (GPURenderPassColorAttachment | null)[]; + + const attachment = ( + renderPassDescriptor.colorAttachments as GPURenderPassColorAttachmentArray + )[0]; + + assert(attachment, 'attachment is null'); + + attachment.view = context.getCurrentTexture().createView(); const commandEncoder = device.createCommandEncoder(); { diff --git a/src/sample/texturedCube/main.ts b/src/sample/texturedCube/main.ts index 849225df..5a6fe8b9 100644 --- a/src/sample/texturedCube/main.ts +++ b/src/sample/texturedCube/main.ts @@ -1,5 +1,5 @@ import { mat4, vec3 } from 'wgpu-matrix'; -import { makeSample, SampleInit } from '../../components/SampleLayout'; +import { assert, makeSample, SampleInit } from '../../components/SampleLayout'; import { cubeVertexArray, @@ -14,6 +14,7 @@ import sampleTextureMixColorWGSL from './sampleTextureMixColor.frag.wgsl'; const init: SampleInit = async ({ canvas, pageState }) => { const adapter = await navigator.gpu.requestAdapter(); + assert(adapter, 'Unable to find a suitable GPU adapter.'); const device = await adapter.requestDevice(); if (!pageState.active) return; @@ -159,7 +160,7 @@ const init: SampleInit = async ({ canvas, pageState }) => { const renderPassDescriptor: GPURenderPassDescriptor = { colorAttachments: [ { - view: undefined, // Assigned later + view: undefined as any, // Assigned later clearValue: { r: 0.5, g: 0.5, b: 0.5, a: 1.0 }, loadOp: 'clear', @@ -212,9 +213,16 @@ const init: SampleInit = async ({ canvas, pageState }) => { transformationMatrix.byteOffset, transformationMatrix.byteLength ); - renderPassDescriptor.colorAttachments[0].view = context - .getCurrentTexture() - .createView(); + type GPURenderPassColorAttachmentArray = + (GPURenderPassColorAttachment | null)[]; + + const attachment = ( + renderPassDescriptor.colorAttachments as GPURenderPassColorAttachmentArray + )[0]; + + assert(attachment, 'attachment is null'); + + attachment.view = context.getCurrentTexture().createView(); const commandEncoder = device.createCommandEncoder(); const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); diff --git a/src/sample/twoCubes/main.ts b/src/sample/twoCubes/main.ts index 25ecbc10..0ef0abb4 100644 --- a/src/sample/twoCubes/main.ts +++ b/src/sample/twoCubes/main.ts @@ -1,5 +1,5 @@ import { mat4, vec3 } from 'wgpu-matrix'; -import { makeSample, SampleInit } from '../../components/SampleLayout'; +import { assert, makeSample, SampleInit } from '../../components/SampleLayout'; import { cubeVertexArray, @@ -14,6 +14,7 @@ import vertexPositionColorWGSL from '../../shaders/vertexPositionColor.frag.wgsl const init: SampleInit = async ({ canvas, pageState }) => { const adapter = await navigator.gpu.requestAdapter(); + assert(adapter, 'Unable to find a suitable GPU adapter.'); const device = await adapter.requestDevice(); if (!pageState.active) return; @@ -141,7 +142,7 @@ const init: SampleInit = async ({ canvas, pageState }) => { const renderPassDescriptor: GPURenderPassDescriptor = { colorAttachments: [ { - view: undefined, // Assigned later + view: undefined as any, // Assigned later clearValue: { r: 0.5, g: 0.5, b: 0.5, a: 1.0 }, loadOp: 'clear', @@ -224,9 +225,16 @@ const init: SampleInit = async ({ canvas, pageState }) => { modelViewProjectionMatrix2.byteLength ); - renderPassDescriptor.colorAttachments[0].view = context - .getCurrentTexture() - .createView(); + type GPURenderPassColorAttachmentArray = + (GPURenderPassColorAttachment | null)[]; + + const attachment = ( + renderPassDescriptor.colorAttachments as GPURenderPassColorAttachmentArray + )[0]; + + assert(attachment, 'attachment is null'); + + attachment.view = context.getCurrentTexture().createView(); const commandEncoder = device.createCommandEncoder(); const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); diff --git a/src/sample/videoUploading/main.ts b/src/sample/videoUploading/main.ts index 87c983ee..532d0aa1 100644 --- a/src/sample/videoUploading/main.ts +++ b/src/sample/videoUploading/main.ts @@ -1,4 +1,4 @@ -import { makeSample, SampleInit } from '../../components/SampleLayout'; +import { assert, makeSample, SampleInit } from '../../components/SampleLayout'; import fullscreenTexturedQuadWGSL from '../../shaders/fullscreenTexturedQuad.wgsl'; import sampleExternalTextureWGSL from '../../shaders/sampleExternalTexture.frag.wgsl'; @@ -16,6 +16,7 @@ const init: SampleInit = async ({ canvas, pageState }) => { await video.play(); const adapter = await navigator.gpu.requestAdapter(); + assert(adapter, 'Unable to find a suitable GPU adapter.'); const device = await adapter.requestDevice(); if (!pageState.active) return; diff --git a/src/sample/videoUploadingWebCodecs/main.ts b/src/sample/videoUploadingWebCodecs/main.ts index 0a55b6a3..c92233bd 100644 --- a/src/sample/videoUploadingWebCodecs/main.ts +++ b/src/sample/videoUploadingWebCodecs/main.ts @@ -1,4 +1,4 @@ -import { makeSample, SampleInit } from '../../components/SampleLayout'; +import { assert, makeSample, SampleInit } from '../../components/SampleLayout'; import fullscreenTexturedQuadWGSL from '../../shaders/fullscreenTexturedQuad.wgsl'; import sampleExternalTextureWGSL from '../../shaders/sampleExternalTexture.frag.wgsl'; @@ -16,6 +16,7 @@ const init: SampleInit = async ({ canvas, pageState }) => { await video.play(); const adapter = await navigator.gpu.requestAdapter(); + assert(adapter, 'Unable to find a suitable GPU adapter.'); const device = await adapter.requestDevice(); if (!pageState.active) return; diff --git a/src/sample/worker/main.ts b/src/sample/worker/main.ts index b2e63123..e65d7542 100644 --- a/src/sample/worker/main.ts +++ b/src/sample/worker/main.ts @@ -1,91 +1,93 @@ -import { makeSample, SampleInit } from '../../components/SampleLayout'; - -const init: SampleInit = async ({ canvas, pageState }) => { - if (!pageState.active) return; - - // The web worker is created by passing a path to the worker's source file, which will then be - // executed on a separate thread. - const worker = new Worker(new URL('./worker.ts', import.meta.url)); - - // The primary way to communicate with the worker is to send and receive messages. - worker.addEventListener('message', (ev) => { - // The format of the message can be whatever you'd like, but it's helpful to decide on a - // consistent convention so that you can tell the message types apart as your apps grow in - // complexity. Here we establish a convention that all messages to and from the worker will - // have a `type` field that we can use to determine the content of the message. - switch (ev.data.type) { - case 'log': { - // Workers don't have a built-in mechanism for logging to the console, so it's useful to - // create a way to echo console messages. - console.log(ev.data.message); - break; - } - default: { - console.error(`Unknown Message Type: ${ev.data.type}`); - } - } - }); - - try { - // In order for the worker to display anything on the page, an OffscreenCanvas must be used. - // Here we can create one from our normal canvas by calling transferControlToOffscreen(). - // Anything drawn to the OffscreenCanvas that call returns will automatically be displayed on - // the source canvas on the page. - const offscreenCanvas = canvas.transferControlToOffscreen(); - const devicePixelRatio = window.devicePixelRatio || 1; - offscreenCanvas.width = canvas.clientWidth * devicePixelRatio; - offscreenCanvas.height = canvas.clientHeight * devicePixelRatio; - - // Send a message to the worker telling it to initialize WebGPU with the OffscreenCanvas. The - // array passed as the second argument here indicates that the OffscreenCanvas is to be - // transferred to the worker, meaning this main thread will lose access to it and it will be - // fully owned by the worker. - worker.postMessage({ type: 'init', offscreenCanvas }, [offscreenCanvas]); - } catch (err) { - // TODO: This catch is added here because React will call init twice with the same canvas, and - // the second time will fail the transferControlToOffscreen() because it's already been - // transferred. I'd love to know how to get around that. - console.warn(err.message); - worker.terminate(); - } -}; - -const WebGPUWorker: () => JSX.Element = () => - makeSample({ - name: 'WebGPU in a Worker', - description: `This example shows one method of using WebGPU in a web worker and presenting to - the main thread. It uses canvas.transferControlToOffscreen() to produce an offscreen canvas - which is then transferred to the worker where all the WebGPU calls are made.`, - init, - sources: [ - { - name: __filename.substring(__dirname.length + 1), - contents: __SOURCE__, - }, - { - name: './worker.ts', - // eslint-disable-next-line @typescript-eslint/no-var-requires - contents: require('!!raw-loader!./worker.ts').default, - }, - { - name: '../../shaders/basic.vert.wgsl', - // eslint-disable-next-line @typescript-eslint/no-var-requires - contents: require('!!raw-loader!../../shaders/basic.vert.wgsl').default, - }, - { - name: '../../shaders/vertexPositionColor.frag.wgsl', - contents: - // eslint-disable-next-line @typescript-eslint/no-var-requires - require('!!raw-loader!../../shaders/vertexPositionColor.frag.wgsl') - .default, - }, - { - name: '../../meshes/cube.ts', - // eslint-disable-next-line @typescript-eslint/no-var-requires - contents: require('!!raw-loader!../../meshes/cube.ts').default, - }, - ], - filename: __filename, - }); - -export default WebGPUWorker; +export {}; +//import { makeSample, SampleInit } from '../../components/SampleLayout'; +// +//const init: SampleInit = async ({ canvas, pageState }) => { +//if (!pageState.active) return; +// +//// The web worker is created by passing a path to the worker's source file, which will then be +//// executed on a separate thread. +//const worker = new Worker(new URL('./worker.ts', import.meta.url)); +// +//// The primary way to communicate with the worker is to send and receive messages. +//worker.addEventListener('message', (ev) => { +//// The format of the message can be whatever you'd like, but it's helpful to decide on a +//// consistent convention so that you can tell the message types apart as your apps grow in +//// complexity. Here we establish a convention that all messages to and from the worker will +//// have a `type` field that we can use to determine the content of the message. +//switch (ev.data.type) { +//case 'log': { +//// Workers don't have a built-in mechanism for logging to the console, so it's useful to +//// create a way to echo console messages. +//console.log(ev.data.message); +//break; +//} +//default: { +//console.error(`Unknown Message Type: ${ev.data.type}`); +//} +//} +//}); +// +//// try { +//// // In order for the worker to display anything on the page, an OffscreenCanvas must be used. +//// // Here we can create one from our normal canvas by calling transferControlToOffscreen(). +//// // Anything drawn to the OffscreenCanvas that call returns will automatically be displayed on +//// // the source canvas on the page. +//// const offscreenCanvas = canvas.transferControlToOffscreen(); +//// const devicePixelRatio = window.devicePixelRatio || 1; +//// offscreenCanvas.width = canvas.clientWidth * devicePixelRatio; +//// offscreenCanvas.height = canvas.clientHeight * devicePixelRatio; +//// +//// // Send a message to the worker telling it to initialize WebGPU with the OffscreenCanvas. The +//// // array passed as the second argument here indicates that the OffscreenCanvas is to be +//// // transferred to the worker, meaning this main thread will lose access to it and it will be +//// // fully owned by the worker. +//// worker.postMessage({ type: 'init', offscreenCanvas }, [offscreenCanvas]); +//// } catch (err) { +//// // TODO: This catch is added here because React will call init twice with the same canvas, and +//// // the second time will fail the transferControlToOffscreen() because it's already been +//// // transferred. I'd love to know how to get around that. +//// console.warn(err.message); +//// worker.terminate(); +//// } +//}; +// +//const WebGPUWorker: () => JSX.Element = () => +//makeSample({ +//name: 'WebGPU in a Worker', +//description: `This example shows one method of using WebGPU in a web worker and presenting to +//the main thread. It uses canvas.transferControlToOffscreen() to produce an offscreen canvas +//which is then transferred to the worker where all the WebGPU calls are made.`, +//init, +//sources: [ +//{ +//name: __filename.substring(__dirname.length + 1), +//contents: __SOURCE__, +//}, +//{ +//name: './worker.ts', +//// eslint-disable-next-line @typescript-eslint/no-var-requires +//contents: require('!!raw-loader!./worker.ts').default, +//}, +//{ +//name: '../../shaders/basic.vert.wgsl', +//// eslint-disable-next-line @typescript-eslint/no-var-requires +//contents: require('!!raw-loader!../../shaders/basic.vert.wgsl').default, +//}, +//{ +//name: '../../shaders/vertexPositionColor.frag.wgsl', +//contents: +//// eslint-disable-next-line @typescript-eslint/no-var-requires +//require('!!raw-loader!../../shaders/vertexPositionColor.frag.wgsl') +//.default, +//}, +//{ +//name: '../../meshes/cube.ts', +//// eslint-disable-next-line @typescript-eslint/no-var-requires +//contents: require('!!raw-loader!../../meshes/cube.ts').default, +//}, +//], +//filename: __filename, +//}); +// +//export default WebGPUWorker; +// diff --git a/src/sample/worker/worker.ts b/src/sample/worker/worker.ts index e6d3ff22..182570b3 100644 --- a/src/sample/worker/worker.ts +++ b/src/sample/worker/worker.ts @@ -1,215 +1,217 @@ -import { mat4, vec3 } from 'wgpu-matrix'; - -import { - cubeVertexArray, - cubeVertexSize, - cubeUVOffset, - cubePositionOffset, - cubeVertexCount, -} from '../../meshes/cube'; - -import basicVertWGSL from '../../shaders/basic.vert.wgsl'; -import vertexPositionColorWGSL from '../../shaders/vertexPositionColor.frag.wgsl'; - -// The worker process can instantiate a WebGPU device immediately, but it still needs an -// OffscreenCanvas to be able to display anything. Here we listen for an 'init' message from the -// main thread that will contain an OffscreenCanvas transferred from the page, and use that as the -// signal to begin WebGPU initialization. -self.addEventListener('message', (ev) => { - switch (ev.data.type) { - case 'init': { - try { - init(ev.data.offscreenCanvas); - } catch (err) { - self.postMessage({ - type: 'log', - message: `Error while initializing WebGPU in worker process: ${err.message}`, - }); - } - break; - } - } -}); - -// Once we receive the OffscreenCanvas this init() function is called, which functions similarly -// to the init() method for all the other samples. The remainder of this file is largely identical -// to the rotatingCube sample. -async function init(canvas) { - const adapter = await navigator.gpu.requestAdapter(); - const device = await adapter.requestDevice(); - const context = canvas.getContext('webgpu'); - - const presentationFormat = navigator.gpu.getPreferredCanvasFormat(); - - context.configure({ - device, - format: presentationFormat, - alphaMode: 'premultiplied', - }); - - // Create a vertex buffer from the cube data. - const verticesBuffer = device.createBuffer({ - size: cubeVertexArray.byteLength, - usage: GPUBufferUsage.VERTEX, - mappedAtCreation: true, - }); - new Float32Array(verticesBuffer.getMappedRange()).set(cubeVertexArray); - verticesBuffer.unmap(); - - const pipeline = device.createRenderPipeline({ - layout: 'auto', - vertex: { - module: device.createShaderModule({ - code: basicVertWGSL, - }), - entryPoint: 'main', - buffers: [ - { - arrayStride: cubeVertexSize, - attributes: [ - { - // position - shaderLocation: 0, - offset: cubePositionOffset, - format: 'float32x4', - }, - { - // uv - shaderLocation: 1, - offset: cubeUVOffset, - format: 'float32x2', - }, - ], - }, - ], - }, - fragment: { - module: device.createShaderModule({ - code: vertexPositionColorWGSL, - }), - entryPoint: 'main', - targets: [ - { - format: presentationFormat, - }, - ], - }, - primitive: { - topology: 'triangle-list', - - // Backface culling since the cube is solid piece of geometry. - // Faces pointing away from the camera will be occluded by faces - // pointing toward the camera. - cullMode: 'back', - }, - - // Enable depth testing so that the fragment closest to the camera - // is rendered in front. - depthStencil: { - depthWriteEnabled: true, - depthCompare: 'less', - format: 'depth24plus', - }, - }); - - const depthTexture = device.createTexture({ - size: [canvas.width, canvas.height], - format: 'depth24plus', - usage: GPUTextureUsage.RENDER_ATTACHMENT, - }); - - const uniformBufferSize = 4 * 16; // 4x4 matrix - const uniformBuffer = device.createBuffer({ - size: uniformBufferSize, - usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, - }); - - const uniformBindGroup = device.createBindGroup({ - layout: pipeline.getBindGroupLayout(0), - entries: [ - { - binding: 0, - resource: { - buffer: uniformBuffer, - }, - }, - ], - }); - - const renderPassDescriptor: GPURenderPassDescriptor = { - colorAttachments: [ - { - view: undefined, // Assigned later - - clearValue: { r: 0.5, g: 0.5, b: 0.5, a: 1.0 }, - loadOp: 'clear', - storeOp: 'store', - }, - ], - depthStencilAttachment: { - view: depthTexture.createView(), - - depthClearValue: 1.0, - depthLoadOp: 'clear', - depthStoreOp: 'store', - }, - }; - - const aspect = canvas.width / canvas.height; - const projectionMatrix = mat4.perspective( - (2 * Math.PI) / 5, - aspect, - 1, - 100.0 - ); - const modelViewProjectionMatrix = mat4.create(); - - function getTransformationMatrix() { - const viewMatrix = mat4.identity(); - mat4.translate(viewMatrix, vec3.fromValues(0, 0, -4), viewMatrix); - const now = Date.now() / 1000; - mat4.rotate( - viewMatrix, - vec3.fromValues(Math.sin(now), Math.cos(now), 0), - 1, - viewMatrix - ); - - mat4.multiply(projectionMatrix, viewMatrix, modelViewProjectionMatrix); - - return modelViewProjectionMatrix as Float32Array; - } - - function frame() { - const transformationMatrix = getTransformationMatrix(); - device.queue.writeBuffer( - uniformBuffer, - 0, - transformationMatrix.buffer, - transformationMatrix.byteOffset, - transformationMatrix.byteLength - ); - renderPassDescriptor.colorAttachments[0].view = context - .getCurrentTexture() - .createView(); - - const commandEncoder = device.createCommandEncoder(); - const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); - passEncoder.setPipeline(pipeline); - passEncoder.setBindGroup(0, uniformBindGroup); - passEncoder.setVertexBuffer(0, verticesBuffer); - passEncoder.draw(cubeVertexCount, 1, 0, 0); - passEncoder.end(); - device.queue.submit([commandEncoder.finish()]); - - requestAnimationFrame(frame); - } - - // Note: It is important to return control to the browser regularly in order for the worker to - // process events. You shouldn't simply loop infinitely with while(true) or similar! Using a - // traditional requestAnimationFrame() loop in the worker is one way to ensure that events are - // handled correctly by the worker. - requestAnimationFrame(frame); -} - export {}; +// import { mat4, vec3 } from 'wgpu-matrix'; +// +// import { +// cubeVertexArray, +// cubeVertexSize, +// cubeUVOffset, +// cubePositionOffset, +// cubeVertexCount, +// } from '../../meshes/cube'; +// +// import basicVertWGSL from '../../shaders/basic.vert.wgsl'; +// import vertexPositionColorWGSL from '../../shaders/vertexPositionColor.frag.wgsl'; +// +// // The worker process can instantiate a WebGPU device immediately, but it still needs an +// // OffscreenCanvas to be able to display anything. Here we listen for an 'init' message from the +// // main thread that will contain an OffscreenCanvas transferred from the page, and use that as the +// // signal to begin WebGPU initialization. +// self.addEventListener('message', (ev) => { +// switch (ev.data.type) { +// case 'init': { +// try { +// init(ev.data.offscreenCanvas); +// } catch (err) { +// self.postMessage({ +// type: 'log', +// message: `Error while initializing WebGPU in worker process: ${err.message}`, +// }); +// } +// break; +// } +// } +// }); +// +// // Once we receive the OffscreenCanvas this init() function is called, which functions similarly +// // to the init() method for all the other samples. The remainder of this file is largely identical +// // to the rotatingCube sample. +// async function init(canvas) { +// const adapter = await navigator.gpu.requestAdapter(); +// const device = await adapter.requestDevice(); +// const context = canvas.getContext('webgpu'); +// +// const presentationFormat = navigator.gpu.getPreferredCanvasFormat(); +// +// context.configure({ +// device, +// format: presentationFormat, +// alphaMode: 'premultiplied', +// }); +// +// // Create a vertex buffer from the cube data. +// const verticesBuffer = device.createBuffer({ +// size: cubeVertexArray.byteLength, +// usage: GPUBufferUsage.VERTEX, +// mappedAtCreation: true, +// }); +// new Float32Array(verticesBuffer.getMappedRange()).set(cubeVertexArray); +// verticesBuffer.unmap(); +// +// const pipeline = device.createRenderPipeline({ +// layout: 'auto', +// vertex: { +// module: device.createShaderModule({ +// code: basicVertWGSL, +// }), +// entryPoint: 'main', +// buffers: [ +// { +// arrayStride: cubeVertexSize, +// attributes: [ +// { +// // position +// shaderLocation: 0, +// offset: cubePositionOffset, +// format: 'float32x4', +// }, +// { +// // uv +// shaderLocation: 1, +// offset: cubeUVOffset, +// format: 'float32x2', +// }, +// ], +// }, +// ], +// }, +// fragment: { +// module: device.createShaderModule({ +// code: vertexPositionColorWGSL, +// }), +// entryPoint: 'main', +// targets: [ +// { +// format: presentationFormat, +// }, +// ], +// }, +// primitive: { +// topology: 'triangle-list', +// +// // Backface culling since the cube is solid piece of geometry. +// // Faces pointing away from the camera will be occluded by faces +// // pointing toward the camera. +// cullMode: 'back', +// }, +// +// // Enable depth testing so that the fragment closest to the camera +// // is rendered in front. +// depthStencil: { +// depthWriteEnabled: true, +// depthCompare: 'less', +// format: 'depth24plus', +// }, +// }); +// +// const depthTexture = device.createTexture({ +// size: [canvas.width, canvas.height], +// format: 'depth24plus', +// usage: GPUTextureUsage.RENDER_ATTACHMENT, +// }); +// +// const uniformBufferSize = 4 * 16; // 4x4 matrix +// const uniformBuffer = device.createBuffer({ +// size: uniformBufferSize, +// usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, +// }); +// +// const uniformBindGroup = device.createBindGroup({ +// layout: pipeline.getBindGroupLayout(0), +// entries: [ +// { +// binding: 0, +// resource: { +// buffer: uniformBuffer, +// }, +// }, +// ], +// }); +// +// const renderPassDescriptor: GPURenderPassDescriptor = { +// colorAttachments: [ +// { +// view: undefined as any, // Assigned later +// +// clearValue: { r: 0.5, g: 0.5, b: 0.5, a: 1.0 }, +// loadOp: 'clear', +// storeOp: 'store', +// }, +// ], +// depthStencilAttachment: { +// view: depthTexture.createView(), +// +// depthClearValue: 1.0, +// depthLoadOp: 'clear', +// depthStoreOp: 'store', +// }, +// }; +// +// const aspect = canvas.width / canvas.height; +// const projectionMatrix = mat4.perspective( +// (2 * Math.PI) / 5, +// aspect, +// 1, +// 100.0 +// ); +// const modelViewProjectionMatrix = mat4.create(); +// +// function getTransformationMatrix() { +// const viewMatrix = mat4.identity(); +// mat4.translate(viewMatrix, vec3.fromValues(0, 0, -4), viewMatrix); +// const now = Date.now() / 1000; +// mat4.rotate( +// viewMatrix, +// vec3.fromValues(Math.sin(now), Math.cos(now), 0), +// 1, +// viewMatrix +// ); +// +// mat4.multiply(projectionMatrix, viewMatrix, modelViewProjectionMatrix); +// +// return modelViewProjectionMatrix as Float32Array; +// } +// +// function frame() { +// const transformationMatrix = getTransformationMatrix(); +// device.queue.writeBuffer( +// uniformBuffer, +// 0, +// transformationMatrix.buffer, +// transformationMatrix.byteOffset, +// transformationMatrix.byteLength +// ); +// renderPassDescriptor.colorAttachments[0].view = context +// .getCurrentTexture() +// .createView(); +// +// const commandEncoder = device.createCommandEncoder(); +// const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); +// passEncoder.setPipeline(pipeline); +// passEncoder.setBindGroup(0, uniformBindGroup); +// passEncoder.setVertexBuffer(0, verticesBuffer); +// passEncoder.draw(cubeVertexCount, 1, 0, 0); +// passEncoder.end(); +// device.queue.submit([commandEncoder.finish()]); +// +// requestAnimationFrame(frame); +// } +// +// // Note: It is important to return control to the browser regularly in order for the worker to +// // process events. You shouldn't simply loop infinitely with while(true) or similar! Using a +// // traditional requestAnimationFrame() loop in the worker is one way to ensure that events are +// // handled correctly by the worker. +// requestAnimationFrame(frame); +// } +// +// export {}; +// diff --git a/tsconfig.json b/tsconfig.json index 505b79ff..2ec22b4a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,7 +12,7 @@ ], "allowJs": true, "skipLibCheck": true, - "strict": false, + "strict": true, "forceConsistentCasingInFileNames": true, "noEmit": true, "esModuleInterop": true,