# Scene Composition and obs_scene_item_t Transform Handling in OBS Studio

> Learn how OBS Studio handles scene composition and obs_scene_item_t transform data by converting user parameters into GPU-ready matrices for efficient rendering.

- Repository: [OBS Project/obs-studio](https://github.com/obsproject/obs-studio)
- Tags: internals
- Published: 2026-03-03

---

**OBS Studio composes scenes using a doubly-linked list of `obs_scene_item` structures that encapsulate position, scale, rotation, and cropping data, converting user-friendly parameters into GPU-compatible 4×4 matrices during each render tick.**

OBS Studio's scene composition engine manages visual stacking through the `obs_scene_item_t` type (defined in [`libobs/obs-scene.h`](https://github.com/obsproject/obs-studio/blob/main/libobs/obs-scene.h)), which wraps individual sources and handles all geometric transformations required to render them correctly on canvas. Understanding how this system converts high-level properties into low-level graphics matrices is essential for plugin developers and contributors working with the `obsproject/obs-studio` codebase.

## Core Data Structures

The scene system relies on four primary structures defined in [`libobs/obs-scene.h`](https://github.com/obsproject/obs-studio/blob/main/libobs/obs-scene.h) and [`libobs/graphics/matrix4.h`](https://github.com/obsproject/obs-studio/blob/main/libobs/graphics/matrix4.h):

- **`obs_scene`** (lines 30–78): Holds the scene source, group flags, custom canvas size, and a pointer to the first scene item. It includes recursive mutexes (`video_mutex`, `audio_mutex`) for thread-safe access to the item list.

- **`obs_scene_item`** (lines 30–99): Represents a single element within a scene. Key fields include reference counters (`ref`, `active_refs`), visibility flags (`visible`, `user_visible`), transform data (`pos`, `scale`, `rot`, `alignment`), cropping (`crop`), bounds configuration, blend settings, and `prev`/`next` pointers for the linked list.

- **`matrix4`** ([`matrix4.h`](https://github.com/obsproject/obs-studio/blob/main/matrix4.h)): A 4×4 transformation matrix used for both drawing and selection box calculations.

- **`obs_scene_item_crop`** (lines 52–53): Stores pixel-based crop values applied before rendering.

## The Transform Pipeline

When `scene_video_render` executes in [`obs-scene.c`](https://github.com/obsproject/obs-studio/blob/main/obs-scene.c) (lines 59–85), it iterates the linked list and processes each visible item through a seven-stage pipeline:

### Update Invalidation and Canvas Scaling

The system first checks whether `update_item_transform` needs execution. This occurs when `update_transforms_and_prune_sources` (lines 33–38) detects changes to the item's `update_transform` flag, source dimensions, or parent scene size. For groups, `item_canvas_scale` maintains independent scaling relative to the parent canvas, while `pos_to_absolute` and `size_to_absolute` convert relative coordinates to absolute pixels when `absolute_coordinates` is disabled.

### Bounds Processing and Alignment

The `calculate_bounds_data` function (lines 46–98) enforces maximum dimensions and scaling constraints, updating `item->bounds_crop` when boundaries require cropping. Subsequently, `add_alignment` (lines 25–36) adjusts the origin point based on alignment flags (top, left, right, bottom, center combinations).

### Matrix Construction

The actual transformation matrix assembly occurs in `update_item_transform` (lines 56–62):

```c
matrix4_identity(&item->draw_transform);
matrix4_scale3f(&item->draw_transform, &item->draw_transform,
                scale.x, scale.y, 1.0f);
matrix4_translate3f(&item->draw_transform,
                &item->draw_transform, -origin.x, -origin.y, 0.0f);
matrix4_rotate_aa4f(&item->draw_transform,
                &item->draw_transform, 0.0f, 0.0f, 1.0f,
                RAD(item->rot));
matrix4_translate3f(&item->draw_transform,
                &item->draw_transform, position.x, position.y, 0.0f);

```

This sequence first applies scaling, then rotates around the centered origin, and finally translates to the absolute position. The same logic populates `item->box_transform`, which UI selection boxes use for hit detection.

### Rendering and Synchronization

After emitting the `"item_transform"` signal (lines 98–100) for plugin notifications, `render_item` (lines 63–84) pushes the matrix onto the graphics stack. It determines whether texture rendering is required via `item_texture_enabled` (lines 36–40), draws the source or its transition, then restores the previous graphics state. All operations execute under the scene-level mutex (`video_lock`/`video_unlock`) to prevent list corruption during concurrent audio, UI, or rendering thread access.

## Visibility, Audio Actions, and Blending

Visibility control involves two flags: `item->visible` (final computed state) and `item->user_visible` (user toggle). When visibility changes, OBS queues an audio action (`struct item_action`) that `scene_audio_render` (lines 13–24) processes to mute or unmute the source's audio buffers per-sample.

Blending modes map through `obs_blend_mode_params` (lines 62–86), translating high-level enums like `OBS_BLEND_NORMAL` or `OBS_BLEND_ADDITIVE` into GPU blend functions applied in `render_item_texture` (lines 69–74).

## Group Scenes and Nested Composition

Groups are scenes marked with `is_group` set to true. When rendered inside a parent scene, group items maintain their own transform list while inheriting the parent matrix. The `resize_group` function recomputes child transforms when the parent scene dimensions change, ensuring the group scales as a single unit rather than distorting individual elements.

## Practical Implementation Examples

### Creating Scenes and Adding Sources

```c
/* Create a new scene source */
struct obs_source *scene_src = obs_source_create("scene", "MyScene", NULL, NULL);

/* Retrieve the internal obs_scene handle */
struct obs_scene *scene = obs_scene_from_source(scene_src);

/* Create and configure a source */
struct obs_source *image_src = obs_source_create(
        "image_source", "Logo", NULL, NULL);
obs_source_set_file(image_src, "/path/to/logo.png");

/* Add to scene - wraps obs_scene_add_internal (obs-scene.c L16-20) */
struct obs_sceneitem *item = obs_scene_add(scene, image_src);

```

### Configuring Position, Scale, and Rotation

```c
struct vec2 pos = {1920.0f, 1080.0f};     /* absolute pixels */
struct vec2 scale = {0.5f, 0.5f};         /* 50% size */
float rot = 45.0f;                       /* degrees */

obs_sceneitem_set_pos(item, &pos);
obs_sceneitem_set_scale(item, &scale);
obs_sceneitem_set_rot(item, rot);
obs_sceneitem_set_alignment(item, OBS_ALIGN_CENTER);

```

Each setter marks the `update_transform` flag, triggering the pipeline on the next render frame.

### Working with Group Scenes

```c
/* Create a group (nested scene) */
struct obs_source *group_src = obs_source_create("scene", "Group", NULL, NULL);
struct obs_scene *group = obs_scene_from_source(group_src);

/* Add content to the group */
struct obs_sceneitem *sub_item = obs_scene_add(group, image_src);
obs_sceneitem_set_pos(sub_item, &(struct vec2){0, 0});

/* Add group to main scene */
struct obs_sceneitem *group_item = obs_scene_add(scene, group_src);
obs_sceneitem_set_scale(group_item, &(struct vec2){2.0f, 2.0f});

```

### Accessing Transform Matrices

```c
const struct matrix4 *m = obs_sceneitem_get_draw_transform(item);
printf("Scale X component: %f\n", m->x.x);

```

This matrix reflects the current state after `update_item_transform` processing.

## Summary

- **Scene items** are nodes in a thread-protected doubly-linked list, each wrapping a source with geometric and visibility state.
- **Transform updates** occur lazily when properties change, calculating `draw_transform` and `box_transform` matrices via scale, rotation, translation, and alignment operations.
- **Bounds and cropping** are applied before matrix construction, with separate handling for groups that maintain internal coordinate spaces.
- **Thread safety** relies on recursive mutexes (`video_mutex`, `audio_mutex`) with strict ordering rules to prevent deadlocks during concurrent render and UI operations.
- **Blending and visibility** affect both visual output and audio mixing through queued actions processed during the audio render path.

## Frequently Asked Questions

### What is the difference between draw_transform and box_transform in OBS Studio?

The **`draw_transform`** matrix (in `obs_scene_item`) is used for actual GPU rendering of the source, incorporating scale, rotation, and position. The **`box_transform`** matrix serves the UI selection box, providing hit-detection boundaries that account for the same geometric properties but without cropping or certain visual effects. Both are updated simultaneously in `update_item_transform` within [`libobs/obs-scene.c`](https://github.com/obsproject/obs-studio/blob/main/libobs/obs-scene.c).

### How does OBS Studio ensure thread safety during scene composition?

OBS uses two recursive mutexes per scene: `video_mutex` and `audio_mutex`. The render thread acquires `video_mutex` before iterating items in `scene_video_render`, while audio processing locks `audio_mutex` in `scene_audio_render`. A critical ordering rule prevents deadlocks: code must never lock the graphics mutex while holding a scene mutex, as documented in comments at lines 38–47 of [`obs-scene.c`](https://github.com/obsproject/obs-studio/blob/main/obs-scene.c).

### What triggers a transform update for an obs_scene_item?

The `update_transform` boolean flag triggers recalculation. Setters like `obs_sceneitem_set_pos`, `obs_sceneitem_set_scale`, or `obs_sceneitem_set_rot` mark this flag true. During the next render tick, `update_transforms_and_prune_sources` detects the flag (or canvas size changes) and invokes `update_item_transform` to rebuild the matrices before drawing.

### How do group scenes handle transform inheritance?

Groups are specialized scenes (`is_group = true`) added as single items to parent scenes. When rendered, the parent scene's `draw_transform` becomes the base matrix for the group's internal items. The `resize_group` function automatically adjusts child positions and scales when the parent canvas changes, ensuring the group maintains its relative layout while scaling as a unified object.