more workers
This commit is contained in:
@ -6,6 +6,7 @@ interface WorkerMessage {
|
||||
height?: number;
|
||||
time?: number;
|
||||
renderMode?: string;
|
||||
startY?: number; // Y offset for tile rendering
|
||||
mouseX?: number;
|
||||
mouseY?: number;
|
||||
mousePressed?: boolean;
|
||||
@ -43,7 +44,9 @@ export class FakeShader {
|
||||
private canvas: HTMLCanvasElement;
|
||||
private ctx: CanvasRenderingContext2D;
|
||||
private code: string;
|
||||
private worker: Worker;
|
||||
private worker: Worker; // Single worker for backwards compatibility
|
||||
private workers: Worker[] = [];
|
||||
private workerCount: number;
|
||||
private animationId: number | null = null;
|
||||
private startTime: number = Date.now();
|
||||
private isCompiled: boolean = false;
|
||||
@ -65,6 +68,12 @@ export class FakeShader {
|
||||
private maxScale: number = 1.0;
|
||||
private renderStartTime: number = 0;
|
||||
|
||||
// Multi-worker state
|
||||
private tileResults: Map<number, ImageData> = new Map();
|
||||
private tilesCompleted: number = 0;
|
||||
private totalTiles: number = 0;
|
||||
private currentRenderID: string = '';
|
||||
|
||||
private mouseX: number = 0;
|
||||
private mouseY: number = 0;
|
||||
private mousePressed: boolean = false;
|
||||
@ -105,10 +114,18 @@ export class FakeShader {
|
||||
// Initialize offscreen canvas if supported
|
||||
this.initializeOffscreenCanvas();
|
||||
|
||||
// Initialize worker
|
||||
this.worker = new Worker(new URL('./ShaderWorker.ts', import.meta.url), { type: 'module' });
|
||||
this.worker.onmessage = (e: MessageEvent<WorkerResponse>) => this.handleWorkerMessage(e.data);
|
||||
this.worker.onerror = (error) => console.error('Worker error:', error);
|
||||
// Always use maximum available cores
|
||||
this.workerCount = navigator.hardwareConcurrency || 4;
|
||||
// Some browsers report logical processors (hyperthreading), which is good
|
||||
// But cap at a reasonable maximum to avoid overhead
|
||||
this.workerCount = Math.min(this.workerCount, 32);
|
||||
console.log(`Auto-detected ${this.workerCount} CPU cores, using all for maximum performance`);
|
||||
|
||||
// Initialize workers
|
||||
this.initializeWorkers();
|
||||
|
||||
// Keep single worker reference for backwards compatibility
|
||||
this.worker = this.workers[0];
|
||||
|
||||
this.compile();
|
||||
}
|
||||
@ -142,7 +159,17 @@ export class FakeShader {
|
||||
}
|
||||
}
|
||||
|
||||
private handleWorkerMessage(response: WorkerResponse): void {
|
||||
private initializeWorkers(): void {
|
||||
// Create worker pool
|
||||
for (let i = 0; i < this.workerCount; i++) {
|
||||
const worker = new Worker(new URL('./ShaderWorker.ts', import.meta.url), { type: 'module' });
|
||||
worker.onmessage = (e: MessageEvent<WorkerResponse>) => this.handleWorkerMessage(e.data, i);
|
||||
worker.onerror = (error) => console.error(`Worker ${i} error:`, error);
|
||||
this.workers.push(worker);
|
||||
}
|
||||
}
|
||||
|
||||
private handleWorkerMessage(response: WorkerResponse, workerIndex: number = 0): void {
|
||||
switch (response.type) {
|
||||
case 'compiled':
|
||||
this.isCompiled = response.success;
|
||||
@ -153,6 +180,10 @@ export class FakeShader {
|
||||
break;
|
||||
|
||||
case 'rendered':
|
||||
if (this.workerCount > 1) {
|
||||
this.handleTileResult(response, workerIndex);
|
||||
} else {
|
||||
// Single worker mode
|
||||
this.isRendering = false;
|
||||
if (response.success && response.imageData) {
|
||||
// Put ImageData on adaptive resolution canvas
|
||||
@ -167,6 +198,7 @@ export class FakeShader {
|
||||
console.error('Render failed:', response.error);
|
||||
this.fillBlack();
|
||||
}
|
||||
}
|
||||
|
||||
// Process pending renders
|
||||
if (this.pendingRenders.length > 0) {
|
||||
@ -196,11 +228,15 @@ export class FakeShader {
|
||||
private compile(): void {
|
||||
this.isCompiled = false;
|
||||
const id = `compile_${Date.now()}`;
|
||||
this.worker.postMessage({
|
||||
|
||||
// Send compile message to all workers
|
||||
this.workers.forEach(worker => {
|
||||
worker.postMessage({
|
||||
id,
|
||||
type: 'compile',
|
||||
code: this.code
|
||||
} as WorkerMessage);
|
||||
});
|
||||
}
|
||||
|
||||
private executeRender(id: string): void {
|
||||
@ -215,9 +251,18 @@ export class FakeShader {
|
||||
this.renderStartTime = performance.now();
|
||||
|
||||
this.isRendering = true;
|
||||
this.currentRenderID = id;
|
||||
const currentTime = (Date.now() - this.startTime) / 1000;
|
||||
|
||||
// Scale mouse coordinates to match render resolution
|
||||
// Always use multiple workers if available
|
||||
if (this.workerCount > 1) {
|
||||
this.renderWithMultipleWorkers(id, currentTime);
|
||||
} else {
|
||||
this.renderWithSingleWorker(id, currentTime);
|
||||
}
|
||||
}
|
||||
|
||||
private renderWithSingleWorker(id: string, currentTime: number): void {
|
||||
const scaledMouseX = this.mouseX * this.currentScale;
|
||||
const scaledMouseY = this.mouseY * this.currentScale;
|
||||
|
||||
@ -254,6 +299,62 @@ export class FakeShader {
|
||||
} as WorkerMessage);
|
||||
}
|
||||
|
||||
private renderWithMultipleWorkers(id: string, currentTime: number): void {
|
||||
// Reset tile tracking
|
||||
this.tileResults.clear();
|
||||
this.tilesCompleted = 0;
|
||||
this.totalTiles = this.workerCount;
|
||||
|
||||
const width = this.adaptiveCanvas.width;
|
||||
const height = this.adaptiveCanvas.height;
|
||||
const tileHeight = Math.ceil(height / this.workerCount);
|
||||
|
||||
// Distribute tiles to workers
|
||||
this.workers.forEach((worker, index) => {
|
||||
const startY = index * tileHeight;
|
||||
const endY = Math.min((index + 1) * tileHeight, height);
|
||||
|
||||
if (startY >= height) return; // Skip if tile is outside canvas
|
||||
|
||||
const scaledMouseX = this.mouseX * this.currentScale;
|
||||
const scaledMouseY = this.mouseY * this.currentScale;
|
||||
|
||||
worker.postMessage({
|
||||
id: `${id}_tile_${index}`,
|
||||
type: 'render',
|
||||
width: width,
|
||||
height: endY - startY,
|
||||
// Pass the Y offset for correct coordinate calculation
|
||||
startY: startY,
|
||||
time: currentTime,
|
||||
renderMode: this.renderMode,
|
||||
mouseX: scaledMouseX,
|
||||
mouseY: scaledMouseY,
|
||||
mousePressed: this.mousePressed,
|
||||
mouseVX: this.mouseVX * this.currentScale,
|
||||
mouseVY: this.mouseVY * this.currentScale,
|
||||
mouseClickTime: this.mouseClickTime,
|
||||
touchCount: this.touchCount,
|
||||
touch0X: this.touch0X * this.currentScale,
|
||||
touch0Y: this.touch0Y * this.currentScale,
|
||||
touch1X: this.touch1X * this.currentScale,
|
||||
touch1Y: this.touch1Y * this.currentScale,
|
||||
pinchScale: this.pinchScale,
|
||||
pinchRotation: this.pinchRotation,
|
||||
accelX: this.accelX,
|
||||
accelY: this.accelY,
|
||||
accelZ: this.accelZ,
|
||||
gyroX: this.gyroX,
|
||||
gyroY: this.gyroY,
|
||||
gyroZ: this.gyroZ,
|
||||
audioLevel: this.audioLevel,
|
||||
bassLevel: this.bassLevel,
|
||||
midLevel: this.midLevel,
|
||||
trebleLevel: this.trebleLevel
|
||||
} as WorkerMessage);
|
||||
});
|
||||
}
|
||||
|
||||
setCode(code: string): void {
|
||||
this.code = code;
|
||||
this.compile();
|
||||
@ -359,7 +460,63 @@ export class FakeShader {
|
||||
|
||||
destroy(): void {
|
||||
this.stopAnimation();
|
||||
this.worker.terminate();
|
||||
this.workers.forEach(worker => worker.terminate());
|
||||
}
|
||||
|
||||
private handleTileResult(response: WorkerResponse, workerIndex: number): void {
|
||||
if (!response.success || !response.imageData) {
|
||||
console.error(`Tile render failed for worker ${workerIndex}:`, response.error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Store tile result
|
||||
this.tileResults.set(workerIndex, response.imageData);
|
||||
this.tilesCompleted++;
|
||||
|
||||
// Check if all tiles are complete
|
||||
if (this.tilesCompleted === this.totalTiles) {
|
||||
this.compositeTiles();
|
||||
}
|
||||
}
|
||||
|
||||
private compositeTiles(): void {
|
||||
const width = this.adaptiveCanvas.width;
|
||||
const height = this.adaptiveCanvas.height;
|
||||
const tileHeight = Math.ceil(height / this.workerCount);
|
||||
|
||||
// Clear adaptive canvas
|
||||
this.adaptiveCtx.clearRect(0, 0, width, height);
|
||||
|
||||
// Composite all tiles
|
||||
for (let i = 0; i < this.workerCount; i++) {
|
||||
const tileData = this.tileResults.get(i);
|
||||
if (tileData) {
|
||||
const startY = i * tileHeight;
|
||||
this.adaptiveCtx.putImageData(tileData, 0, startY);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear tile results
|
||||
this.tileResults.clear();
|
||||
|
||||
// Mark rendering as complete
|
||||
this.isRendering = false;
|
||||
|
||||
// Upscale to main canvas
|
||||
this.upscaleToMainCanvas();
|
||||
|
||||
// Monitor performance
|
||||
this.updatePerformanceMetrics();
|
||||
|
||||
// Process pending renders
|
||||
if (this.pendingRenders.length > 0) {
|
||||
this.pendingRenders.shift();
|
||||
if (this.pendingRenders.length > 0) {
|
||||
const latestId = this.pendingRenders[this.pendingRenders.length - 1];
|
||||
this.pendingRenders = [latestId];
|
||||
this.executeRender(latestId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private upscaleToMainCanvas(): void {
|
||||
@ -438,6 +595,16 @@ export class FakeShader {
|
||||
return this.currentScale;
|
||||
}
|
||||
|
||||
// Simplified method - kept for backward compatibility but always uses all cores
|
||||
setMultiWorkerMode(enabled: boolean, workerCount?: number): void {
|
||||
// Always use all available cores, ignore the enabled parameter
|
||||
console.log(`Multi-worker mode is always enabled, using ${this.workerCount} cores for maximum performance`);
|
||||
}
|
||||
|
||||
getWorkerCount(): number {
|
||||
return this.workerCount;
|
||||
}
|
||||
|
||||
static generateRandomCode(): string {
|
||||
const presets = [
|
||||
'x^y',
|
||||
|
||||
@ -7,6 +7,7 @@ interface WorkerMessage {
|
||||
height?: number;
|
||||
time?: number;
|
||||
renderMode?: string;
|
||||
startY?: number; // Y offset for tile rendering
|
||||
mouseX?: number;
|
||||
mouseY?: number;
|
||||
mousePressed?: boolean;
|
||||
@ -114,7 +115,7 @@ class ShaderWorker {
|
||||
this.compileShader(message.id, message.code!);
|
||||
break;
|
||||
case 'render':
|
||||
this.renderShader(message.id, message.width!, message.height!, message.time!, message.renderMode || 'classic', message);
|
||||
this.renderShader(message.id, message.width!, message.height!, message.time!, message.renderMode || 'classic', message, message.startY || 0);
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
@ -219,7 +220,7 @@ class ShaderWorker {
|
||||
return hash.toString(36);
|
||||
}
|
||||
|
||||
private renderShader(id: string, width: number, height: number, time: number, renderMode: string, message: WorkerMessage): void {
|
||||
private renderShader(id: string, width: number, height: number, time: number, renderMode: string, message: WorkerMessage, startY: number = 0): void {
|
||||
if (!this.compiledFunction) {
|
||||
this.postError(id, 'No compiled shader');
|
||||
return;
|
||||
@ -232,14 +233,14 @@ class ShaderWorker {
|
||||
|
||||
try {
|
||||
// Use tiled rendering for better timeout handling
|
||||
this.renderTiled(data, width, height, time, renderMode, message, startTime, maxRenderTime);
|
||||
this.renderTiled(data, width, height, time, renderMode, message, startTime, maxRenderTime, startY);
|
||||
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, message: WorkerMessage, startTime: number, maxRenderTime: number): void {
|
||||
private renderTiled(data: Uint8ClampedArray, width: number, height: number, time: number, renderMode: string, message: WorkerMessage, startTime: number, maxRenderTime: number, yOffset: number = 0): void {
|
||||
const tileSize = 64; // 64x64 tiles for better granularity
|
||||
const tilesX = Math.ceil(width / tileSize);
|
||||
const tilesY = Math.ceil(height / tileSize);
|
||||
@ -259,20 +260,23 @@ class ShaderWorker {
|
||||
const tileEndX = Math.min(tileStartX + tileSize, width);
|
||||
const tileEndY = Math.min(tileStartY + tileSize, height);
|
||||
|
||||
this.renderTile(data, width, tileStartX, tileStartY, tileEndX, tileEndY, time, renderMode, message);
|
||||
this.renderTile(data, width, tileStartX, tileStartY, tileEndX, tileEndY, time, renderMode, message, yOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private renderTile(data: Uint8ClampedArray, width: number, startX: number, startY: number, endX: number, endY: number, time: number, renderMode: string, message: WorkerMessage): void {
|
||||
private renderTile(data: Uint8ClampedArray, width: number, startX: number, startY: number, endX: number, endY: number, time: number, renderMode: string, message: WorkerMessage, yOffset: number = 0): void {
|
||||
for (let y = startY; y < endY; y++) {
|
||||
for (let x = startX; x < endX; x++) {
|
||||
const i = (y * width + x) * 4;
|
||||
const pixelIndex = y * width + x;
|
||||
|
||||
// Adjust y coordinate to account for tile offset
|
||||
const actualY = y + yOffset;
|
||||
|
||||
try {
|
||||
const value = this.compiledFunction!(
|
||||
x, y, time, pixelIndex,
|
||||
x, actualY, time, pixelIndex,
|
||||
message.mouseX || 0, message.mouseY || 0,
|
||||
message.mousePressed || false,
|
||||
message.mouseVX || 0, message.mouseVY || 0,
|
||||
|
||||
Reference in New Issue
Block a user