-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.js
266 lines (208 loc) · 6.19 KB
/
main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
/// Goal: create a webgpu impl of the mandelbulb fractal
/// Source: https://iquilezles.org/articles/mandelbulb/
// import WebMWriter from "webm-writer";
let FPS = 20;
let mean = 0.0;
let numTimes = 0;
const canvas = document.querySelector("canvas");
canvas.width = 1024;
canvas.height = 1024;
// webgpu constructs
let device, videoWriter, renderPipeline, time, context, vertexBuffer, vertices, rotation;
// user input constructs
let isMouseDown = false;
let priorX = 0;
let priorY = 0;
let targetX = 0;
let targetY = 0;
let currentX = 0;
let currentY = 0;
let pauseDuration = 0;
let priorTime = 0;
const setup = async () => {
if (!navigator.gpu) {
alert("WebGPU is not supported in your browser - Try the latest version of Chrome or Microsoft Edge");
return;
}
const adapter = await navigator.gpu.requestAdapter();
device = await adapter.requestDevice({
powerPreference: "high-performance",
});
const shaders = await (await fetch("shaders.wgsl")).text();
context = canvas.getContext("webgpu");
const canvasFormat = navigator.gpu.getPreferredCanvasFormat();
context.configure({
// tells the canvas that you will receive data from this `device` in this `format`
device: device,
format: canvasFormat,
});
// videoWriter = new WebMWriter({
// quality: 1.0,
// frameRate: FPS,
// });
// with the device, setup a render pipeline
// vertex buffer
vertices = new Float32Array([
-1, -1, 1, 1, 1, -1,
-1, -1, 1, 1, -1, 1,
]);
vertexBuffer = device.createBuffer({
label: "Cell vertices", // I think this is for debug purposes only -> shows up in error messages
size: vertices.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
});
time = device.createBuffer({
label: "Time buffer",
size: 4,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
});
rotation = device.createBuffer({
label: "Mouse Rotation",
size: 8,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
});
device.queue.writeBuffer(rotation, 0, new Float32Array([0, 0]));
device.queue.writeBuffer(vertexBuffer, 0, vertices);
// layout
const vertexBufferLayout = {
arrayStride: 8, // each vertex is 2 floats which are each two bytes, so each point is 8 bytes
attributes: [
{
format: "float32x2",
offset: 0,
shaderLocation: 0,
},
],
};
// shader module
const shaderModule = device.createShaderModule({
label: "Mandelbulb shader module",
code: shaders,
});
// create pipeline
renderPipeline = device.createRenderPipeline({
label: "set pipeline",
layout: "auto",
vertex: {
module: shaderModule,
entryPoint: "vertexShader",
buffers: [vertexBufferLayout],
},
fragment: {
module: shaderModule,
entryPoint: "fragmentShader",
targets: [
{
format: canvasFormat,
},
],
},
});
setInterval(render, 1000 / FPS);
// setting up mouse listeners
canvas.addEventListener('mousedown', mouseDown);
// both leaving and not clicking stop the animation from being interactive
canvas.addEventListener('mouseup', mouseUp);
canvas.addEventListener('mouseleave', mouseUp);
// dragging the mouse
canvas.addEventListener('mousemove', canvasDragged);
};
setup();
const canvasDragged = (event) => {
if(!isMouseDown) return; // do nothing
let diffX = event.clientX - priorX;
let diffY = event.clientY - priorY;
const rect = canvas.getBoundingClientRect();
priorX = event.clientX - rect.left;
priorY = event.clientY - rect.top;
// scale down diffX and diffY
targetX += diffX / canvas.width;
targetY += diffY / canvas.height;
};
const mouseUp = (event) => {
isMouseDown = false;
};
const mouseDown = (event) => {
isMouseDown = true;
const rect = canvas.getBoundingClientRect();
priorX = event.clientX - rect.left;
priorY = event.clientY - rect.top;
};
const render = async () => {
// write the time buffer
const timeBuffer = new Float32Array([performance.now() / 1000 - pauseDuration]);
if (!paused) {
device.queue.writeBuffer(time, 0, timeBuffer);
} else {
pauseDuration += performance.now() / 1000 - priorTime;
}
priorTime = performance.now() / 1000;
// update current from target
currentX -= (currentX - targetX) * 0.1;
currentY -= (currentY - targetY) * 0.1;
let rotationBuffer = new Float32Array([currentY, -1 *currentX]);
device.queue.writeBuffer(rotation, 0, rotationBuffer);
// create the bind group for the time buffer
const bindGroup = device.createBindGroup({
layout: renderPipeline.getBindGroupLayout(0),
entries: [
{
binding: 0,
resource: {
buffer: time,
},
},
{
binding: 1,
resource: {
buffer: rotation,
},
},
],
});
// actual render function
let begin = performance.now();
// creating an encoder
const encoder = device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [
{
view: context.getCurrentTexture().createView(),
loadOp: "clear", // no persistence of data
clearValue: { r: 0, g: 0, b: 0.4, a: 1 },
storeOp: "store", // at the end of the render pass, store the result into the texture
},
],
});
pass.setBindGroup(0, bindGroup);
pass.setPipeline(renderPipeline);
pass.setVertexBuffer(0, vertexBuffer);
pass.draw(vertices.length / 2, 1);
pass.end();
// submitting the render pass
device.queue.submit([encoder.finish()]);
await device.queue.onSubmittedWorkDone();
let elapsed = performance.now() - begin;
mean = (mean * numTimes + elapsed) / (numTimes + 1);
numTimes += 1;
};
// scheduling the interval
let paused = false;
// for (let i = 0; i < 30; i++) {
// render();
// videoWriter.addFrame(canvas);
// }
// videoWriter.complete().then(function (webMBlob) {
// const a = document.createElement("a");
// const url = URL.createObjectURL(webMBlob);
// a.download = "video.webm";
// a.href = url;
// a.action = "download";
// //
// a.click();
// URL.revokeObjectURL(href);
// });
// render();
document.addEventListener("keydown", (e) => {
paused = e.key === " " ? !paused : paused;
});