432 lines
12 KiB
JavaScript
432 lines
12 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;
|
|
var copyTexture = 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");
|
|
|
|
var downloadFramebufferPending = false;
|
|
var tempBuffer = undefined;
|
|
|
|
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,
|
|
});
|
|
}
|
|
|
|
const tempBufferSize = canvasTexture.width * canvasTexture.height * 4;
|
|
const tempResized = tempBuffer === undefined || tempBufferSize != tempBuffer.size;
|
|
if (tempResized) {
|
|
if (tempBuffer !== undefined) {
|
|
tempBuffer.destroy();
|
|
}
|
|
tempBuffer = device.createBuffer({
|
|
label: "temp buffer",
|
|
size: tempBufferSize,
|
|
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
|
|
});
|
|
}
|
|
|
|
const copyResized = copyTexture === undefined || canvasTexture.width !== copyTexture.width || canvasTexture.height !== copyTexture.height;
|
|
if (copyResized) {
|
|
if (copyTexture !== undefined) {
|
|
copyTexture.destroy();
|
|
}
|
|
|
|
copyTexture = device.createTexture({
|
|
format: canvasTexture.format,
|
|
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
|
|
size: [canvasTexture.width, canvasTexture.height],
|
|
sampleCount: 1,
|
|
});
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
function handleDownloadButton(e)
|
|
{
|
|
downloadFramebufferPending = true;
|
|
console.log("download framebuffer");
|
|
}
|
|
|
|
window.addEventListener('keydown', handleKeydown, false);
|
|
window.addEventListener('keyup', handleKeyup, false);
|
|
|
|
const downloadFramebufferButton = document.getElementById("download-framebuffer");
|
|
downloadFramebufferButton.addEventListener("click", handleDownloadButton, 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;
|
|
|
|
function tgaBlob(texture, mappedRange)
|
|
{
|
|
const tgaHeaderSize = 18;
|
|
const tgaBuffer = new ArrayBuffer(mappedRange.byteLength + tgaHeaderSize);
|
|
const tgaView = new DataView(tgaBuffer);
|
|
tgaView.setUint8(0, 0); // idLength
|
|
tgaView.setUint8(1, 0); // colorMapType
|
|
tgaView.setUint8(2, 2); // imageTypeCode: 2 uncompressed RGB
|
|
tgaView.setUint16(3, 0, true); // colorMap origin
|
|
tgaView.setUint16(5, 0, true); // colorMap length
|
|
tgaView.setUint8(7, 0); // colorMap depth
|
|
tgaView.setUint16(8, 0, true); // xOrigin
|
|
tgaView.setUint16(10, 0, true); // yOrigin
|
|
tgaView.setUint16(12, texture.width, true); // width
|
|
tgaView.setUint16(14, texture.height, true); // height
|
|
tgaView.setUint8(16, 32); // bits per pixel
|
|
const descriptor = ((8 << 0) | // number of attribute bits
|
|
(1 << 5) | // origin in upper left
|
|
(0 << 6)); // non-interleaved
|
|
tgaView.setUint8(17, descriptor); // descriptor
|
|
const tgaU8 = new Uint8Array(tgaBuffer);
|
|
tgaU8.set(new Uint8Array(mappedRange), 18);
|
|
return new Blob([tgaBuffer]);
|
|
}
|
|
|
|
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 = ;
|
|
const clearValue = (downloadFramebufferPending) ? { r: 0.0, g: 0.0, b: 0.0, a: 0.0 } : { r: 0.2, g: 0.2, b: 0.4, a: 1.0 };
|
|
|
|
const viewTexture = (downloadFramebufferPending) ? copyTexture : canvasTexture;
|
|
|
|
const attachment0 = (sampleCount == 1) ? {
|
|
view: viewTexture.createView(),
|
|
loadOp: "clear",
|
|
clearValue: clearValue,
|
|
storeOp: "store",
|
|
} : {
|
|
view: multisampleTexture.createView(),
|
|
resolveTarget: viewTexture.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();
|
|
|
|
if (downloadFramebufferPending) {
|
|
encoder.copyTextureToBuffer({
|
|
texture: copyTexture,
|
|
}, {
|
|
buffer: tempBuffer,
|
|
bytesPerRow: canvasTexture.width * 4,
|
|
}, {
|
|
width: copyTexture.width,
|
|
height: copyTexture.height,
|
|
depthOrArrayLayers: 1,
|
|
});
|
|
}
|
|
|
|
const commandBuffer = encoder.finish();
|
|
device.queue.submit([commandBuffer]);
|
|
|
|
frameNumber = (frameNumber + 1) % 2;
|
|
|
|
if (downloadFramebufferPending) {
|
|
tempBuffer.mapAsync(GPUMapMode.READ).then(() => {
|
|
const mappedRange = tempBuffer.getMappedRange(); // arraybuffer
|
|
const blob = tgaBlob(copyTexture, mappedRange);
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement("a");
|
|
a.setAttribute("href", url);
|
|
a.setAttribute("download", "framebuffer.tga");
|
|
a.click();
|
|
tempBuffer.unmap();
|
|
requestAnimationFrame(render2);
|
|
});
|
|
downloadFramebufferPending = false;
|
|
} else {
|
|
requestAnimationFrame(render2);
|
|
}
|
|
} else {
|
|
requestAnimationFrame(render2);
|
|
}
|
|
}
|
|
function setCanRender()
|
|
{
|
|
canRender = true;
|
|
}
|
|
setInterval(setCanRender, 33.33); // 15 fps
|
|
|
|
requestAnimationFrame(render2);
|