Managing Multiple Canvas Outputs with obs_canvas_t in OBS Studio

The obs_canvas_t API in OBS Studio enables developers to create independent rendering targets with dedicated video mixes, allowing simultaneous output to multiple canvases from a single instance.

Managing multiple canvas outputs with obs_canvas_t provides a powerful mechanism for plugins and modules in the obsproject/obs-studio repository to generate secondary video streams, preview feeds, or off-screen compositing targets. Each canvas maintains its own video mix, resolution, and frame rate, operating independently from the main program output while integrating seamlessly with the existing source and scene graph.

Understanding the OBS Canvas Architecture

The canvas subsystem centers around the obs_canvas_t structure, a reference-counted object defined in libobs/obs.h and implemented in libobs/obs-canvas.c. Unlike the single global canvas used for standard broadcasting, this API allows instantiation of multiple concurrent rendering contexts.

Each canvas encapsulates:

  • A dedicated video_t structure for independent format configuration
  • A private video mix for compositing sources
  • Channel assignments (0-based indices) for source binding
  • Reference counting for thread-safe lifecycle management

The architecture distinguishes between public canvases (visible in the UI, renameable) and private canvases (ephemeral, UI-invisible), controlled through separate constructor functions.

Creating and Configuring Canvas Outputs

Public vs Private Canvases

To create a canvas that integrates with the OBS UI and persists across sessions, use obs_canvas_create:

struct obs_video_info vinfo = {
    .width = 1920,
    .height = 1080,
    .fps_num = 60,
    .fps_den = 1,
    .format = VIDEO_FORMAT_NV12
};

obs_canvas_t *canvas = obs_canvas_create(
    "SecondaryOutput",
    &vinfo,
    OBS_CANVAS_ACTIVATE | OBS_CANVAS_MIX_AUDIO
);

For hidden, temporary canvases suitable for plugins or background processing, obs_canvas_create_private in libobs/obs-canvas.c (lines 200-204) allocates a canvas that bypasses the UI registry:

obs_canvas_t *private_canvas = obs_canvas_create_private(
    "BackgroundCanvas", &vinfo, OBS_CANVAS_ACTIVATE
);

Setting Canvas Properties

Canvas configuration occurs at creation time through the obs_video_info structure. Unlike the main output, each canvas can maintain distinct resolutions and frame rates, enabling scenarios like generating a 640x360 preview while broadcasting 1920x1080 main content.

Attaching Sources and Scenes to Canvases

Channel Assignment with obs_canvas_set_channel

Sources attach to canvases through channel binding, implemented in libobs/obs-canvas.c (lines 429-475). The function obs_canvas_set_channel binds an obs_source_t to a specific channel index:

obs_source_t *source = obs_get_source_by_name("Webcam");
if (source) {
    obs_canvas_set_channel(canvas, 0, source);  // Bind to channel 0
    obs_source_release(source);  // Release our reference; canvas holds its own
}

Channel assignment automatically manages source activation and deactivation lifecycle events. When the canvas becomes active, bound sources initialize; when deactivated, they release resources.

Creating Dedicated Canvas Scenes

For complex compositions, obs_canvas_scene_create (lines 82-86 in libobs/obs-canvas.c) creates a scene source specifically bound to the canvas:

obs_scene_t *scene = obs_canvas_scene_create(canvas, "CanvasScene");
if (scene) {
    // Add sources to the scene as normal
    obs_sceneitem_t *item = obs_scene_add(scene, some_source);
}

This approach leverages the existing scene graph while redirecting output to the secondary canvas rather than the main program mix.

Rendering and Output Management

On-Demand Texture Rendering

Canvases render through obs_render_canvas_texture, defined in libobs/obs.c (lines 32-36). This function draws the canvas's video mix to the current graphics context:

void render_preview(void)
{
    // Called within a valid graphics context (e.g., filter draw callback)
    obs_render_canvas_texture(preview_canvas);
    
    // Access the underlying texture if needed for custom processing
    video_t *video = obs_canvas_get_video(preview_canvas);
    // video->texrender contains the rendered output
}

The color-only variant allows rendering without alpha blending for specific encoding scenarios.

Canvas Enumeration and Cleanup

For lifecycle management, obs_enum_canvases (line 1954 in libobs/obs.c) iterates all existing canvases:

static bool canvas_callback(void *param, obs_canvas_t *canvas)
{
    const char *name = obs_canvas_get_name(canvas);
    blog(LOG_INFO, "Active canvas: %s", name);
    return true;  // Continue enumeration
}

