From 0d3a666453aed88409597d60709080b2892bca0c Mon Sep 17 00:00:00 2001 From: Alan Baker Date: Mon, 22 Jun 2026 21:48:03 -0400 Subject: [PATCH 1/4] Add a buffer_view variant option to wireframe sample * Combines all model data into a single buffer * Beginning of the buffer has model metadata * vertex and index buffers for each draw set from offsets into the buffer * buffer_view shaders passed the whole buffer --- sample/wireframe/main.ts | 254 ++++++++++++++++++---- sample/wireframe/meta.ts | 1 + sample/wireframe/wireframeBufferView.wgsl | 112 ++++++++++ 3 files changed, 320 insertions(+), 47 deletions(-) create mode 100644 sample/wireframe/wireframeBufferView.wgsl diff --git a/sample/wireframe/main.ts b/sample/wireframe/main.ts index a82b427c..5eb1a10a 100644 --- a/sample/wireframe/main.ts +++ b/sample/wireframe/main.ts @@ -4,11 +4,15 @@ import { modelData } from './models'; import { randElement, randColor } from './utils'; import solidColorLitWGSL from './solidColorLit.wgsl'; import wireframeWGSL from './wireframe.wgsl'; +import wireframeBufferViewWGSL from './wireframeBufferView.wgsl'; import { quitIfWebGPUNotAvailableOrMissingFeatures, quitIfLimitLessThan, } from '../util'; +// TODO: Check WGSLLanguageFeatures when buffer_view is released. +const supportsBufferView = true; + const settings = { barycentricCoordinatesBased: false, thickness: 2, @@ -18,6 +22,7 @@ const settings = { depthBias: 1, depthBiasSlopeScale: 0.5, models: true, + bufferView: false, }; type TypedArrayView = Float32Array | Uint32Array; @@ -39,31 +44,13 @@ type Model = { vertexBuffer: GPUBuffer; indexBuffer: GPUBuffer; indexFormat: GPUIndexFormat; + modelIndex: number; vertexCount: number; + indexCount: number; + vertexOffset: number; + indexOffset: number; }; -function createVertexAndIndexBuffer( - device: GPUDevice, - { vertices, indices }: { vertices: Float32Array; indices: Uint32Array } -): Model { - const vertexBuffer = createBufferWithData( - device, - vertices, - GPUBufferUsage.VERTEX | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST - ); - const indexBuffer = createBufferWithData( - device, - indices, - GPUBufferUsage.INDEX | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST - ); - return { - vertexBuffer, - indexBuffer, - indexFormat: 'uint32', - vertexCount: indices.length, - }; -} - const adapter = await navigator.gpu?.requestAdapter({ featureLevel: 'compatibility', }); @@ -86,9 +73,79 @@ context.configure({ }); const depthFormat = 'depth24plus'; -const models = Object.values(modelData).map((data) => - createVertexAndIndexBuffer(device, data) -); +// Combined buffer contents: +// Metadata: a pair of offsets for each model (vertex and index) +// Model data: each model's vertices and indices +// +// | metadata | model 0 | ... | model N-1 +// --------------------------------------------------------------------------------------------------- +// | [, ...] | [vertices], [indices] | ... | [vertices], [indices] +let numModels = 0; +let size = 0; +Object.values(modelData).forEach((model) => { + size += model.vertices.length + model.indices.length + 4; + numModels++; +}); +size *= 4; +const backingBuffer = new ArrayBuffer(size); +const f32Buffer = new Float32Array(backingBuffer); +const u32Buffer = new Uint32Array(backingBuffer); +let modelIdx = 0; +let offset = numModels * 4 * 4; +Object.values(modelData).map((data) => { + u32Buffer[modelIdx * 4 + 0] = offset; + u32Buffer[modelIdx * 4 + 1] = data.vertices.byteLength; + f32Buffer.set(data.vertices, offset / 4); + offset += data.vertices.byteLength; + u32Buffer[modelIdx * 4 + 2] = offset; + u32Buffer[modelIdx * 4 + 3] = data.indices.byteLength; + u32Buffer.set(data.indices, offset / 4); + offset += data.indices.byteLength; + modelIdx++; +}); + +const buffer = device.createBuffer({ + size: backingBuffer.byteLength, + usage: + GPUBufferUsage.VERTEX | + GPUBufferUsage.INDEX | + GPUBufferUsage.COPY_DST | + GPUBufferUsage.STORAGE, +}); +device.queue.writeBuffer(buffer, 0, backingBuffer); + +function createModel( + idx: number, + { vertices, indices }: { vertices: Float32Array; indices: Uint32Array } +): Model { + const vBuffer = createBufferWithData( + device, + vertices, + GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST + ); + const iBuffer = createBufferWithData( + device, + indices, + GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST + ); + return { + vertexBuffer: vBuffer, + indexBuffer: iBuffer, + indexFormat: 'uint32', + modelIndex: idx, + vertexCount: vertices.length, + indexCount: indices.length, + vertexOffset: u32Buffer[4 * idx], + indexOffset: u32Buffer[4 * idx + 2], + }; +} + +modelIdx = 0; +const models = Object.values(modelData).map((data) => { + const thisIdx = modelIdx; + modelIdx++; + return createModel(thisIdx, data); +}); const litModule = device.createShaderModule({ code: solidColorLitWGSL, @@ -98,6 +155,13 @@ const wireframeModule = device.createShaderModule({ code: wireframeWGSL, }); +let wireframeBufferViewModule = wireframeModule; +if (supportsBufferView) { + wireframeBufferViewModule = device.createShaderModule({ + code: wireframeBufferViewWGSL, + }); +} + const litBindGroupLayout = device.createBindGroupLayout({ label: 'lit bind group layout', entries: [ @@ -182,6 +246,31 @@ const wireframePipeline = device.createRenderPipeline({ }, }); +let wireframeBufferViewPipeline = wireframePipeline; +if (supportsBufferView) { + wireframeBufferViewPipeline = device.createRenderPipeline({ + label: 'wireframe pipeline', + layout: 'auto', + vertex: { + module: wireframeBufferViewModule, + entryPoint: 'vsIndexedU32', + }, + fragment: { + module: wireframeBufferViewModule, + entryPoint: 'fs', + targets: [{ format: presentationFormat }], + }, + primitive: { + topology: 'line-list', + }, + depthStencil: { + depthWriteEnabled: true, + depthCompare: 'less-equal', + format: depthFormat, + }, + }); +} + const barycentricCoordinatesBasedWireframePipeline = device.createRenderPipeline({ label: 'barycentric coordinates based wireframe pipeline', @@ -219,6 +308,46 @@ const barycentricCoordinatesBasedWireframePipeline = }, }); +let wireframeBufferViewBarycentricsPipeline = + barycentricCoordinatesBasedWireframePipeline; +if (supportsBufferView) { + wireframeBufferViewBarycentricsPipeline = device.createRenderPipeline({ + label: 'barycentric coordinates based wireframe pipeline', + layout: 'auto', + vertex: { + module: wireframeBufferViewModule, + entryPoint: 'vsIndexedU32BarycentricCoordinateBasedLines', + }, + fragment: { + module: wireframeBufferViewModule, + 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, + }, + }); +} + type ObjectInfo = { worldViewProjectionMatrixValue: Float32Array; worldMatrixValue: Float32Array; @@ -276,30 +405,47 @@ for (let i = 0; i < numObjects; ++i) { usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, }); lineUniformValuesAsU32[0] = 6; // the array stride for positions for this model. + lineUniformValuesAsU32[3] = model.modelIndex; // the index of the model in the combined buffer. // We're creating 2 bindGroups, one for each pipeline. // We could create just one since they are identical. To do // so we'd have to manually create a bindGroupLayout. + let entries = [ + { binding: 0, resource: uniformBuffer }, + { binding: 1, resource: model.vertexBuffer }, + { binding: 2, resource: model.indexBuffer }, + { binding: 3, resource: lineUniformBuffer }, + ]; const wireframeBindGroup = device.createBindGroup({ layout: wireframePipeline.getBindGroupLayout(0), - entries: [ - { binding: 0, resource: uniformBuffer }, - { binding: 1, resource: model.vertexBuffer }, - { binding: 2, resource: model.indexBuffer }, - { binding: 3, resource: lineUniformBuffer }, - ], + entries, }); const barycentricCoordinatesBasedWireframeBindGroup = device.createBindGroup({ layout: barycentricCoordinatesBasedWireframePipeline.getBindGroupLayout(0), - entries: [ - { binding: 0, resource: uniformBuffer }, - { binding: 1, resource: model.vertexBuffer }, - { binding: 2, resource: model.indexBuffer }, - { binding: 3, resource: lineUniformBuffer }, - ], + entries, }); + // Create two more bindGroups for the bufferView variants of each pipeline. + let wireframeBufferViewBindGroup = wireframeBindGroup; + let wireframeBufferViewBarycentricsBindGroup = + barycentricCoordinatesBasedWireframeBindGroup; + if (supportsBufferView) { + entries = [ + { binding: 0, resource: uniformBuffer }, + { binding: 1, resource: buffer }, + { binding: 2, resource: lineUniformBuffer }, + ]; + wireframeBufferViewBindGroup = device.createBindGroup({ + layout: wireframeBufferViewPipeline.getBindGroupLayout(0), + entries, + }); + wireframeBufferViewBarycentricsBindGroup = device.createBindGroup({ + layout: wireframeBufferViewBarycentricsPipeline.getBindGroupLayout(0), + entries, + }); + } + objectInfos.push({ worldViewProjectionMatrixValue, worldMatrixValue, @@ -311,6 +457,8 @@ for (let i = 0; i < numObjects; ++i) { wireframeBindGroups: [ wireframeBindGroup, barycentricCoordinatesBasedWireframeBindGroup, + wireframeBufferViewBindGroup, + wireframeBufferViewBarycentricsBindGroup, ], model, }); @@ -339,6 +487,9 @@ gui.add(settings, 'barycentricCoordinatesBased').onChange(addRemoveGUI); gui.add(settings, 'lines'); gui.add(settings, 'models'); gui.add(settings, 'animate'); +if (supportsBufferView) { + gui.add(settings, 'bufferView'); +} const guis = []; function addRemoveGUI() { @@ -427,7 +578,13 @@ function render(ts: number) { worldViewProjectionMatrixValue, worldMatrixValue, litBindGroup, - model: { vertexBuffer, indexBuffer, indexFormat, vertexCount }, + model: { + indexFormat, + vertexCount, + indexCount, + vertexOffset, + indexOffset, + }, }, i ) => { @@ -453,10 +610,10 @@ function render(ts: number) { device.queue.writeBuffer(uniformBuffer, 0, uniformValues); if (settings.models) { - pass.setVertexBuffer(0, vertexBuffer); - pass.setIndexBuffer(indexBuffer, indexFormat); + pass.setVertexBuffer(0, buffer, vertexOffset, vertexCount * 4); + pass.setIndexBuffer(buffer, indexFormat, indexOffset, indexCount * 4); pass.setBindGroup(0, litBindGroup); - pass.drawIndexed(vertexCount); + pass.drawIndexed(indexCount); } } ); @@ -465,14 +622,17 @@ function render(ts: number) { // 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.bufferView + ? settings.barycentricCoordinatesBased + ? [3, 1, wireframeBufferViewBarycentricsPipeline] + : [2, 2, wireframeBufferViewPipeline] + : settings.barycentricCoordinatesBased + ? [1, 1, barycentricCoordinatesBasedWireframePipeline] + : [0, 2, wireframePipeline]; pass.setPipeline(pipeline); - objectInfos.forEach(({ wireframeBindGroups, model: { vertexCount } }) => { + objectInfos.forEach(({ wireframeBindGroups, model: { indexCount } }) => { pass.setBindGroup(0, wireframeBindGroups[bindGroupNdx]); - pass.draw(vertexCount * countMult); + pass.draw(indexCount * countMult); }); } diff --git a/sample/wireframe/meta.ts b/sample/wireframe/meta.ts index 4f9f0a27..37363308 100644 --- a/sample/wireframe/meta.ts +++ b/sample/wireframe/meta.ts @@ -12,6 +12,7 @@ export default { sources: [ { path: 'main.ts' }, { path: 'wireframe.wgsl' }, + { path: 'wireframeBufferView.wgsl' }, { path: 'solidColorLit.wgsl' }, { path: 'models.ts' }, { path: '../../meshes/box.ts' }, diff --git a/sample/wireframe/wireframeBufferView.wgsl b/sample/wireframe/wireframeBufferView.wgsl new file mode 100644 index 00000000..5dcd5c21 --- /dev/null +++ b/sample/wireframe/wireframeBufferView.wgsl @@ -0,0 +1,112 @@ +// The vertex shaders in this file make use of the buffer_view WGSL language +// feature. This feature allows us to reinterpret the contents of the buffer as +// multiple different types and/or sizes. We leverage it to combine all the +// model vertex and index information into a single buffer instead of having a +// buffer for each object. +requires buffer_view; + +struct Uniforms { + worldViewProjectionMatrix: mat4x4f, + worldMatrix: mat4x4f, + color: vec4f, +}; + +struct LineUniforms { + stride: u32, + thickness: f32, + alphaThreshold: f32, + modelIndex: u32 +}; + +struct VSOut { + @builtin(position) position: vec4f, +}; + +@group(0) @binding(0) var uni: Uniforms; +@group(0) @binding(1) var inputs: buffer; +@group(0) @binding(2) var line: LineUniforms; + +@vertex fn vsIndexedU32(@builtin(vertex_index) vNdx: u32) -> VSOut { + // Get the metadata for this model. + let metdata = *bufferView(&inputs, line.modelIndex * 16); + let vertexOffset = metdata[0]; + let vertexSize = metdata[1]; + let indexOffset = metdata[2]; + let indexSize = metdata[3]; + + // Create a pointer to vertices and indices for this model. + let positions = bufferArrayView>(&inputs, vertexOffset, vertexSize); + let indices = bufferArrayView>(&inputs, indexOffset, indexSize); + + // indices make a triangle so for every 3 indices we need to output + // 6 values + let triNdx = vNdx / 6; + // 0 1 0 1 0 1 0 1 0 1 0 1 vNdx % 2 + // 0 0 1 1 2 2 3 3 4 4 5 5 vNdx / 2 + // 0 1 1 2 2 3 3 4 4 5 5 6 vNdx % 2 + vNdx / 2 + // 0 1 1 2 2 0 0 1 1 2 2 0 (vNdx % 2 + vNdx / 2) % 3 + let vertNdx = (vNdx % 2 + vNdx / 2) % 3; + let index = (*indices)[triNdx * 3 + vertNdx]; + + let pNdx = index * line.stride; + let position = vec4f((*positions)[pNdx], (*positions)[pNdx + 1], (*positions)[pNdx + 2], 1); + + var vOut: VSOut; + vOut.position = uni.worldViewProjectionMatrix * position; + return vOut; +} + +@fragment fn fs() -> @location(0) vec4f { + return uni.color + vec4f(0.5); +} + +struct BarycentricCoordinateBasedVSOutput { + @builtin(position) position: vec4f, + @location(0) barycenticCoord: vec3f, +}; + +@vertex fn vsIndexedU32BarycentricCoordinateBasedLines( + @builtin(vertex_index) vNdx: u32 +) -> BarycentricCoordinateBasedVSOutput { + // Get the metadata for this model. + let metdata = *bufferView(&inputs, line.modelIndex * 16); + let vertexOffset = metdata[0]; + let vertexSize = metdata[1]; + let indexOffset = metdata[2]; + let indexSize = metdata[3]; + + // Create a pointer to vertices and indices for this model. + let positions = bufferArrayView>(&inputs, vertexOffset, vertexSize); + let indices = bufferArrayView>(&inputs, indexOffset, indexSize); + + let vertNdx = vNdx % 3; + let index = (*indices)[vNdx]; + + let pNdx = index * line.stride; + let position = vec4f((*positions)[pNdx], (*positions)[pNdx + 1], (*positions)[pNdx + 2], 1); + + var vsOut: BarycentricCoordinateBasedVSOutput; + vsOut.position = uni.worldViewProjectionMatrix * position; + + // emit a barycentric coordinate + vsOut.barycenticCoord = vec3f(0); + vsOut.barycenticCoord[vertNdx] = 1.0; + return vsOut; +} + +fn edgeFactor(bary: vec3f) -> f32 { + let d = fwidth(bary); + let a3 = smoothstep(vec3f(0.0), d * line.thickness, bary); + return min(min(a3.x, a3.y), a3.z); +} + +@fragment fn fsBarycentricCoordinateBasedLines( + v: BarycentricCoordinateBasedVSOutput +) -> @location(0) vec4f { + let a = 1.0 - edgeFactor(v.barycenticCoord); + if (a < line.alphaThreshold) { + discard; + } + + return vec4((uni.color.rgb + 0.5) * a, a); +} From f055a0e2d081c939771d439b4974a5945d50576c Mon Sep 17 00:00:00 2001 From: Alan Baker Date: Tue, 23 Jun 2026 10:02:32 -0400 Subject: [PATCH 2/4] changes for review --- sample/wireframe/main.ts | 207 ++++++++++------------ sample/wireframe/wireframeBufferView.wgsl | 8 +- 2 files changed, 94 insertions(+), 121 deletions(-) diff --git a/sample/wireframe/main.ts b/sample/wireframe/main.ts index 5eb1a10a..a546bca8 100644 --- a/sample/wireframe/main.ts +++ b/sample/wireframe/main.ts @@ -10,8 +10,8 @@ import { quitIfLimitLessThan, } from '../util'; -// TODO: Check WGSLLanguageFeatures when buffer_view is released. -const supportsBufferView = true; +const supportsBufferView = + navigator.gpu?.wgslLanguageFeatures.has('buffer_view'); const settings = { barycentricCoordinatesBased: false, @@ -90,18 +90,18 @@ size *= 4; const backingBuffer = new ArrayBuffer(size); const f32Buffer = new Float32Array(backingBuffer); const u32Buffer = new Uint32Array(backingBuffer); -let modelIdx = 0; let offset = numModels * 4 * 4; -Object.values(modelData).map((data) => { - u32Buffer[modelIdx * 4 + 0] = offset; - u32Buffer[modelIdx * 4 + 1] = data.vertices.byteLength; +Object.values(modelData).map((data, index) => { + const baseIndex = index * 4; + u32Buffer[baseIndex + 0] = offset; + u32Buffer[baseIndex + 1] = data.vertices.byteLength; f32Buffer.set(data.vertices, offset / 4); offset += data.vertices.byteLength; - u32Buffer[modelIdx * 4 + 2] = offset; - u32Buffer[modelIdx * 4 + 3] = data.indices.byteLength; + + u32Buffer[baseIndex + 2] = offset; + u32Buffer[baseIndex + 3] = data.indices.byteLength; u32Buffer.set(data.indices, offset / 4); offset += data.indices.byteLength; - modelIdx++; }); const buffer = device.createBuffer({ @@ -140,11 +140,8 @@ function createModel( }; } -modelIdx = 0; -const models = Object.values(modelData).map((data) => { - const thisIdx = modelIdx; - modelIdx++; - return createModel(thisIdx, data); +const models = Object.values(modelData).map((data, index) => { + return createModel(index, data); }); const litModule = device.createShaderModule({ @@ -155,12 +152,9 @@ const wireframeModule = device.createShaderModule({ code: wireframeWGSL, }); -let wireframeBufferViewModule = wireframeModule; -if (supportsBufferView) { - wireframeBufferViewModule = device.createShaderModule({ - code: wireframeBufferViewWGSL, - }); -} +const wireframeBufferViewModule = supportsBufferView + ? device.createShaderModule({ code: wireframeBufferViewWGSL }) + : wireframeModule; const litBindGroupLayout = device.createBindGroupLayout({ label: 'lit bind group layout', @@ -224,40 +218,22 @@ function rebuildLitPipeline() { } rebuildLitPipeline(); -const wireframePipeline = device.createRenderPipeline({ - label: 'wireframe pipeline', - layout: 'auto', - vertex: { - module: wireframeModule, - entryPoint: 'vsIndexedU32', - }, - fragment: { - module: wireframeModule, - entryPoint: 'fs', - targets: [{ format: presentationFormat }], - }, - primitive: { - topology: 'line-list', - }, - depthStencil: { - depthWriteEnabled: true, - depthCompare: 'less-equal', - format: depthFormat, - }, -}); - -let wireframeBufferViewPipeline = wireframePipeline; -if (supportsBufferView) { - wireframeBufferViewPipeline = device.createRenderPipeline({ - label: 'wireframe pipeline', +function createWireframePipeline( + label: string, + vsEntry: string, + fsEntry: string, + shaderModule: GPUShaderModule +): GPURenderPipeline { + return device.createRenderPipeline({ + label: label, layout: 'auto', vertex: { - module: wireframeBufferViewModule, - entryPoint: 'vsIndexedU32', + module: shaderModule, + entryPoint: vsEntry, }, fragment: { - module: wireframeBufferViewModule, - entryPoint: 'fs', + module: shaderModule, + entryPoint: fsEntry, targets: [{ format: presentationFormat }], }, primitive: { @@ -271,56 +247,38 @@ if (supportsBufferView) { }); } -const barycentricCoordinatesBasedWireframePipeline = - device.createRenderPipeline({ - label: 'barycentric coordinates based wireframe pipeline', +const wireframePipeline = createWireframePipeline( + 'wireframe pipeline', + 'vsIndexedU32', + 'fs', + wireframeModule +); + +const wireframeBufferViewPipeline = supportsBufferView + ? createWireframePipeline( + 'wireframe buffer_view pipeline', + 'vsIndexedU32BufferView', + 'fsBufferView', + wireframeBufferViewModule + ) + : wireframePipeline; + +function createBarycentricsWireframePipeline( + label: string, + vsEntry: string, + fsEntry: string, + shaderModule: GPUShaderModule +): GPURenderPipeline { + return device.createRenderPipeline({ + label: label, layout: 'auto', vertex: { - module: wireframeModule, - entryPoint: 'vsIndexedU32BarycentricCoordinateBasedLines', + module: shaderModule, + entryPoint: vsEntry, }, 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, - }, - }); - -let wireframeBufferViewBarycentricsPipeline = - barycentricCoordinatesBasedWireframePipeline; -if (supportsBufferView) { - wireframeBufferViewBarycentricsPipeline = device.createRenderPipeline({ - label: 'barycentric coordinates based wireframe pipeline', - layout: 'auto', - vertex: { - module: wireframeBufferViewModule, - entryPoint: 'vsIndexedU32BarycentricCoordinateBasedLines', - }, - fragment: { - module: wireframeBufferViewModule, - entryPoint: 'fsBarycentricCoordinateBasedLines', + module: shaderModule, + entryPoint: fsEntry, targets: [ { format: presentationFormat, @@ -347,6 +305,22 @@ if (supportsBufferView) { }, }); } +const barycentricCoordinatesBasedWireframePipeline = + createBarycentricsWireframePipeline( + 'barycentric coordinates based wireframe pipeline', + 'vsIndexedU32BarycentricCoordinateBasedLines', + 'fsBarycentricCoordinateBasedLines', + wireframeModule + ); + +const wireframeBufferViewBarycentricsPipeline = supportsBufferView + ? createBarycentricsWireframePipeline( + 'barycentric coordinates based wireframe buffer_view pipeline', + 'vsIndexedU32BarycentricBufferView', + 'fsBarycentricBufferView', + wireframeBufferViewModule + ) + : barycentricCoordinatesBasedWireframePipeline; type ObjectInfo = { worldViewProjectionMatrixValue: Float32Array; @@ -410,7 +384,7 @@ for (let i = 0; i < numObjects; ++i) { // We're creating 2 bindGroups, one for each pipeline. // We could create just one since they are identical. To do // so we'd have to manually create a bindGroupLayout. - let entries = [ + const bgEntries = [ { binding: 0, resource: uniformBuffer }, { binding: 1, resource: model.vertexBuffer }, { binding: 2, resource: model.indexBuffer }, @@ -418,33 +392,32 @@ for (let i = 0; i < numObjects; ++i) { ]; const wireframeBindGroup = device.createBindGroup({ layout: wireframePipeline.getBindGroupLayout(0), - entries, + entries: bgEntries, }); const barycentricCoordinatesBasedWireframeBindGroup = device.createBindGroup({ layout: barycentricCoordinatesBasedWireframePipeline.getBindGroupLayout(0), - entries, + entries: bgEntries, }); // Create two more bindGroups for the bufferView variants of each pipeline. - let wireframeBufferViewBindGroup = wireframeBindGroup; - let wireframeBufferViewBarycentricsBindGroup = - barycentricCoordinatesBasedWireframeBindGroup; - if (supportsBufferView) { - entries = [ - { binding: 0, resource: uniformBuffer }, - { binding: 1, resource: buffer }, - { binding: 2, resource: lineUniformBuffer }, - ]; - wireframeBufferViewBindGroup = device.createBindGroup({ - layout: wireframeBufferViewPipeline.getBindGroupLayout(0), - entries, - }); - wireframeBufferViewBarycentricsBindGroup = device.createBindGroup({ - layout: wireframeBufferViewBarycentricsPipeline.getBindGroupLayout(0), - entries, - }); - } + const bufferViewBGEntries = [ + { binding: 0, resource: uniformBuffer }, + { binding: 1, resource: buffer }, + { binding: 2, resource: lineUniformBuffer }, + ]; + const wireframeBufferViewBindGroup = supportsBufferView + ? device.createBindGroup({ + layout: wireframeBufferViewPipeline.getBindGroupLayout(0), + entries: bufferViewBGEntries, + }) + : wireframeBindGroup; + const wireframeBufferViewBarycentricsBindGroup = supportsBufferView + ? device.createBindGroup({ + layout: wireframeBufferViewBarycentricsPipeline.getBindGroupLayout(0), + entries: bufferViewBGEntries, + }) + : barycentricCoordinatesBasedWireframeBindGroup; objectInfos.push({ worldViewProjectionMatrixValue, diff --git a/sample/wireframe/wireframeBufferView.wgsl b/sample/wireframe/wireframeBufferView.wgsl index 5dcd5c21..aa4ecdb0 100644 --- a/sample/wireframe/wireframeBufferView.wgsl +++ b/sample/wireframe/wireframeBufferView.wgsl @@ -26,7 +26,7 @@ struct VSOut { @group(0) @binding(1) var inputs: buffer; @group(0) @binding(2) var line: LineUniforms; -@vertex fn vsIndexedU32(@builtin(vertex_index) vNdx: u32) -> VSOut { +@vertex fn vsIndexedU32BufferView(@builtin(vertex_index) vNdx: u32) -> VSOut { // Get the metadata for this model. let metdata = *bufferView(&inputs, line.modelIndex * 16); let vertexOffset = metdata[0]; @@ -56,7 +56,7 @@ struct VSOut { return vOut; } -@fragment fn fs() -> @location(0) vec4f { +@fragment fn fsBufferView() -> @location(0) vec4f { return uni.color + vec4f(0.5); } @@ -65,7 +65,7 @@ struct BarycentricCoordinateBasedVSOutput { @location(0) barycenticCoord: vec3f, }; -@vertex fn vsIndexedU32BarycentricCoordinateBasedLines( +@vertex fn vsIndexedU32BarycentricBufferView( @builtin(vertex_index) vNdx: u32 ) -> BarycentricCoordinateBasedVSOutput { // Get the metadata for this model. @@ -100,7 +100,7 @@ fn edgeFactor(bary: vec3f) -> f32 { return min(min(a3.x, a3.y), a3.z); } -@fragment fn fsBarycentricCoordinateBasedLines( +@fragment fn fsBarycentricBufferView( v: BarycentricCoordinateBasedVSOutput ) -> @location(0) vec4f { let a = 1.0 - edgeFactor(v.barycenticCoord); From cb64d9f890a4f3f9e2030925ae777d2b48d498ec Mon Sep 17 00:00:00 2001 From: Alan Baker Date: Tue, 23 Jun 2026 12:44:37 -0400 Subject: [PATCH 3/4] change for review --- sample/wireframe/main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sample/wireframe/main.ts b/sample/wireframe/main.ts index a546bca8..f2186017 100644 --- a/sample/wireframe/main.ts +++ b/sample/wireframe/main.ts @@ -91,7 +91,7 @@ const backingBuffer = new ArrayBuffer(size); const f32Buffer = new Float32Array(backingBuffer); const u32Buffer = new Uint32Array(backingBuffer); let offset = numModels * 4 * 4; -Object.values(modelData).map((data, index) => { +Object.values(modelData).forEach((data, index) => { const baseIndex = index * 4; u32Buffer[baseIndex + 0] = offset; u32Buffer[baseIndex + 1] = data.vertices.byteLength; From 461ebf1cdc85cb5b8c53e506a8fce0a9f04a5689 Mon Sep 17 00:00:00 2001 From: Alan Baker Date: Tue, 23 Jun 2026 17:00:03 -0400 Subject: [PATCH 4/4] fix comment --- sample/wireframe/main.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/sample/wireframe/main.ts b/sample/wireframe/main.ts index f2186017..8b906504 100644 --- a/sample/wireframe/main.ts +++ b/sample/wireframe/main.ts @@ -74,12 +74,23 @@ context.configure({ const depthFormat = 'depth24plus'; // Combined buffer contents: -// Metadata: a pair of offsets for each model (vertex and index) +// Metadata: a vec4u of vertex offset and size, index offset and size. // Model data: each model's vertices and indices // // | metadata | model 0 | ... | model N-1 // --------------------------------------------------------------------------------------------------- // | [, ...] | [vertices], [indices] | ... | [vertices], [indices] +// +// Q: Why not just bind a subregion of the buffer as distinct bindings? +// A: We could, but each buffer would need to be aligned to storage buffer +// alignment (256B). With buffer_view, each access needs to be aligned. So each +// model's metadata needs to be 16B aligned and the vertices and indices for +// each model only need to be 4B aligned. +// +// Q: Do you need all that metadata? +// A: We could get away with just a pair of offsets if we switched to +// bufferView calls in the shaders, but using bufferArrayView adds some extra +// robustness by preventing reading into another array. let numModels = 0; let size = 0; Object.values(modelData).forEach((model) => {