# Choose a render target
Attach a software pixel buffer, an OpenGL framebuffer, or a WebGPU surface as the dotLottie render target, and control fit, alignment, and viewport.

A player draws nothing until you give it somewhere to draw. The runtime supports three render targets, each enabled by a different build feature, and you attach exactly one per player.

| Target   | Function                    | Feature   | Use when                                                             |
| -------- | --------------------------- | --------- | -------------------------------------------------------------------- |
| Software | `dotlottie_set_sw_target()` | `tvg-cpu` | You want pixels in memory: image export, custom compositing, no GPU  |
| OpenGL   | `dotlottie_set_gl_target()` | `tvg-gl`  | You already have a GL or GLES context and want to render into it     |
| WebGPU   | `dotlottie_set_wg_target()` | `tvg-wg`  | You have a WebGPU device — typically Metal-backed on Apple platforms |

Attaching a target for a feature that wasn't compiled in returns `FeatureNotEnabled`.

## Software rendering

The software target is the simplest and the most portable: you allocate a buffer, and the player rasterizes into it.

```c
uint32_t *buffer = malloc(width * height * sizeof(uint32_t));

dotlottie_set_sw_target(player, buffer, width, height, ABGR8888);
```

The buffer holds exactly `width × height` 32-bit pixels with contiguous rows and no stride. You own it, so it must outlive the player — see [Memory and safety rules](/en/runtimes/distributions/native/v0.x/memory-and-safety).

### Pixel formats

The final argument selects how the eight-bit channels are packed into each 32-bit word, listed from most to least significant byte.

| Constant    | Layout       | Alpha             |
| ----------- | ------------ | ----------------- |
| `ABGR8888`  | `0xAABBGGRR` | Premultiplied     |
| `ARGB8888`  | `0xAARRGGBB` | Premultiplied     |
| `ABGR8888S` | `0xAABBGGRR` | Not premultiplied |
| `ARGB8888S` | `0xAARRGGBB` | Not premultiplied |

Pick the one that matches the surface you're copying into: `ARGB8888` suits Windows GDI and Cairo, `ABGR8888` suits most OpenGL texture uploads and SDL's `RGBA32` on little-endian machines.

The `S` variants give you straight (un-premultiplied) alpha. Choose them when your compositor expects straight alpha, or when you plan to save a transparent PNG — otherwise you would have to divide the color channels by alpha yourself. If you render onto an opaque background the distinction doesn't arise, because alpha is 255 everywhere and the two encodings coincide.

### Resizing

To change size, attach a new buffer and only then release the old one:

```c
uint32_t *resized = malloc(new_width * new_height * sizeof(uint32_t));

dotlottie_set_sw_target(player, resized, new_width, new_height, ABGR8888);

free(buffer);      /* safe: the player no longer references it */
buffer = resized;
```

## OpenGL rendering

The GL target renders into a framebuffer object in a context you already own. Because the runtime does not create or manage the context, you must make it current before every call that draws.

```c
dotlottie_set_gl_target(player, display, surface, context, fbo_id, width, height);
```

| Parameter | Meaning                                                                   |
| --------- | ------------------------------------------------------------------------- |
| `display` | Platform display handle — `EGLDisplay`, `HDC`. `NULL` when not applicable |
| `surface` | Platform surface handle — `EGLSurface`. `NULL` when not applicable        |
| `context` | The GL context to render with                                             |
| `id`      | The framebuffer object to draw into. `0` targets the default framebuffer  |
| `width`   | Target width in pixels                                                    |
| `height`  | Target height in pixels                                                   |

Pass `NULL` for `display` and `surface` on platforms that have no such handles — macOS CGL, for example, where the context alone is enough. On Android with EGL, all three are required:

```c
dotlottie_set_gl_target(
  player,
  eglGetCurrentDisplay(),
  eglGetCurrentSurface(EGL_DRAW),
  eglGetCurrentContext(),
  0, width, height
);
```

<Callout type="warning" title="Make the context current on the rendering thread">
  The runtime issues GL calls on whichever thread you call `dotlottie_render()` or `dotlottie_tick()` from, and it never
  binds the context for you. If the context isn't current, calls fail silently or corrupt state. Keep all rendering for
  one player on a single thread with its context bound.
</Callout>

Android builds enable `tvg-gl` alongside the software renderer by default, so both targets are available without a custom feature list.

## WebGPU rendering

The WebGPU target accepts handles from a `wgpu-native` device and renders either into a surface or into a texture.

```c
dotlottie_set_wg_target(player, device, instance, target, width, height, Surface);
```

`target_type` selects what `target` points at:

| Constant  | Numeric | `target` is                    |
| --------- | ------- | ------------------------------ |
| `Surface` | 0       | A `WGPUSurface` to present to  |
| `Texture` | 1       | A `WGPUTexture` to render into |

Use `Texture` when you want to composite the animation into a larger scene rather than present it directly.

<Callout type="warning" title="The WebGPU backend is not available on every platform">
  `tvg-wg` links prebuilt `wgpu-native` binaries, which exist only for macOS, iOS and its simulator, Linux, and Android.
  There is **no artifact for any Windows target**, nor for macCatalyst, tvOS, visionOS, or watchOS. Use `tvg-cpu` or
  `tvg-gl` there. On Linux, a WebGPU build also needs `libvulkan` at link time.
</Callout>

## Fit and alignment

The animation has its own intrinsic size, which rarely matches your target exactly. `dotlottie_set_layout()` decides how one maps onto the other.

```c
dotlottieLayout layout;
layout.fit = Contain;
layout.align[0] = 0.5f;   /* horizontal: 0.0 left, 0.5 centre, 1.0 right */
layout.align[1] = 0.5f;   /* vertical:   0.0 top,  0.5 centre, 1.0 bottom */

dotlottie_set_layout(player, layout);
```

| Fit mode    | Behavior                                                                |
| ----------- | ----------------------------------------------------------------------- |
| `Contain`   | Scale to fit entirely inside the target, preserving aspect ratio        |
| `Cover`     | Scale to fill the target, preserving aspect ratio and cropping overflow |
| `Fill`      | Stretch to the target exactly, ignoring aspect ratio                    |
| `FitWidth`  | Match the target width; height follows the aspect ratio                 |
| `FitHeight` | Match the target height; width follows the aspect ratio                 |
| `None`      | Draw at the animation's intrinsic size, unscaled                        |

The default is `Contain` with alignment centred at `[0.5, 0.5]`.

Read the animation's intrinsic size when you need to size a target around it:

```c
float w = 0.0f, h = 0.0f;
dotlottie_get_animation_size(player, &w, &h);
```

## Drawing into part of the target

`dotlottie_set_viewport()` restricts drawing to a rectangle, which is useful when the animation shares a surface with other content:

```c
dotlottie_set_viewport(player, 20, 20, 200, 200);
```

Coordinates are in pixels, with the origin at the target's top-left corner. Layout fit and alignment then apply within that rectangle rather than the whole target.

## Learn more

- [Control playback](/en/runtimes/distributions/native/v0.x/playback-control) — driving frames over time
- [Build for your target architecture](/en/runtimes/distributions/native/v0.x/building) — enabling renderer features
- [C API reference](/en/runtimes/distributions/native/v0.x/api-reference) — full signatures
