# Troubleshooting ## Performance ### Step Zero — Disable FES The Friendly Error System (FES) adds massive overhead — up to 10x slowdown. Disable it in every production sketch: ```javascript // BEFORE any p5 code p5.disableFriendlyErrors = true; // Or use p5.min.js instead of p5.js — FES is stripped from minified build ``` ### Step One — pixelDensity(1) Retina/HiDPI displays default to 2x or 3x density, multiplying pixel count by 4-9x: ```javascript function setup() { pixelDensity(1); // force 1:1 — always do this first createCanvas(1920, 1080); } ``` ### Use Math.* in Hot Loops p5's `sin()`, `cos()`, `random()`, `min()`, `max()`, `abs()` are wrapper functions with overhead. In hot loops (thousands of iterations per frame), use native `Math.*`: ```javascript // SLOW — p5 wrappers for (let p of particles) { let a = sin(p.angle); let d = dist(p.x, p.y, mx, my); } // FAST — native Math for (let p of particles) { let a = Math.sin(p.angle); let dx = p.x - mx, dy = p.y - my; let dSq = dx * dx + dy * dy; // skip sqrt entirely } ``` Use `magSq()` instead of `mag()` for distance comparisons — avoids expensive `sqrt()`. ### Diagnosis Open Chrome DevTools > Performance tab > Record while sketch runs. Common bottlenecks: 1. **FES enabled** — 10x overhead on every p5 function call 2. **pixelDensity > 1** — 4x pixel count, 4x slower 3. **Too many draw calls** — thousands of `ellipse()`, `rect()` per frame 4. **Large canvas + pixel operations** — `loadPixels()`/`updatePixels()` on 4K canvas 5. **Unoptimized particle systems** — checking all-vs-all distances (O(n^2)) 6. **Memory leaks** — creating objects every frame without cleanup 7. **Shader compilation** — calling `createShader()` in `draw()` instead of `setup()` 8. **console.log() in draw()** — DOM write per frame, destroys performance 9. **DOM manipulation in draw()** — layout thrashing (400-500x slower than canvas ops) ### Solutions **Reduce draw calls:** ```javascript // BAD: 10000 individual circles for (let p of particles) { ellipse(p.x, p.y, p.size); } // GOOD: single shape with vertices beginShape(POINTS); for (let p of particles) { vertex(p.x, p.y); } endShape(); // BEST: direct pixel manipulation loadPixels(); for (let p of particles) { let idx = 4 * (floor(p.y) * width + floor(p.x)); pixels[idx] = p.r; pixels[idx+1] = p.g; pixels[idx+2] = p.b; pixels[idx+3] = 255; } updatePixels(); ``` **Spatial hashing for neighbor queries:** ```javascript class SpatialHash { constructor(cellSize) { this.cellSize = cellSize; this.cells = new Map(); } clear() { this.cells.clear(); } _key(x, y) { return `${floor(x / this.cellSize)},${floor(y / this.cellSize)}`; } insert(obj) { let key = this._key(obj.pos.x, obj.pos.y); if (!this.cells.has(key)) this.cells.set(key, []); this.cells.get(key).push(obj); } query(x, y, radius) { let results = []; let minCX = floor((x - radius) / this.cellSize); let maxCX = floor((x + radius) / this.cellSize); let minCY = floor((y - radius) / this.cellSize); let maxCY = floor((y + radius) / this.cellSize); for (let cx = minCX; cx <= maxCX; cx++) { for (let cy = minCY; cy <= maxCY; cy++) { let key = `${cx},${cy}`; let cell = this.cells.get(key); if (cell) { for (let obj of cell) { if (dist(x, y, obj.pos.x, obj.pos.y) <= radius) { results.push(obj); } } } } } return results; } } ``` **Object pooling:** ```javascript class ParticlePool { constructor(maxSize) { this.pool = []; this.active = []; for (let i = 0; i < maxSize; i++) { this.pool.push(new Particle(0, 0)); } } spawn(x, y) { let p = this.pool.pop(); if (p) { p.reset(x, y); this.active.push(p); } } update() { for (let i = this.active.length - 1; i >= 0; i--) { this.active[i].update(); if (this.active[i].isDead()) { this.pool.push(this.active.splice(i, 1)[0]); } } } } ``` **Throttle heavy operations:** ```javascript // Only update flow field every N frames if (frameCount % 5 === 0) { flowField.update(frameCount * 0.001); } ``` ### Frame Rate Targets | Context | Target | Acceptable | |---------|--------|------------| | Interactive sketch | 60fps | 30fps | | Ambient animation | 30fps | 20fps | | Export/recording | 30fps render | Any (offline) | | Mobile | 30fps | 20fps | ### Per-Pixel Rendering Budgets Pixel-level operations (`loadPixels()` loops) are the most expensive common pattern. Budget depends on canvas size and computation per pixel. | Canvas | Pixels | Simple noise (1 call) | fBM (4 octave) | Domain warp (3-layer fBM) | |--------|--------|----------------------|----------------|--------------------------| | 540x540 | 291K | ~5ms | ~20ms | ~80ms | | 1080x1080 | 1.17M | ~20ms | ~80ms | ~300ms+ | | 1920x1080 | 2.07M | ~35ms | ~140ms | ~500ms+ | | 3840x2160 | 8.3M | ~140ms | ~560ms | WILL CRASH | **Rules of thumb:** - 1 `noise()` call per pixel at 1080x1080 = ~20ms/frame (OK at 30fps) - 4-octave fBM per pixel at 1080x1080 = ~80ms/frame (borderline) - Multi-layer domain warp at 1080x1080 = 300ms+ (too slow for real-time, fine for `noLoop()` export) - **Headless Chrome is 2-5x slower** than desktop Chrome for pixel ops **Solution: render at lower resolution, fill blocks:** ```javascript let step = 3; // render 1/9 of pixels, fill 3x3 blocks loadPixels(); for (let y = 0; y < H; y += step) { for (let x = 0; x < W; x += step) { let v = expensiveNoise(x, y); for (let dy = 0; dy < step && y+dy < H; dy++) for (let dx = 0; dx < step && x+dx < W; dx++) { let i = 4 * ((y+dy) * W + (x+dx)); pixels[i] = v; pixels[i+1] = v; pixels[i+2] = v; pixels[i+3] = 255; } } } updatePixels(); ``` Step=2 gives 4x speedup. Step=3 gives 9x. Visible at 1080p but acceptable for video (motion hides it). ## Common Mistakes ### 1. Forgetting to reset blend mode ```javascript blendMode(ADD); image(glowLayer, 0, 0); // WRONG: everything after this is ADD blended blendMode(BLEND); // ALWAYS reset ``` ### 2. Creating objects in draw() ```javascript // BAD: creates new font object every frame function draw() { let f = loadFont('font.otf'); // NEVER load in draw() } // GOOD: load in preload, use in draw let f; function preload() { f = loadFont('font.otf'); } ``` ### 3. Not using push()/pop() with transforms ```javascript // BAD: transforms accumulate translate(100, 0); rotate(0.1); ellipse(0, 0, 50); // Everything after this is also translated and rotated // GOOD: isolated transforms push(); translate(100, 0); rotate(0.1); ellipse(0, 0, 50); pop(); ``` ### 4. Integer coordinates for crisp lines ```javascript // BLURRY: sub-pixel rendering line(10.5, 20.3, 100.7, 80.2); // CRISP: integer + 0.5 for 1px lines line(10.5, 20.5, 100.5, 80.5); // on pixel boundary ``` ### 5. Pixel density confusion ```javascript // WRONG: assuming pixel array matches canvas dimensions loadPixels(); let idx = 4 * (y * width + x); // wrong if pixelDensity > 1 // RIGHT: account for pixel density let d = pixelDensity(); loadPixels(); let idx = 4 * ((y * d) * (width * d) + (x * d)); // SIMPLEST: set pixelDensity(1) at the start ``` ### 6. Color mode confusion ```javascript // In HSB mode, fill(255) is NOT white colorMode(HSB, 360, 100, 100); fill(255); // This is hue=255, sat=100, bri=100 = vivid purple // White in HSB: fill(0, 0, 100); // any hue, 0 saturation, 100 brightness // Black in HSB: fill(0, 0, 0); ``` ### 7. WebGL origin is center ```javascript // In WEBGL mode, (0,0) is CENTER, not top-left function draw() { // This draws at the center, not the corner rect(0, 0, 100, 100); // For top-left behavior: translate(-width/2, -height/2); rect(0, 0, 100, 100); // now at top-left } ``` ### 8. createGraphics cleanup ```javascript // BAD: memory leak — buffer never freed function draw() { let temp = createGraphics(width, height); // new buffer every frame! // ... } // GOOD: create once, reuse let temp; function setup() { temp = createGraphics(width, height); } function draw() { temp.clear(); // ... reuse temp } // If you must create/destroy: temp.remove(); // explicitly free ``` ### 9. noise() returns 0-1, not -1 to 1 ```javascript let n = noise(x); // 0.0 to 1.0 (biased toward 0.5) // For -1 to 1 range: let n = noise(x) * 2 - 1; // For a specific range: let n = map(noise(x), 0, 1, -100, 100); ``` ### 10. saveCanvas() in draw() saves every frame ```javascript // BAD: saves a PNG every single frame function draw() { // ... render ... saveCanvas('output', 'png'); // DON'T DO THIS } // GOOD: save once via keyboard function keyPressed() { if (key === 's') saveCanvas('output', 'png'); } // GOOD: save once after rendering static piece function draw() { // ... render ... saveCanvas('output', 'png'); noLoop(); // stop after saving } ``` ### 11. console.log() in draw() ```javascript // BAD: writes to DOM console every frame — massive overhead function draw() { console.log(particles.length); // 60 DOM writes/second } // GOOD: log periodically or conditionally function draw() { if (frameCount % 60 === 0) console.log('FPS:', frameRate().toFixed(1)); } ``` ### 12. DOM manipulation in draw() ```javascript // BAD: layout thrashing — 400-500x slower than canvas ops function draw() { document.getElementById('counter').innerText = frameCount; let el = document.querySelector('.info'); // DOM query per frame } // GOOD: cache DOM refs, update infrequently let counterEl; function setup() { counterEl = document.getElementById('counter'); } function draw() { if (frameCount % 30 === 0) counterEl.innerText = frameCount; } ``` ### 13. Not disabling FES in production ```javascript // BAD: every p5 function call has error-checking overhead (up to 10x slower) function setup() { createCanvas(800, 800); } // GOOD: disable before any p5 code p5.disableFriendlyErrors = true; function setup() { createCanvas(800, 800); } // ALSO GOOD: use p5.min.js (FES stripped from minified build) ``` ## Browser Compatibility ### Safari Issues - WebGL shader precision: always declare `precision mediump float;` - `AudioContext` requires user gesture (`userStartAudio()`) - Some `blendMode()` options behave differently ### Firefox Issues - `textToPoints()` may return slightly different point counts - WebGL extensions may differ from Chrome - Color profile handling can shift colors ### Mobile Issues - Touch events need `return false` to prevent scroll - `devicePixelRatio` can be 2x or 3x — use `pixelDensity(1)` for performance - Smaller canvas recommended (720p or less) - Audio requires explicit user gesture to start ## CORS Issues ```javascript // Loading images/fonts from external URLs requires CORS headers // Local files need a server: // python3 -m http.server 8080 // Or use a CORS proxy for external resources (not recommended for production) ``` ## Memory Leaks ### Symptoms - Framerate degrading over time - Browser tab memory growing unbounded - Page becomes unresponsive after minutes ### Common Causes ```javascript // 1. Growing arrays let history = []; function draw() { history.push(someData); // grows forever } // FIX: cap the array if (history.length > 1000) history.shift(); // 2. Creating p5 objects in draw() function draw() { let v = createVector(0, 0); // allocation every frame } // FIX: reuse pre-allocated objects // 3. Unreleased graphics buffers let layers = []; function reset() { for (let l of layers) l.remove(); // free old buffers layers = []; } // 4. Event listener accumulation function setup() { // BAD: adds new listener every time setup runs window.addEventListener('resize', handler); } // FIX: use p5's built-in windowResized() ``` ## Debugging Tips ### Console Logging ```javascript // Log once (not every frame) if (frameCount === 1) { console.log('Canvas:', width, 'x', height); console.log('Pixel density:', pixelDensity()); console.log('Renderer:', drawingContext.constructor.name); } // Log periodically if (frameCount % 60 === 0) { console.log('FPS:', frameRate().toFixed(1)); console.log('Particles:', particles.length); } ``` ### Visual Debugging ```javascript // Show frame rate function draw() { // ... your sketch ... if (CONFIG.debug) { fill(255, 0, 0); noStroke(); textSize(14); textAlign(LEFT, TOP); text('FPS: ' + frameRate().toFixed(1), 10, 10); text('Particles: ' + particles.length, 10, 28); text('Frame: ' + frameCount, 10, 46); } } // Toggle debug with 'd' key function keyPressed() { if (key === 'd') CONFIG.debug = !CONFIG.debug; } ``` ### Isolating Issues ```javascript // Comment out layers to find the slow one function draw() { renderBackground(); // comment out to test // renderParticles(); // this might be slow // renderPostEffects(); // or this } ```