fixed paths.
This commit is contained in:
167
demos/WebGpuFrameworkWasm/index.html
Normal file
167
demos/WebGpuFrameworkWasm/index.html
Normal file
@@ -0,0 +1,167 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Wasm & WebGpu.</title>
|
||||
</head>
|
||||
|
||||
<style >
|
||||
body{
|
||||
margin: 0;
|
||||
}
|
||||
#fps {
|
||||
position: fixed;
|
||||
top: 5px; left: 5px;
|
||||
color: white;
|
||||
background: rgba(0,0,0,0.5);
|
||||
padding: 4px 8px;
|
||||
font-family: monospace;
|
||||
font-size: 14px;
|
||||
z-index: 100;
|
||||
}
|
||||
</style>
|
||||
<script type="module">
|
||||
|
||||
import { ParticleSimulation } from "/demos/WebGpuFrameworkWasm/rayTracingWebGpuWasm.js";
|
||||
|
||||
|
||||
var particleSimulation = new ParticleSimulation();
|
||||
|
||||
|
||||
var useWebWorker = false;
|
||||
|
||||
const canvas = document.querySelector(".mainCanvas");
|
||||
|
||||
particleSimulation.setCanvas( canvas );
|
||||
|
||||
resizeCanvasToDisplaySize( canvas );
|
||||
|
||||
|
||||
var worker;
|
||||
|
||||
if ( !useWebWorker ) {
|
||||
|
||||
|
||||
await particleSimulation.setup( canvas, canvas.width, canvas.height, true );
|
||||
|
||||
console.log("document.bufferMap", document.bufferMap);
|
||||
|
||||
} else if ( useWebWorker ) {
|
||||
|
||||
worker = new Worker("../framework/GpuWorker.js", { type: "module" });
|
||||
|
||||
worker.onmessage = function ( event ) {
|
||||
|
||||
console.log("From worker:", event.data);
|
||||
|
||||
};
|
||||
|
||||
const offscreen = canvas.transferControlToOffscreen();
|
||||
|
||||
worker.addEventListener("error", function ( event ) {
|
||||
|
||||
console.error( "Worker failed:",
|
||||
event.message, "at",
|
||||
event.filename + ":" +
|
||||
event.lineno + ":" +
|
||||
event.colno );
|
||||
|
||||
});
|
||||
|
||||
var request = {
|
||||
method: "setup",
|
||||
canvas: offscreen,
|
||||
width: canvas.width,
|
||||
height: canvas.height
|
||||
};
|
||||
|
||||
worker.postMessage( request, [offscreen] );
|
||||
|
||||
}
|
||||
|
||||
|
||||
var events = new Array( "mousemove", "mousedown", "mouseup", "onwheel", "wheel" );
|
||||
|
||||
for ( var i = 0; i < events.length; i++ ) {
|
||||
|
||||
let eventName = events[i];
|
||||
|
||||
console.log("createEvent", eventName);
|
||||
|
||||
canvas.addEventListener( eventName, function ( event ) {
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
|
||||
const x = event.clientX - rect.left;
|
||||
|
||||
const y = event.clientY - rect.top;
|
||||
|
||||
var request = {
|
||||
method: eventName,
|
||||
clientX: x,
|
||||
clientY: y,
|
||||
deltaY: event.deltaY
|
||||
};
|
||||
|
||||
if ( useWebWorker ) {
|
||||
|
||||
worker.postMessage( request );
|
||||
|
||||
} else {
|
||||
|
||||
particleSimulation.eventManager[ eventName ]( request );
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function resizeCanvasToDisplaySize( canvas ) {
|
||||
|
||||
const width = window.innerWidth;
|
||||
const height = window.innerHeight;
|
||||
|
||||
var request = {
|
||||
method: "resize",
|
||||
width: width,
|
||||
height: height
|
||||
};
|
||||
|
||||
if ( useWebWorker ) {
|
||||
|
||||
worker.postMessage( request );
|
||||
|
||||
} else {
|
||||
|
||||
particleSimulation.eventManager.resize( request );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
window.addEventListener( "resize", function () {
|
||||
|
||||
resizeCanvasToDisplaySize( canvas );
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<body>
|
||||
<div id="fps">FPS: 0</div>
|
||||
|
||||
<canvas class="mainCanvas" width="1000" height="1000"></canvas>
|
||||
</body>
|
||||
</html>
|
||||
357
demos/WebGpuFrameworkWasm/rayTracingWebGpuWasm.js
Normal file
357
demos/WebGpuFrameworkWasm/rayTracingWebGpuWasm.js
Normal file
@@ -0,0 +1,357 @@
|
||||
import Shader from "../../framework/WebGpu.js";
|
||||
|
||||
import Matrix4 from "../../framework/Matrix4.js";
|
||||
|
||||
import Vector3 from "../../framework/Vector3.js";
|
||||
|
||||
import Camera from "../../framework/Camera.js";
|
||||
|
||||
import EventManager from "../../framework/eventManager.js";
|
||||
|
||||
import ShaderInpector from "../../framework/ShaderInpector.js";
|
||||
|
||||
import WasmModule from "./wasm_renderer.js";
|
||||
|
||||
|
||||
|
||||
export class ParticleSimulation {
|
||||
|
||||
canvas;
|
||||
|
||||
device;
|
||||
|
||||
camera;
|
||||
|
||||
eventManager = new EventManager();
|
||||
|
||||
frameCount = 0;
|
||||
|
||||
wasm;
|
||||
|
||||
framebufferPtr;
|
||||
|
||||
framebufferUint8;
|
||||
|
||||
frameBufferTexture;
|
||||
|
||||
fbSampler;
|
||||
|
||||
width;
|
||||
|
||||
height;
|
||||
|
||||
renderShader;
|
||||
|
||||
time = 0;
|
||||
|
||||
lastTime = 0;
|
||||
|
||||
lastFrameTime;
|
||||
|
||||
|
||||
setCanvas ( canvas ) {
|
||||
|
||||
this.canvas = canvas;
|
||||
|
||||
this.eventManager.setCanvas( canvas );
|
||||
|
||||
}
|
||||
|
||||
createPlane ( width, height, repeatU, repeatV ) {
|
||||
|
||||
const vertices = new Float32Array( new Array(
|
||||
-width / 2, -height / 2, 0,
|
||||
width / 2, -height / 2, 0,
|
||||
-width / 2, height / 2, 0,
|
||||
width / 2, -height / 2, 0,
|
||||
width / 2, height / 2, 0,
|
||||
-width / 2, height / 2, 0
|
||||
) );
|
||||
|
||||
const uvs = new Float32Array( new Array(
|
||||
0, 0,
|
||||
repeatU, 0,
|
||||
0, repeatV,
|
||||
repeatU, 0,
|
||||
repeatU, repeatV,
|
||||
0, repeatV
|
||||
) );
|
||||
|
||||
const result = {
|
||||
vertices: vertices,
|
||||
normals: null,
|
||||
uvs: uvs
|
||||
};
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
async setup ( offscreenCanvas, width, height ) {
|
||||
|
||||
this.fpsElement = document.getElementById( "fps" );
|
||||
|
||||
this.width = width;
|
||||
|
||||
this.height = height;
|
||||
|
||||
offscreenCanvas.width = width;
|
||||
|
||||
offscreenCanvas.height = height;
|
||||
|
||||
this.canvas = offscreenCanvas;
|
||||
|
||||
const context = offscreenCanvas.getContext( "webgpu" );
|
||||
|
||||
this.camera = new Camera( new Array( 0, 0, 1115 ), new Array( 0, -0.3, 0 ), new Array( 0, 1, 0 ) );
|
||||
|
||||
this.eventManager.setup( offscreenCanvas, this.camera );
|
||||
|
||||
const adapter = await navigator.gpu.requestAdapter();
|
||||
|
||||
if ( adapter === null ) {
|
||||
|
||||
throw new Error( "Failed to get GPU adapter" );
|
||||
|
||||
}
|
||||
|
||||
this.device = await adapter.requestDevice();
|
||||
|
||||
const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
|
||||
|
||||
context.configure({
|
||||
device: this.device,
|
||||
format: presentationFormat,
|
||||
alphaMode: "opaque"
|
||||
});
|
||||
|
||||
this.renderShader = new Shader( this.device );
|
||||
|
||||
this.renderShader.setCanvas( this.canvas );
|
||||
|
||||
this.wasm = await WasmModule();
|
||||
|
||||
this.wasm._set_resolution( width, height );
|
||||
|
||||
this.framebufferPtr = this.wasm._get_framebuffer_ptr();
|
||||
|
||||
this.framebufferUint8 = new Uint8Array(
|
||||
this.wasm.HEAPU8.buffer,
|
||||
this.framebufferPtr,
|
||||
width * height * 4
|
||||
);
|
||||
|
||||
this.frameBufferTexture = this.device.createTexture({
|
||||
size: [ width, height, 1 ],
|
||||
format: "rgba8unorm",
|
||||
usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING
|
||||
});
|
||||
|
||||
this.fbSampler = this.device.createSampler({
|
||||
magFilter: "nearest",
|
||||
minFilter: "nearest",
|
||||
addressModeU: "clamp-to-edge",
|
||||
addressModeV: "clamp-to-edge"
|
||||
});
|
||||
|
||||
const plane = this.createPlane( 1800, 1800, 1, 1 );
|
||||
|
||||
this.renderShader.setAttribute( "position", plane.vertices );
|
||||
|
||||
this.renderShader.setAttribute( "uv", plane.uvs );
|
||||
|
||||
this.vertexCount = plane.vertices.length / 3;
|
||||
|
||||
this.renderShader.topology = "triangle-list";
|
||||
|
||||
await this.renderShader.setup( "../../shaders/triangle-list-texture-solid.wgsl" );
|
||||
|
||||
this.renderShader.setVariable( "mySampler", this.fbSampler );
|
||||
|
||||
this.renderShader.setVariable( "myTexture", this.frameBufferTexture );
|
||||
|
||||
|
||||
await this.setupFxaa();
|
||||
|
||||
await this.setupTaa();
|
||||
|
||||
|
||||
this.lastFrameTime = performance.now();
|
||||
|
||||
this.bindRenderLoop();
|
||||
|
||||
|
||||
}
|
||||
|
||||
async setupFxaa() {
|
||||
|
||||
this.fxaaShader = new Shader(this.device);
|
||||
|
||||
this.fxaaShader.setCanvas(this.canvas);
|
||||
|
||||
const fullscreenQuad = this.createPlane(2, 2, 1, 1); // covers NDC [-1,1]
|
||||
|
||||
this.fxaaShader.setAttribute("position", fullscreenQuad.vertices);
|
||||
|
||||
this.fxaaShader.setAttribute("uv", fullscreenQuad.uvs);
|
||||
|
||||
this.fxaaShader.topology = "triangle-list";
|
||||
|
||||
await this.fxaaShader.setup("../../shaders/fxaa.wgsl");
|
||||
|
||||
this.fxaaShader.setVariable("mySampler", this.fbSampler);
|
||||
|
||||
this.fxaaShader.setVariable("myTexture", this.frameBufferTexture);
|
||||
|
||||
this.fxaaShader.setVariable("resolution", new Float32Array([this.width, this.height]));
|
||||
|
||||
|
||||
}
|
||||
|
||||
updateTimeDelta () {
|
||||
|
||||
const now = performance.now();
|
||||
|
||||
this.deltaTimeValue = ( now - this.lastFrameTime ) / 1000;
|
||||
|
||||
this.time += this.deltaTimeValue;
|
||||
|
||||
this.lastFrameTime = now;
|
||||
|
||||
this.frameCount++;
|
||||
|
||||
if ( now - this.lastTime >= 1000 ) {
|
||||
|
||||
const fps = this.frameCount / ( ( now - this.lastTime ) / 1000 );
|
||||
|
||||
const fpsText = "FPS: " + fps.toFixed( 1 );
|
||||
|
||||
this.fpsElement.textContent = fpsText;
|
||||
|
||||
this.frameCount = 0;
|
||||
|
||||
this.lastTime = now;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
async render () {
|
||||
|
||||
this.updateTimeDelta();
|
||||
|
||||
this.wasm._update_framebuffer( this.time );
|
||||
|
||||
this.device.queue.writeTexture(
|
||||
{ texture: this.frameBufferTexture },
|
||||
this.framebufferUint8,
|
||||
{
|
||||
bytesPerRow: this.width * 4
|
||||
},
|
||||
{
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
depthOrArrayLayers: 1
|
||||
}
|
||||
);
|
||||
/*
|
||||
const viewMatrix = this.camera.getViewMatrix();
|
||||
|
||||
const projectionMatrix = Matrix4.createProjectionMatrix( this.camera, this.canvas );
|
||||
|
||||
const viewProjectionMatrix = Matrix4.multiply( projectionMatrix, viewMatrix );
|
||||
|
||||
const cameraWorldMatrix = Matrix4.invert( viewMatrix );
|
||||
|
||||
const cameraPosition = Matrix4.getColumn( cameraWorldMatrix, 3 );
|
||||
|
||||
this.renderShader.setVariable( "viewProjectionMatrix", viewProjectionMatrix );
|
||||
|
||||
this.renderShader.renderToCanvas( this.vertexCount, 1, 0 );
|
||||
*/
|
||||
|
||||
|
||||
this.fxaaShader.renderToCanvas(6, 1, 0);
|
||||
|
||||
}
|
||||
|
||||
renderTaa() {
|
||||
|
||||
const prevIndex = this.currentHistoryIndex;
|
||||
const nextIndex = 1 - prevIndex;
|
||||
|
||||
// Set framebuffer into history
|
||||
this.device.queue.writeTexture(
|
||||
{ texture: this.historyTextures[nextIndex] },
|
||||
this.framebufferUint8,
|
||||
{ bytesPerRow: this.width * 4 },
|
||||
{ width: this.width, height: this.height, depthOrArrayLayers: 1 }
|
||||
);
|
||||
|
||||
// Apply TAA blend
|
||||
this.taaShader.setVariable("currentFrame", this.historyTextures[nextIndex]);
|
||||
this.taaShader.setVariable("historyFrame", this.historyTextures[prevIndex]);
|
||||
|
||||
this.taaShader.renderToCanvas(6, 1, 0);
|
||||
|
||||
// Swap history
|
||||
this.currentHistoryIndex = nextIndex;
|
||||
|
||||
}
|
||||
|
||||
|
||||
async setupTaa() {
|
||||
|
||||
this.taaShader = new Shader(this.device);
|
||||
|
||||
this.taaShader.setCanvas(this.canvas);
|
||||
|
||||
const quad = this.createPlane(2, 2, 1, 1);
|
||||
|
||||
this.taaShader.setAttribute("position", quad.vertices);
|
||||
|
||||
this.taaShader.setAttribute("uv", quad.uvs);
|
||||
|
||||
this.taaShader.topology = "triangle-list";
|
||||
|
||||
await this.taaShader.setup("../../shaders/taa.wgsl");
|
||||
|
||||
this.taaShader.setVariable("mySampler", this.fbSampler);
|
||||
|
||||
this.taaShader.setVariable("blendAmount", new Float32Array([0.1])); // you can tweak this
|
||||
|
||||
this.historyTextures = [
|
||||
this.frameBufferTexture,
|
||||
this.device.createTexture({
|
||||
size: [this.width, this.height, 1],
|
||||
format: "rgba8unorm",
|
||||
usage:
|
||||
GPUTextureUsage.COPY_DST |
|
||||
GPUTextureUsage.TEXTURE_BINDING |
|
||||
GPUTextureUsage.RENDER_ATTACHMENT
|
||||
})
|
||||
];
|
||||
|
||||
this.currentHistoryIndex = 0;
|
||||
|
||||
}
|
||||
|
||||
|
||||
bindRenderLoop () {
|
||||
|
||||
requestAnimationFrame( this.loop.bind( this ) );
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
async loop () {
|
||||
|
||||
await this.render();
|
||||
|
||||
this.bindRenderLoop();
|
||||
|
||||
}
|
||||
}
|
||||
2
demos/WebGpuFrameworkWasm/wasm_renderer.js
Normal file
2
demos/WebGpuFrameworkWasm/wasm_renderer.js
Normal file
File diff suppressed because one or more lines are too long
BIN
demos/WebGpuFrameworkWasm/wasm_renderer.wasm
Executable file
BIN
demos/WebGpuFrameworkWasm/wasm_renderer.wasm
Executable file
Binary file not shown.
168
demos/webGpuFrameworkAndWasmParticles/index.html
Normal file
168
demos/webGpuFrameworkAndWasmParticles/index.html
Normal file
@@ -0,0 +1,168 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Wasm & WebGpu.</title>
|
||||
<base href="./">
|
||||
</head>
|
||||
|
||||
<style >
|
||||
body{
|
||||
margin: 0;
|
||||
}
|
||||
#fps {
|
||||
position: fixed;
|
||||
top: 5px; left: 5px;
|
||||
color: white;
|
||||
background: rgba(0,0,0,0.5);
|
||||
padding: 4px 8px;
|
||||
font-family: monospace;
|
||||
font-size: 14px;
|
||||
z-index: 100;
|
||||
}
|
||||
</style>
|
||||
<script type="module">
|
||||
|
||||
import { ParticleSimulation } from "/demos/webGpuFrameworkAndWasmParticles/particlesWasmWebGpu.js";
|
||||
|
||||
|
||||
var particleSimulation = new ParticleSimulation();
|
||||
|
||||
|
||||
var useWebWorker = false;
|
||||
|
||||
const canvas = document.querySelector(".mainCanvas");
|
||||
|
||||
particleSimulation.setCanvas( canvas );
|
||||
|
||||
resizeCanvasToDisplaySize( canvas );
|
||||
|
||||
|
||||
var worker;
|
||||
|
||||
if ( !useWebWorker ) {
|
||||
|
||||
|
||||
await particleSimulation.setup( canvas, canvas.width, canvas.height, true );
|
||||
|
||||
console.log("document.bufferMap", document.bufferMap);
|
||||
|
||||
} else if ( useWebWorker ) {
|
||||
|
||||
worker = new Worker("../framework/GpuWorker.js", { type: "module" });
|
||||
|
||||
worker.onmessage = function ( event ) {
|
||||
|
||||
console.log("From worker:", event.data);
|
||||
|
||||
};
|
||||
|
||||
const offscreen = canvas.transferControlToOffscreen();
|
||||
|
||||
worker.addEventListener("error", function ( event ) {
|
||||
|
||||
console.error( "Worker failed:",
|
||||
event.message, "at",
|
||||
event.filename + ":" +
|
||||
event.lineno + ":" +
|
||||
event.colno );
|
||||
|
||||
});
|
||||
|
||||
var request = {
|
||||
method: "setup",
|
||||
canvas: offscreen,
|
||||
width: canvas.width,
|
||||
height: canvas.height
|
||||
};
|
||||
|
||||
worker.postMessage( request, [offscreen] );
|
||||
|
||||
}
|
||||
|
||||
|
||||
var events = new Array( "mousemove", "mousedown", "mouseup", "onwheel", "wheel" );
|
||||
|
||||
for ( var i = 0; i < events.length; i++ ) {
|
||||
|
||||
let eventName = events[i];
|
||||
|
||||
console.log("createEvent", eventName);
|
||||
|
||||
canvas.addEventListener( eventName, function ( event ) {
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
|
||||
const x = event.clientX - rect.left;
|
||||
|
||||
const y = event.clientY - rect.top;
|
||||
|
||||
var request = {
|
||||
method: eventName,
|
||||
clientX: x,
|
||||
clientY: y,
|
||||
deltaY: event.deltaY
|
||||
};
|
||||
|
||||
if ( useWebWorker ) {
|
||||
|
||||
worker.postMessage( request );
|
||||
|
||||
} else {
|
||||
|
||||
particleSimulation.eventManager[ eventName ]( request );
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function resizeCanvasToDisplaySize( canvas ) {
|
||||
|
||||
const width = window.innerWidth;
|
||||
const height = window.innerHeight;
|
||||
|
||||
var request = {
|
||||
method: "resize",
|
||||
width: width,
|
||||
height: height
|
||||
};
|
||||
|
||||
if ( useWebWorker ) {
|
||||
|
||||
worker.postMessage( request );
|
||||
|
||||
} else {
|
||||
|
||||
particleSimulation.eventManager.resize( request );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
window.addEventListener( "resize", function () {
|
||||
|
||||
resizeCanvasToDisplaySize( canvas );
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<body>
|
||||
<div id="fps">FPS: 0</div>
|
||||
|
||||
<canvas class="mainCanvas" width="1000" height="1000"></canvas>
|
||||
</body>
|
||||
</html>
|
||||
313
demos/webGpuFrameworkAndWasmParticles/particlesWasmWebGpu.js
Normal file
313
demos/webGpuFrameworkAndWasmParticles/particlesWasmWebGpu.js
Normal file
@@ -0,0 +1,313 @@
|
||||
import Shader from "../../framework/WebGpu.js";
|
||||
|
||||
import Matrix4 from "../../framework/Matrix4.js";
|
||||
|
||||
import Vector3 from "../../framework/Vector3.js";
|
||||
|
||||
import Camera from "../../framework/Camera.js";
|
||||
|
||||
import EventManager from "../../framework/eventManager.js";
|
||||
|
||||
import ShaderInpector from "../../framework/ShaderInpector.js";
|
||||
|
||||
import WasmModule from "./wasm_particles.js";
|
||||
|
||||
|
||||
|
||||
export class ParticleSimulation {
|
||||
|
||||
canvas;
|
||||
|
||||
device;
|
||||
|
||||
camera;
|
||||
|
||||
eventManager = new EventManager();
|
||||
|
||||
frameCount = 0;
|
||||
|
||||
wasm;
|
||||
|
||||
framebufferPtr;
|
||||
|
||||
framebufferUint8;
|
||||
|
||||
frameBufferTexture;
|
||||
|
||||
fbSampler;
|
||||
|
||||
width;
|
||||
|
||||
height;
|
||||
|
||||
renderShader;
|
||||
|
||||
time = 0;
|
||||
|
||||
lastTime = 0;
|
||||
|
||||
lastFrameTime;
|
||||
|
||||
|
||||
setCanvas ( canvas ) {
|
||||
|
||||
this.canvas = canvas;
|
||||
|
||||
this.eventManager.setCanvas( canvas );
|
||||
|
||||
}
|
||||
|
||||
createPlane ( width, height, repeatU, repeatV ) {
|
||||
|
||||
const vertices = new Float32Array( new Array(
|
||||
-width / 2, -height / 2, 0,
|
||||
width / 2, -height / 2, 0,
|
||||
-width / 2, height / 2, 0,
|
||||
width / 2, -height / 2, 0,
|
||||
width / 2, height / 2, 0,
|
||||
-width / 2, height / 2, 0
|
||||
) );
|
||||
|
||||
const uvs = new Float32Array( new Array(
|
||||
0, 0,
|
||||
repeatU, 0,
|
||||
0, repeatV,
|
||||
repeatU, 0,
|
||||
repeatU, repeatV,
|
||||
0, repeatV
|
||||
) );
|
||||
|
||||
const result = {
|
||||
vertices: vertices,
|
||||
normals: null,
|
||||
uvs: uvs
|
||||
};
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
async setup ( offscreenCanvas, width, height ) {
|
||||
|
||||
this.fpsElement = document.getElementById( "fps" );
|
||||
|
||||
this.width = width;
|
||||
|
||||
this.height = height;
|
||||
|
||||
offscreenCanvas.width = width;
|
||||
|
||||
offscreenCanvas.height = height;
|
||||
|
||||
this.canvas = offscreenCanvas;
|
||||
|
||||
const context = offscreenCanvas.getContext( "webgpu" );
|
||||
|
||||
this.camera = new Camera( new Array( 0, 0, 1115 ), new Array( 0, -0.3, 0 ), new Array( 0, 1, 0 ) );
|
||||
|
||||
this.eventManager.setup( offscreenCanvas, this.camera );
|
||||
|
||||
const adapter = await navigator.gpu.requestAdapter();
|
||||
|
||||
if ( adapter === null ) {
|
||||
|
||||
throw new Error( "Failed to get GPU adapter" );
|
||||
|
||||
}
|
||||
|
||||
this.device = await adapter.requestDevice();
|
||||
|
||||
this.wasm = await WasmModule();
|
||||
|
||||
this.wasm._init_particles();
|
||||
|
||||
this.particleCount = this.wasm._get_particle_count();
|
||||
|
||||
this.particlePtr = this.wasm._get_particles_ptr();
|
||||
|
||||
|
||||
const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
|
||||
|
||||
context.configure({
|
||||
device: this.device,
|
||||
format: presentationFormat,
|
||||
alphaMode: "opaque"
|
||||
});
|
||||
|
||||
this.particleSize = 8 * 4;
|
||||
|
||||
this.particleBufferSize = this.particleCount * this.particleSize;
|
||||
|
||||
this.renderShader = new Shader( this.device );
|
||||
|
||||
this.renderShader.setCanvas( this.canvas );
|
||||
|
||||
this.particleFloatArray = new Float32Array(
|
||||
this.wasm.HEAPU8.buffer,
|
||||
this.wasm._get_particles_ptr(),
|
||||
this.particleCount * 8
|
||||
);
|
||||
|
||||
await this.renderShader.setup( "../../shaders/simplePoint.wgsl" );
|
||||
|
||||
this.renderShader.setVariable("particles", this.particleFloatArray);
|
||||
|
||||
this.lastFrameTime = performance.now();
|
||||
|
||||
this.bindRenderLoop();
|
||||
}
|
||||
|
||||
|
||||
|
||||
updateTimeDelta () {
|
||||
|
||||
const now = performance.now();
|
||||
|
||||
this.deltaTimeValue = ( now - this.lastFrameTime ) / 1000;
|
||||
|
||||
this.time += this.deltaTimeValue;
|
||||
|
||||
this.lastFrameTime = now;
|
||||
|
||||
this.frameCount++;
|
||||
|
||||
if ( now - this.lastTime >= 1000 ) {
|
||||
|
||||
const fps = this.frameCount / ( ( now - this.lastTime ) / 1000 );
|
||||
|
||||
const fpsText = "FPS: " + fps.toFixed( 1 );
|
||||
|
||||
this.fpsElement.textContent = fpsText;
|
||||
|
||||
this.frameCount = 0;
|
||||
|
||||
this.lastTime = now;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async render () {
|
||||
|
||||
this.updateTimeDelta();
|
||||
|
||||
this.wasm._update_particles( this.deltaTimeValue );
|
||||
|
||||
let buffer = this.renderShader.buffers.get( "particles" );
|
||||
|
||||
this.device.queue.writeBuffer(
|
||||
buffer,
|
||||
0,
|
||||
this.particleFloatArray.buffer,
|
||||
this.particleFloatArray.byteOffset,
|
||||
this.particleFloatArray.byteLength
|
||||
);
|
||||
|
||||
//this.renderShader.setVariable("particles", this.particleFloatArray);
|
||||
|
||||
|
||||
const viewMatrix = this.camera.getViewMatrix();
|
||||
|
||||
const projectionMatrix = Matrix4.createProjectionMatrix( this.camera, this.canvas );
|
||||
|
||||
const viewProjectionMatrix = Matrix4.multiply( projectionMatrix, viewMatrix );
|
||||
|
||||
const cameraWorldMatrix = Matrix4.invert( viewMatrix );
|
||||
|
||||
const cameraPosition = Matrix4.getColumn( cameraWorldMatrix, 3 );
|
||||
|
||||
const cameraRight = Matrix4.getColumn( cameraWorldMatrix, 0 );
|
||||
|
||||
const cameraUp = Matrix4.getColumn( cameraWorldMatrix, 1 );
|
||||
|
||||
|
||||
this.renderShader.setVariable( "cameraRight", cameraRight );
|
||||
|
||||
this.renderShader.setVariable( "cameraUp", cameraUp );
|
||||
|
||||
this.renderShader.setVariable("viewProjectionMatrix", viewProjectionMatrix);
|
||||
|
||||
this.renderShader.renderToCanvas( 6, this.particleCount, 0 ); // 6 vertices per quad
|
||||
|
||||
//this.fxaaShader.renderToCanvas(6, 1, 0);
|
||||
|
||||
}
|
||||
|
||||
renderTaa() {
|
||||
|
||||
const prevIndex = this.currentHistoryIndex;
|
||||
const nextIndex = 1 - prevIndex;
|
||||
|
||||
// Set framebuffer into history
|
||||
this.device.queue.writeTexture(
|
||||
{ texture: this.historyTextures[nextIndex] },
|
||||
this.framebufferUint8,
|
||||
{ bytesPerRow: this.width * 4 },
|
||||
{ width: this.width, height: this.height, depthOrArrayLayers: 1 }
|
||||
);
|
||||
|
||||
// Apply TAA blend
|
||||
this.taaShader.setVariable("currentFrame", this.historyTextures[nextIndex]);
|
||||
this.taaShader.setVariable("historyFrame", this.historyTextures[prevIndex]);
|
||||
|
||||
this.taaShader.renderToCanvas(6, 1, 0);
|
||||
|
||||
// Swap history
|
||||
this.currentHistoryIndex = nextIndex;
|
||||
|
||||
}
|
||||
|
||||
|
||||
async setupTaa() {
|
||||
|
||||
this.taaShader = new Shader(this.device);
|
||||
|
||||
this.taaShader.setCanvas(this.canvas);
|
||||
|
||||
const quad = this.createPlane(2, 2, 1, 1);
|
||||
|
||||
this.taaShader.setAttribute("position", quad.vertices);
|
||||
|
||||
this.taaShader.setAttribute("uv", quad.uvs);
|
||||
|
||||
this.taaShader.topology = "triangle-list";
|
||||
|
||||
await this.taaShader.setup("../../shaders/taa.wgsl");
|
||||
|
||||
this.taaShader.setVariable("mySampler", this.fbSampler);
|
||||
|
||||
this.taaShader.setVariable("blendAmount", new Float32Array([0.1])); // you can tweak this
|
||||
|
||||
this.historyTextures = [
|
||||
this.frameBufferTexture,
|
||||
this.device.createTexture({
|
||||
size: [this.width, this.height, 1],
|
||||
format: "rgba8unorm",
|
||||
usage:
|
||||
GPUTextureUsage.COPY_DST |
|
||||
GPUTextureUsage.TEXTURE_BINDING |
|
||||
GPUTextureUsage.RENDER_ATTACHMENT
|
||||
})
|
||||
];
|
||||
|
||||
this.currentHistoryIndex = 0;
|
||||
|
||||
}
|
||||
|
||||
|
||||
bindRenderLoop () {
|
||||
|
||||
requestAnimationFrame( this.loop.bind( this ) );
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
async loop () {
|
||||
|
||||
await this.render();
|
||||
|
||||
this.bindRenderLoop();
|
||||
|
||||
}
|
||||
}
|
||||
2
demos/webGpuFrameworkAndWasmParticles/wasm_particles.js
Normal file
2
demos/webGpuFrameworkAndWasmParticles/wasm_particles.js
Normal file
File diff suppressed because one or more lines are too long
BIN
demos/webGpuFrameworkAndWasmParticles/wasm_particles.wasm
Executable file
BIN
demos/webGpuFrameworkAndWasmParticles/wasm_particles.wasm
Executable file
Binary file not shown.
218
demos/webGpuNativeWasm/index.html
Normal file
218
demos/webGpuNativeWasm/index.html
Normal file
@@ -0,0 +1,218 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<base href="./">
|
||||
<title>Fullscreen WASM Raytracer - WebGPU</title>
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0; padding: 0; overflow: hidden; height: 100%;
|
||||
background: black;
|
||||
}
|
||||
canvas {
|
||||
display: block;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
#fps {
|
||||
position: fixed;
|
||||
top: 5px; left: 5px;
|
||||
color: white;
|
||||
background: rgba(0,0,0,0.5);
|
||||
padding: 4px 8px;
|
||||
font-family: monospace;
|
||||
font-size: 14px;
|
||||
z-index: 100;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="fps">FPS: 0</div>
|
||||
<canvas id="gpu-canvas"></canvas>
|
||||
|
||||
<script type="module">
|
||||
import WasmModule from '/demos/webGpuNativeWasm/wasm_renderer.js';
|
||||
|
||||
async function main() {
|
||||
const canvas = document.getElementById('gpu-canvas');
|
||||
const fpsElem = document.getElementById('fps');
|
||||
|
||||
// Get WebGPU context
|
||||
if (!navigator.gpu) {
|
||||
alert("WebGPU not supported on this browser.");
|
||||
return;
|
||||
}
|
||||
|
||||
const adapter = await navigator.gpu.requestAdapter();
|
||||
const device = await adapter.requestDevice();
|
||||
const context = canvas.getContext('webgpu');
|
||||
|
||||
const format = navigator.gpu.getPreferredCanvasFormat();
|
||||
context.configure({
|
||||
device,
|
||||
format,
|
||||
alphaMode: "opaque"
|
||||
});
|
||||
|
||||
// Load WASM module
|
||||
const wasm = await WasmModule();
|
||||
|
||||
let width = window.innerWidth;
|
||||
let height = window.innerHeight;
|
||||
|
||||
function resize() {
|
||||
width = window.innerWidth;
|
||||
height = window.innerHeight;
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
wasm._set_resolution(width, height);
|
||||
}
|
||||
|
||||
window.addEventListener('resize', resize);
|
||||
resize();
|
||||
|
||||
const framebufferPtr = wasm._get_framebuffer_ptr();
|
||||
let framebufferUint8 = new Uint8Array(wasm.HEAPU8.buffer, framebufferPtr, width * height * 4);
|
||||
|
||||
// Create a GPU texture for the framebuffer
|
||||
const texture = device.createTexture({
|
||||
size: [width, height],
|
||||
format: 'rgba8unorm',
|
||||
usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING
|
||||
});
|
||||
|
||||
// Create sampler
|
||||
const sampler = device.createSampler({
|
||||
magFilter: 'nearest',
|
||||
minFilter: 'nearest'
|
||||
});
|
||||
|
||||
// Create quad rendering pipeline
|
||||
const shaderModule = device.createShaderModule({
|
||||
code: `
|
||||
@group(0) @binding(0) var mySampler: sampler;
|
||||
@group(0) @binding(1) var myTexture: texture_2d<f32>;
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) pos: vec4<f32>,
|
||||
@location(0) uv: vec2<f32>,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vertex_main(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
|
||||
var pos = array<vec2<f32>, 6>(
|
||||
vec2<f32>(-1.0, -1.0),
|
||||
vec2<f32>( 1.0, -1.0),
|
||||
vec2<f32>(-1.0, 1.0),
|
||||
vec2<f32>(-1.0, 1.0),
|
||||
vec2<f32>( 1.0, -1.0),
|
||||
vec2<f32>( 1.0, 1.0)
|
||||
);
|
||||
|
||||
var uv = (pos[vertexIndex] + vec2<f32>(1.0)) * 0.5;
|
||||
|
||||
var output: VertexOutput;
|
||||
output.pos = vec4<f32>(pos[vertexIndex], 0.0, 1.0);
|
||||
output.uv = uv;
|
||||
return output;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fragment_main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
|
||||
return textureSample(myTexture, mySampler, uv);
|
||||
}
|
||||
`
|
||||
});
|
||||
|
||||
const pipeline = device.createRenderPipeline({
|
||||
layout: 'auto',
|
||||
vertex: {
|
||||
module: shaderModule,
|
||||
entryPoint: 'vertex_main',
|
||||
},
|
||||
fragment: {
|
||||
module: shaderModule,
|
||||
entryPoint: 'fragment_main',
|
||||
targets: [{ format }],
|
||||
},
|
||||
primitive: {
|
||||
topology: 'triangle-list',
|
||||
},
|
||||
});
|
||||
|
||||
// Bind group for texture/sampler
|
||||
const bindGroup = device.createBindGroup({
|
||||
layout: pipeline.getBindGroupLayout(0),
|
||||
entries: [
|
||||
{ binding: 0, resource: sampler },
|
||||
{ binding: 1, resource: texture.createView() },
|
||||
],
|
||||
});
|
||||
|
||||
let frameCount = 0;
|
||||
let lastTime = performance.now();
|
||||
|
||||
const encoder = () => device.createCommandEncoder();
|
||||
|
||||
const uploadToTexture = () => {
|
||||
device.queue.writeTexture(
|
||||
{ texture: texture },
|
||||
framebufferUint8,
|
||||
{
|
||||
bytesPerRow: width * 4,
|
||||
},
|
||||
{
|
||||
width: width,
|
||||
height: height,
|
||||
depthOrArrayLayers: 1,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
function frame(time) {
|
||||
wasm._update_framebuffer(time * 0.001);
|
||||
|
||||
// In case of resize
|
||||
framebufferUint8 = new Uint8Array(wasm.HEAPU8.buffer, framebufferPtr, width * height * 4);
|
||||
|
||||
uploadToTexture();
|
||||
|
||||
const commandEncoder = encoder();
|
||||
const pass = commandEncoder.beginRenderPass({
|
||||
colorAttachments: [{
|
||||
view: context.getCurrentTexture().createView(),
|
||||
loadOp: 'clear',
|
||||
storeOp: 'store',
|
||||
clearValue: { r: 0, g: 0, b: 0, a: 1 },
|
||||
}]
|
||||
});
|
||||
|
||||
pass.setPipeline(pipeline);
|
||||
pass.setBindGroup(0, bindGroup);
|
||||
pass.draw(6);
|
||||
pass.end();
|
||||
|
||||
device.queue.submit([commandEncoder.finish()]);
|
||||
|
||||
// FPS
|
||||
frameCount++;
|
||||
const now = performance.now();
|
||||
const delta = now - lastTime;
|
||||
if (delta >= 1000) {
|
||||
const fps = (frameCount * 1000) / delta;
|
||||
fpsElem.textContent = `FPS: ${fps.toFixed(1)}`;
|
||||
frameCount = 0;
|
||||
lastTime = now;
|
||||
}
|
||||
|
||||
requestAnimationFrame(frame);
|
||||
}
|
||||
|
||||
requestAnimationFrame(frame);
|
||||
}
|
||||
|
||||
main();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
2
demos/webGpuNativeWasm/wasm_renderer.js
Normal file
2
demos/webGpuNativeWasm/wasm_renderer.js
Normal file
File diff suppressed because one or more lines are too long
BIN
demos/webGpuNativeWasm/wasm_renderer.wasm
Executable file
BIN
demos/webGpuNativeWasm/wasm_renderer.wasm
Executable file
Binary file not shown.
Reference in New Issue
Block a user