feature/webGPU #25

Merged
lobo merged 14 commits from feature/webGPU into main 2026-02-21 12:32:04 +01:00
6 changed files with 155 additions and 165 deletions
Showing only changes of commit 13b59d0b36 - Show all commits

View File

@@ -8,7 +8,7 @@ import {ConwayGolComponent} from '../pages/algorithms/conway-gol/conway-gol.comp
import {LabyrinthComponent} from '../pages/algorithms/pathfinding/labyrinth/labyrinth.component';
import {FractalComponent} from '../pages/algorithms/fractal/fractal.component';
import {Fractal3dComponent} from '../pages/algorithms/fractal3d/fractal3d.component';
import {PendulumComponent} from '../pages/algorithms/pendle/pendulum.component';
import {PendulumComponent} from '../pages/algorithms/pendulum/pendulum.component';
export class RouterConstants {

View File

@@ -1,164 +0,0 @@
import {Component} from '@angular/core';
import {BabylonCanvas, RenderConfig, SceneReadyEvent} from '../../../shared/rendering/canvas/babylon-canvas.component';
import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card';
import {ComputeShader, ShaderLanguage, StorageBuffer, UniformBuffer} from '@babylonjs/core';
@Component({
selector: 'app-pendulum',
imports: [
BabylonCanvas,
MatCard,
MatCardContent,
MatCardHeader,
MatCardTitle,
],
templateUrl: './pendulum.component.html',
styleUrl: './pendulum.component.scss',
})
export class PendulumComponent {
// --- VERTEX SHADER ---
// Das hat funktioniert. Wir definieren Attribute, Babylon baut 'VertexInputs'.
// Wir geben 'FragmentInputs' zurück (was Babylon aus unseren varying baut).
private readonly vertexShaderWGSL = `
attribute position : vec3<f32>;
attribute uv : vec2<f32>;
varying vUV : vec2<f32>;
@vertex
fn main(input : VertexInputs) -> FragmentInputs {
var output : FragmentInputs;
output.position = vec4<f32>(input.position, 1.0);
output.vUV = input.uv;
return output;
}
`;
// --- FRAGMENT SHADER (FIXED) ---
// Änderungen:
// 1. Rückgabetyp ist jetzt 'FragmentOutputs' (das Babylon Struct).
// 2. Wir schreiben in 'fragmentOutputs.color'.
// 3. Wir returnen 'fragmentOutputs'.
private readonly fragmentShaderWGSL = `
varying vUV : vec2<f32>;
var<storage, read> pixelBuffer : array<f32>;
var<uniform> params : Params;
struct Params {
resolution: vec2<f32>,
time: f32
};
@fragment
fn main(input : FragmentInputs) -> FragmentOutputs {
let x = u32(input.vUV.x * params.resolution.x);
let y = u32(input.vUV.y * params.resolution.y);
let width = u32(params.resolution.x);
let index = y * width + x;
let total = u32(params.resolution.x * params.resolution.y);
// Default Farbe (schwarz)
var color = vec4<f32>(0.0, 0.0, 0.0, 1.0);
if (index < total) {
let val = pixelBuffer[index];
color = vec4<f32>(val, val * 0.5, 0.2, 1.0);
}
// Babylon stellt die Variable 'fragmentOutputs' bereit.
// Das Feld heißt standardmäßig 'color'.
fragmentOutputs.color = color;
return fragmentOutputs;
}
`;
// --- COMPUTE SHADER (Unverändert) ---
private readonly computeShaderWGSL = `
@group(0) @binding(0) var<storage, read_write> pixelBuffer : array<f32>;
@group(0) @binding(1) var<uniform> params : Params;
struct Params {
resolution: vec2<f32>,
time: f32
};
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) global_id : vec3<u32>) {
let index = global_id.x;
let totalPixels = u32(params.resolution.x * params.resolution.y);
if (index >= totalPixels) { return; }
let width = u32(params.resolution.x);
let x = f32(index % width);
let y = f32(index / width);
// Zeit-Variable nutzen für Animation
let value = sin(x * 0.05 + params.time) * cos(y * 0.05 + params.time);
pixelBuffer[index] = value * 0.5 + 0.5;
}
`;
renderConfig: RenderConfig = {
mode: '2D',
initialViewSize: 2,
shaderLanguage: ShaderLanguage.WGSL,
vertexShader: this.vertexShaderWGSL,
fragmentShader: this.fragmentShaderWGSL,
uniformNames: ["params"]
};
onSceneReady(event: SceneReadyEvent) {
const engine = event.engine;
const scene = event.scene;
const width = engine.getRenderWidth();
const height = engine.getRenderHeight();
const totalPixels = width * height;
// Buffer: 1 Float pro Pixel
const bufferSize = totalPixels * 4;
const pixelBuffer = new StorageBuffer(engine, bufferSize);
// Uniform Buffer
const ubo = new UniformBuffer(engine);
ubo.addUniform("resolution", 2);
ubo.addUniform("time", 1);
ubo.update();
// Compute Shader
const cs = new ComputeShader("myCompute", engine, {
computeSource: this.computeShaderWGSL
}, {
bindingsMapping: {
"pixelBuffer": { group: 0, binding: 0 },
"params": { group: 0, binding: 1 }
}
});
cs.setStorageBuffer("pixelBuffer", pixelBuffer);
cs.setUniformBuffer("params", ubo);
// Material Setup
const plane = scene.getMeshByName("plane");
if (plane && plane.material) {
const mat = plane.material as any;
mat.setStorageBuffer("pixelBuffer", pixelBuffer);
mat.setUniformBuffer("params", ubo);
}
// Render Loop
let time = 0;
scene.onBeforeRenderObservable.add(() => {
time += engine.getDeltaTime() / 1000.0;
ubo.updateFloat2("resolution", width, height);
ubo.updateFloat("time", time);
ubo.update();
const dispatchCount = Math.ceil(totalPixels / 64);
cs.dispatch(dispatchCount, 1, 1);
});
}
}

View File

@@ -0,0 +1,80 @@
import {Component} from '@angular/core';
import {BabylonCanvas, RenderConfig, SceneReadyEvent} from '../../../shared/rendering/canvas/babylon-canvas.component';
import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card';
import {ComputeShader, ShaderLanguage, StorageBuffer, UniformBuffer} from '@babylonjs/core';
import {PENDULUM_COMPUTE_SHADER_WGSL, PENDULUM_FRAGMENT_SHADER_WGSL, PENDULUM_VERTEX_SHADER_WGSL} from './pendulum.shader';
@Component({
selector: 'app-pendulum',
imports: [
BabylonCanvas,
MatCard,
MatCardContent,
MatCardHeader,
MatCardTitle,
],
templateUrl: './pendulum.component.html',
styleUrl: './pendulum.component.scss',
})
export class PendulumComponent {
renderConfig: RenderConfig = {
mode: '2D',
initialViewSize: 2,
shaderLanguage: ShaderLanguage.WGSL,
vertexShader: PENDULUM_VERTEX_SHADER_WGSL,
fragmentShader: PENDULUM_FRAGMENT_SHADER_WGSL,
uniformNames: ["params"]
};
onSceneReady(event: SceneReadyEvent) {
const engine = event.engine;
const scene = event.scene;
const width = engine.getRenderWidth();
const height = engine.getRenderHeight();
const totalPixels = width * height;
// Buffer: 1 Float pro Pixel
const bufferSize = totalPixels * 4;
const pixelBuffer = new StorageBuffer(engine, bufferSize);
// Uniform Buffer
const ubo = new UniformBuffer(engine);
ubo.addUniform("resolution", 2);
ubo.addUniform("time", 1);
ubo.update();
// Compute Shader
const cs = new ComputeShader("Pendulum Compute Shader", engine, {
computeSource: PENDULUM_COMPUTE_SHADER_WGSL
}, {
bindingsMapping: {
"pixelBuffer": { group: 0, binding: 0 },
"params": { group: 0, binding: 1 }
}
});
cs.setStorageBuffer("pixelBuffer", pixelBuffer);
cs.setUniformBuffer("params", ubo);
// Material Setup
const plane = scene.getMeshByName("plane");
if (plane?.material) {
const mat = plane.material as any;
mat.setStorageBuffer("pixelBuffer", pixelBuffer);
mat.setUniformBuffer("params", ubo);
}
// Render Loop
let time = 0;
scene.onBeforeRenderObservable.add(() => {
time += engine.getDeltaTime() / 1000.0;
ubo.updateFloat2("resolution", width, height);
ubo.updateFloat("time", time);
ubo.update();
const dispatchCount = Math.ceil(totalPixels / 64);
cs.dispatch(dispatchCount, 1, 1);
});
}
}

View File

@@ -0,0 +1,74 @@
export const PENDULUM_VERTEX_SHADER_WGSL = `
attribute position : vec3<f32>;
attribute uv : vec2<f32>;
varying vUV : vec2<f32>;
@vertex
fn main(input : VertexInputs) -> FragmentInputs {
var output : FragmentInputs;
output.position = vec4<f32>(input.position, 1.0);
output.vUV = input.uv;
return output;
}
`;
export const PENDULUM_FRAGMENT_SHADER_WGSL = `
varying vUV : vec2<f32>;
var<storage, read> pixelBuffer : array<f32>;
var<uniform> params : Params;
struct Params {
resolution: vec2<f32>,
time: f32
};
@fragment
fn main(input : FragmentInputs) -> FragmentOutputs {
let x = u32(input.vUV.x * params.resolution.x);
let y = u32(input.vUV.y * params.resolution.y);
let width = u32(params.resolution.x);
let index = y * width + x;
let total = u32(params.resolution.x * params.resolution.y);
// Default Color (Black)
var color = vec4<f32>(0.0, 0.0, 0.0, 1.0);
if (index < total) {
let val = pixelBuffer[index];
color = vec4<f32>(val, val * 0.5, 0.2, 1.0);
}
//fragmentOutput is provided by babylon
fragmentOutputs.color = color;
return fragmentOutputs;
}
`;
export const PENDULUM_COMPUTE_SHADER_WGSL = `
@group(0) @binding(0) var<storage, read_write> pixelBuffer : array<f32>;
@group(0) @binding(1) var<uniform> params : Params;
struct Params {
resolution: vec2<f32>,
time: f32
};
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) global_id : vec3<u32>) {
let index = global_id.x;
let totalPixels = u32(params.resolution.x * params.resolution.y);
if (index >= totalPixels) { return; }
let width = u32(params.resolution.x);
let x = f32(index % width);
let y = f32(index / width);
// Zeit-Variable nutzen für Animation
let value = sin(x * 0.05 + params.time) * cos(y * 0.05 + params.time);
pixelBuffer[index] = value * 0.5 + 0.5;
}
`;