import { getPathText } from "./common.js" // Triangle vertices, one vertex per line. Hand-transcribed from a // .obj file exported from Blender to this form. // // I've also written myriad scripts to do this automatically for // larger models. // // Float32Array is a fairly convenient way to convert floating point // literals to bytes. const triangleVertices = new Float32Array([ // [position ] [texture ] [normal ] 0.0, 1.0, 0.0, 0.5, 0.0, 0.0, 1.0, 0.0, 1.0, -1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, -1.0, -1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, ]); class TriangleTexturedRenderer { constructor(device, canvasFormat, wgsl, image) { const label = "TriangleRenderer"; ////////////////////////////////////////////////////////////////////// // shader module ////////////////////////////////////////////////////////////////////// const shaderModule = device.createShaderModule({ label: `${label} shader`, code: wgsl, }); ////////////////////////////////////////////////////////////////////// // pipeline ////////////////////////////////////////////////////////////////////// // "bind group layouts" are a declaration of which `var` bindings // exist in the shader programs loaded by this pipeline const bindGroupLayouts = [ device.createBindGroupLayout({ label: "gltf bind group layout 0", entries: [ { binding: 0, visibility: GPUShaderStage.FRAGMENT, sampler: {}, }, { binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: {}, } ] }), ]; const pipelineLayout = device.createPipelineLayout({ label: `${label} pipeline layout`, bindGroupLayouts: bindGroupLayouts, }); // vertexBufferLayouts describes the position of each element for // each buffer. There is only one vertex buffer used in this demo, // so there is only one vertex buffer layout in this list. // // shaderLocation matches the location numbers defined in // triangle.wgsl. // // "offset" is the starting offset in bytes, 4 bytes per floating // point number. This matches the arrangement in bytes of the // triangleVertices Float32Array. const arrayStride = (4 * 3) + (4 * 2) + (4 * 3); const vertexBufferLayouts = [ { arrayStride: arrayStride, attributes: [{ format: "float32x3", offset: 0, shaderLocation: 0, }, { format: "float32x2", offset: 12, shaderLocation: 1, }, { format: "float32x3", offset: 20, shaderLocation: 2, }], }, ]; this.renderPipeline = device.createRenderPipeline({ label: `${label} render pipeline`, layout: pipelineLayout, vertex: { module: shaderModule, entryPoint: "vertexMain", buffers: vertexBufferLayouts, }, fragment: { module: shaderModule, entryPoint: "fragmentMain", targets: [{ format: canvasFormat, }] }, primitive: { topology: "triangle-list", }, depthStencil: { depthWriteEnabled: true, depthCompare: "less", format: "depth24plus", }, }); ////////////////////////////////////////////////////////////////////// // samplers and textures ////////////////////////////////////////////////////////////////////// // default sampler configuration this.sampler = device.createSampler(); this.texture = device.createTexture({ size: [image.width, image.height], format: 'rgba8unorm', usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST, }); device.queue.copyExternalImageToTexture( { source: image }, { texture: this.texture }, { width: image.width, height: image.height }, ); ////////////////////////////////////////////////////////////////////// // bind group ////////////////////////////////////////////////////////////////////// this.bindGroups = [ device.createBindGroup({ label: "${label} bind group", layout: bindGroupLayouts[0], entries: [{ binding: 0, resource: this.sampler, }, { binding: 1, resource: this.texture, }], }), ]; ////////////////////////////////////////////////////////////////////// // buffer ////////////////////////////////////////////////////////////////////// this.vertexBuffer = device.createBuffer({ label: `${label} vertex buffer`, size: triangleVertices.byteLength, usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST, }); // copy the (system RAM) triangleVertices to the (gpu RAM) vertexBuffer. device.queue.writeBuffer(this.vertexBuffer, 0, triangleVertices); } render(renderPass) { renderPass.setPipeline(this.renderPipeline); renderPass.setVertexBuffer(0, this.vertexBuffer); renderPass.setBindGroup(0, this.bindGroups[0]); const vertexCount = 3; renderPass.draw(vertexCount); } } // This "loadTriangleRenderer" function is separate from the // constructor because Javascript class constructors can't be async // functions. async function loadTriangleTexturedRenderer(device, canvasFormat) { const triangleWgsl = await getPathText("triangleTextured.wgsl"); const image = new Image(); image.src = "texture.png"; await image.decode(); const renderer = new TriangleTexturedRenderer(device, canvasFormat, triangleWgsl, image); return renderer; } export { loadTriangleTexturedRenderer };