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

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, 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 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 (lines 995-1014) and implemented in 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.

#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) appends function pointers to the display's internal array. These callbacks receive the current render dimensions (cx, cy) and a user-defined parameter.

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), 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:

#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:

#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:

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.
  • 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, 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), 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 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). For custom implementations, invoke this function after detecting display configuration changes to ensure correct color output.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →