webgpu-app/index.js

365 lines
9.3 KiB
JavaScript

import { getPath } from "./common.js";
import { loadGltf } from "./index2.js";
import { loadLight } from "./light.js";
import { loadFlat } from "./flat.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");
}
const device = await adapter.requestDevice();
const canvas = document.querySelector("canvas");
canvas.width = canvas.clientWidth
canvas.height = canvas.clientHeight
const context = canvas.getContext("webgpu");
const canvasFormat = navigator.gpu.getPreferredCanvasFormat();
context.configure({
device: device,
format: canvasFormat,
});
const encoder = device.createCommandEncoder();
const renderPass = encoder.beginRenderPass({
colorAttachments: [{
view: context.getCurrentTexture().createView(),
loadOp: "clear",
clearValue: { r: 0.9, g: 0.5, b: 0.2, a: 1 },
storeOp: "store",
}]
});
renderPass.end();
const commandBuffer = encoder.finish();
device.queue.submit([commandBuffer]);
const memory = new WebAssembly.Memory({ initial: 258 * 2 });
const module = await WebAssembly.instantiateStreaming(fetch("src/module.wasm"), {
env: {
memory: memory,
log: console.log,
}
});
function createDepthTexture() {
return device.createTexture({
size: [canvas.width, canvas.height],
format: 'depth24plus',
usage: GPUTextureUsage.RENDER_ATTACHMENT,
});
}
var depthTexture = undefined;
const eyeValueX = document.getElementById("eye-value-x");
const eyeValueY = document.getElementById("eye-value-y");
const eyeValueZ = document.getElementById("eye-value-z");
const yawValue = document.getElementById("yaw");
const pitchValue = document.getElementById("pitch");
function recreateDepth(force)
{
if (canvas.clientWidth !== canvas.width || canvas.clientWidth !== canvas.height || force === true) {
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
if (depthTexture !== undefined) {
depthTexture.destroy();
}
depthTexture = createDepthTexture();
}
}
const matrixSize = 4 * 4 * 4;
const float4Size = 4 * 4;
const float3Size = 4 * 3;
const viewUniformBufferSize = (matrixSize * 2) + (float4Size * 2);
const viewUniformBuffer = device.createBuffer({
label: "view uniform buffer",
size: viewUniformBufferSize,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
});
const cameraStateSize = (float3Size * 3) + (4 * 3) + (float3Size * 1);
const viewStateAddress = module.instance.exports.mem_alloc(viewUniformBufferSize);
const cameraStateAddress = module.instance.exports.mem_alloc(cameraStateSize);
module.instance.exports.camera_init(cameraStateAddress);
//const gltfRenderer = await loadGltf(device, canvasFormat, viewUniformBuffer, memory, module);
//const lightRenderer = await loadLight(device, canvasFormat, viewUniformBuffer);
const flatRenderer = await loadFlat(device, canvasFormat, viewUniformBuffer);
const KEY = {
A: 65,
B: 66,
C: 67,
D: 68,
E: 69,
F: 70,
G: 71,
H: 72,
I: 73,
J: 74,
K: 75,
L: 76,
M: 77,
N: 78,
O: 79,
P: 80,
Q: 81,
R: 82,
S: 83,
T: 84,
U: 85,
V: 86,
W: 87,
X: 88,
Y: 89,
Z: 90,
Left: 37,
Up: 38,
Right: 39,
Down: 40,
};
const keyCodes = new Set(Object.values(KEY));
const keyState = {};
for (const code of Object.values(KEY)) {
keyState[code] = false;
}
function handleKeydown(e)
{
//console.log(e.keyCode);
if (keyCodes.has(e.keyCode)) {
keyState[e.keyCode] = true;
}
}
function handleKeyup(e)
{
if (keyCodes.has(e.keyCode)) {
keyState[e.keyCode] = false;
}
}
window.addEventListener('keydown', handleKeydown, false);
window.addEventListener('keyup', handleKeyup, false);
function updateView()
{
const lu = keyState[KEY.W] === true;
const ll = keyState[KEY.A] === true;
const ld = keyState[KEY.S] === true;
const lr = keyState[KEY.D] === true;
const lal = keyState[KEY.Q] === true;
const lar = keyState[KEY.E] === true;
const ru = keyState[KEY.Up] === true;
const rl = keyState[KEY.Left] === true;
const rd = keyState[KEY.Down] === true;
const rr = keyState[KEY.Right] === true;
module.instance.exports.camera_move(cameraStateAddress,
lu, ll, ld, lr,
lal, lar,
ru, rl, rd, rr);
const aspect = canvas.width / canvas.height;
module.instance.exports.camera_view_projection(cameraStateAddress,
viewStateAddress,
aspect);
device.queue.writeBuffer(viewUniformBuffer, 0,
memory.buffer, viewStateAddress,
viewUniformBufferSize);
const cameraStateF32 = new Float32Array(memory.buffer, cameraStateAddress, cameraStateSize);
/*
eyeValueX.innerHTML = cameraStateF32[0].toFixed(1);
eyeValueY.innerHTML = cameraStateF32[1].toFixed(1);
eyeValueZ.innerHTML = cameraStateF32[2].toFixed(1);
yawValue.innerHTML = cameraStateF32[9].toFixed(2);
pitchValue.innerHTML = cameraStateF32[10].toFixed(2);
*/
}
const controlMain = document.getElementById("control-main");
function remap(low1, high1, low2, high2, value)
{
return low2 + (value - low1) * (high2 - low2) / (high1 - low1);
}
const sliderDefaults = {
"circle radius": 385,
"circle thickness": 284,
"mass12 mix": 1,
"particle count": 360,
"particle mass1": 126,
"particle mass2": 417,
"particle size": 51,
"test intensity": 0,
};
class Slider {
constructor(label, low, high, max = 1024)
{
const labelE = document.createElement("span");
labelE.className = "label";
labelE.innerHTML = label;
const valueE = document.createElement("div");
valueE.className = "value";
const inputE = document.createElement("input");
inputE.className = "range";
inputE.min = 0;
inputE.max = max;
inputE.type = "range";
const spanE = document.createElement("span");
spanE.className = "range-span code";
valueE.appendChild(inputE);
valueE.appendChild(spanE);
controlMain.appendChild(labelE);
controlMain.appendChild(valueE);
this.label = label;
this.input = inputE;
this.span = spanE;
this.low = low;
this.high = high;
this.lastValue = undefined;
const storageValue = localStorage.getItem(this.label);
if (storageValue !== null) {
this.input.value = storageValue;
} else if (this.label in sliderDefaults) {
this.input.value = sliderDefaults[this.label];
}
this.update();
}
update()
{
const rawValue = parseInt(this.input.value);
const value = remap(this.input.min, this.input.max, this.low, this.high, this.input.value);
if (rawValue !== this.lastValue) {
this.lastValue = rawValue;
localStorage.setItem(this.label, rawValue);
}
if (this.high > 10) {
this.span.innerHTML = value.toFixed(0);
} else {
this.span.innerHTML = value.toFixed(5);
}
return value;
}
}
const sliders = {
particleSize: new Slider("particle size", 0.0, 0.05),
particleMass1: new Slider("particle mass1", 0.0, 0.01),
particleMass2: new Slider("particle mass2", 0.0, 1.0),
particleCount: new Slider("particle count", 0.0, 1024.0),
circleRadius: new Slider("circle radius", 0.0, 0.5),
circleThickness: new Slider("circle thickness", 0.0, 0.01),
testIntensity: new Slider("test intensity", 0.0, 2.0),
mass12Mix: new Slider("mass12 mix", 0.0, 1.0, 1),
};
const mousePosition = new Float32Array([0, 0, 0, 0]);
function updateSliders()
{
return {
mousePosition: mousePosition,
particleSize: sliders.particleSize.update(),
particleMass1: sliders.particleMass1.update(),
particleMass2: sliders.particleMass2.update(),
particleCount: sliders.particleCount.update(),
circleRadius: sliders.circleRadius.update(),
circleThickness: sliders.circleThickness.update(),
testIntensity: sliders.testIntensity.update(),
mass12Mix: sliders.mass12Mix.update(),
};
}
function onClick(e)
{
mousePosition[0] = e.clientX / canvas.width;
mousePosition[1] = 1.0 - (e.clientY / canvas.height);
mousePosition[2] = 0;
mousePosition[3] = 1;
}
canvas.addEventListener("click", onClick);
var canRender = false;
var frameNumber = 0;
function render2()
{
if (canRender === true) {
const configuration = updateSliders();
canRender = false;
recreateDepth(false);
const colorView = context.getCurrentTexture().createView();
updateView();
const encoder = device.createCommandEncoder();
const renderPass = encoder.beginRenderPass({
colorAttachments: [{
view: colorView,
loadOp: "clear",
clearValue: { r: 0.2, g: 0.2, b: 0.4, a: 1 },
storeOp: "store",
}],
depthStencilAttachment: {
view: depthTexture.createView(),
depthClearValue: 1.0,
depthLoadOp: 'clear',
depthStoreOp: 'store',
},
});
//gltfRenderer.render(renderPass);
//lightRenderer.render(renderPass);
flatRenderer.render(device, renderPass, frameNumber, configuration);
renderPass.end();
const commandBuffer = encoder.finish();
device.queue.submit([commandBuffer]);
frameNumber = (frameNumber + 1) % 2;
}
requestAnimationFrame(render2);
}
function setCanRender()
{
canRender = true;
}
setInterval(setCanRender, 66.67); // 15 fps
recreateDepth(true);
requestAnimationFrame(render2);