# Optimizing Rendering Performance in Manim: Proven Strategies from the Source Code

> Boost Manim rendering speed! Discover proven strategies like reducing resolution, optimizing caching, and minimizing GPU overhead. Learn how to improve performance directly from the source code.

- Repository: [Grant Sanderson/manim](https://github.com/3b1b/manim)
- Tags: performance
- Published: 2026-02-28

---

**To optimize rendering performance in Manim, reduce resolution using CLI quality flags, leverage disk and memory caching for expensive LaTeX and shader operations, and minimize GPU overhead by reusing vertex buffers and disabling unused rendering features.**

Manim, the mathematical animation engine created by 3Blue1Brown and maintained at `3b1b/manim`, uses an OpenGL-based rendering pipeline via the `moderngl` wrapper. Optimizing rendering performance in Manim requires tuning three orthogonal layers: resolution and quality settings, resource caching strategies, and GPU-side data management. This guide examines the specific implementation details in the source code to provide actionable optimization strategies.

## Resolution and Quality Optimization

The fastest way to improve rendering speed is reducing the pixel count processed by the fragment shaders. Manim provides several CLI flags in [`manimlib/config.py`](https://github.com/3b1b/manim/blob/main/manimlib/config.py) (lines 79-61) that map to specific resolution tuples:

- `-l` or `--low_quality`: Renders at 854×480 (480p)
- `-m` or `--medium_quality`: Renders at 1280×720 (720p)
- `--hd`: Renders at 1920×1080 (1080p)
- `--uhd`: Renders at 3840×2160 (4K)

For custom resolutions, use `-r WIDTHxHEIGHT` which is parsed by `get_resolution_from_args` (lines 50-61). For rapid iteration during development, always use `-l` or `-m` before final export.

## Caching Strategies

Manim implements aggressive caching to avoid recomputing expensive resources. The caching layer spans both disk-based persistence for LaTeX assets and in-memory memoization for shader programs.

### LaTeX Compilation Caching

LaTeX string compilation is one of the most expensive operations in Manim. The `@cache_on_disk` decorator in [`manimlib/utils/tex_file_writing.py`](https://github.com/3b1b/manim/blob/main/manimlib/utils/tex_file_writing.py) (lines 7-30) automatically stores compiled PDF and PNG outputs in `~/.cache/manim/tex`. The cache key is derived from the LaTeX source string, ensuring identical equations reuse existing assets.

To clear stale LaTeX caches, use the `--clear-cache` flag handled in [`manimlib/__main__.py`](https://github.com/3b1b/manim/blob/main/manimlib/__main__.py) (line 8), which invokes the cache clearing logic defined in [`manimlib/utils/cache.py`](https://github.com/3b1b/manim/blob/main/manimlib/utils/cache.py).

### Shader Program Caching

Shader compilation and file I/O are memoized using `functools.lru_cache` in [`manimlib/utils/shaders.py`](https://github.com/3b1b/manim/blob/main/manimlib/utils/shaders.py) (lines 5-24). This prevents re-reading `.glsl` source files and recompiling identical shader programs across multiple mobjects. The cache is particularly effective when scenes reuse common shaders like `quadratic_bezier_fill` or `textured_surface`.

## GPU Data Management and Buffer Reuse

Manim's `ShaderWrapper` class in [`manimlib/shader_wrapper.py`](https://github.com/3b1b/manim/blob/main/manimlib/shader_wrapper.py) implements several strategies to minimize GPU overhead through buffer reuse and efficient data transfer.

### Vertex Buffer Object (VBO) Optimization

The `ShaderWrapper.read_in` method (lines 53-65) batches vertex data into a single VBO and reuses Vertex Array Objects (VAOs). Critically, the implementation only recreates the VBO when the total vertex count changes:

```python
if len(vert_data) != len(self.vert_data):
    self.vbo = self.ctx.buffer(vert_data.tobytes())
else:
    self.vbo.write(vert_data.tobytes())

```

This pattern (lines 66-85) avoids expensive GPU memory allocation during animations where vertex counts remain constant. For custom mobjects, ensure you reuse `ShaderWrapper` instances rather than recreating them each frame.

### Fill Canvas Sharing

For vector graphics fills, `VShaderWrapper` in [`manimlib/shader_wrapper.py`](https://github.com/3b1b/manim/blob/main/manimlib/shader_wrapper.py) uses a cached static method `get_fill_canvas` (lines 400-405) decorated with `@lru_cache`. This shares a single off-screen framebuffer object (FBO) across all fill operations, eliminating per-object framebuffer allocation overhead.

## Disabling Unused Features

Manim's shader system allows disabling expensive rendering features when they are not required. In [`manimlib/shader_wrapper.py`](https://github.com/3b1b/manim/blob/main/manimlib/shader_wrapper.py) (lines 41-46 and 48-50), depth testing and clipping planes are only enabled when specific uniforms are present:

- Pass `depth_test=False` to `ShaderWrapper` to skip `glEnable(GL_DEPTH_TEST)` for 2D scenes
- Avoid defining `clip_plane` uniforms unless necessary to prevent clipping calculations

Additionally, use `-s` or `--skip_animations` (handled in [`manimlib/config.py`](https://github.com/3b1b/manim/blob/main/manimlib/config.py) lines 70-74) to write only the final frame, bypassing the per-frame render loop entirely for static diagrams.

## Profiling with Built-in Logging

Set `manim_config.log_level` to `DEBUG` via the CLI flag `--log-level DEBUG` to output timing information for scene loading, shader compilation, and frame rendering. This data helps identify whether bottlenecks occur in the Python layer (caching, vertex generation) or the GPU layer (fragment shader complexity, texture binding).

## Summary

- **Reduce resolution** using `-l`, `-m`, or `-r` flags to minimize fragment shader workload
- **Leverage caching** via `--clear-cache` management; rely on `@cache_on_disk` for LaTeX and `lru_cache` for shaders
- **Reuse GPU buffers** by maintaining constant vertex counts in `ShaderWrapper` and reusing VBOs/VAOs
- **Share resources** through `VShaderWrapper`'s cached fill canvas to avoid per-object FBO creation
- **Disable unused features** like depth testing and animations when rendering static 2D content
- **Profile first** using `--log-level DEBUG` to identify actual bottlenecks before optimizing

## Frequently Asked Questions

### How do I quickly preview a Manim scene without waiting for full quality rendering?

Use the `-l` (low quality) or `-m` (medium quality) CLI flags when invoking Manim. These trigger `get_resolution_from_args` in [`manimlib/config.py`](https://github.com/3b1b/manim/blob/main/manimlib/config.py) to set resolutions of 854×480 or 1280×720 respectively, dramatically reducing the fragment shader workload and render time.

### Why does Manim recompile LaTeX strings every time I run my scene?

It shouldn't. Manim uses the `@cache_on_disk` decorator in [`manimlib/utils/tex_file_writing.py`](https://github.com/3b1b/manim/blob/main/manimlib/utils/tex_file_writing.py) to store compiled LaTeX outputs in `~/.cache/manim/tex`. If you see recompilation, clear the cache with `--clear-cache` (handled in [`manimlib/__main__.py`](https://github.com/3b1b/manim/blob/main/manimlib/__main__.py)) to remove stale entries or verify that your LaTeX strings are identical byte-for-byte.

### What is the most efficient way to handle many similar objects in Manim?

Reuse vertex buffer objects (VBOs) through `ShaderWrapper`. The `read_in` method in [`manimlib/shader_wrapper.py`](https://github.com/3b1b/manim/blob/main/manimlib/shader_wrapper.py) only recreates the VBO when the vertex count changes; otherwise it writes directly to the existing buffer. For fills, `VShaderWrapper` automatically shares a single off-screen canvas via the cached `get_fill_canvas` method, avoiding per-object framebuffer allocation.

### Does disabling depth testing improve rendering speed?

Yes, for 2D scenes. By passing `depth_test=False` to `ShaderWrapper`, you prevent the OpenGL `glEnable(GL_DEPTH_TEST)` call (see lines 41-46 in [`manimlib/shader_wrapper.py`](https://github.com/3b1b/manim/blob/main/manimlib/shader_wrapper.py)). This eliminates depth buffer comparisons and writes, which is particularly beneficial when rendering complex 2D vector graphics where z-ordering is irrelevant.