essais
This commit is contained in:
317
src/shader/worker/ShaderWorker.ts
Normal file
317
src/shader/worker/ShaderWorker.ts
Normal file
@ -0,0 +1,317 @@
|
||||
import { WorkerMessage, WorkerResponse, createDefaultShaderContext } from '../types';
|
||||
import { ShaderCompiler } from '../core/ShaderCompiler';
|
||||
import { ShaderCache } from '../core/ShaderCache';
|
||||
import { FeedbackSystem } from '../rendering/FeedbackSystem';
|
||||
import { PixelRenderer } from '../rendering/PixelRenderer';
|
||||
import { PERFORMANCE } from '../../utils/constants';
|
||||
|
||||
/**
|
||||
* Main shader worker class - handles compilation and rendering
|
||||
*/
|
||||
class ShaderWorker {
|
||||
private compiledFunction: any = null;
|
||||
private lastCode: string = '';
|
||||
private cache: ShaderCache;
|
||||
private feedbackSystem: FeedbackSystem;
|
||||
private pixelRenderer: PixelRenderer;
|
||||
private shaderContext = createDefaultShaderContext();
|
||||
private lastFrameTime: number = 0;
|
||||
|
||||
constructor() {
|
||||
this.cache = new ShaderCache();
|
||||
this.feedbackSystem = new FeedbackSystem();
|
||||
this.pixelRenderer = new PixelRenderer(this.feedbackSystem, this.shaderContext);
|
||||
|
||||
self.onmessage = (e: MessageEvent<WorkerMessage>) => {
|
||||
this.handleMessage(e.data);
|
||||
};
|
||||
}
|
||||
|
||||
private handleMessage(message: WorkerMessage): void {
|
||||
try {
|
||||
switch (message.type) {
|
||||
case 'compile':
|
||||
this.compileShader(message.id, message.code!);
|
||||
break;
|
||||
case 'render':
|
||||
this.renderShader(
|
||||
message.id,
|
||||
message.width!,
|
||||
message.height!,
|
||||
message.time!,
|
||||
message.renderMode || 'classic',
|
||||
message.valueMode || 'integer',
|
||||
message,
|
||||
message.startY || 0
|
||||
);
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
this.postError(
|
||||
message.id,
|
||||
error instanceof Error ? error.message : 'Unknown error'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private compileShader(id: string, code: string): void {
|
||||
const codeHash = ShaderCompiler.hashCode(code);
|
||||
|
||||
if (code === this.lastCode && this.compiledFunction) {
|
||||
this.postMessage({ id, type: 'compiled', success: true });
|
||||
return;
|
||||
}
|
||||
|
||||
// Check compilation cache
|
||||
const cachedFunction = this.cache.getCompiledShader(codeHash);
|
||||
if (cachedFunction) {
|
||||
this.compiledFunction = cachedFunction;
|
||||
this.lastCode = code;
|
||||
this.postMessage({ id, type: 'compiled', success: true });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.compiledFunction = ShaderCompiler.compile(code);
|
||||
|
||||
// Cache the compiled function
|
||||
if (this.compiledFunction) {
|
||||
this.cache.setCompiledShader(codeHash, this.compiledFunction);
|
||||
}
|
||||
|
||||
this.lastCode = code;
|
||||
this.postMessage({ id, type: 'compiled', success: true });
|
||||
} catch (error) {
|
||||
this.compiledFunction = null;
|
||||
this.postError(
|
||||
id,
|
||||
error instanceof Error ? error.message : 'Compilation failed'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private renderShader(
|
||||
id: string,
|
||||
width: number,
|
||||
height: number,
|
||||
time: number,
|
||||
renderMode: string,
|
||||
valueMode: string,
|
||||
message: WorkerMessage,
|
||||
startY: number = 0
|
||||
): void {
|
||||
if (!this.compiledFunction) {
|
||||
this.postError(id, 'No compiled shader');
|
||||
return;
|
||||
}
|
||||
|
||||
const imageData = this.cache.getOrCreateImageData(width, height);
|
||||
const data = imageData.data;
|
||||
const startTime = performance.now();
|
||||
const maxRenderTime = PERFORMANCE.MAX_RENDER_TIME_MS;
|
||||
|
||||
// Initialize feedback buffers if needed
|
||||
this.feedbackSystem.initializeBuffers(width, height);
|
||||
|
||||
// Update frame timing for frame rate independence
|
||||
const deltaTime = time - this.lastFrameTime;
|
||||
this.lastFrameTime = time;
|
||||
|
||||
try {
|
||||
// Use tiled rendering for better timeout handling
|
||||
this.renderTiled(
|
||||
data,
|
||||
width,
|
||||
height,
|
||||
time,
|
||||
renderMode,
|
||||
valueMode,
|
||||
message,
|
||||
startTime,
|
||||
maxRenderTime,
|
||||
startY,
|
||||
deltaTime
|
||||
);
|
||||
|
||||
// Finalize frame processing
|
||||
this.feedbackSystem.finalizeFrame();
|
||||
|
||||
this.postMessage({ id, type: 'rendered', success: true, imageData });
|
||||
} catch (error) {
|
||||
this.postError(
|
||||
id,
|
||||
error instanceof Error ? error.message : 'Render failed'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private renderTiled(
|
||||
data: Uint8ClampedArray,
|
||||
width: number,
|
||||
height: number,
|
||||
time: number,
|
||||
renderMode: string,
|
||||
valueMode: string,
|
||||
message: WorkerMessage,
|
||||
startTime: number,
|
||||
maxRenderTime: number,
|
||||
yOffset: number = 0,
|
||||
deltaTime: number = 0.016
|
||||
): void {
|
||||
const tileSize = PERFORMANCE.DEFAULT_TILE_SIZE;
|
||||
const tilesX = Math.ceil(width / tileSize);
|
||||
const tilesY = Math.ceil(height / tileSize);
|
||||
|
||||
// Pre-calculate constants outside the loop for performance
|
||||
const fullWidth = message.fullWidth || width;
|
||||
const fullHeight = message.fullHeight || message.height! + yOffset;
|
||||
const centerX = fullWidth / 2;
|
||||
const centerY = fullHeight / 2;
|
||||
const maxDistance = Math.sqrt(centerX ** 2 + centerY ** 2);
|
||||
const invMaxDistance = 1 / maxDistance;
|
||||
const invFullWidth = 1 / fullWidth;
|
||||
const invFullHeight = 1 / fullHeight;
|
||||
const frameCount = Math.floor(time * 60);
|
||||
const goldenRatio = 1.618033988749;
|
||||
const phase = (time * Math.PI * 2) % (Math.PI * 2);
|
||||
const timeTwoPi = time * 2 * Math.PI;
|
||||
const fullWidthHalf = fullWidth >> 1;
|
||||
const fullHeightHalf = fullHeight >> 1;
|
||||
|
||||
for (let tileY = 0; tileY < tilesY; tileY++) {
|
||||
for (let tileX = 0; tileX < tilesX; tileX++) {
|
||||
// Check timeout before each tile
|
||||
if (performance.now() - startTime > maxRenderTime) {
|
||||
const startX = tileX * tileSize;
|
||||
const startY = tileY * tileSize;
|
||||
this.fillRemainingPixels(data, width, height, startY, startX);
|
||||
return;
|
||||
}
|
||||
|
||||
const tileStartX = tileX * tileSize;
|
||||
const tileStartY = tileY * tileSize;
|
||||
const tileEndX = Math.min(tileStartX + tileSize, width);
|
||||
const tileEndY = Math.min(tileStartY + tileSize, height);
|
||||
|
||||
this.renderTile(
|
||||
data,
|
||||
width,
|
||||
tileStartX,
|
||||
tileStartY,
|
||||
tileEndX,
|
||||
tileEndY,
|
||||
time,
|
||||
renderMode,
|
||||
valueMode,
|
||||
message,
|
||||
yOffset,
|
||||
deltaTime,
|
||||
// Pre-calculated constants
|
||||
centerX,
|
||||
centerY,
|
||||
maxDistance,
|
||||
invMaxDistance,
|
||||
invFullWidth,
|
||||
invFullHeight,
|
||||
frameCount,
|
||||
goldenRatio,
|
||||
phase,
|
||||
timeTwoPi,
|
||||
fullWidthHalf,
|
||||
fullHeightHalf
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private renderTile(
|
||||
data: Uint8ClampedArray,
|
||||
width: number,
|
||||
startX: number,
|
||||
startY: number,
|
||||
endX: number,
|
||||
endY: number,
|
||||
time: number,
|
||||
renderMode: string,
|
||||
valueMode: string,
|
||||
message: WorkerMessage,
|
||||
yOffset: number,
|
||||
deltaTime: number,
|
||||
// Pre-calculated constants
|
||||
centerX: number,
|
||||
centerY: number,
|
||||
maxDistance: number,
|
||||
invMaxDistance: number,
|
||||
invFullWidth: number,
|
||||
invFullHeight: number,
|
||||
frameCount: number,
|
||||
goldenRatio: number,
|
||||
phase: number,
|
||||
timeTwoPi: number,
|
||||
fullWidthHalf: number,
|
||||
fullHeightHalf: number
|
||||
): void {
|
||||
for (let y = startY; y < endY; y++) {
|
||||
for (let x = startX; x < endX; x++) {
|
||||
const actualY = y + yOffset;
|
||||
|
||||
this.pixelRenderer.renderPixel(
|
||||
data,
|
||||
x,
|
||||
y,
|
||||
actualY,
|
||||
width,
|
||||
time,
|
||||
renderMode,
|
||||
valueMode,
|
||||
message,
|
||||
this.compiledFunction,
|
||||
// Pre-calculated constants
|
||||
centerX,
|
||||
centerY,
|
||||
maxDistance,
|
||||
invMaxDistance,
|
||||
invFullWidth,
|
||||
invFullHeight,
|
||||
frameCount,
|
||||
goldenRatio,
|
||||
phase,
|
||||
timeTwoPi,
|
||||
fullWidthHalf,
|
||||
fullHeightHalf,
|
||||
deltaTime
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fillRemainingPixels(
|
||||
data: Uint8ClampedArray,
|
||||
width: number,
|
||||
height: number,
|
||||
startY: number,
|
||||
startX: number
|
||||
): void {
|
||||
for (let remainingY = startY; remainingY < height; remainingY++) {
|
||||
const xStart = remainingY === startY ? startX : 0;
|
||||
for (let remainingX = xStart; remainingX < width; remainingX++) {
|
||||
const i = (remainingY * width + remainingX) * 4;
|
||||
data[i] = 0;
|
||||
data[i + 1] = 0;
|
||||
data[i + 2] = 0;
|
||||
data[i + 3] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private postMessage(response: WorkerResponse): void {
|
||||
self.postMessage(response);
|
||||
}
|
||||
|
||||
private postError(id: string, error: string): void {
|
||||
this.postMessage({ id, type: 'error', success: false, error });
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize worker
|
||||
new ShaderWorker();
|
||||
Reference in New Issue
Block a user