diff --git a/sample/wireframe/main.ts b/sample/wireframe/main.ts index d664fcf9..c53145a7 100644 --- a/sample/wireframe/main.ts +++ b/sample/wireframe/main.ts @@ -1,4 +1,3 @@ -/* eslint-disable prettier/prettier */ import { mat4, mat3 } from 'wgpu-matrix'; import { GUI } from 'dat.gui'; import { modelData } from './models'; @@ -11,7 +10,7 @@ type TypedArrayView = Float32Array | Uint32Array; function createBufferWithData( device: GPUDevice, data: TypedArrayView, - usage: GPUBufferUsageFlags, + usage: GPUBufferUsageFlags ) { const buffer = device.createBuffer({ size: data.byteLength, @@ -30,7 +29,7 @@ type Model = { function createVertexAndIndexBuffer( device: GPUDevice, - { vertices, indices }: { vertices: Float32Array, indices: Uint32Array }, + { vertices, indices }: { vertices: Float32Array; indices: Uint32Array } ): Model { const vertexBuffer = createBufferWithData( device, @@ -65,8 +64,9 @@ context.configure({ }); const depthFormat = 'depth24plus'; - -const models = Object.values(modelData).map(data => createVertexAndIndexBuffer(device, data)); +const models = Object.values(modelData).map((data) => + createVertexAndIndexBuffer(device, data) +); const litModule = device.createShaderModule({ code: solidColorLitWGSL, @@ -137,41 +137,42 @@ const wireframePipeline = device.createRenderPipeline({ }, }); -const barycentricCoordinatesBasedWireframePipeline = device.createRenderPipeline({ - label: 'barycentric coordinates based wireframe pipeline', - layout: 'auto', - vertex: { - module: wireframeModule, - entryPoint: 'vsIndexedU32BarycentricCoordinateBasedLines', - }, - fragment: { - module: wireframeModule, - entryPoint: 'fsBarycentricCoordinateBasedLines', - targets: [ - { - format: presentationFormat, - blend: { - color: { - srcFactor: 'one', - dstFactor: 'one-minus-src-alpha', - }, - alpha: { - srcFactor: 'one', - dstFactor: 'one-minus-src-alpha', +const barycentricCoordinatesBasedWireframePipeline = + device.createRenderPipeline({ + label: 'barycentric coordinates based wireframe pipeline', + layout: 'auto', + vertex: { + module: wireframeModule, + entryPoint: 'vsIndexedU32BarycentricCoordinateBasedLines', + }, + fragment: { + module: wireframeModule, + entryPoint: 'fsBarycentricCoordinateBasedLines', + targets: [ + { + format: presentationFormat, + blend: { + color: { + srcFactor: 'one', + dstFactor: 'one-minus-src-alpha', + }, + alpha: { + srcFactor: 'one', + dstFactor: 'one-minus-src-alpha', + }, }, }, - }, - ], - }, - primitive: { - topology: 'triangle-list', - }, - depthStencil: { - depthWriteEnabled: true, - depthCompare: 'less-equal', - format: depthFormat, - }, -}); + ], + }, + primitive: { + topology: 'triangle-list', + }, + depthStencil: { + depthWriteEnabled: true, + depthCompare: 'less-equal', + format: depthFormat, + }, + }); type ObjectInfo = { worldViewProjectionMatrixValue: Float32Array; @@ -201,7 +202,8 @@ for (let i = 0; i < numObjects; ++i) { const kColorOffset = 32; const worldViewProjectionMatrixValue = uniformValues.subarray( kWorldViewProjectionMatrixOffset, - kWorldViewProjectionMatrixOffset + 16); + kWorldViewProjectionMatrixOffset + 16 + ); const worldMatrixValue = uniformValues.subarray( kWorldMatrixOffset, kWorldMatrixOffset + 15 @@ -220,7 +222,7 @@ for (let i = 0; i < numObjects; ++i) { // Note: We're making one lineUniformBuffer per object. // This is only because stride might be different per object. // In this sample stride is the same across all objects so - // we could have made just a single shared uniform buffer for + // we could have made just a single shared uniform buffer for // these settings. const lineUniformValues = new Float32Array(3 + 1); const lineUniformValuesAsU32 = new Uint32Array(lineUniformValues.buffer); @@ -228,7 +230,7 @@ for (let i = 0; i < numObjects; ++i) { size: lineUniformValues.byteLength, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, }); - lineUniformValuesAsU32[0] = 6; // the array stride for positions for this model. + lineUniformValuesAsU32[0] = 6; // the array stride for positions for this model. // We're creating 2 bindGroups, one for each pipeline. // We could create just one since they are identical. To do @@ -305,19 +307,16 @@ function addRemoveGUI() { if (settings.barycentricCoordinatesBased) { guis.push( gui.add(settings, 'thickness', 0.0, 10).onChange(updateThickness), - gui.add(settings, 'alphaThreshold', 0, 1).onChange(updateThickness), + gui.add(settings, 'alphaThreshold', 0, 1).onChange(updateThickness) ); } else { - guis.forEach(g => g.remove()); + guis.forEach((g) => g.remove()); guis.length = 0; } } function updateThickness() { - objectInfos.forEach(({ - lineUniformBuffer, - lineUniformValues, - }) => { + objectInfos.forEach(({ lineUniformBuffer, lineUniformValues }) => { lineUniformValues[1] = settings.thickness; lineUniformValues[2] = settings.alphaThreshold; device.queue.writeBuffer(lineUniformBuffer, 0, lineUniformValues); @@ -372,43 +371,56 @@ function render(time: number) { const pass = encoder.beginRenderPass(renderPassDescriptor); pass.setPipeline(litPipeline); - objectInfos.forEach(({ - uniformBuffer, - uniformValues, - worldViewProjectionMatrixValue, - worldMatrixValue, - litBindGroup, - model: { vertexBuffer, indexBuffer, indexFormat, vertexCount }, - }, i) => { - - const world = mat4.identity(); - mat4.translate(world, [0, 0, Math.sin(i * 3.721 + time * 0.1) * 200], world); - mat4.rotateX(world, i * 4.567, world); - mat4.rotateY(world, i * 2.967, world); - mat4.translate(world, [0, 0, Math.sin(i * 9.721 + time * 0.1) * 200], world); - mat4.rotateX(world, time * 0.53 + i, world); - - mat4.multiply(viewProjection, world, worldViewProjectionMatrixValue); - mat3.fromMat4(world, worldMatrixValue); - - // Upload our uniform values. - device.queue.writeBuffer(uniformBuffer, 0, uniformValues); - - if (settings.models) { - pass.setVertexBuffer(0, vertexBuffer); - pass.setIndexBuffer(indexBuffer, indexFormat); - pass.setBindGroup(0, litBindGroup); - pass.drawIndexed(vertexCount); + objectInfos.forEach( + ( + { + uniformBuffer, + uniformValues, + worldViewProjectionMatrixValue, + worldMatrixValue, + litBindGroup, + model: { vertexBuffer, indexBuffer, indexFormat, vertexCount }, + }, + i + ) => { + const world = mat4.identity(); + mat4.translate( + world, + [0, 0, Math.sin(i * 3.721 + time * 0.1) * 200], + world + ); + mat4.rotateX(world, i * 4.567, world); + mat4.rotateY(world, i * 2.967, world); + mat4.translate( + world, + [0, 0, Math.sin(i * 9.721 + time * 0.1) * 200], + world + ); + mat4.rotateX(world, time * 0.53 + i, world); + + mat4.multiply(viewProjection, world, worldViewProjectionMatrixValue); + mat3.fromMat4(world, worldMatrixValue); + + // Upload our uniform values. + device.queue.writeBuffer(uniformBuffer, 0, uniformValues); + + if (settings.models) { + pass.setVertexBuffer(0, vertexBuffer); + pass.setIndexBuffer(indexBuffer, indexFormat); + pass.setBindGroup(0, litBindGroup); + pass.drawIndexed(vertexCount); + } } - }); + ); if (settings.lines) { // Note: If we're using the line-list based pipeline then we need to // multiply the vertex count by 2 since we need to emit 6 vertices // for each triangle (3 edges). - const [bindGroupNdx, countMult, pipeline] = settings.barycentricCoordinatesBased - ? [1, 1, barycentricCoordinatesBasedWireframePipeline] - : [0, 2, wireframePipeline]; + const [bindGroupNdx, countMult, pipeline] = + settings.barycentricCoordinatesBased + ? [1, 1, barycentricCoordinatesBasedWireframePipeline] + : [0, 2, wireframePipeline]; pass.setPipeline(pipeline); objectInfos.forEach(({ wireframeBindGroups, model: { vertexCount } }) => { pass.setBindGroup(0, wireframeBindGroups[bindGroupNdx]); diff --git a/sample/wireframe/meta.ts b/sample/wireframe/meta.ts index 3cb04590..4f9f0a27 100644 --- a/sample/wireframe/meta.ts +++ b/sample/wireframe/meta.ts @@ -4,7 +4,7 @@ export default { This example demonstrates drawing a wireframe from triangles in 2 ways. Both use the vertex and index buffers as storage buffers and the use \`@builtin(vertex_index)\` to index the vertex data. One method generates 6 vertices per triangle and uses line-list to draw lines. - The other method draws triangles with a fragment shader that uses barycentric coordinates to draw edges. + The other method draws triangles with a fragment shader that uses barycentric coordinates to draw edges as detailed [here](https://web.archive.org/web/20130424093557/http://codeflow.org/entries/2012/aug/02/easy-wireframe-display-with-barycentric-coordinates/). `, diff --git a/sample/wireframe/utils.ts b/sample/wireframe/utils.ts index 54980422..0557de92 100644 --- a/sample/wireframe/utils.ts +++ b/sample/wireframe/utils.ts @@ -1,189 +1,3 @@ -type BindGroupBindingLayout = - | GPUBufferBindingLayout - | GPUTextureBindingLayout - | GPUSamplerBindingLayout - | GPUStorageTextureBindingLayout - | GPUExternalTextureBindingLayout; - -export type BindGroupsObjectsAndLayout = { - bindGroups: GPUBindGroup[]; - bindGroupLayout: GPUBindGroupLayout; -}; - -type ResourceTypeName = - | 'buffer' - | 'texture' - | 'sampler' - | 'externalTexture' - | 'storageTexture'; - -/** - * @param {number[]} bindings - The binding value of each resource in the bind group. - * @param {number[]} visibilities - The GPUShaderStage visibility of the resource at the corresponding index. - * @param {ResourceTypeName[]} resourceTypes - The resourceType at the corresponding index. - * @returns {BindGroupsObjectsAndLayout} An object containing an array of bindGroups and the bindGroupLayout they implement. - */ -export const createBindGroupDescriptor = ( - bindings: number[], - visibilities: number[], - resourceTypes: ResourceTypeName[], - resourceLayouts: BindGroupBindingLayout[], - resources: GPUBindingResource[][], - label: string, - device: GPUDevice -): BindGroupsObjectsAndLayout => { - // Create layout of each entry within a bindGroup - const layoutEntries: GPUBindGroupLayoutEntry[] = []; - for (let i = 0; i < bindings.length; i++) { - layoutEntries.push({ - binding: bindings[i], - visibility: visibilities[i % visibilities.length], - [resourceTypes[i]]: resourceLayouts[i], - }); - } - - // Apply entry layouts to bindGroupLayout - const bindGroupLayout = device.createBindGroupLayout({ - label: `${label}.bindGroupLayout`, - entries: layoutEntries, - }); - - // Create bindGroups that conform to the layout - const bindGroups: GPUBindGroup[] = []; - for (let i = 0; i < resources.length; i++) { - const groupEntries: GPUBindGroupEntry[] = []; - for (let j = 0; j < resources[0].length; j++) { - groupEntries.push({ - binding: j, - resource: resources[i][j], - }); - } - const newBindGroup = device.createBindGroup({ - label: `${label}.bindGroup${i}`, - layout: bindGroupLayout, - entries: groupEntries, - }); - bindGroups.push(newBindGroup); - } - - return { - bindGroups, - bindGroupLayout, - }; -}; - -export type ShaderKeyInterface = { - [K in T[number]]: number; -}; - -interface AttribAcc { - attributes: GPUVertexAttribute[]; - arrayStride: number; -} - -/** - * @param {GPUVertexFormat} vf - A valid GPUVertexFormat, representing a per-vertex value that can be passed to the vertex shader. - * @returns {number} The number of bytes present in the value to be passed. - */ -export const convertVertexFormatToBytes = (vf: GPUVertexFormat): number => { - const splitFormat = vf.split('x'); - const bytesPerElement = parseInt(splitFormat[0].replace(/[^0-9]/g, '')) / 8; - - const bytesPerVec = - bytesPerElement * - (splitFormat[1] !== undefined ? parseInt(splitFormat[1]) : 1); - - return bytesPerVec; -}; - -/** Creates a GPUVertexBuffer Layout that maps to an interleaved vertex buffer. - * @param {GPUVertexFormat[]} vertexFormats - An array of valid GPUVertexFormats. - * @returns {GPUVertexBufferLayout} A GPUVertexBufferLayout representing an interleaved vertex buffer. - */ -export const createVBuffer = ( - vertexFormats: GPUVertexFormat[] -): GPUVertexBufferLayout => { - const initialValue: AttribAcc = { attributes: [], arrayStride: 0 }; - - const vertexBuffer = vertexFormats.reduce( - (acc: AttribAcc, curr: GPUVertexFormat, idx: number) => { - const newAttribute: GPUVertexAttribute = { - shaderLocation: idx, - offset: acc.arrayStride, - format: curr, - }; - const nextOffset: number = - acc.arrayStride + convertVertexFormatToBytes(curr); - - const retVal: AttribAcc = { - attributes: [...acc.attributes, newAttribute], - arrayStride: nextOffset, - }; - return retVal; - }, - initialValue - ); - - const layout: GPUVertexBufferLayout = { - arrayStride: vertexBuffer.arrayStride, - attributes: vertexBuffer.attributes, - }; - - return layout; -}; - -export const create3DRenderPipeline = ( - device: GPUDevice, - label: string, - bgLayouts: GPUBindGroupLayout[], - vertexShader: string, - vBufferFormats: GPUVertexFormat[], - fragmentShader: string, - presentationFormat: GPUTextureFormat, - depthTest = false, - topology: GPUPrimitiveTopology = 'triangle-list', - cullMode: GPUCullMode = 'back' -) => { - const pipelineDescriptor: GPURenderPipelineDescriptor = { - label: `${label}.pipeline`, - layout: device.createPipelineLayout({ - label: `${label}.pipelineLayout`, - bindGroupLayouts: bgLayouts, - }), - vertex: { - module: device.createShaderModule({ - label: `${label}.vertexShader`, - code: vertexShader, - }), - buffers: - vBufferFormats.length !== 0 ? [createVBuffer(vBufferFormats)] : [], - }, - fragment: { - module: device.createShaderModule({ - label: `${label}.fragmentShader`, - code: fragmentShader, - }), - targets: [ - { - format: presentationFormat, - }, - ], - }, - primitive: { - topology: topology, - cullMode: cullMode, - }, - }; - if (depthTest) { - pipelineDescriptor.depthStencil = { - depthCompare: 'less', - depthWriteEnabled: true, - format: 'depth24plus', - }; - } - return device.createRenderPipeline(pipelineDescriptor); -}; - export function rand(min?: number, max?: number) { if (min === undefined) { max = 1; diff --git a/sample/wireframe/wireframe.wgsl b/sample/wireframe/wireframe.wgsl index 7c0e1341..3e6b8ba9 100644 --- a/sample/wireframe/wireframe.wgsl +++ b/sample/wireframe/wireframe.wgsl @@ -38,7 +38,7 @@ struct VSOut { // let twoIndices = indices[indexNdx / 2]; // indices is u32 but we want u16 // let index = (twoIndices >> ((indexNdx & 1) * 16)) & 0xFFFF; // - // * if you're not using indicies you could use this + // * if you're not using indices you could use this // // let index = triNdx * 3 + vertNdx; @@ -72,7 +72,7 @@ struct BarycentricCoordinateBasedVSOutput { // let twoIndices = indices[vNdx / 2]; // indices is u32 but we want u16 // let index = (twoIndices >> ((vNdx & 1) * 16)) & 0xFFFF; // - // * if you're not using indicies you could use this + // * if you're not using indices you could use this // // let index = vNdx; @@ -101,5 +101,6 @@ fn edgeFactor(bary: vec3f) -> f32 { if (a < line.alphaThreshold) { discard; } + return vec4((uni.color.rgb + 0.5) * a, a); } \ No newline at end of file