# Sensor performance and troubleshooting

> How sensor count, resolution and passes change render time, GPU memory and cloud price; the limits; and the rig errors you can hit, with their fixes.

Source: https://simforge.ai/docs/sensor-troubleshooting

## Render time

A 3D render draws every camera at every one of its frames, so its time grows with the number of cameras x pixels x frames, plus a fixed cost to load the map. Lidar and radar add ray casts and a review video each. Measured on an RTX 5080 with the three [example rigs](/docs/sensor-examples) on easterbrook-discovery-school, a 10 second scenario:

| Rig | Preset | Frames written | Render time | Of which drawing | Output on disk |
|---|---|---|---|---|---|
| Front camera, 1280x720 | showcase | 200 x 4 passes | 15.3 s | 9.1 s | 0.9 GB |
| Seven cameras, 1280x720 | training | 1400 x 4 passes | 36.0 s | 23.8 s | 6.3 GB |
| Camera, lidar and radar | showcase | 200 x 4 passes, 200 scans, 200 radar frames | 25.6 s | 14.8 s | 1.8 GB |

Peak GPU memory stayed under 2.5 GiB for all three, with full textures. Most of the rest of the time is encoding the videos (10.9 s for the seven cameras) and loading the map and actors (about 3 s). A single `simforge preview frame` took 9 s.

To render faster: use `--preset training`, lower `width` and `height`, render a part of the scenario with the rig's `clip`, ask only for the passes you need (`--passes rgb`), and skip videos you do not need (`--video off`). Check a mount with `simforge preview frame` before a full render.

## GPU memory

GPU memory depends far more on the map and the texture tier than on the cameras. On san-ramon-phase-1 with full textures, one 1280x720 camera peaks at 11.3 GiB and six 1920x1080 cameras at 11.9 GiB; with 512 px textures (`--textures bc7-512`), 3.8 and 4.5 GiB. The full table per map is in [Preview and render](/docs/render#gpu-memory-and-texture-tiers). A render that does not fit fails with `native_gpu_out_of_memory`: use `--textures bc7-512`, fewer or smaller cameras, or `--cloud`.

## Cloud price

A cloud render's [price](/docs/cloud#prices) does not depend on the rig: more cameras, lidar or radar do not change it, and 2D renders are free. `--dry-run` shows the price before anything runs, and `--max-cost` caps it ([Run in the cloud](/docs/cloud)). A cloud render returns each sensor's video, `render.json` and `results.json`; render locally for the per-frame images, depth and point clouds.

## Limits

| What | Limit |
|---|---|
| Sources in a rig | 1 to 64 |
| Camera size | 64 to 8192 pixels in the file; the renderer draws at most 4096x4096 |
| Camera frame rate | above 0, up to 240 fps |
| Pinhole field of view | above 0, up to 180 degrees |
| Lidar | 1 to 256 channels, range up to 1000 m, up to 240 revolutions per second, symmetric vertical field |
| Radar | fields up to 180 degrees, range up to 1000 m, at least one ray per frame |
| Rotation | yaw and roll -pi to pi, pitch -pi/2 to pi/2, in radians |

## Rig errors and fixes

`simforge rig validate` prints one JSON error and exits 2. These are its real messages:

| Message (`render_rig_invalid`) | Fix |
|---|---|
| `sources[0].attributes: unknown field fx, expected one of width, height, fps, horizontalFovDeg, nearM, farM, cameraModel` | The rig has no fx/fy/cx/cy: give `horizontalFovDeg` = 2 x atan(width / (2 x fx)) in degrees |
| `sources[0]: a camera declares exactly one of horizontalFovDeg and cameraModel` | Keep the field of view or the lens, not both and not neither |
| `sources[0]: camera size 10000x720 is outside 64..8192` | Lower `width` or `height` (the renderer draws at most 4096) |
| `sources[0]: fps 0 is outside (0, 240]` | Give a frame rate above 0 |
| `sources[0]: nearM must be positive and farM greater than nearM` | Use `nearM` 0.05 and `farM` 1000 unless you need others |
| `sources[6]: transform.rotation is outside yaw/roll [-pi, pi], pitch [-pi/2, pi/2]` | A rear camera is yaw 3.14159, not 3.1416; a downward camera is pitch -1.5708 |
| `duplicate source outputName "cam-front"` | Give every source its own `outputName` |
| `sources[0]: outputName "front cam" must match ^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$` | Use letters, digits, `-` and `_`, no spaces |
| `sources[0].modality "thermal" (rgb\|depth\|semantic\|instance\|lidar\|radar)` | Thermal is not supported; use `rgb`, `lidar` or `radar` |
| `rig schema "simforge.render-rig/v2" (expected simforge.render-rig/v1)` | Set `schema` to `simforge.render-rig/v1` |

A render then checks what the renderer supports; its errors are listed in the [rig file reference](/docs/sensor-reference#what-the-renderer-adds-on-top). The most common:

| Error | Fix |
|---|---|
| `native_modality_unsupported` | A `depth`, `semantic` or `instance` source: make it `rgb` and choose passes with `--passes` |
| `native_lidar_asymmetric_fov_unsupported` | Make the lidar's field symmetric: `upperFovDeg = -lowerFovDeg` |
| `render_schedule_invalid` (a radar) | Add `video` to the rig; its `fps` is the radar's sample rate |
| `native_camera_size_unsupported` | Keep cameras at 4096x4096 or smaller |
| `render_ego_missing` | The scenario has no ego: `simforge scenario apply s.json set-ego --role-id <actor>` |
| `preview_camera_unknown` | `--camera` takes a camera's `outputName`; the error lists the rig's cameras |
| `out_not_empty` | `--out` must be a new or empty folder; a failed render can leave `<out>.input` behind: remove it too |

## FAQ

**My images are mirrored left to right, or a side camera shows the wrong side.** The frame is x forward, y up, z left, and yaw is positive to the left. A calibration in a right-handed frame needs [converting](/docs/sensors#converting-a-calibration-you-already-have). `simforge rig show` names each sensor's `facing`.

**Can I give fx, fy, cx and cy?** Not yet. A pinhole camera is square-pixelled with the principal point at the centre: set `horizontalFovDeg` = 2 x atan(width / (2 x fx)). For a lens with distortion, fit an f-theta polynomial.

**Which distortion models are supported?** The f-theta polynomial only. Brown-Conrady (OpenCV), Kannala-Brandt and equidistant fisheye are not supported yet.

**Do I need one rig per scenario?** No. A rig names no scenario: pass the same file to every render. Every source is mounted on the scenario's ego.

**Do I need one rig per vehicle?** Positions are absolute metres, so a car rig on a truck keeps the car's mount heights. Write one rig per vehicle class, or start from `simforge rig init --preset <preset> --class <class>`.

**Can Studio use my rig file?** Not yet: Studio offers the built-in rigs and the rig a scenario last rendered with. Render your rig with the CLI, locally or with `--cloud`.

**Why does my cloud render have no PNGs or point clouds?** A cloud 3D render returns the videos, `render.json` and `results.json`. Render locally for the per-frame files.

**Every radar detection is a few centimetres away.** The radar is inside the vehicle's body and sees only the body. Move it just outside: the example's front radar sits 0.2 m ahead of the box's front edge.

**My lidar wrote more scans than it made revolutions.** It writes one full scan per rendered frame, at the camera rate, not one per revolution. See [lidar files](/docs/sensor-outputs#lidar-files).

**Are IMU or GNSS supported?** Not as sensors. The ego's exact pose at every frame is in `observed-frames.jsonl`.