void list_all_canvases(void)
{
    obs_enum_canvases(canvas_callback, NULL);
}

Reference counting ensures thread safety. Obtain strong references via obs_canvas_get_ref and release with obs_canvas_release. Weak references (obs_weak_canvas_t) allow safe cross-thread usage without preventing destruction.

To remove a canvas, call obs_canvas_remove, which marks the canvas as removed and releases internal references. Subsequent API calls on that canvas will fail safely.

Practical Implementation Examples

The following complete example demonstrates creating a private preview canvas, attaching a scene, and rendering it:

#include <obs-module.h>
#include <obs-scene.h>

static obs_canvas_t *preview_canvas = NULL;
static obs_scene_t *preview_scene = NULL;

bool init_preview_canvas(void)
{
    // Configure video parameters for 640x360 preview
    struct obs_video_info vinfo = {
        .width = 640,
        .height = 360,
        .fps_num = 30,
        .fps_den = 1,
        .format = VIDEO_FORMAT_NV12
    };
    
    // Create private canvas (invisible to UI)
    preview_canvas = obs_canvas_create_private("PreviewCanvas", &vinfo, 
                                               OBS_CANVAS_ACTIVATE);
    if (!preview_canvas) {
        blog(LOG_ERROR, "Failed to create preview canvas");
        return false;
    }
    
    // Create scene bound to this canvas
    preview_scene = obs_canvas_scene_create(preview_canvas, "PreviewScene");
    if (!preview_scene) {
        blog(LOG_ERROR, "Failed to create scene");
        obs_canvas_remove(preview_canvas);
        return false;
    }
    
    // Add main program view as source
    obs_source_t *program = obs_get_source_by_name("Program");
    if (program) {
        obs_sceneitem_t *item = obs_scene_add(preview_scene, program);
        obs_source_release(program);
        
        // Scale to fit canvas
        struct vec2 scale = {0.5f, 0.5f};
        obs_sceneitem_set_scale(item, &scale);
    }
    
    return true;
}

void render_preview_frame(void)
{
    if (preview_canvas) {
        obs_render_canvas_texture(preview_canvas);
    }
}

void cleanup_preview(void)
{
    if (preview_scene) {
        obs_scene_release(preview_scene);
        preview_scene = NULL;
    }
    if (preview_canvas) {
        obs_canvas_remove(preview_canvas);
        obs_canvas_release(preview_canvas);
        preview_canvas = NULL;
    }
}

Summary

Managing multiple canvas outputs with obs_canvas_t enables sophisticated multi-streaming and compositing workflows in OBS Studio:

  • Independent rendering contexts allow simultaneous outputs with different resolutions, frame rates, and formats from a single OBS instance.
  • Public and private canvas types support both UI-integrated workflows and background processing tasks.
  • Channel-based source binding via obs_canvas_set_channel integrates existing sources and scenes into secondary canvases without duplication.
  • Reference-counted lifecycle management ensures thread-safe creation, enumeration, and destruction of canvas objects.
  • On-demand rendering through obs_render_canvas_texture provides flexible output capture for encoders, filters, or external applications.

Frequently Asked Questions

What is the difference between obs_canvas_create and obs_canvas_create_private?

obs_canvas_create generates a public canvas registered in the UI canvas list, allowing users to rename and manage it through the OBS interface, while obs_canvas_create_private creates an ephemeral canvas invisible to the UI that cannot be renamed or persisted. Private canvases suit plugin background processing, while public canvases suit user-facing secondary outputs.

How does reference counting work for obs_canvas_t objects?

OBS implements strong and weak reference counting for thread-safe canvas management. Obtain a strong reference with obs_canvas_get_ref to ensure the canvas persists while in use, and release it with obs_canvas_release. Weak references (obs_weak_canvas_t) allow checking canvas validity across threads without preventing destruction, converting to strong references only when needed.

Can different canvases have different video formats and resolutions?

Yes, each canvas maintains an independent video_t structure configured during creation via the obs_video_info parameter. This allows one canvas to render at 1920x1080 NV12 for broadcast while another renders at 640x360 I420 for preview, with independent frame rates and color spaces, all processed within the same OBS instance.

What is the proper cleanup sequence for a canvas?

First, remove all sources from the canvas using obs_canvas_set_channel with NULL for each channel. Second, release any scene references created with obs_canvas_scene_create. Third, call obs_canvas_remove to mark the canvas as destroyed. Finally, release your strong reference with obs_canvas_release to allow complete deallocation. This sequence prevents dangling references and ensures proper cleanup of the underlying video mix.

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 →