mirror of
https://github.com/Floriansylvain/WhateverWebGPU.git
synced 2026-08-19 11:43:26 +02:00
feat: fixed canvas size & enhance WebGPU rendering with grid and time uniforms
This commit is contained in:
+1
-1
@@ -7,7 +7,7 @@
|
|||||||
<title>Whatever WebGPU</title>
|
<title>Whatever WebGPU</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<canvas id="canvas"></canvas>
|
<canvas id="canvas" width="512" height="512"></canvas>
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+141
-41
@@ -1,51 +1,60 @@
|
|||||||
import "./style.css"
|
import "./style.css"
|
||||||
|
|
||||||
async function init() {
|
const GRID_SIZE = 32
|
||||||
|
|
||||||
|
async function getCanvas(): Promise<HTMLCanvasElement> {
|
||||||
const canvas = document.querySelector<HTMLCanvasElement>("#canvas")
|
const canvas = document.querySelector<HTMLCanvasElement>("#canvas")
|
||||||
if (!canvas) {
|
if (!canvas) throw new Error("Canvas element not found")
|
||||||
throw new Error("Canvas element not found")
|
return canvas
|
||||||
}
|
}
|
||||||
if (!navigator.gpu) {
|
|
||||||
throw new Error("WebGPU not supported on this browser.")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
async function getAdapter(): Promise<GPUAdapter> {
|
||||||
|
if (!navigator.gpu) throw new Error("WebGPU not supported on this browser.")
|
||||||
const adapter = await navigator.gpu.requestAdapter()
|
const adapter = await navigator.gpu.requestAdapter()
|
||||||
if (!adapter) {
|
if (!adapter) throw new Error("No appropriate GPUAdapter found.")
|
||||||
throw new Error("No appropriate GPUAdapter found.")
|
return adapter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getDevice(adapter: GPUAdapter): Promise<GPUDevice> {
|
||||||
|
return await adapter.requestDevice()
|
||||||
|
}
|
||||||
|
|
||||||
|
function configureContext(
|
||||||
|
canvas: HTMLCanvasElement,
|
||||||
|
device: GPUDevice,
|
||||||
|
): GPUCanvasContext {
|
||||||
const context = canvas.getContext("webgpu")
|
const context = canvas.getContext("webgpu")
|
||||||
if (!context) {
|
if (!context) throw new Error("Failed to get WebGPU context.")
|
||||||
throw new Error("Failed to get WebGPU context.")
|
|
||||||
}
|
|
||||||
|
|
||||||
const device = await adapter.requestDevice()
|
|
||||||
const canvasFormat = navigator.gpu.getPreferredCanvasFormat()
|
const canvasFormat = navigator.gpu.getPreferredCanvasFormat()
|
||||||
context.configure({ device, format: canvasFormat })
|
context.configure({ device, format: canvasFormat })
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
const encoder = device.createCommandEncoder()
|
function createVertexBufferLayout(): GPUVertexBufferLayout {
|
||||||
|
return {
|
||||||
const vertexBufferLayout = {
|
|
||||||
arrayStride: 8,
|
arrayStride: 8,
|
||||||
attributes: [
|
attributes: [
|
||||||
{
|
{ format: "float32x2" as GPUVertexFormat, offset: 0, shaderLocation: 0 },
|
||||||
format: "float32x2" as GPUVertexFormat,
|
|
||||||
offset: 0,
|
|
||||||
shaderLocation: 0,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createCellPipeline(
|
||||||
|
device: GPUDevice,
|
||||||
|
canvasFormat: GPUTextureFormat,
|
||||||
|
): Promise<GPURenderPipeline> {
|
||||||
const cellShaderModule = device.createShaderModule({
|
const cellShaderModule = device.createShaderModule({
|
||||||
label: "Cell shader",
|
label: "Cell shader",
|
||||||
code: (await import("./shaders/cell.wgsl?raw")).default,
|
code: (await import("./shaders/cell.wgsl?raw")).default,
|
||||||
})
|
})
|
||||||
const cellPipeline = device.createRenderPipeline({
|
|
||||||
|
return device.createRenderPipeline({
|
||||||
label: "Cell pipeline",
|
label: "Cell pipeline",
|
||||||
layout: "auto",
|
layout: "auto",
|
||||||
vertex: {
|
vertex: {
|
||||||
module: cellShaderModule,
|
module: cellShaderModule,
|
||||||
entryPoint: "vertexMain",
|
entryPoint: "vertexMain",
|
||||||
buffers: [vertexBufferLayout],
|
buffers: [createVertexBufferLayout()],
|
||||||
},
|
},
|
||||||
fragment: {
|
fragment: {
|
||||||
module: cellShaderModule,
|
module: cellShaderModule,
|
||||||
@@ -53,7 +62,44 @@ async function init() {
|
|||||||
targets: [{ format: canvasFormat }],
|
targets: [{ format: canvasFormat }],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function createGridUniformBuffer(device: GPUDevice): GPUBuffer {
|
||||||
|
const gridUniformArray = new Float32Array([GRID_SIZE, GRID_SIZE])
|
||||||
|
const gridUniformBuffer = device.createBuffer({
|
||||||
|
label: "Grid Uniforms",
|
||||||
|
size: gridUniformArray.byteLength,
|
||||||
|
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
||||||
|
})
|
||||||
|
device.queue.writeBuffer(gridUniformBuffer, 0, gridUniformArray)
|
||||||
|
return gridUniformBuffer
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTimeBuffer(device: GPUDevice): GPUBuffer {
|
||||||
|
return device.createBuffer({
|
||||||
|
label: "Time Uniform",
|
||||||
|
size: 4,
|
||||||
|
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function createBindGroup(
|
||||||
|
device: GPUDevice,
|
||||||
|
cellPipeline: GPURenderPipeline,
|
||||||
|
gridUniformBuffer: GPUBuffer,
|
||||||
|
timeBuffer: GPUBuffer,
|
||||||
|
): GPUBindGroup {
|
||||||
|
return device.createBindGroup({
|
||||||
|
label: "Cell renderer bind group",
|
||||||
|
layout: cellPipeline.getBindGroupLayout(0),
|
||||||
|
entries: [
|
||||||
|
{ binding: 0, resource: { buffer: gridUniformBuffer } },
|
||||||
|
{ binding: 1, resource: { buffer: timeBuffer } },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function createVertexBuffer(device: GPUDevice): GPUBuffer {
|
||||||
const vertices = new Float32Array([
|
const vertices = new Float32Array([
|
||||||
-0.8, -0.8, 0.8, -0.8, 0.8, 0.8, -0.8, -0.8, 0.8, 0.8, -0.8, 0.8,
|
-0.8, -0.8, 0.8, -0.8, 0.8, 0.8, -0.8, -0.8, 0.8, 0.8, -0.8, 0.8,
|
||||||
])
|
])
|
||||||
@@ -63,24 +109,78 @@ async function init() {
|
|||||||
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
||||||
})
|
})
|
||||||
device.queue.writeBuffer(vertexBuffer, 0, vertices)
|
device.queue.writeBuffer(vertexBuffer, 0, vertices)
|
||||||
|
return vertexBuffer
|
||||||
|
}
|
||||||
|
|
||||||
const pass = encoder.beginRenderPass({
|
class Renderer {
|
||||||
colorAttachments: [
|
constructor(
|
||||||
{
|
private device: GPUDevice,
|
||||||
view: context.getCurrentTexture().createView(),
|
private context: GPUCanvasContext,
|
||||||
loadOp: "clear",
|
private cellPipeline: GPURenderPipeline,
|
||||||
clearValue: [0.0, 0.0, 0.4, 1.0],
|
private vertexBuffer: GPUBuffer,
|
||||||
storeOp: "store",
|
private bindGroup: GPUBindGroup,
|
||||||
},
|
private timeBuffer: GPUBuffer,
|
||||||
],
|
private vertices: Float32Array,
|
||||||
})
|
) {}
|
||||||
pass.setPipeline(cellPipeline)
|
|
||||||
pass.setVertexBuffer(0, vertexBuffer)
|
public render(timeMs: number): void {
|
||||||
pass.draw(vertices.length / 2)
|
const time = timeMs / 1000
|
||||||
pass.end()
|
this.device.queue.writeBuffer(this.timeBuffer, 0, new Float32Array([time]))
|
||||||
device.queue.submit([encoder.finish()])
|
|
||||||
|
const encoder = this.device.createCommandEncoder()
|
||||||
|
const pass = encoder.beginRenderPass({
|
||||||
|
colorAttachments: [
|
||||||
|
{
|
||||||
|
view: this.context.getCurrentTexture().createView(),
|
||||||
|
loadOp: "clear",
|
||||||
|
clearValue: [0.0, 0.0, 0.4, 1.0],
|
||||||
|
storeOp: "store",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
pass.setPipeline(this.cellPipeline)
|
||||||
|
pass.setVertexBuffer(0, this.vertexBuffer)
|
||||||
|
pass.setBindGroup(0, this.bindGroup)
|
||||||
|
pass.draw(this.vertices.length / 2, GRID_SIZE * GRID_SIZE)
|
||||||
|
pass.end()
|
||||||
|
|
||||||
|
this.device.queue.submit([encoder.finish()])
|
||||||
|
requestAnimationFrame((time) => this.render(time))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function init() {
|
||||||
|
const device = await getDevice(await getAdapter())
|
||||||
|
const context = configureContext(await getCanvas(), device)
|
||||||
|
const canvasFormat = navigator.gpu.getPreferredCanvasFormat()
|
||||||
|
|
||||||
|
const cellPipeline = await createCellPipeline(device, canvasFormat)
|
||||||
|
const gridUniformBuffer = createGridUniformBuffer(device)
|
||||||
|
const timeBuffer = createTimeBuffer(device)
|
||||||
|
const bindGroup = createBindGroup(
|
||||||
|
device,
|
||||||
|
cellPipeline,
|
||||||
|
gridUniformBuffer,
|
||||||
|
timeBuffer,
|
||||||
|
)
|
||||||
|
const vertexBuffer = createVertexBuffer(device)
|
||||||
|
const vertices = new Float32Array([
|
||||||
|
-0.8, -0.8, 0.8, -0.8, 0.8, 0.8, -0.8, -0.8, 0.8, 0.8, -0.8, 0.8,
|
||||||
|
])
|
||||||
|
|
||||||
|
const renderer = new Renderer(
|
||||||
|
device,
|
||||||
|
context,
|
||||||
|
cellPipeline,
|
||||||
|
vertexBuffer,
|
||||||
|
bindGroup,
|
||||||
|
timeBuffer,
|
||||||
|
vertices,
|
||||||
|
)
|
||||||
|
requestAnimationFrame((time) => renderer.render(time))
|
||||||
}
|
}
|
||||||
|
|
||||||
init().catch((error) => {
|
init().catch((error) => {
|
||||||
console.error("Failed to initialize the application:", error)
|
console.error("Error initializing WebGPU:", error)
|
||||||
})
|
})
|
||||||
|
|||||||
+40
-4
@@ -1,9 +1,45 @@
|
|||||||
|
@group(0) @binding(0) var<uniform> grid: vec2f;
|
||||||
|
@group(0) @binding(1) var<uniform> time: f32;
|
||||||
|
|
||||||
|
struct VertexInput {
|
||||||
|
@location(0) pos: vec2f,
|
||||||
|
@builtin(instance_index) instance: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VertexOutput {
|
||||||
|
@builtin(position) pos: vec4f,
|
||||||
|
@location(0) cell: vec2f,
|
||||||
|
};
|
||||||
|
|
||||||
@vertex
|
@vertex
|
||||||
fn vertexMain(@location(0) pos: vec2f) -> @builtin(position) vec4f {
|
fn vertexMain(input: VertexInput) -> VertexOutput {
|
||||||
return vec4f(pos, 0, 1);
|
let i = f32(input.instance);
|
||||||
|
let cell = vec2f(i % grid.x, floor(i / grid.x));
|
||||||
|
let cellOffset = cell / grid * 2.0;
|
||||||
|
let gridPos = (input.pos + 1.0) / grid - 1.0 + cellOffset;
|
||||||
|
|
||||||
|
var output: VertexOutput;
|
||||||
|
output.pos = vec4f(gridPos, 0.0, 1.0);
|
||||||
|
output.cell = cell;
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hueToRGB(hue: f32) -> vec3f {
|
||||||
|
let r = 0.5 + 0.5 * sin(6.28318 * (hue + 0.0));
|
||||||
|
let g = 0.5 + 0.5 * sin(6.28318 * (hue + 0.33));
|
||||||
|
let b = 0.5 + 0.5 * sin(6.28318 * (hue + 0.66));
|
||||||
|
return vec3f(r, g, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
@fragment
|
@fragment
|
||||||
fn fragmentMain() -> @location(0) vec4f {
|
fn fragmentMain(input: VertexOutput) -> @location(0) vec4f {
|
||||||
return vec4f(1, 0, 0, 1);
|
let center = grid * 0.5;
|
||||||
|
let delta = input.cell - center;
|
||||||
|
let angle = atan2(delta.y, delta.x);
|
||||||
|
let normalizedAngle = (angle / (2.0 * 3.14159)) + 0.5;
|
||||||
|
|
||||||
|
let speed = 0.3;
|
||||||
|
let hue = fract(normalizedAngle + time * speed);
|
||||||
|
let color = hueToRGB(hue);
|
||||||
|
return vec4f(color, 1.0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,3 @@ body {
|
|||||||
height: 100vh;
|
height: 100vh;
|
||||||
background-color: #222;
|
background-color: #222;
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas {
|
|
||||||
width: 512px;
|
|
||||||
height: 512px;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user