initial
This commit is contained in:
commit
90b28c6396
11
common.js
Normal file
11
common.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
async function getPathText(path)
|
||||||
|
{
|
||||||
|
const response = await fetch(path);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`${path}: ${response.status}`)
|
||||||
|
}
|
||||||
|
const blob = await response.blob();
|
||||||
|
return blob.text();
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getPathText };
|
||||||
0
favicon.ico
Normal file
0
favicon.ico
Normal file
23
index.html
Normal file
23
index.html
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>WebGPU</title>
|
||||||
|
<style>
|
||||||
|
canvas {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script src="index.js" type="module"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<canvas></canvas>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
140
index.js
Normal file
140
index.js
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
import { loadTriangleRenderer } from "./triangle.js"
|
||||||
|
import { loadTriangleTexturedRenderer } from "./triangleTextured.js"
|
||||||
|
|
||||||
|
if (!navigator.gpu) {
|
||||||
|
throw new Error("WebGPU not supported on this browser.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const adapter = await navigator.gpu.requestAdapter();
|
||||||
|
if (!adapter) {
|
||||||
|
throw new Error("No WebGPU adapter");
|
||||||
|
}
|
||||||
|
|
||||||
|
// The next few lines, being nitpicky over bgra8unorm vs rgba8unorm is
|
||||||
|
// needed for using the canvas framebuffer as a storage texture, which
|
||||||
|
// is mostly only relevant in the specific case that you want to
|
||||||
|
// directly write to the canvas framebuffer from a compute shader.
|
||||||
|
//
|
||||||
|
// I very highly doubt this has any negative performance impact
|
||||||
|
// whatsoever on any device that has a WebGPU implementation, and
|
||||||
|
// certainly no desktop/laptop GPU is affected by this. Nevertheless,
|
||||||
|
// the WebGPU developers did deliberately decide to make
|
||||||
|
// 'bgra8unorm-storage' an optional feature, so perhaps there is some
|
||||||
|
// obscure device somewhere that actually cares about this.
|
||||||
|
const hasBgraStorage = adapter.features.has("bgra8unorm-storage");
|
||||||
|
const device = await adapter.requestDevice({
|
||||||
|
requiredFeatures: hasBgraStorage ? ["bgra8unorm-storage"] : []
|
||||||
|
});
|
||||||
|
const canvas = document.querySelector("canvas");
|
||||||
|
const context = canvas.getContext("webgpu");
|
||||||
|
const canvasFormat = hasBgraStorage ? navigator.gpu.getPreferredCanvasFormat() : "rgba8unorm";
|
||||||
|
context.configure({
|
||||||
|
device: device,
|
||||||
|
format: canvasFormat,
|
||||||
|
alphaMode: 'premultiplied',
|
||||||
|
usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
|
||||||
|
});
|
||||||
|
|
||||||
|
// render targets, initialized in handleResize
|
||||||
|
var depthTexture = undefined;
|
||||||
|
|
||||||
|
function handleResize(canvasTexture)
|
||||||
|
{
|
||||||
|
// The internal color buffer returned by context.getCurrentTexture()
|
||||||
|
// is automatically resized with the canvas. All other render
|
||||||
|
// targets (e.g depth buffers) are not.
|
||||||
|
//
|
||||||
|
// If there were other off-screen buffers (deferred lighting,
|
||||||
|
// geometry buffers, etc..) those would also be recreated as needed here.
|
||||||
|
|
||||||
|
const depthResized = depthTexture === undefined || canvasTexture.width != depthTexture.width || canvasTexture.height != depthTexture.height;
|
||||||
|
|
||||||
|
if (depthResized) {
|
||||||
|
if (depthTexture !== undefined) {
|
||||||
|
depthTexture.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
depthTexture = device.createTexture({
|
||||||
|
format: 'depth24plus',
|
||||||
|
usage: GPUTextureUsage.RENDER_ATTACHMENT,
|
||||||
|
size: [canvasTexture.width, canvasTexture.height],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const triangleRenderer = await loadTriangleRenderer(device, canvasFormat);
|
||||||
|
const triangleTexturedRenderer = await loadTriangleTexturedRenderer(device, canvasFormat);
|
||||||
|
|
||||||
|
function render()
|
||||||
|
{
|
||||||
|
// `canvas.clientWidth` and `canvas.clientHeight` are the current
|
||||||
|
// size of the canvas element. The "canvas element" in this demo is
|
||||||
|
// sized by the `canvas` CSS rule, which scales the canvas element
|
||||||
|
// to 100% the width and height of the viewport.
|
||||||
|
//
|
||||||
|
// Web is a shit platform. The reported sizes are in "logical pixel"
|
||||||
|
// units, which are not the same size as the physical pixels of the
|
||||||
|
// device's screen.
|
||||||
|
//
|
||||||
|
// On the devices I've tested, multiplying clientWidth and
|
||||||
|
// clientHeight by window.devicePixelRatio does give the real
|
||||||
|
// dimensions of the canvas in physical screen pixels, and does
|
||||||
|
// result in sharper font rendering.
|
||||||
|
//
|
||||||
|
// On devices like Nico's phone though, devicePixelRatio
|
||||||
|
// multiplication results in ~12x more pixels being rendered per
|
||||||
|
// frame, which can be significantly slower for things like
|
||||||
|
// raymarching/raytracing.
|
||||||
|
//
|
||||||
|
// --
|
||||||
|
//
|
||||||
|
// This causes the canvas color buffer to be resized to the canvas
|
||||||
|
// element as sized by CSS, including the browser window being
|
||||||
|
// resized.
|
||||||
|
canvas.width = canvas.clientWidth * window.devicePixelRatio;
|
||||||
|
canvas.height = canvas.clientHeight * window.devicePixelRatio;
|
||||||
|
|
||||||
|
// possibly recreate any textures that depend on the size of the canvas
|
||||||
|
// changing.
|
||||||
|
const canvasTexture = context.getCurrentTexture();
|
||||||
|
handleResize(canvasTexture);
|
||||||
|
|
||||||
|
const clearValue = { r: 0.2, g: 0.2, b: 0.4, a: 1.0 };
|
||||||
|
const colorAttachments = [
|
||||||
|
{
|
||||||
|
view: canvasTexture.createView(),
|
||||||
|
loadOp: "clear",
|
||||||
|
clearValue: clearValue,
|
||||||
|
storeOp: "store",
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const encoder = device.createCommandEncoder();
|
||||||
|
const renderPass = encoder.beginRenderPass({
|
||||||
|
colorAttachments: colorAttachments,
|
||||||
|
depthStencilAttachment: {
|
||||||
|
view: depthTexture.createView(),
|
||||||
|
depthClearValue: 1.0,
|
||||||
|
depthLoadOp: 'clear',
|
||||||
|
depthStoreOp: 'store',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Every time you want to use a different shader, WebGPU also
|
||||||
|
// requires you use a different GPURenderPipeline. It seems like a
|
||||||
|
// decent pattern to wrap each collection of
|
||||||
|
// buffers/pipelines/shaders in its own object:
|
||||||
|
|
||||||
|
// uncomment either of these to see the result of each render
|
||||||
|
// pipeline / shader
|
||||||
|
|
||||||
|
//triangleRenderer.render(renderPass);
|
||||||
|
triangleTexturedRenderer.render(renderPass);
|
||||||
|
|
||||||
|
renderPass.end();
|
||||||
|
|
||||||
|
const commandBuffer = encoder.finish();
|
||||||
|
device.queue.submit([commandBuffer]);
|
||||||
|
}
|
||||||
|
|
||||||
|
requestAnimationFrame(render);
|
||||||
BIN
texture.png
Normal file
BIN
texture.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 357 KiB |
144
triangle.js
Normal file
144
triangle.js
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
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, 1.0, 0.0, 1.0, 0.0,
|
||||||
|
1.0, -1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0,
|
||||||
|
-1.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0,
|
||||||
|
]);
|
||||||
|
|
||||||
|
class TriangleRenderer {
|
||||||
|
constructor(device, canvasFormat, wgsl)
|
||||||
|
{
|
||||||
|
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
|
||||||
|
//
|
||||||
|
// None are used in triangle.wgsl, so this list is empty.
|
||||||
|
const bindGroupLayouts = [
|
||||||
|
];
|
||||||
|
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
// bind group
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// there are no bind groups, because triangle.wgsl doesn't use any.
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
// 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);
|
||||||
|
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 loadTriangleRenderer(device, canvasFormat)
|
||||||
|
{
|
||||||
|
const triangleWgsl = await getPathText("triangle.wgsl");
|
||||||
|
|
||||||
|
const renderer = new TriangleRenderer(device, canvasFormat, triangleWgsl);
|
||||||
|
|
||||||
|
return renderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { loadTriangleRenderer };
|
||||||
33
triangle.wgsl
Normal file
33
triangle.wgsl
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// The `VertexInput` location numbers match the numbers in
|
||||||
|
// `vertexBufferLayouts`
|
||||||
|
struct VertexInput {
|
||||||
|
@location(0) position: vec3f,
|
||||||
|
@location(1) texture: vec2f,
|
||||||
|
@location(2) normal: vec3f,
|
||||||
|
};
|
||||||
|
|
||||||
|
// location numbers for passing values from the vertex stage to the
|
||||||
|
// fragment stage are arbitrary--they just need to match. Because
|
||||||
|
// fragmentMain uses the same VertexOutput struct that vertexMain
|
||||||
|
// returns, they do match.
|
||||||
|
struct VertexOutput {
|
||||||
|
@builtin(position) position: vec4f,
|
||||||
|
@location(0) texture: vec2f,
|
||||||
|
};
|
||||||
|
|
||||||
|
@vertex
|
||||||
|
fn vertexMain(input: VertexInput) -> VertexOutput
|
||||||
|
{
|
||||||
|
let position = vec4f(input.position * 0.5, 1.0);
|
||||||
|
|
||||||
|
var output: VertexOutput;
|
||||||
|
output.position = position;
|
||||||
|
output.texture = input.texture;
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fragmentMain(input: VertexOutput) -> @location(0) vec4f
|
||||||
|
{
|
||||||
|
return vec4(input.texture, 0.0, 1.0);
|
||||||
|
}
|
||||||
193
triangleTextured.js
Normal file
193
triangleTextured.js
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
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 };
|
||||||
43
triangleTextured.wgsl
Normal file
43
triangleTextured.wgsl
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// The `var` group() numbers match the ordering of the layouts in the
|
||||||
|
// `bindGroupLayouts` array, `0` being the 0th index of that array.
|
||||||
|
//
|
||||||
|
// The `var` binding() numbers match the numbers of the entries in
|
||||||
|
// each layout in `bindGroupLayouts`.
|
||||||
|
@group(0) @binding(0) var Sampler: sampler;
|
||||||
|
@group(0) @binding(1) var Texture: texture_2d<f32>;
|
||||||
|
|
||||||
|
// The `VertexInput` location numbers match the numbers in
|
||||||
|
// `vertexBufferLayouts`
|
||||||
|
struct VertexInput {
|
||||||
|
@location(0) position: vec3f,
|
||||||
|
@location(1) texture: vec2f,
|
||||||
|
@location(2) normal: vec3f,
|
||||||
|
};
|
||||||
|
|
||||||
|
// location numbers for passing values from the vertex stage to the
|
||||||
|
// fragment stage are arbitrary--they just need to match. Because
|
||||||
|
// fragmentMain uses the same VertexOutput struct that vertexMain
|
||||||
|
// returns, they do match.
|
||||||
|
struct VertexOutput {
|
||||||
|
@builtin(position) position: vec4f,
|
||||||
|
@location(0) texture: vec2f,
|
||||||
|
};
|
||||||
|
|
||||||
|
@vertex
|
||||||
|
fn vertexMain(input: VertexInput) -> VertexOutput
|
||||||
|
{
|
||||||
|
let position = vec4f(input.position * 0.5, 1.0);
|
||||||
|
|
||||||
|
var output: VertexOutput;
|
||||||
|
output.position = position;
|
||||||
|
output.texture = input.texture;
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fragmentMain(input: VertexOutput) -> @location(0) vec4f
|
||||||
|
{
|
||||||
|
let color = textureSample(Texture, Sampler, input.texture);
|
||||||
|
|
||||||
|
return vec4(color.xyz, 1.0);
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user