> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/paper-design/shaders/llms.txt
> Use this file to discover all available pages before exploring further.

# Performance

> Performance optimization tips and best practices for Paper Shaders

## Overview

Paper Shaders are designed for maximum performance, but understanding the optimization options helps you deliver smooth experiences across all devices.

## Automatic Optimizations

Paper Shaders includes several automatic performance optimizations:

### Automatic Pausing

Shaders automatically pause when the browser tab is hidden:

```javascript theme={null}
// Happens automatically - no code needed
// - Pauses requestAnimationFrame when tab hidden
// - Resumes when tab becomes visible
// - Saves CPU/GPU resources in background
```

### Smart Resolution Scaling

Shaders automatically adjust rendering resolution based on:

* Device pixel ratio (retina displays)
* Browser zoom level
* Pinch zoom on mobile
* Container size changes

### Uniform Caching

Only changed uniforms trigger GPU updates:

```jsx theme={null}
// Only u_distortion updates - other uniforms stay cached
shaderMount.setUniforms({ u_distortion: 0.5 });
```

## Resolution Control

### Min Pixel Ratio

Controls minimum rendering quality:

<Tabs>
  <Tab title="React">
    ```jsx theme={null}
    // Default: 2x for crisp rendering even on 1x displays
    <MeshGradient minPixelRatio={2} />

    // Lower for better performance on low-end devices
    <MeshGradient minPixelRatio={1} />

    // Higher for ultra-sharp rendering
    <MeshGradient minPixelRatio={3} />
    ```
  </Tab>

  <Tab title="Vanilla JS">
    ```javascript theme={null}
    // Set during initialization
    const shaderMount = new ShaderMount(
      container,
      fragmentShader,
      uniforms,
      undefined,
      1,    // speed
      0,    // frame
      2     // minPixelRatio (default: 2)
    );

    // Update dynamically
    shaderMount.setMinPixelRatio(1);
    ```
  </Tab>
</Tabs>

**Impact:**

* `minPixelRatio={1}`: Matches CSS pixels, fastest
* `minPixelRatio={2}`: 4x pixels, great quality/performance balance (default)
* `minPixelRatio={3}`: 9x pixels, ultra-sharp but slower

<Tip>
  On 1x displays, `minPixelRatio={2}` still renders at 2x for better antialiasing.
  On 2x displays (retina), it renders at actual device resolution.
</Tip>

### Max Pixel Count

Limits total rendered pixels regardless of display size:

<Tabs>
  <Tab title="React">
    ```jsx theme={null}
    // Default: 1920 × 1080 × 4 = 8,294,400 pixels
    <MeshGradient maxPixelCount={1920 * 1080 * 4} />

    // Lower for better performance
    <MeshGradient maxPixelCount={1920 * 1080 * 2} />

    // Higher for 4K+ displays
    <MeshGradient maxPixelCount={3840 * 2160 * 4} />
    ```
  </Tab>

  <Tab title="Vanilla JS">
    ```javascript theme={null}
    // Set during initialization
    const shaderMount = new ShaderMount(
      container,
      fragmentShader,
      uniforms,
      undefined,
      1,    // speed
      0,    // frame
      2,    // minPixelRatio
      1920 * 1080 * 4  // maxPixelCount
    );

    // Update dynamically
    shaderMount.setMaxPixelCount(1920 * 1080 * 2);
    ```
  </Tab>
</Tabs>

**When to adjust:**

* Lower for mobile-first sites
* Lower for complex shaders with many colors
* Higher for desktop applications with large displays
* Higher for simpler shaders on powerful hardware

### Responsive Resolution

Adjust resolution based on device:

```jsx theme={null}
import { useState, useEffect } from 'react';

function ResponsiveShader() {
  const [device, setDevice] = useState('desktop');
  
  useEffect(() => {
    const updateDevice = () => {
      const width = window.innerWidth;
      if (width < 768) setDevice('mobile');
      else if (width < 1024) setDevice('tablet');
      else setDevice('desktop');
    };
    
    updateDevice();
    window.addEventListener('resize', updateDevice);
    return () => window.removeEventListener('resize', updateDevice);
  }, []);
  
  const config = {
    mobile: { minPixelRatio: 1, maxPixelCount: 1920 * 1080 },
    tablet: { minPixelRatio: 1.5, maxPixelCount: 1920 * 1080 * 2 },
    desktop: { minPixelRatio: 2, maxPixelCount: 1920 * 1080 * 4 },
  }[device];
  
  return <MeshGradient {...config} colors={['#ff0000', '#00ff00']} />;
}
```

