# obs_display_t Display Rendering Setup: A Complete Guide for OBS Studio Developers

> Master obs_display_t display rendering setup in OBS Studio. This guide covers swap chains, draw callbacks, and color spaces for developers using the C API.

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

---

**The `obs_display_t` structure is OBS Studio's core abstraction for rendering video frames onto native windows, managing swap chains, draw callbacks, and color spaces through a C API that underlies the Qt frontend.**

The `obs_display_t` API in **obsproject/obs-studio** provides the foundation for all video rendering in OBS, from preview panels to multiview projectors. Living in the **libobs** core library, this abstraction handles graphics context initialization, swap chain management, and callback-driven rendering loops. Whether you're building a custom frontend or extending the existing Qt interface, understanding this display rendering setup is essential for integrating OBS's graphics pipeline into native windowing systems.

## Core Architecture of obs_display_t

At its heart, `obs_display_t` encapsulates a graphics swap chain and a list of draw callbacks. In [`libobs/obs-display.c`](https://github.com/obsproject/obs-studio/blob/main/libobs/obs-display.c), the implementation manages the complete lifecycle from creation to destruction, handling platform-specific window integration through the `gs_init_data` structure.

The structure maintains a dynamic array of `draw_callbacks` protected by a mutex for thread-safe additions and removals. When OBS's main video loop triggers rendering, it calls `render_display()` (defined in [`libobs/obs-display.c`](https://github.com/obsproject/obs-studio/blob/main/libobs/obs-display.c) around line 237), which loads the swap chain, applies pending resizes, clears the target with the configured background color, and executes all registered callbacks.

## The obs_display_t Lifecycle

Creating a functional display requires following a specific sequence of API calls that bridge the graphics initialization data to the rendering loop.

### Creating the Display

The entry point is `obs_display_create()`, declared in [`libobs/obs.h`](https://github.com/obsproject/obs-studio/blob/main/libobs/obs.h) (lines 995-1014) and implemented in [`libobs/obs-display.c`](https://github.com/obsproject/obs-studio/blob/main/libobs/obs-display.c) (lines 63-82). This function allocates the `struct obs_display`, enters the graphics context, initializes mutexes, creates the swap chain via `obs_display_init()`, and registers the display in the global `obs->data.first_display` list.

```cpp
#include <obs.h>

// Configure the graphics initialization data
gs_init_data init = {};
init.cx = 640;
init.cy = 480;
init.format = GS_BGRA;
init.zsformat = GS_ZS_NONE;

// Create display with dark grey background (RGBA: 0xFF101010)
obs_display_t *disp = obs_display_create(&init, 0xFF101010);

```

### Managing Draw Callbacks

Rendering logic is injected through callbacks rather than direct drawing. The function `obs_display_add_draw_callback()` (lines 144-168 in [`libobs/obs-display.c`](https://github.com/obsproject/obs-studio/blob/main/libobs/obs-display.c)) appends function pointers to the display's internal array. These callbacks receive the current render dimensions (`cx`, `cy`) and a user-defined parameter.

```cpp
void MyRenderCallback(void *param, uint32_t cx, uint32_t cy)
{
    gs_clear(GS_COLOR_BLACK);
    // Custom rendering logic here
}

// Register the callback
obs_display_add_draw_callback(disp, MyRenderCallback, nullptr);

```

### Handling Resizing and Color Space

Resizing follows a deferred pattern. When you call `obs_display_resize()` (lines 119-129), it stores the new dimensions but does not immediately recreate the swap chain. The actual resize occurs inside `render_display_begin()` when the display is next drawn, preventing mid-frame resource reallocation.

For multi-monitor setups or HDR workflows, `obs_display_update_color_space()` allows dynamic adjustment of the display's color output without recreating the entire display object.

### Cleanup and Destruction

When the display is no longer needed, `obs_display_destroy()` removes it from the global display list and frees all associated resources, including the swap chain and callback array.

## Qt Frontend Integration with OBSQTDisplay

The OBS Studio frontend wraps the C API in the `OBSQTDisplay` widget (found in [`frontend/widgets/OBSQTDisplay.cpp`](https://github.com/obsproject/obs-studio/blob/main/frontend/widgets/OBSQTDisplay.cpp)), providing a Qt-friendly interface that handles platform-specific window mapping.

### Window Setup and Conversion

During construction (lines 73-82), `OBSQTDisplay` sets critical Qt attributes (`WA_PaintOnScreen`, `WA_NativeWindow`) to ensure OBS can draw directly onto the widget. The helper function `QTToGSWindow()` (lines 39-70) maps `QWindow` handles to the `gs_window` struct required by `obs_display_create()`, with platform-specific branches for Win32, macOS, X11, and Wayland.

### Display Creation and Event Management

The `CreateDisplay()` method builds the `gs_init_data` structure and instantiates the underlying `obs_display_t`. The widget connects to Qt's `resizeEvent` to trigger `obs_display_resize()` and monitors move events to call `obs_display_update_color_space()`. Background color changes propagate through `SetDisplayBackgroundColor()`, which converts `QColor` to the integer format expected by `obs_display_set_background_color()`.

Notably, the `paintEvent()` (lines 58-62) does not perform actual rendering—it merely ensures the display exists. The real rendering is driven by OBS's separate video thread calling the registered draw callbacks.

## Practical Implementation Examples

### Minimal C Implementation

For headless or custom UI scenarios, you can manage the display entirely through the C API:

```cpp
#include <obs.h>

void SimpleRender(void *param, uint32_t cx, uint32_t cy)
{
    gs_clear(GS_COLOR_BLACK);
    // Add custom geometry or shader code here
}

void SetupDisplay()
{
    gs_init_data init = {};
    init.cx = 1280;
    init.cy = 720;
    init.format = GS_BGRA;
    init.zsformat = GS_ZS_NONE;

    obs_display_t *disp = obs_display_create(&init, 0xFF101010);
    obs_display_add_draw_callback(disp, SimpleRender, nullptr);
    
    // Resize later when needed
    obs_display_resize(disp, 1920, 1080);
}

```

### Qt Widget Integration

When building Qt-based frontends, inherit from or compose `OBSQTDisplay`:

```cpp
#include "OBSQTDisplay.hpp"

class PreviewWidget : public QWidget {
    OBSQTDisplay display;
public:
    explicit PreviewWidget(QWidget *parent = nullptr) 
        : QWidget(parent), display(this) 
    {
        connect(&display, &OBSQTDisplay::DisplayCreated, 
                this, &PreviewWidget::OnDisplayReady);
        
        // Add custom draw callback
        obs_display_add_draw_callback(
            display.GetDisplay(), 
            [](void*, uint32_t cx, uint32_t cy) {
                gs_clear(GS_COLOR_BLACK);
            }, 
            nullptr
        );
        
        display.SetDisplayBackgroundColor(QColor(20, 20, 20));
    }
};

```

### Projector Implementation

The `OBSProjector` class demonstrates advanced usage with conditional rendering:

```cpp
void OBSProjector::SetupDisplay()
{
    obs_display_t *d = GetDisplay();
    
    obs_display_add_draw_callback(
        d,
        isMultiview ? OBSRenderMultiview : OBSRender,
        this
    );
    
    obs_display_set_background_color(d, 0x000000);
    obs_display_set_enabled(d, true);
}

```

## Summary

- **`obs_display_t`** serves as the primary interface between OBS's graphics engine and native windowing systems, living in [`libobs/obs-display.c`](https://github.com/obsproject/obs-studio/blob/main/libobs/obs-display.c).
- **Creation** requires populating `gs_init_data` and calling `obs_display_create()`, which registers the display in a global list and initializes the swap chain.
- **Rendering** occurs through registered callbacks executed by `render_display()` in the video thread, not the UI thread.
- **Resizing** is deferred until the next render cycle to avoid resource contention, implemented in `render_display_begin()`.
- **Qt integration** uses `OBSQTDisplay` to bridge `QWidget` events to the C API, handling platform-specific window conversion via `QTToGSWindow()`.
- **Cleanup** requires explicit `obs_display_destroy()` calls to prevent memory leaks and remove the display from the global render list.

## Frequently Asked Questions

### How does obs_display_t handle window resizing?

Resizing uses a deferred update pattern. When you call `obs_display_resize()` (implemented in [`libobs/obs-display.c`](https://github.com/obsproject/obs-studio/blob/main/libobs/obs-display.c), lines 119-129), the function stores the new dimensions in the display structure but does not immediately recreate the swap chain. The actual resize and resource reallocation occur inside `render_display_begin()` when the display is next rendered, ensuring thread safety and preventing graphical artifacts during the resize operation.

### What is the purpose of draw callbacks in the obs_display_t architecture?

Draw callbacks decouple the rendering logic from the display management. Rather than subclassing or modifying the display structure, you register function pointers via `obs_display_add_draw_callback()`. These callbacks execute in sequence during `render_display()` (lines 237-260 of [`libobs/obs-display.c`](https://github.com/obsproject/obs-studio/blob/main/libobs/obs-display.c)), receiving the current canvas dimensions and a user parameter. This design allows multiple components—such as previews, scopes, and multiview layouts—to share a single display while maintaining independent rendering logic.

### Can obs_display_t be used without the Qt frontend?

Yes. While `OBSQTDisplay` provides convenient Qt integration, the underlying `obs_display_t` API is fully functional from plain C or other UI frameworks. You must manually construct the `gs_init_data` structure with platform-specific window handles (Win32 HWND, macOS WindowRef, or X11 Window) and manage resize events by calling `obs_display_resize()` when the window dimensions change. The core API in [`libobs/obs.h`](https://github.com/obsproject/obs-studio/blob/main/libobs/obs.h) has no Qt dependencies.

### When should obs_display_update_color_space() be called?

Call `obs_display_update_color_space()` whenever the display moves between monitors with different color profiles or when the window transitions between SDR and HDR contexts. In the Qt frontend, `OBSQTDisplay` handles this automatically by connecting to move events and display change notifications (lines 106-110 in [`frontend/widgets/OBSQTDisplay.cpp`](https://github.com/obsproject/obs-studio/blob/main/frontend/widgets/OBSQTDisplay.cpp)). For custom implementations, invoke this function after detecting display configuration changes to ensure correct color output.