diff --git a/sample/wireframe/index.html b/sample/wireframe/index.html
new file mode 100644
index 00000000..5ab83229
--- /dev/null
+++ b/sample/wireframe/index.html
@@ -0,0 +1,30 @@
+
+
+
+
+
+ webgpu-samples: wireframe
+
+
+
+
+
+
+
+
diff --git a/sample/wireframe/main.ts b/sample/wireframe/main.ts
new file mode 100644
index 00000000..d65b3029
--- /dev/null
+++ b/sample/wireframe/main.ts
@@ -0,0 +1,336 @@
+/* eslint-disable prettier/prettier */
+import { mat4, mat3 } from 'wgpu-matrix';
+import { modelData } from './models';
+import solidColorLitWGSL from './solidColorLit.wgsl';
+import wireframeWGSL from './wireframe.wgsl';
+
+type TypedArrayView = Float32Array | Uint32Array;
+
+function createBufferWithData(
+ device: GPUDevice,
+ data: TypedArrayView,
+ usage: number
+) {
+ const buffer = device.createBuffer({
+ size: data.byteLength,
+ usage: usage,
+ });
+ device.queue.writeBuffer(buffer, 0, data);
+ return buffer;
+}
+
+type Model = {
+ vertexBuffer: GPUBuffer;
+ indexBuffer: GPUBuffer;
+ indexFormat: GPUIndexFormat;
+ vertexCount: 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();
+const device = await adapter.requestDevice();
+const canvas = document.querySelector('canvas') as HTMLCanvasElement;
+const context = canvas.getContext('webgpu') as GPUCanvasContext;
+const devicePixelRatio = window.devicePixelRatio;
+canvas.width = canvas.clientWidth * devicePixelRatio;
+canvas.height = canvas.clientHeight * devicePixelRatio;
+const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
+context.configure({
+ device,
+ format: presentationFormat,
+ alphaMode: 'premultiplied',
+});
+const depthFormat = 'depth24plus';
+
+
+const models = Object.values(modelData).map(data => createVertexAndIndexBuffer(device, data));
+
+function rand(min?: number, max?: number) {
+ if (min === undefined) {
+ max = 1;
+ min = 0;
+ } else if (max === undefined) {
+ max = min;
+ min = 0;
+ }
+ return Math.random() * (max - min) + min;
+}
+
+function randInt(min: number, max?: number) {
+ return Math.floor(rand(min, max));
+}
+
+function randColor() {
+ return [rand(), rand(), rand(), 1];
+}
+
+const litModule = device.createShaderModule({
+ code: solidColorLitWGSL,
+});
+
+const wireframeModule = device.createShaderModule({
+ code: wireframeWGSL,
+});
+
+const litPipeline = device.createRenderPipeline({
+ label: 'lit pipeline',
+ layout: 'auto',
+ vertex: {
+ module: litModule,
+ buffers: [
+ {
+ arrayStride: 6 * 4, // position, normal
+ attributes: [
+ {
+ // position
+ shaderLocation: 0,
+ offset: 0,
+ format: 'float32x3',
+ },
+ {
+ // normal
+ shaderLocation: 1,
+ offset: 3 * 4,
+ format: 'float32x3',
+ },
+ ],
+ },
+ ],
+ },
+ fragment: {
+ module: litModule,
+ targets: [{ format: presentationFormat }],
+ },
+ primitive: {
+ cullMode: 'back',
+ },
+ depthStencil: {
+ depthWriteEnabled: true,
+ depthCompare: 'less',
+ format: depthFormat,
+ },
+});
+
+const wireframePipeline = device.createRenderPipeline({
+ label: 'wireframe pipeline',
+ layout: 'auto',
+ vertex: {
+ module: wireframeModule,
+ entryPoint: 'vsIndexedU32',
+ },
+ fragment: {
+ module: wireframeModule,
+ targets: [{ format: presentationFormat }],
+ },
+ primitive: {
+ topology: 'line-list',
+ },
+ depthStencil: {
+ depthWriteEnabled: true,
+ depthCompare: 'less-equal',
+ format: depthFormat,
+ },
+});
+
+type ObjectInfo = {
+ worldViewProjectionMatrixValue: Float32Array;
+ worldMatrixValue: Float32Array;
+ uniformValues: Float32Array;
+ uniformBuffer: GPUBuffer;
+ litBindGroup: GPUBindGroup;
+ wireframeBindGroup: GPUBindGroup;
+ model: Model;
+};
+
+const objectInfos: ObjectInfo[] = [];
+
+const numObjects = 200;
+for (let i = 0; i < numObjects; ++i) {
+ // Make a uniform buffer and type array views
+ // for our uniforms.
+ const uniformValues = new Float32Array(16 + 16 + 4);
+ const uniformBuffer = device.createBuffer({
+ size: uniformValues.byteLength,
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
+ });
+ const kWorldViewProjectionMatrixOffset = 0;
+ const kWorldMatrixOffset = 16;
+ const kColorOffset = 32;
+ const worldViewProjectionMatrixValue = uniformValues.subarray(
+ kWorldViewProjectionMatrixOffset,
+ kWorldViewProjectionMatrixOffset + 16);
+ const worldMatrixValue = uniformValues.subarray(
+ kWorldMatrixOffset,
+ kWorldMatrixOffset + 15
+ );
+ const colorValue = uniformValues.subarray(kColorOffset, kColorOffset + 4);
+ colorValue.set(randColor());
+
+ const model = models[randInt(models.length)];
+
+ // Make a bind group for this uniform
+ const litBindGroup = device.createBindGroup({
+ layout: litPipeline.getBindGroupLayout(0),
+ entries: [{ binding: 0, resource: { buffer: uniformBuffer } }],
+ });
+
+ const strideValues = new Uint32Array(1 + 3);
+ const strideBuffer = device.createBuffer({
+ size: strideValues.byteLength,
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
+ });
+ strideValues[0] = 6;
+ device.queue.writeBuffer(strideBuffer, 0, strideValues);
+
+ const wireframeBindGroup = device.createBindGroup({
+ layout: wireframePipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: { buffer: uniformBuffer } },
+ { binding: 1, resource: { buffer: model.vertexBuffer } },
+ { binding: 2, resource: { buffer: model.indexBuffer } },
+ { binding: 3, resource: { buffer: strideBuffer } },
+ ],
+ });
+
+ objectInfos.push({
+ worldViewProjectionMatrixValue,
+ worldMatrixValue,
+ uniformValues,
+ uniformBuffer,
+ litBindGroup,
+ wireframeBindGroup,
+ model,
+ });
+}
+
+const renderPassDescriptor: GPURenderPassDescriptor = {
+ label: 'our basic canvas renderPass',
+ colorAttachments: [
+ {
+ view: undefined, // <- to be filled out when we render
+ clearValue: [0.3, 0.3, 0.3, 1],
+ loadOp: 'clear',
+ storeOp: 'store',
+ },
+ ],
+ depthStencilAttachment: {
+ view: undefined, // <- to be filled out when we render
+ depthClearValue: 1.0,
+ depthLoadOp: 'clear',
+ depthStoreOp: 'store',
+ },
+};
+
+let depthTexture;
+
+function render(time: number) {
+ time *= 0.001; // convert to seconds;
+
+ // Get the current texture from the canvas context and
+ // set it as the texture to render to.
+ const canvasTexture = context.getCurrentTexture();
+ renderPassDescriptor.colorAttachments[0].view = canvasTexture.createView();
+
+ // If we don't have a depth texture OR if its size is different
+ // from the canvasTexture when make a new depth texture
+ if (
+ !depthTexture ||
+ depthTexture.width !== canvasTexture.width ||
+ depthTexture.height !== canvasTexture.height
+ ) {
+ if (depthTexture) {
+ depthTexture.destroy();
+ }
+ depthTexture = device.createTexture({
+ size: [canvasTexture.width, canvasTexture.height],
+ format: 'depth24plus',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ });
+ }
+ renderPassDescriptor.depthStencilAttachment.view = depthTexture.createView();
+
+ const fov = (60 * Math.PI) / 180;
+ const aspect = canvas.clientWidth / canvas.clientHeight;
+ const projection = mat4.perspective(fov, aspect, 0.1, 1000);
+
+ const view = mat4.lookAt(
+ [-300, 0, 300], // eye
+ [0, 0, 0], // target
+ [0, 1, 0] // up
+ );
+
+ const viewProjection = mat4.multiply(projection, view);
+
+ // make a command encoder to start encoding commands
+ const encoder = device.createCommandEncoder();
+
+ // make a render pass encoder to encode render specific commands
+ 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);
+
+ pass.setVertexBuffer(0, vertexBuffer);
+ pass.setIndexBuffer(indexBuffer, indexFormat);
+ pass.setBindGroup(0, litBindGroup);
+ pass.drawIndexed(vertexCount);
+ });
+
+ objectInfos.forEach(({
+ wireframeBindGroup,
+ model: { vertexCount },
+ }) => {
+ pass.setPipeline(wireframePipeline);
+ pass.setBindGroup(0, wireframeBindGroup)
+ pass.draw(vertexCount * 2);
+ });
+
+ pass.end();
+
+ const commandBuffer = encoder.finish();
+ device.queue.submit([commandBuffer]);
+
+ requestAnimationFrame(render);
+}
+requestAnimationFrame(render);
diff --git a/sample/wireframe/meta.ts b/sample/wireframe/meta.ts
new file mode 100644
index 00000000..8e0cacbd
--- /dev/null
+++ b/sample/wireframe/meta.ts
@@ -0,0 +1,20 @@
+export default {
+ name: 'Wireframe',
+ description: `
+ This example demonstrates drawing a wireframe from triangles by using the
+ vertex buffers as storage buffers and then using \`@builtin(vertex_index)\`
+ to index the vertex data to generate 3 lines per triangle. It uses
+ array in the vertex shader so it can pull out \`vec3f\` positions
+ at any valid stride.
+ `,
+ filename: __DIRNAME__,
+ sources: [
+ { path: 'main.ts' },
+ { path: 'wireframe.wgsl' },
+ { path: 'solidColorLit.wgsl' },
+ { path: 'models.ts' },
+ { path: '../../meshes/box.ts' },
+ { path: '../../meshes/mesh.ts' },
+ { path: 'utils.ts' },
+ ],
+};
diff --git a/sample/wireframe/models.ts b/sample/wireframe/models.ts
new file mode 100644
index 00000000..4b433e35
--- /dev/null
+++ b/sample/wireframe/models.ts
@@ -0,0 +1,112 @@
+// Ideally all the models would be the same format
+// and we'd determine that format at build time or before
+// but, we want to reuse the model data in this repo
+// so we'll normalize it here
+
+import { vec3 } from 'wgpu-matrix';
+import { mesh as teapot } from '../../meshes/teapot';
+import { createSphereMesh } from '../../meshes/sphere';
+
+type Mesh = {
+ positions: [number, number, number][];
+ triangles: [number, number, number][];
+ normals: [number, number, number][];
+};
+
+export function convertMeshToTypedArrays(
+ mesh: Mesh,
+ scale: number,
+ offset = [0, 0, 0]
+) {
+ const { positions, normals, triangles } = mesh;
+ const scaledPositions = positions.map((p) =>
+ p.map((v, i) => v * scale + offset[i % 3])
+ );
+ const vertices = new Float32Array(scaledPositions.length * 6);
+ for (let i = 0; i < scaledPositions.length; ++i) {
+ vertices.set(scaledPositions[i], 6 * i);
+ vertices.set(normals[i], 6 * i + 3);
+ }
+ const indices = new Uint32Array(triangles.length * 3);
+ for (let i = 0; i < triangles.length; ++i) {
+ indices.set(triangles[i], 3 * i);
+ }
+
+ return {
+ vertices,
+ indices,
+ };
+}
+
+function createSphereTypedArrays(
+ radius: number,
+ widthSegments = 32,
+ heightSegments = 16,
+ randomness = 0
+) {
+ const { vertices: verticesWithUVs, indices } = createSphereMesh(
+ radius,
+ widthSegments,
+ heightSegments,
+ randomness
+ );
+ const numVertices = verticesWithUVs.length / 8;
+ const vertices = new Float32Array(numVertices * 6);
+ for (let i = 0; i < numVertices; ++i) {
+ const srcNdx = i * 8;
+ const dstNdx = i * 6;
+ vertices.set(verticesWithUVs.subarray(srcNdx, srcNdx + 6), dstNdx);
+ }
+ return {
+ vertices,
+ indices: new Uint32Array(indices),
+ };
+}
+
+function flattenNormals({
+ vertices,
+ indices,
+}: {
+ vertices: Float32Array;
+ indices: Uint32Array;
+}) {
+ const newVertices = new Float32Array(indices.length * 6);
+ const newIndices = new Uint32Array(indices.length);
+ for (let i = 0; i < indices.length; i += 3) {
+ const positions = [];
+ for (let j = 0; j < 3; ++j) {
+ const ndx = indices[i + j];
+ const srcNdx = ndx * 6;
+ const dstNdx = (i + j) * 6;
+ // copy position
+ const pos = vertices.subarray(srcNdx, srcNdx + 3);
+ newVertices.set(pos, dstNdx);
+ positions.push(pos);
+ newIndices[i + j] = i + j;
+ }
+
+ const normal = vec3.normalize(
+ vec3.cross(
+ vec3.normalize(vec3.subtract(positions[1], positions[0])),
+ vec3.normalize(vec3.subtract(positions[2], positions[1]))
+ )
+ );
+
+ for (let j = 0; j < 3; ++j) {
+ const dstNdx = (i + j) * 6;
+ newVertices.set(normal, dstNdx + 3);
+ }
+ }
+
+ return {
+ vertices: newVertices,
+ indices: newIndices,
+ };
+}
+
+export const modelData = {
+ teapot: convertMeshToTypedArrays(teapot, 1.5),
+ sphere: createSphereTypedArrays(20),
+ jewel: flattenNormals(createSphereTypedArrays(20, 5, 3)),
+ rock: flattenNormals(createSphereTypedArrays(20, 32, 16, 0.1)),
+};
diff --git a/sample/wireframe/solidColorLit.wgsl b/sample/wireframe/solidColorLit.wgsl
new file mode 100644
index 00000000..c29c3f6c
--- /dev/null
+++ b/sample/wireframe/solidColorLit.wgsl
@@ -0,0 +1,30 @@
+struct Uniforms {
+ worldViewProjectionMatrix: mat4x4f,
+ worldMatrix: mat4x4f,
+ color: vec4f,
+};
+
+struct Vertex {
+ @location(0) position: vec4f,
+ @location(1) normal: vec3f,
+};
+
+struct VSOut {
+ @builtin(position) position: vec4f,
+ @location(0) normal: vec3f,
+};
+
+@group(0) @binding(0) var uni: Uniforms;
+
+@vertex fn vs(vin: Vertex) -> VSOut {
+ var vOut: VSOut;
+ vOut.position = uni.worldViewProjectionMatrix * vin.position;
+ vOut.normal = (uni.worldMatrix * vec4f(vin.normal, 0)).xyz;
+ return vOut;
+}
+
+@fragment fn fs(vin: VSOut) -> @location(0) vec4f {
+ let lightDirection = normalize(vec3f(4, 10, 6));
+ let light = dot(normalize(vin.normal), lightDirection) * 0.5 + 0.5;
+ return vec4f(uni.color.rgb * light, uni.color.a);
+}
diff --git a/sample/wireframe/utils.ts b/sample/wireframe/utils.ts
new file mode 100644
index 00000000..50bdc2f1
--- /dev/null
+++ b/sample/wireframe/utils.ts
@@ -0,0 +1,205 @@
+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 const createTextureFromImage = (
+ device: GPUDevice,
+ bitmap: ImageBitmap
+) => {
+ const texture: GPUTexture = device.createTexture({
+ size: [bitmap.width, bitmap.height, 1],
+ format: 'rgba8unorm',
+ usage:
+ GPUTextureUsage.TEXTURE_BINDING |
+ GPUTextureUsage.COPY_DST |
+ GPUTextureUsage.RENDER_ATTACHMENT,
+ });
+ device.queue.copyExternalImageToTexture(
+ { source: bitmap },
+ { texture: texture },
+ [bitmap.width, bitmap.height]
+ );
+ return texture;
+};
diff --git a/sample/wireframe/wireframe.wgsl b/sample/wireframe/wireframe.wgsl
new file mode 100644
index 00000000..64521c55
--- /dev/null
+++ b/sample/wireframe/wireframe.wgsl
@@ -0,0 +1,74 @@
+struct Uniforms {
+ worldViewProjectionMatrix: mat4x4f,
+ worldMatrix: mat4x4f,
+ color: vec4f,
+};
+
+struct VSOut {
+ @builtin(position) position: vec4f,
+};
+
+@group(0) @binding(0) var uni: Uniforms;
+@group(0) @binding(1) var positions: array;
+@group(0) @binding(2) var indices: array;
+@group(0) @binding(3) var stride: u32;
+
+@vertex fn vsIndexedU32(@builtin(vertex_index) vNdx: u32) -> VSOut {
+ // 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 * stride;
+ let position = vec4f(positions[pNdx], positions[pNdx + 1], positions[pNdx + 2], 1);
+
+ var vOut: VSOut;
+ vOut.position = uni.worldViewProjectionMatrix * position;
+ return vOut;
+}
+
+@vertex fn vsIndexedU16(@builtin(vertex_index) vNdx: u32) -> VSOut {
+ // 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 indexNdx = triNdx * 3 + vertNdx;
+ let twoIndices = indices[indexNdx / 2]; // indices is u32 but we want u16
+ let index = (twoIndices >> ((indexNdx & 1) * 16)) & 0xFFFF;
+ let pNdx = index * stride;
+ let position = vec4f(positions[pNdx], positions[pNdx + 1], positions[pNdx + 2], 1);
+
+ var vOut: VSOut;
+ vOut.position = uni.worldViewProjectionMatrix * position;
+ return vOut;
+}
+
+@vertex fn vsUnindexed(@builtin(vertex_index) vNdx: u32) -> VSOut {
+ // 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 = triNdx * 3 + vertNdx;
+ let pNdx = index * 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);
+}
diff --git a/src/samples.ts b/src/samples.ts
index 378bcf49..45db7cb2 100644
--- a/src/samples.ts
+++ b/src/samples.ts
@@ -34,6 +34,7 @@ import texturedCube from '../sample/texturedCube/meta';
import twoCubes from '../sample/twoCubes/meta';
import videoUploading from '../sample/videoUploading/meta';
import volumeRenderingTexture3D from '../sample/volumeRenderingTexture3D/meta';
+import wireframe from '../sample/wireframe/meta';
import worker from '../sample/worker/meta';
import workloadSimulator from '../sample/workloadSimulator/meta';
@@ -123,6 +124,7 @@ export const pageCategories: PageCategory[] = [
skinnedMesh,
textRenderingMsdf,
volumeRenderingTexture3D,
+ wireframe,
},
},