## Animation Performance

### Static Shaders

Static shaders (speed=0) have zero ongoing performance cost:

```jsx theme={null}
// No requestAnimationFrame, no CPU/GPU usage after initial render
<MeshGradient speed={0} colors={['#ff0000', '#00ff00']} />
```

**Best for:**

* Background textures
* Hero sections that don't need motion
* Mobile devices
* Battery-conscious applications

### Reduced Animation Speed

Slower animations use less CPU:

```jsx theme={null}
// Half speed = half the frame rate needed for smooth motion
<MeshGradient speed={0.5} colors={['#ff0000', '#00ff00']} />
```

### Conditional Animation

Animate only when visible:

```jsx theme={null}
import { useState, useEffect, useRef } from 'react';

function AnimateOnView() {
  const [isVisible, setIsVisible] = useState(false);
  const ref = useRef(null);
  
  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => setIsVisible(entry.isIntersecting),
      { threshold: 0.1 }
    );
    
    if (ref.current) observer.observe(ref.current);
    return () => observer.disconnect();
  }, []);
  
  return (
    <div ref={ref}>
      <MeshGradient
        speed={isVisible ? 1 : 0}
        colors={['#ff0000', '#00ff00']}
      />
    </div>
  );
}
```

<Tip>
  The browser's Page Visibility API automatically pauses shaders in background tabs.
  This Intersection Observer pattern further optimizes for scrolled-out-of-view shaders.
</Tip>

## Shader Complexity

### Color Count

Fewer colors = better performance:

```jsx theme={null}
// Fastest: 2-3 colors
<MeshGradient colors={['#ff0000', '#00ff00', '#0000ff']} />

// Good: 4-5 colors
<MeshGradient colors={['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff']} />

// Slower: Maximum colors (10 for MeshGradient)
<MeshGradient colors={[
  '#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff',
  '#00ffff', '#ff8800', '#8800ff', '#00ff88', '#ff0088'
]} />
```

**Performance impact:**

* Each color adds GPU calculations
* 2-4 colors: minimal impact
* 5-7 colors: noticeable on low-end devices
* 8+ colors: avoid on mobile

### Parameter Complexity

Some parameters are more expensive:

```jsx theme={null}
// Cheaper parameters
<MeshGradient
  distortion={0.8}   // Moderate cost
  swirl={0.1}        // Moderate cost
/>

// Expensive parameters
<MeshGradient
  grainMixer={0.5}   // Higher cost (adds noise calculations)
  grainOverlay={0.5} // Higher cost (post-processing)
/>
```

**Optimization strategy:**

* Start with low/zero grain parameters
* Increase only if needed visually
* Test on target devices

## Image-Based Shaders

### Image Size

Larger images require more memory and processing:

```jsx theme={null}
// Optimize images before using
<Water
  image="/optimized-image.jpg"  // Max 2048×2048 recommended
  highlights={0.3}
  speed={1}
/>
```

**Best practices:**

* Resize images to actual display size
* Use WebP or AVIF for better compression
* Aim for 1024×1024 or smaller for filters
* Use 2048×2048 max for high-quality needs

### Mipmaps

Enable mipmaps for better performance with large images:

<Tabs>
  <Tab title="React">
    ```jsx theme={null}
    // React package automatically uses mipmaps for image shaders
    <Water image="/large-image.jpg" />
    ```
  </Tab>

  <Tab title="Vanilla JS">
    ```javascript theme={null}
    const shaderMount = new ShaderMount(
      container,
      waterFragmentShader,
      uniforms,
      undefined,
      1,    // speed
      0,    // frame
      2,    // minPixelRatio
      1920 * 1080 * 4,  // maxPixelCount
      ['u_image']  // Enable mipmaps for u_image
    );
    ```
  </Tab>
</Tabs>

### Image Loading

Preload images to avoid stutters:

```jsx theme={null}
import { useState, useEffect } from 'react';

function PreloadedWater() {
  const [imageLoaded, setImageLoaded] = useState(false);
  
  useEffect(() => {
    const img = new Image();
    img.onload = () => setImageLoaded(true);
    img.src = '/path/to/image.jpg';
  }, []);
  
  if (!imageLoaded) {
    return <div>Loading...</div>;
  }
  
  return <Water image="/path/to/image.jpg" speed={1} />;
}
```

