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) => { 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();