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"; import { loadCompute } from "./compute.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 hasBgraStorage = adapter.features.has("bgra8unorm-storage"); const device = await adapter.requestDevice({ requiredFeatures: hasBgraStorage ? ["bgra8unorm-storage"] : [] }); const canvas = document.querySelector("canvas"); canvas.width = canvas.clientWidth canvas.height = canvas.clientHeight const context = canvas.getContext("webgpu"); const canvasFormat = hasBgraStorage ? navigator.gpu.getPreferredCanvasFormat() : "rgba8unorm"; context.configure({ device: device, format: canvasFormat, alphaMode: 'premultiplied', usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT }); console.log("canvasFormat", 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, 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 downloadRenderBufferPending = 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 compute = await loadCompute(device, canvasFormat, 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) { downloadRenderBufferPending = true; console.log("download framebuffer"); } window.addEventListener('keydown', handleKeydown, false); window.addEventListener('keyup', handleKeyup, false); const downloadRenderBufferButton = document.getElementById("download-render-buffer"); downloadRenderBufferButton.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 sampleCount = 1; //const configuration = updateSliders(flatSliders); //const configuration = updateSliders(snakeSliders); const canvasTexture = context.getCurrentTexture(); recreateDepth(canvasTexture, sampleCount); updateView(); //const clearValue = ; const clearValue = (downloadRenderBufferPending) ? { 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 = (downloadRenderBufferPending) ? 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 (downloadRenderBufferPending) { 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]); compute.render(device, frameNumber, canvasTexture); frameNumber = (frameNumber + 1) % 2; if (downloadRenderBufferPending) { 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); }); downloadRenderBufferPending = false; } else { requestAnimationFrame(render2); } } else { requestAnimationFrame(render2); } } function setCanRender() { canRender = true; } setInterval(setCanRender, 33.33); // 15 fps requestAnimationFrame(render2);