## Multiple Shaders

### Lazy Loading

Load shaders on demand:

```jsx theme={null}
import { lazy, Suspense } from 'react';

const MeshGradient = lazy(() =>
  import('@paper-design/shaders-react').then(m => ({ default: m.MeshGradient }))
);

function App() {
  return (
    <Suspense fallback={<div>Loading shader...</div>}>
      <MeshGradient colors={['#ff0000', '#00ff00']} />
    </Suspense>
  );
}
```

### Stagger Initialization

Initialize shaders one at a time:

```jsx theme={null}
import { useState, useEffect } from 'react';

function StaggeredShaders() {
  const [activeCount, setActiveCount] = useState(1);
  
  useEffect(() => {
    if (activeCount < 3) {
      const timer = setTimeout(() => setActiveCount(prev => prev + 1), 100);
      return () => clearTimeout(timer);
    }
  }, [activeCount]);
  
  return (
    <>
      <MeshGradient colors={['#ff0000', '#00ff00']} />
      {activeCount >= 2 && <DotOrbit colors={['#0000ff', '#ffff00']} />}
      {activeCount >= 3 && <SmokeRing colors={['#ff00ff', '#00ffff']} />}
    </>
  );
}
```

### Limit Active Shaders

Only render visible shaders:

```jsx theme={null}
function ShaderCarousel() {
  const [activeIndex, setActiveIndex] = useState(0);
  const shaders = [
    <MeshGradient key="mesh" colors={['#ff0000', '#00ff00']} />,
    <DotOrbit key="dots" colors={['#0000ff', '#ffff00']} />,
    <SmokeRing key="smoke" colors={['#ff00ff', '#00ffff']} />,
  ];
  
  return (
    <div>
      {shaders[activeIndex]}
      <button onClick={() => setActiveIndex((activeIndex + 1) % 3)}>
        Next
      </button>
    </div>
  );
}
```

## Memory Management

### Proper Cleanup

Always dispose shaders when unmounting:

<Tabs>
  <Tab title="React">
    ```jsx theme={null}
    // React components handle cleanup automatically
    // No manual cleanup needed
    function App() {
      const [show, setShow] = useState(true);
      
      return (
        <>
          {show && <MeshGradient colors={['#ff0000', '#00ff00']} />}
          <button onClick={() => setShow(!show)}>Toggle</button>
        </>
      );
    }
    ```
  </Tab>

  <Tab title="Vanilla JS">
    ```javascript theme={null}
    // Always call dispose() when removing shaders
    const shaderMount = new ShaderMount(/* ... */);

    // Later, when removing:
    shaderMount.dispose();

    // Or on page unload:
    window.addEventListener('beforeunload', () => {
      shaderMount.dispose();
    });
    ```
  </Tab>
</Tabs>

<Warning>
  Failing to dispose vanilla shaders causes memory leaks. Always call `dispose()`
  when removing shaders from the page.
</Warning>

### Stable References

Avoid creating new arrays on every render:

```jsx theme={null}
import { useMemo } from 'react';

// Bad: New array every render
function Bad() {
  return <MeshGradient colors={['#ff0000', '#00ff00']} />;
}

// Good: Stable reference
const COLORS = ['#ff0000', '#00ff00'];
function Good() {
  return <MeshGradient colors={COLORS} />;
}

// Also good: useMemo
function AlsoGood() {
  const colors = useMemo(() => ['#ff0000', '#00ff00'], []);
  return <MeshGradient colors={colors} />;
}
```

## WebGL Context

### Context Limits

Browsers limit concurrent WebGL contexts (typically 8-16):

```jsx theme={null}
// Check available contexts
function checkWebGLAvailability() {
  const canvas = document.createElement('canvas');
  const gl = canvas.getContext('webgl2');
  if (!gl) {
    console.warn('WebGL 2 not available');
    return false;
  }
  return true;
}
```

**Best practices:**

* Limit to 3-5 simultaneous shaders per page
* Dispose unused shaders to free contexts
* Show fallback content if contexts exhausted

### Context Attributes

Optimize context creation:

