webgpu-app/index.js

370 lines
10 KiB
JavaScript

import { getPath } from "./common.js";
import { loadGltf } from "./index2.js";
import { loadLight } from "./light.js";
import { loadFlat } from "./flat.js";
import { loadSnake } from "./snake.js";
import { loadFont } from "./font.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,
alphaMode: 'premultiplied',
});
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,
log4: console.log,
logStrInt: console.log,
}
});
module.memory = memory;
var depthTexture = undefined;
var multisampleTexture = 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(canvasTexture, sampleCount)
{
const depthResized = depthTexture === undefined || canvasTexture.width != depthTexture.width || canvasTexture.height != depthTexture.height;
if (depthResized || depthTexture.sampleCount !== sampleCount) {
if (depthTexture !== undefined) {
depthTexture.destroy();
}
depthTexture = device.createTexture({
format: 'depth24plus',
usage: GPUTextureUsage.RENDER_ATTACHMENT,
size: [canvasTexture.width, canvasTexture.height],
sampleCount: sampleCount,
});
}
if (sampleCount > 1) {
const multisampleResized = multisampleTexture === undefined || canvasTexture.width !== multisampleTexture.width || canvasTexture.height !== multisampleTexture.height;
if (multisampleResized || multisampleTexture.sampleCount !== sampleCount) {
if ((multisampleTexture !== undefined)) {
multisampleTexture.destroy();
}
multisampleTexture = device.createTexture({
format: canvasTexture.format,
usage: GPUTextureUsage.RENDER_ATTACHMENT,
size: [canvasTexture.width, canvasTexture.height],
sampleCount: sampleCount,
});
}
}
}
const matrixSize = 4 * 4 * 4;
const float4Size = 4 * 4;
const float3Size = 4 * 3;
const viewUniformBufferSize = (matrixSize * 2) + (float4Size * 2) + (4 * 4);
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, module);
//const snakeRenderer = await loadSnake(device, canvasFormat, viewUniformBuffer, module);
const fontRenderer = await loadFont(device, canvasFormat, viewUniformBuffer, module);
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 flatSliders = {
particleSize: new Slider("particle size", 0.0, 0.05),
particleMass1: new Slider("particle mass1", 0.0, 0.05),
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),
lineThickness: new Slider("line thickness", 0.0, 0.005),
lineLength: new Slider("line length", 0.0, 9.0),
targetDensity: new Slider("target density", -10.0, 10.0),
pressure: new Slider("pressure", 0.0, 1.0),
};
*/
const mousePosition = new Float32Array([0, 0, 0, 0]);
const mousePositionClick = new Float32Array([0, 0, 0, 0]);
/*
const snakeSliders = {
boneCount: new Slider("bone count", 0.0, 100.0, 100),
boneLength: new Slider("bone length", 0.0, 0.5),
velocityDamping: new Slider("velocity damp.", 0.0, 1.0),
velocityAcceleration: new Slider("velocity acc.", 0.0, 1.0),
};
*/
function onClick(e)
{
mousePositionClick[0] = e.clientX;
mousePositionClick[1] = e.clientY;
}
canvas.addEventListener("click", onClick);
function onMouseMove(e)
{
const aspect = canvas.width / canvas.height;
mousePosition[0] = ((e.clientX / canvas.width) * 2 - 1) * aspect;
mousePosition[1] = (1.0 - (e.clientY / canvas.height)) * 2 - 1;
}
canvas.addEventListener("mousemove", onMouseMove);
var canRender = false;
var frameNumber = 0;
var tempBuffer = undefined;
function render2()
{
if (canRender === true) {
canRender = false;
canvas.width = canvas.clientWidth * window.devicePixelRatio;
canvas.height = canvas.clientHeight * window.devicePixelRatio;
const configuration = fontRenderer.update(device, frameNumber);
const sampleCount = configuration.multisampleCount;
//const configuration = updateSliders(flatSliders);
//const configuration = updateSliders(snakeSliders);
const canvasTexture = context.getCurrentTexture();
recreateDepth(canvasTexture, sampleCount);
updateView();
const clearValue = { r: 0.2, g: 0.2, b: 0.4, a: 1.0 };
const attachment0 = (sampleCount == 1) ? {
view: canvasTexture.createView(),
loadOp: "clear",
clearValue: clearValue,
storeOp: "store",
} : {
view: multisampleTexture.createView(),
resolveTarget: canvasTexture.createView(),
loadOp: "clear",
clearValue: clearValue,
storeOp: "store",
};
const encoder = device.createCommandEncoder();
const renderPass = encoder.beginRenderPass({
colorAttachments: [attachment0],
depthStencilAttachment: {
view: depthTexture.createView(),
depthClearValue: 1.0,
depthLoadOp: 'clear',
depthStoreOp: 'store',
},
});
//gltfRenderer.render(renderPass);
//lightRenderer.render(renderPass);
//flatRenderer.render(device, renderPass, frameNumber, configuration);
//snakeRenderer.update(device, frameNumber, mousePosition, configuration);
//snakeRenderer.render(renderPass, frameNumber);
fontRenderer.render(renderPass, frameNumber, sampleCount);
renderPass.end();
/*
encoder.copyTextureToBuffer({
texture: multisampleTexture,
}, {
buffer: tempBuffer,
bytesPerRow: canvasTexture.width * 4 * 4,
}, {
width: canvasTexture.width,
height: canvasTexture.height,
depthOrArrayLayers: 1,
});
*/
const commandBuffer = encoder.finish();
device.queue.submit([commandBuffer]);
frameNumber = (frameNumber + 1) % 2;
/*
tempBuffer.mapAsync(GPUMapMode.READ).then(() => {
const readBuffer = tempBuffer.getMappedRange(); // arraybuffer
//console.log(mousePositionClick);
const x = mousePositionClick[0];
const y = mousePositionClick[1];
const f32ReadBuffer = new Float32Array(readBuffer);
for (let i = 0; i < 2; i++) {
const a = f32ReadBuffer[((y + i) * canvasTexture.width + x) * 4 + 0];
const b = f32ReadBuffer[((y + i) * canvasTexture.width + x) * 4 + 1];
const c = f32ReadBuffer[((y + i) * canvasTexture.width + x) * 4 + 2];
const d = f32ReadBuffer[((y + i) * canvasTexture.width + x) * 4 + 3];
//console.log(a, b, c, d);
}
tempBuffer.unmap();
requestAnimationFrame(render2);
});
*/
requestAnimationFrame(render2);
} else {
requestAnimationFrame(render2);
}
}
function setCanRender()
{
canRender = true;
}
setInterval(setCanRender, 33.33); // 15 fps
requestAnimationFrame(render2);