small optimizations
This commit is contained in:
@ -476,21 +476,36 @@ export class FakeShader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private compositeTiles(): void {
|
private async compositeTiles(): Promise<void> {
|
||||||
const width = this.canvas.width;
|
|
||||||
const height = this.canvas.height;
|
const height = this.canvas.height;
|
||||||
const tileHeight = Math.ceil(height / this.workerCount);
|
const tileHeight = Math.ceil(height / this.workerCount);
|
||||||
|
|
||||||
// Clear main canvas
|
// Use ImageBitmap for faster compositing if available
|
||||||
this.ctx.clearRect(0, 0, width, height);
|
if (typeof createImageBitmap !== 'undefined') {
|
||||||
|
try {
|
||||||
|
const bitmapPromises: Promise<ImageBitmap>[] = [];
|
||||||
|
const positions: number[] = [];
|
||||||
|
|
||||||
// Composite all tiles directly on main canvas
|
for (let i = 0; i < this.workerCount; i++) {
|
||||||
for (let i = 0; i < this.workerCount; i++) {
|
const tileData = this.tileResults.get(i);
|
||||||
const tileData = this.tileResults.get(i);
|
if (tileData) {
|
||||||
if (tileData) {
|
bitmapPromises.push(createImageBitmap(tileData));
|
||||||
const startY = i * tileHeight;
|
positions.push(i * tileHeight);
|
||||||
this.ctx.putImageData(tileData, 0, startY);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const bitmaps = await Promise.all(bitmapPromises);
|
||||||
|
|
||||||
|
for (let i = 0; i < bitmaps.length; i++) {
|
||||||
|
this.ctx.drawImage(bitmaps[i], 0, positions[i]);
|
||||||
|
bitmaps[i].close(); // Free memory
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Fallback to putImageData if ImageBitmap fails
|
||||||
|
this.fallbackCompositeTiles();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
this.fallbackCompositeTiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear tile results
|
// Clear tile results
|
||||||
@ -510,6 +525,18 @@ export class FakeShader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fallbackCompositeTiles(): void {
|
||||||
|
const tileHeight = Math.ceil(this.canvas.height / this.workerCount);
|
||||||
|
|
||||||
|
for (let i = 0; i < this.workerCount; i++) {
|
||||||
|
const tileData = this.tileResults.get(i);
|
||||||
|
if (tileData) {
|
||||||
|
const startY = i * tileHeight;
|
||||||
|
this.ctx.putImageData(tileData, 0, startY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Simplified method - kept for backward compatibility but always uses all cores
|
// Simplified method - kept for backward compatibility but always uses all cores
|
||||||
setMultiWorkerMode(_enabled: boolean, _workerCount?: number): void {
|
setMultiWorkerMode(_enabled: boolean, _workerCount?: number): void {
|
||||||
// Always use all available cores, ignore the enabled parameter
|
// Always use all available cores, ignore the enabled parameter
|
||||||
|
|||||||
@ -46,7 +46,7 @@ interface WorkerResponse {
|
|||||||
|
|
||||||
import { LRUCache } from './utils/LRUCache';
|
import { LRUCache } from './utils/LRUCache';
|
||||||
import { calculateColorDirect } from './utils/colorModes';
|
import { calculateColorDirect } from './utils/colorModes';
|
||||||
import { PERFORMANCE, COLOR_TABLE_SIZE } from './utils/constants';
|
import { PERFORMANCE, COLOR_TABLE_SIZE, RENDER_MODES, RENDER_MODE_INDEX } from './utils/constants';
|
||||||
|
|
||||||
type ShaderFunction = (...args: number[]) => number;
|
type ShaderFunction = (...args: number[]) => number;
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ class ShaderWorker {
|
|||||||
private compilationCache: LRUCache<string, ShaderFunction> = new LRUCache(
|
private compilationCache: LRUCache<string, ShaderFunction> = new LRUCache(
|
||||||
PERFORMANCE.COMPILATION_CACHE_SIZE
|
PERFORMANCE.COMPILATION_CACHE_SIZE
|
||||||
);
|
);
|
||||||
private colorTables: Map<string, Uint8Array> = new Map();
|
private colorTables: Uint8Array[] = [];
|
||||||
private feedbackBuffer: Float32Array | null = null;
|
private feedbackBuffer: Float32Array | null = null;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -73,19 +73,11 @@ class ShaderWorker {
|
|||||||
private initializeColorTables(): void {
|
private initializeColorTables(): void {
|
||||||
const tableSize = COLOR_TABLE_SIZE;
|
const tableSize = COLOR_TABLE_SIZE;
|
||||||
|
|
||||||
// Pre-compute color tables for each render mode
|
// Pre-compute color tables for each render mode using array indexing
|
||||||
const modes = [
|
this.colorTables = new Array(RENDER_MODES.length);
|
||||||
'classic',
|
|
||||||
'grayscale',
|
|
||||||
'red',
|
|
||||||
'green',
|
|
||||||
'blue',
|
|
||||||
'rgb',
|
|
||||||
'hsv',
|
|
||||||
'rainbow',
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const mode of modes) {
|
for (let modeIndex = 0; modeIndex < RENDER_MODES.length; modeIndex++) {
|
||||||
|
const mode = RENDER_MODES[modeIndex];
|
||||||
const colorTable = new Uint8Array(tableSize * 3); // RGB triplets
|
const colorTable = new Uint8Array(tableSize * 3); // RGB triplets
|
||||||
|
|
||||||
for (let i = 0; i < tableSize; i++) {
|
for (let i = 0; i < tableSize; i++) {
|
||||||
@ -95,7 +87,7 @@ class ShaderWorker {
|
|||||||
colorTable[i * 3 + 2] = b;
|
colorTable[i * 3 + 2] = b;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.colorTables.set(mode, colorTable);
|
this.colorTables[modeIndex] = colorTable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,44 +220,10 @@ class ShaderWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private isStaticExpression(code: string): boolean {
|
private isStaticExpression(code: string): boolean {
|
||||||
// Check if code contains any variables
|
// Check if code contains any variables using regex for better accuracy
|
||||||
const variables = [
|
const variablePattern = /\b(x|y|t|i|r|a|u|v|c|f|d|n|b|mouse[XY]|mousePressed|mouseV[XY]|mouseClickTime|touchCount|touch[01][XY]|pinchScale|pinchRotation|accel[XYZ]|gyro[XYZ]|audioLevel|bassLevel|midLevel|trebleLevel)\b/;
|
||||||
'x',
|
|
||||||
'y',
|
|
||||||
't',
|
|
||||||
'i',
|
|
||||||
'mouseX',
|
|
||||||
'mouseY',
|
|
||||||
'mousePressed',
|
|
||||||
'mouseVX',
|
|
||||||
'mouseVY',
|
|
||||||
'mouseClickTime',
|
|
||||||
'touchCount',
|
|
||||||
'touch0X',
|
|
||||||
'touch0Y',
|
|
||||||
'touch1X',
|
|
||||||
'touch1Y',
|
|
||||||
'pinchScale',
|
|
||||||
'pinchRotation',
|
|
||||||
'accelX',
|
|
||||||
'accelY',
|
|
||||||
'accelZ',
|
|
||||||
'gyroX',
|
|
||||||
'gyroY',
|
|
||||||
'gyroZ',
|
|
||||||
'audioLevel',
|
|
||||||
'bassLevel',
|
|
||||||
'midLevel',
|
|
||||||
'trebleLevel',
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const variable of variables) {
|
return !variablePattern.test(code);
|
||||||
if (code.includes(variable)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private evaluateStaticExpression(code: string): number {
|
private evaluateStaticExpression(code: string): number {
|
||||||
@ -836,14 +794,15 @@ class ShaderWorker {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use pre-computed color table if available
|
// Use pre-computed color table with O(1) array indexing
|
||||||
const colorTable = this.colorTables.get(renderMode);
|
const modeIndex = RENDER_MODE_INDEX[renderMode];
|
||||||
if (colorTable) {
|
if (modeIndex !== undefined && this.colorTables[modeIndex]) {
|
||||||
|
const colorTable = this.colorTables[modeIndex];
|
||||||
const index = Math.floor(processedValue) * 3;
|
const index = Math.floor(processedValue) * 3;
|
||||||
return [colorTable[index], colorTable[index + 1], colorTable[index + 2]];
|
return [colorTable[index], colorTable[index + 1], colorTable[index + 2]];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to direct calculation
|
// Fallback to direct calculation for unknown render modes
|
||||||
return calculateColorDirect(processedValue, renderMode);
|
return calculateColorDirect(processedValue, renderMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -56,6 +56,16 @@ export const defaultInputState: InputState = {
|
|||||||
|
|
||||||
export const $input = atom<InputState>(defaultInputState);
|
export const $input = atom<InputState>(defaultInputState);
|
||||||
|
|
||||||
|
let mouseUpdatePending = false;
|
||||||
|
let pendingMouseData = {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
pressed: false,
|
||||||
|
vx: 0,
|
||||||
|
vy: 0,
|
||||||
|
clickTime: 0,
|
||||||
|
};
|
||||||
|
|
||||||
export function updateMousePosition(
|
export function updateMousePosition(
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
@ -64,17 +74,37 @@ export function updateMousePosition(
|
|||||||
vy: number,
|
vy: number,
|
||||||
clickTime: number
|
clickTime: number
|
||||||
) {
|
) {
|
||||||
$input.set({
|
pendingMouseData = { x, y, pressed, vx, vy, clickTime };
|
||||||
...$input.get(),
|
|
||||||
mouseX: x,
|
if (!mouseUpdatePending) {
|
||||||
mouseY: y,
|
mouseUpdatePending = true;
|
||||||
mousePressed: pressed,
|
requestAnimationFrame(() => {
|
||||||
mouseVX: vx,
|
const current = $input.get();
|
||||||
mouseVY: vy,
|
$input.set({
|
||||||
mouseClickTime: clickTime,
|
...current,
|
||||||
});
|
mouseX: pendingMouseData.x,
|
||||||
|
mouseY: pendingMouseData.y,
|
||||||
|
mousePressed: pendingMouseData.pressed,
|
||||||
|
mouseVX: pendingMouseData.vx,
|
||||||
|
mouseVY: pendingMouseData.vy,
|
||||||
|
mouseClickTime: pendingMouseData.clickTime,
|
||||||
|
});
|
||||||
|
mouseUpdatePending = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let touchUpdatePending = false;
|
||||||
|
let pendingTouchData = {
|
||||||
|
count: 0,
|
||||||
|
x0: 0,
|
||||||
|
y0: 0,
|
||||||
|
x1: 0,
|
||||||
|
y1: 0,
|
||||||
|
scale: 1,
|
||||||
|
rotation: 0,
|
||||||
|
};
|
||||||
|
|
||||||
export function updateTouchPosition(
|
export function updateTouchPosition(
|
||||||
count: number,
|
count: number,
|
||||||
x0: number,
|
x0: number,
|
||||||
@ -84,16 +114,25 @@ export function updateTouchPosition(
|
|||||||
scale: number,
|
scale: number,
|
||||||
rotation: number
|
rotation: number
|
||||||
) {
|
) {
|
||||||
$input.set({
|
pendingTouchData = { count, x0, y0, x1, y1, scale, rotation };
|
||||||
...$input.get(),
|
|
||||||
touchCount: count,
|
if (!touchUpdatePending) {
|
||||||
touch0X: x0,
|
touchUpdatePending = true;
|
||||||
touch0Y: y0,
|
requestAnimationFrame(() => {
|
||||||
touch1X: x1,
|
const current = $input.get();
|
||||||
touch1Y: y1,
|
$input.set({
|
||||||
pinchScale: scale,
|
...current,
|
||||||
pinchRotation: rotation,
|
touchCount: pendingTouchData.count,
|
||||||
});
|
touch0X: pendingTouchData.x0,
|
||||||
|
touch0Y: pendingTouchData.y0,
|
||||||
|
touch1X: pendingTouchData.x1,
|
||||||
|
touch1Y: pendingTouchData.y1,
|
||||||
|
pinchScale: pendingTouchData.scale,
|
||||||
|
pinchRotation: pendingTouchData.rotation,
|
||||||
|
});
|
||||||
|
touchUpdatePending = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateDeviceMotion(
|
export function updateDeviceMotion(
|
||||||
|
|||||||
@ -7,18 +7,47 @@ export const UI_HEIGHTS = {
|
|||||||
|
|
||||||
// Performance Constants
|
// Performance Constants
|
||||||
export const PERFORMANCE = {
|
export const PERFORMANCE = {
|
||||||
DEFAULT_TILE_SIZE: 64,
|
DEFAULT_TILE_SIZE: 128,
|
||||||
MAX_RENDER_TIME_MS: 50,
|
MAX_RENDER_TIME_MS: 50,
|
||||||
MAX_SHADER_TIMEOUT_MS: 5,
|
MAX_SHADER_TIMEOUT_MS: 5,
|
||||||
TIMEOUT_CHECK_INTERVAL: 1000,
|
TIMEOUT_CHECK_INTERVAL: 1000,
|
||||||
MAX_SAVED_SHADERS: 50,
|
MAX_SAVED_SHADERS: 50,
|
||||||
IMAGE_DATA_CACHE_SIZE: 5,
|
IMAGE_DATA_CACHE_SIZE: 10,
|
||||||
COMPILATION_CACHE_SIZE: 20,
|
COMPILATION_CACHE_SIZE: 30,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
// Color Constants
|
// Color Constants
|
||||||
export const COLOR_TABLE_SIZE = 256;
|
export const COLOR_TABLE_SIZE = 256;
|
||||||
|
|
||||||
|
// Render Mode Constants - Keep in sync with color modes
|
||||||
|
export const RENDER_MODES = [
|
||||||
|
'classic',
|
||||||
|
'grayscale',
|
||||||
|
'red',
|
||||||
|
'green',
|
||||||
|
'blue',
|
||||||
|
'rgb',
|
||||||
|
'hsv',
|
||||||
|
'rainbow',
|
||||||
|
'thermal',
|
||||||
|
'neon',
|
||||||
|
'cyberpunk',
|
||||||
|
'vaporwave',
|
||||||
|
'dithered',
|
||||||
|
'palette',
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export type RenderMode = (typeof RENDER_MODES)[number];
|
||||||
|
|
||||||
|
// Create a mapping from render mode to index for O(1) lookups
|
||||||
|
export const RENDER_MODE_INDEX: Record<string, number> = RENDER_MODES.reduce(
|
||||||
|
(acc, mode, index) => {
|
||||||
|
acc[mode] = index;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<string, number>
|
||||||
|
);
|
||||||
|
|
||||||
// Storage Keys
|
// Storage Keys
|
||||||
export const STORAGE_KEYS = {
|
export const STORAGE_KEYS = {
|
||||||
SHADERS: 'bitfielder_shaders',
|
SHADERS: 'bitfielder_shaders',
|
||||||
|
|||||||
Reference in New Issue
Block a user