```jsx theme={null}
const contextAttributes = {
  alpha: true,              // Transparent backgrounds
  antialias: false,         // Disable for better performance
  depth: false,             // Not needed for 2D shaders
  stencil: false,           // Not needed for 2D shaders
  preserveDrawingBuffer: false,  // Better performance
  powerPreference: 'default',    // Or 'low-power' for mobile
};

<MeshGradient
  webGlContextAttributes={contextAttributes}
  colors={['#ff0000', '#00ff00']}
/>
```

## Monitoring Performance

### FPS Monitoring

```javascript theme={null}
let lastTime = performance.now();
let frames = 0;

function measureFPS() {
  frames++;
  const now = performance.now();
  
  if (now >= lastTime + 1000) {
    const fps = Math.round((frames * 1000) / (now - lastTime));
    console.log(`FPS: ${fps}`);
    frames = 0;
    lastTime = now;
  }
  
  requestAnimationFrame(measureFPS);
}

measureFPS();
```

### Chrome DevTools

Use Chrome's Performance panel:

<Steps>
  <Step title="Open DevTools">
    Press F12 or Cmd+Option+I
  </Step>

  <Step title="Go to Performance tab">
    Enable "Screenshots" and "Web Vitals"
  </Step>

  <Step title="Record">
    Click record, interact with shaders, stop recording
  </Step>

  <Step title="Analyze">
    Look for long frames (>16ms for 60fps) and GPU activity
  </Step>
</Steps>

### Memory Profiling

<Steps>
  <Step title="Open Memory tab in DevTools">
    Take heap snapshots before and after shader operations
  </Step>

  <Step title="Look for retained memory">
    Check if memory grows after disposing shaders
  </Step>

  <Step title="Find leaks">
    Compare snapshots to identify undisposed resources
  </Step>
</Steps>

## Performance Checklist

<AccordionGroup>
  <Accordion title="Essential Optimizations">
    * [ ] Set `speed={0}` for static shaders
    * [ ] Use `minPixelRatio={1}` on mobile
    * [ ] Limit colors to 3-5 for mobile
    * [ ] Dispose shaders properly (vanilla only)
    * [ ] Optimize images to 2048×2048 or smaller
  </Accordion>

  <Accordion title="Advanced Optimizations">
    * [ ] Reduce `maxPixelCount` on mobile
    * [ ] Pause shaders when out of viewport
    * [ ] Lazy load shader components
    * [ ] Use stable color references
    * [ ] Minimize grain parameters
    * [ ] Limit simultaneous shaders to 3-5
  </Accordion>

  <Accordion title="Testing">
    * [ ] Test on actual mobile devices
    * [ ] Check FPS with DevTools
    * [ ] Monitor memory usage
    * [ ] Test on low-end devices
    * [ ] Verify battery impact
  </Accordion>
</AccordionGroup>

## Performance Targets

### Desktop

* 60 FPS with 2-3 shaders
* 2x pixel ratio
* Full color complexity
* Smooth animations

### Mobile

* 30-60 FPS with 1-2 shaders
* 1-1.5x pixel ratio
* Reduced color count
* Consider static shaders

### Low-End Devices

* 30 FPS with 1 shader
* 1x pixel ratio
* Minimal colors (2-3)
* Static shaders preferred

## Troubleshooting

### Low FPS

<Steps>
  <Step title="Reduce pixel ratio">
    Try `minPixelRatio={1}`
  </Step>

  <Step title="Lower max pixel count">
    Try `maxPixelCount={1920 * 1080 * 2}`
  </Step>

  <Step title="Simplify shader">
    Reduce colors, disable grain effects
  </Step>

  <Step title="Check other shaders">
    Limit to 1-2 simultaneous shaders
  </Step>
</Steps>

### High Memory Usage

* Dispose unused shaders
* Optimize image sizes
* Reduce number of simultaneous shaders
* Check for memory leaks with DevTools

### Stuttering

* Preload images
* Use stable prop references
* Avoid creating new objects in render
* Check browser's Performance tab for long frames

## Next Steps

<CardGroup cols={2}>
  <Card title="Sizing & Fit" icon="arrows-maximize" href="/guides/sizing-and-fit">
    Optimize shader sizing for performance
  </Card>

  <Card title="Customization" icon="sliders" href="/guides/customization">
    Balance visual quality with performance
  </Card>

  <Card title="React Usage" icon="react" href="/guides/react-usage">
    React-specific performance patterns
  </Card>

  <Card title="Vanilla Usage" icon="js" href="/guides/vanilla-usage">
    Vanilla JS performance control
  </Card>
</CardGroup>
