[WebGPU] Fix up demos on and add compute demo to webkit.org/demos
[WebKit-https.git] / Websites / webkit.org / demos / webgpu / hello-cube.html
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <meta name="viewport" content="width=1000">
5 <title>WebGPU Cube</title>
6 <script src="scripts/gl-matrix-min.js"></script>
7 <link rel="stylesheet" href="css/style.css"/>
8 <style>
9 body {
10     font-family: system-ui;
11     color: #f7f7ff;
12     background-color: rgb(38, 38, 127);
13     text-align: center;
14 }
15 canvas {
16     margin: 0 auto;
17 }
18 </style>
19 </head>
20 <body>
21 <div id="contents">
22     <h1>Simple Cube</h1>
23     <p>
24         This demo uses a rotating set of GPUBuffers to upload rotation matrix data every frame.
25     </p>
26     <canvas width="1200" height="1200"></canvas>
27 </div>
28 <div id="error">
29     <h2>WebGPU not available</h2>
30     <p>
31         Make sure you are on a system with WebGPU enabled. In
32         Safari, first make sure the Developer Menu is visible (Preferences →
33         Advanced), then Develop → Experimental Features → WebGPU.
34     </p>
35 </div>
36 <script>
37 if (!navigator.gpu)
38     document.body.className = 'error';
39
40 const positionAttributeNum  = 0;
41 const colorAttributeNum = 1;
42
43 const transformBindingNum   = 0;
44
45 const bindGroupIndex        = 0;
46
47 const shader = `
48 struct FragmentData {
49     float4 position : SV_Position;
50     float4 color : attribute(${colorAttributeNum});
51 }
52
53 vertex FragmentData vertex_main(
54     float4 position : attribute(${positionAttributeNum}), 
55     float4 color : attribute(${colorAttributeNum}), 
56     constant float4x4[] modelViewProjectionMatrix : register(b${transformBindingNum}))
57 {
58     FragmentData out;
59     out.position = mul(modelViewProjectionMatrix[0], position);
60     out.color = color;
61     
62     return out;
63 }
64
65 fragment float4 fragment_main(float4 color : attribute(${colorAttributeNum})) : SV_Target 0
66 {
67     return color;
68 }
69 `;
70
71 let device, swapChain, verticesBuffer, bindGroupLayout, pipeline, renderPassDescriptor;
72 let projectionMatrix = mat4.create();
73
74 const colorOffset = 4 * 4;
75 const vertexSize = 4 * 8;
76 const verticesArray = new Float32Array([
77     // float4 position, float4 color
78     1, -1, 1, 1, 1, 0, 1, 1,
79     -1, -1, 1, 1, 0, 0, 1, 1,
80     -1, -1, -1, 1, 0, 0, 0, 1,
81     1, -1, -1, 1, 1, 0, 0, 1,
82     1, -1, 1, 1, 1, 0, 1, 1,
83     -1, -1, -1, 1, 0, 0, 0, 1,
84
85     1, 1, 1, 1, 1, 1, 1, 1,
86     1, -1, 1, 1, 1, 0, 1, 1,
87     1, -1, -1, 1, 1, 0, 0, 1,
88     1, 1, -1, 1, 1, 1, 0, 1,
89     1, 1, 1, 1, 1, 1, 1, 1,
90     1, -1, -1, 1, 1, 0, 0, 1,
91
92     -1, 1, 1, 1, 0, 1, 1, 1,
93     1, 1, 1, 1, 1, 1, 1, 1,
94     1, 1, -1, 1, 1, 1, 0, 1,
95     -1, 1, -1, 1, 0, 1, 0, 1,
96     -1, 1, 1, 1, 0, 1, 1, 1,
97     1, 1, -1, 1, 1, 1, 0, 1,
98
99     -1, -1, 1, 1, 0, 0, 1, 1,
100     -1, 1, 1, 1, 0, 1, 1, 1,
101     -1, 1, -1, 1, 0, 1, 0, 1,
102     -1, -1, -1, 1, 0, 0, 0, 1,
103     -1, -1, 1, 1, 0, 0, 1, 1,
104     -1, 1, -1, 1, 0, 1, 0, 1,
105
106     1, 1, 1, 1, 1, 1, 1, 1,
107     -1, 1, 1, 1, 0, 1, 1, 1,
108     -1, -1, 1, 1, 0, 0, 1, 1,
109     -1, -1, 1, 1, 0, 0, 1, 1,
110     1, -1, 1, 1, 1, 0, 1, 1,
111     1, 1, 1, 1, 1, 1, 1, 1,
112
113     1, -1, -1, 1, 1, 0, 0, 1,
114     -1, -1, -1, 1, 0, 0, 0, 1,
115     -1, 1, -1, 1, 0, 1, 0, 1,
116     1, 1, -1, 1, 1, 1, 0, 1,
117     1, -1, -1, 1, 1, 0, 0, 1,
118     -1, 1, -1, 1, 0, 1, 0, 1,
119 ]);
120
121 async function init() {
122     const adapter = await navigator.gpu.requestAdapter();
123     device = await adapter.requestDevice();
124
125     const canvas = document.querySelector('canvas');
126     let canvasSize = canvas.getBoundingClientRect();
127     canvas.width = canvasSize.width;
128     canvas.height = canvasSize.height;
129
130     const aspect = Math.abs(canvas.width / canvas.height);
131     mat4.perspective(projectionMatrix, (2 * Math.PI) / 5, aspect, 1, 100.0);
132
133     const context = canvas.getContext('gpu');
134
135     const swapChainDescriptor = { 
136         device: device, 
137         format: "bgra8unorm"
138     };
139     swapChain = context.configureSwapChain(swapChainDescriptor);
140
141     const shaderModuleDescriptor = { code: shader, isWHLSL: true };
142     const shaderModule = device.createShaderModule(shaderModuleDescriptor);
143
144     const verticesBufferDescriptor = { 
145         size: verticesArray.byteLength, 
146         usage: GPUBufferUsage.VERTEX | GPUBufferUsage.TRANSFER_DST
147     };
148     let verticesArrayBuffer;
149     [verticesBuffer, verticesArrayBuffer] = device.createBufferMapped(verticesBufferDescriptor);
150
151     const verticesWriteArray = new Float32Array(verticesArrayBuffer);
152     verticesWriteArray.set(verticesArray);
153     verticesBuffer.unmap();
154
155     // Vertex Input
156     const positionAttributeDescriptor = {
157         shaderLocation: positionAttributeNum,  // [[attribute(0)]]
158         offset: 0,
159         format: "float4"
160     };
161     const colorAttributeDescriptor = {
162         shaderLocation: colorAttributeNum,
163         offset: colorOffset,
164         format: "float4"
165     }
166     const vertexBufferDescriptor = {
167         attributeSet: [positionAttributeDescriptor, colorAttributeDescriptor],
168         stride: vertexSize,
169         stepMode: "vertex"
170     };
171     const vertexInputDescriptor = { vertexBuffers: [vertexBufferDescriptor] };
172
173     // Bind group binding layout
174     const transformBufferBindGroupLayoutBinding = {
175         binding: transformBindingNum, // id[[(0)]]
176         visibility: GPUShaderStageBit.VERTEX,
177         type: "uniform-buffer"
178     };
179
180     const bindGroupLayoutDescriptor = { bindings: [transformBufferBindGroupLayoutBinding] };
181     bindGroupLayout = device.createBindGroupLayout(bindGroupLayoutDescriptor);
182
183     // Pipeline
184     const depthStateDescriptor = {
185         depthWriteEnabled: true,
186         depthCompare: "less"
187     };
188
189     const pipelineLayoutDescriptor = { bindGroupLayouts: [bindGroupLayout] };
190     const pipelineLayout = device.createPipelineLayout(pipelineLayoutDescriptor);
191     const vertexStageDescriptor = {
192         module: shaderModule,
193         entryPoint: "vertex_main"
194     };
195     const fragmentStageDescriptor = {
196         module: shaderModule,
197         entryPoint: "fragment_main"
198     };
199     const colorState = {
200         format: "bgra8unorm",
201         alphaBlend: {
202             srcFactor: "src-alpha",
203             dstFactor: "one-minus-src-alpha",
204             operation: "add"
205         },
206         colorBlend: {
207             srcFactor: "src-alpha",
208             dstFactor: "one-minus-src-alpha",
209             operation: "add"
210         },
211         writeMask: GPUColorWriteBits.ALL
212     };
213     const pipelineDescriptor = {
214         layout: pipelineLayout,
215
216         vertexStage: vertexStageDescriptor,
217         fragmentStage: fragmentStageDescriptor,
218
219         primitiveTopology: "triangle-list",
220         colorStates: [colorState],
221         depthStencilState: depthStateDescriptor,
222         vertexInput: vertexInputDescriptor
223     };
224     pipeline = device.createRenderPipeline(pipelineDescriptor);
225
226     let colorAttachment = {
227         // attachment is acquired in render loop.
228         loadOp: "clear",
229         storeOp: "store",
230         clearColor: { r: 0.15, g: 0.15, b: 0.5, a: 1.0 } // GPUColor
231     };
232
233     // Depth stencil texture
234
235     // GPUExtent3D
236     const depthSize = {
237         width: canvas.width,
238         height: canvas.height,
239         depth: 1
240     };
241
242     const depthTextureDescriptor = {
243         size: depthSize,
244         arrayLayerCount: 1,
245         mipLevelCount: 1,
246         sampleCount: 1,
247         dimension: "2d",
248         format: "depth32float-stencil8",
249         usage: GPUTextureUsage.OUTPUT_ATTACHMENT
250     };
251
252     const depthTexture = device.createTexture(depthTextureDescriptor);
253
254     // GPURenderPassDepthStencilAttachmentDescriptor
255     const depthAttachment = {
256         attachment: depthTexture.createDefaultView(),
257         depthLoadOp: "clear",
258         depthStoreOp: "store",
259         clearDepth: 1.0
260     };
261
262     renderPassDescriptor = { 
263         colorAttachments: [colorAttachment],
264         depthStencilAttachment: depthAttachment
265     };
266
267     render();
268 }
269
270 /* Transform Buffers and Bindings */
271 const transformSize = 4 * 16;
272
273 const transformBufferDescriptor = {
274     size: transformSize,
275     usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.MAP_WRITE
276 };
277
278 let mappedGroups = [];
279
280 function render() {
281     if (mappedGroups.length === 0) {
282         const [buffer, arrayBuffer] = device.createBufferMapped(transformBufferDescriptor);
283         const group = device.createBindGroup(createBindGroupDescriptor(buffer));
284         let mappedGroup = { buffer: buffer, arrayBuffer: arrayBuffer, bindGroup: group };
285         drawCommands(mappedGroup);
286     } else
287         drawCommands(mappedGroups.shift());
288 }
289
290 function createBindGroupDescriptor(transformBuffer) {
291     const transformBufferBinding = {
292         buffer: transformBuffer,
293         offset: 0,
294         size: transformSize
295     };
296     const transformBufferBindGroupBinding = {
297         binding: transformBindingNum,
298         resource: transformBufferBinding
299     };
300     return {
301         layout: bindGroupLayout,
302         bindings: [transformBufferBindGroupBinding]
303     };
304 }
305
306 function drawCommands(mappedGroup) {
307     updateTransformArray(new Float32Array(mappedGroup.arrayBuffer));
308     mappedGroup.buffer.unmap();
309
310     const commandEncoder = device.createCommandEncoder();
311     renderPassDescriptor.colorAttachments[0].attachment = swapChain.getCurrentTexture().createDefaultView();
312     const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
313
314     // Encode drawing commands
315
316     passEncoder.setPipeline(pipeline);
317     // Vertex attributes
318     passEncoder.setVertexBuffers(0, [verticesBuffer], [0]);
319     // Bind groups
320     passEncoder.setBindGroup(bindGroupIndex, mappedGroup.bindGroup);
321     // 36 vertices, 1 instance, 0th vertex, 0th instance.
322     passEncoder.draw(36, 1, 0, 0);
323     passEncoder.endPass();
324
325     device.getQueue().submit([commandEncoder.finish()]);
326
327     // Ready the current buffer for update after GPU is done with it.
328     mappedGroup.buffer.mapWriteAsync().then((arrayBuffer) => {
329         mappedGroup.arrayBuffer = arrayBuffer;
330         mappedGroups.push(mappedGroup);
331     });
332
333     requestAnimationFrame(render);
334 }
335
336 function updateTransformArray(array) {
337     let viewMatrix = mat4.create();
338     mat4.translate(viewMatrix, viewMatrix, vec3.fromValues(0, 0, -5));
339     let now = Date.now() / 1000;
340     mat4.rotate(viewMatrix, viewMatrix, 1, vec3.fromValues(Math.sin(now), Math.cos(now), 0));
341     let modelViewProjectionMatrix = mat4.create();
342     mat4.multiply(modelViewProjectionMatrix, projectionMatrix, viewMatrix);
343     mat4.copy(array, modelViewProjectionMatrix);
344 }
345
346 window.addEventListener("load", init);
347 </script>
348 </body>
349 </html>