diff --git a/libobs/CMakeLists.txt b/libobs/CMakeLists.txt index c07ae6dab..6593cb472 100644 --- a/libobs/CMakeLists.txt +++ b/libobs/CMakeLists.txt @@ -38,6 +38,7 @@ target_sources( obs-av1.h obs-avc.c obs-avc.h + obs-canvas.c obs-config.h obs-data.c obs-data.h diff --git a/libobs/obs-audio.c b/libobs/obs-audio.c index 29d805666..0b9569d6a 100644 --- a/libobs/obs-audio.c +++ b/libobs/obs-audio.c @@ -512,7 +512,7 @@ bool audio_callback(void *param, uint64_t start_ts_in, uint64_t end_ts_in, uint6 obs_source_enum_active_tree(source, push_audio_tree, audio); push_audio_tree(NULL, source, audio); - if (obs->video.mixes.array[j] == obs->video.main_mix) + if (obs->video.mixes.array[j]->mix_audio) da_push_back(audio->root_nodes, &source); } pthread_mutex_unlock(&view->channels_mutex); diff --git a/libobs/obs-canvas.c b/libobs/obs-canvas.c new file mode 100644 index 000000000..fc43bea9e --- /dev/null +++ b/libobs/obs-canvas.c @@ -0,0 +1,555 @@ +/****************************************************************************** + Copyright (C) 2025 by Dennis Sädtler + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include "obs.h" +#include "obs-internal.h" +#include "obs-scene.h" + +/* The primary canvas has static name/uuid. */ +static const char *MAIN_CANVAS_NAME = "Main"; +static const char *MAIN_CANVAS_UUID = "6c69626f-6273-4c00-9d88-c5136d61696e"; +/* Internal flag to mark a canvas as removed */ +static const uint32_t REMOVED = 1u << 31; + +/*** Signals ***/ + +static const char *canvas_signals[] = { + "void destroy(ptr canvas)", + "void remove(ptr canvas)", + "void video_reset(ptr canvas)", + + "void source_add(ptr canvas, ptr source)", + "void source_remove(ptr canvas, ptr source)", + + "void rename(ptr source, string new_name, string prev_name)", + + "void channel_change(ptr canvas, int channel, in out ptr source, ptr prev_source)", + + NULL, +}; + +static inline void canvas_dosignal(obs_canvas_t *canvas, const char *signal_obs, const char *signal_source) +{ + struct calldata data; + uint8_t stack[128]; + + calldata_init_fixed(&data, stack, sizeof(stack)); + calldata_set_ptr(&data, "canvas", canvas); + if (signal_obs) + signal_handler_signal(obs->signals, signal_obs, &data); + if (signal_source) + signal_handler_signal(canvas->context.signals, signal_source, &data); +} + +static inline void canvas_dosignal_source(const char *signal, obs_canvas_t *canvas, obs_source_t *source) +{ + struct calldata data; + uint8_t stack[128]; + + calldata_init_fixed(&data, stack, sizeof(stack)); + calldata_set_ptr(&data, "canvas", canvas); + calldata_set_ptr(&data, "source", source); + + signal_handler_signal(source->context.signals, signal, &data); +} + +/*** Reference Counting ***/ + +void obs_canvas_release(obs_canvas_t *canvas) +{ + if (!obs && canvas) { + blog(LOG_WARNING, "Tried to release a canvas when the OBS core is shut down!"); + return; + } + + if (!canvas) + return; + + obs_weak_canvas_t *control = (obs_weak_canvas_t *)canvas->context.control; + if (obs_ref_release(&control->ref)) { + obs_canvas_destroy(canvas); + obs_weak_canvas_release(control); + } +} + +void obs_weak_canvas_addref(obs_weak_canvas_t *weak) +{ + if (!weak) + return; + + obs_weak_ref_addref(&weak->ref); +} + +void obs_weak_canvas_release(obs_weak_canvas_t *weak) +{ + if (!weak) + return; + + if (obs_weak_ref_release(&weak->ref)) + bfree(weak); +} + +obs_canvas_t *obs_canvas_get_ref(obs_canvas_t *canvas) +{ + if (!canvas) + return NULL; + + return obs_weak_canvas_get_canvas((obs_weak_canvas_t *)canvas->context.control); +} + +obs_weak_canvas_t *obs_canvas_get_weak_canvas(obs_canvas_t *canvas) +{ + if (!canvas) + return NULL; + + obs_weak_canvas_t *weak = (obs_weak_canvas_t *)canvas->context.control; + obs_weak_canvas_addref(weak); + return weak; +} + +obs_canvas_t *obs_weak_canvas_get_canvas(obs_weak_canvas_t *weak) +{ + if (!weak) + return NULL; + + if (obs_weak_ref_get_ref(&weak->ref)) + return weak->canvas; + + return NULL; +} + +/*** Creation / Destruction ***/ + +static obs_canvas_t *obs_canvas_create_internal(const char *name, const char *uuid, struct obs_video_info *ovi, + uint32_t flags, bool private) +{ + struct obs_canvas *canvas = bzalloc(sizeof(struct obs_canvas)); + canvas->flags = flags; + + if (!obs_context_data_init(&canvas->context, OBS_OBJ_TYPE_CANVAS, NULL, name, uuid, NULL, private)) + return NULL; + + if (!signal_handler_add_array(canvas->context.signals, canvas_signals)) { + obs_context_data_free(&canvas->context); + bfree(canvas); + return NULL; + } + + if (pthread_mutex_init_recursive(&canvas->sources_mutex) != 0) { + obs_context_data_free(&canvas->context); + bfree(canvas); + return NULL; + } + + obs_view_init(&canvas->view, flags & ACTIVATE ? MAIN_VIEW : AUX_VIEW); + obs_context_init_control(&canvas->context, canvas, (obs_destroy_cb)obs_canvas_destroy); + + /* A canvas can be created without a mix. */ + if (ovi) { + canvas->ovi = *ovi; + canvas->mix = obs_create_video_mix(ovi); + if (canvas->mix) { + canvas->mix->view = &canvas->view; + canvas->mix->mix_audio = (flags & MIX_AUDIO) != 0; + + pthread_mutex_lock(&obs->video.mixes_mutex); + da_push_back(obs->video.mixes, &canvas->mix); + pthread_mutex_unlock(&obs->video.mixes_mutex); + } + } + + obs_context_data_insert_uuid(&canvas->context, &obs->data.canvases_mutex, &obs->data.canvases); + if (!private) { + obs_context_data_insert_name(&canvas->context, &obs->data.canvases_mutex, &obs->data.named_canvases); + canvas_dosignal(canvas, "canvas_create", NULL); + } + + blog(LOG_DEBUG, "%scanvas '%s' (%s) created", private ? "private " : "", canvas->context.name, + canvas->context.uuid); + + return canvas; +} + +obs_canvas_t *obs_create_main_canvas(void) +{ + const uint32_t main_flags = MAIN | PROGRAM; + return obs_canvas_create_internal(MAIN_CANVAS_NAME, MAIN_CANVAS_UUID, NULL, main_flags, false); +} + +obs_canvas_t *obs_canvas_create(const char *name, struct obs_video_info *ovi, uint32_t flags) +{ + flags &= ~MAIN; /* Prevent user from creating a MAIN canvas. */ + return obs_canvas_create_internal(name, NULL, ovi, flags, false); +} + +obs_canvas_t *obs_canvas_create_private(const char *name, struct obs_video_info *ovi, uint32_t flags) +{ + flags &= ~MAIN; /* Prevent user from creating a MAIN canvas. */ + return obs_canvas_create_internal(name, NULL, ovi, flags, true); +} + +void obs_canvas_destroy(obs_canvas_t *canvas) +{ + canvas_dosignal(canvas, "canvas_destroy", "destroy"); + + obs_canvas_clear_mix(canvas); + + obs_source_t *source = canvas->sources; + while (source) { + /* Canvases can hold strong refs to scene sources, release them here. */ + if (canvas->flags & SCENE_REF && obs_source_is_scene(source)) + obs_source_release(source); + + source = source->context.hh.next; + } + + obs_context_data_remove_uuid(&canvas->context, &obs->data.canvases_mutex, &obs->data.canvases); + if (!canvas->context.private) { + obs_context_data_remove_name(&canvas->context, &obs->data.canvases_mutex, &obs->data.named_canvases); + } + + blog(LOG_DEBUG, "%scanvas '%s' (%s) destroyed", canvas->context.private ? "private " : "", canvas->context.name, + canvas->context.uuid); + + pthread_mutex_destroy(&canvas->sources_mutex); + obs_context_data_free(&canvas->context); + obs_view_free(&canvas->view); + bfree(canvas); +} + +/*** Saving / Loading ***/ + +obs_data_t *obs_save_canvas(obs_canvas_t *canvas) +{ + if (canvas->flags & (EPHEMERAL | REMOVED)) + return NULL; + + obs_data_t *canvas_data = obs_data_create(); + + obs_data_set_string(canvas_data, "name", canvas->context.name); + obs_data_set_string(canvas_data, "uuid", canvas->context.uuid); + obs_data_set_bool(canvas_data, "private", canvas->context.private); + obs_data_set_int(canvas_data, "flags", canvas->flags); + + return canvas_data; +} + +obs_canvas_t *obs_load_canvas(obs_data_t *data) +{ + const char *name = obs_data_get_string(data, "name"); + const char *uuid = obs_data_get_string(data, "uuid"); + const bool private = obs_data_get_bool(data, "private"); + uint32_t flags = (uint32_t)obs_data_get_int(data, "flags"); + + flags &= ~MAIN; /* Prevent user from creating a MAIN canvas. */ + return obs_canvas_create_internal(name, uuid, NULL, flags, private); +} + +/*** Internal API ***/ + +/* Free canvas mix (if any) */ +void obs_canvas_clear_mix(obs_canvas_t *canvas) +{ + if (!canvas->mix) + return; + + pthread_mutex_lock(&obs->video.mixes_mutex); + for (size_t i = 0; i < obs->video.mixes.num; i++) { + struct obs_core_video_mix *mix = obs->video.mixes.array[i]; + if (mix == canvas->mix) { + da_erase(obs->video.mixes, i); + obs_free_video_mix(mix); + break; + } + } + pthread_mutex_unlock(&obs->video.mixes_mutex); + + canvas->mix = NULL; +} + +/* Clear mixes attached to canvases */ +void obs_free_canvas_mixes(void) +{ + pthread_mutex_lock(&obs->data.canvases_mutex); + struct obs_context_data *ctx, *tmp; + HASH_ITER (hh, (struct obs_context_data *)obs->data.canvases, ctx, tmp) { + obs_canvas_t *canvas = (obs_canvas_t *)ctx; + obs_canvas_clear_mix(canvas); + } + pthread_mutex_unlock(&obs->data.canvases_mutex); +} + +bool obs_canvas_reset_video_internal(obs_canvas_t *canvas, struct obs_video_info *ovi) +{ + obs_canvas_clear_mix(canvas); + + if (ovi) + canvas->ovi = *ovi; + + canvas->mix = obs_create_video_mix(&canvas->ovi); + if (canvas->mix) { + canvas->mix->view = &canvas->view; + canvas->mix->mix_audio = (canvas->flags & MIX_AUDIO) != 0; + + pthread_mutex_lock(&obs->video.mixes_mutex); + da_push_back(obs->video.mixes, &canvas->mix); + pthread_mutex_unlock(&obs->video.mixes_mutex); + } + + canvas_dosignal(canvas, "canvas_video_reset", "video_reset"); + + return !!canvas->mix; +} + +void obs_canvas_insert_source(obs_canvas_t *canvas, obs_source_t *source) +{ + if (canvas->flags & SCENE_REF && obs_source_is_scene(source)) + obs_source_get_ref(source); + if (source->canvas) + obs_canvas_remove_source(source); + + source->canvas = obs_canvas_get_weak_canvas(canvas); + obs_context_data_insert_name(&source->context, &canvas->sources_mutex, &canvas->sources); + canvas_dosignal_source("source_add", canvas, source); +} + +static bool remove_groups_items_cb(obs_scene_t *scene, obs_sceneitem_t *item, void *param) +{ + UNUSED_PARAMETER(scene); + + obs_source_t *source = param; + if (item->source == source) + obs_sceneitem_remove(item); + + return true; +} + +static bool remove_groups_enum_cb(void *param, obs_source_t *scene_source) +{ + obs_source_t *source = param; + obs_scene_t *scene = obs_scene_from_source(scene_source); + + obs_scene_enum_items(scene, remove_groups_items_cb, source); + return true; +} + +void obs_canvas_remove_source(obs_source_t *source) +{ + obs_canvas_t *canvas = obs_weak_canvas_get_canvas(source->canvas); + if (canvas) { + obs_weak_canvas_release(source->canvas); + obs_context_data_remove_name(&source->context, &canvas->sources_mutex, &canvas->sources); + + canvas_dosignal_source("source_remove", canvas, source); + if (canvas->flags & SCENE_REF && obs_source_is_scene(source)) + obs_source_release(source); + + /* If source is a group, also remove it from all other scenes in the old canvas */ + if (obs_source_is_group(source)) + obs_canvas_enum_scenes(canvas, remove_groups_enum_cb, source); + + obs_canvas_release(canvas); + } + source->canvas = NULL; +} + +/*** Public Canvas Object API ***/ + +bool obs_canvas_reset_video(obs_canvas_t *canvas, struct obs_video_info *ovi) +{ + if (canvas->flags & MAIN || obs_video_active()) + return false; + + return obs_canvas_reset_video_internal(canvas, ovi); +} + +video_t *obs_canvas_get_video(const obs_canvas_t *canvas) +{ + return canvas->mix ? canvas->mix->video : NULL; +} + +bool obs_canvas_get_video_info(const obs_canvas_t *canvas, struct obs_video_info *ovi) +{ + if (!obs->video.graphics || !canvas->mix) + return false; + + *ovi = canvas->ovi; + return true; +} + +void obs_canvas_set_channel(obs_canvas_t *canvas, uint32_t channel, obs_source_t *source) +{ + assert(channel < MAX_CHANNELS); + + if (channel >= MAX_CHANNELS) + return; + + struct obs_view *view = &canvas->view; + + pthread_mutex_lock(&view->channels_mutex); + + source = obs_source_get_ref(source); + + obs_source_t *prev_source = view->channels[channel]; + + if (source == prev_source) { + obs_source_release(source); + pthread_mutex_unlock(&view->channels_mutex); + return; + } + + struct calldata params = {0}; + calldata_set_ptr(¶ms, "canvas", canvas); + calldata_set_int(¶ms, "channel", channel); + calldata_set_ptr(¶ms, "prev_source", prev_source); + calldata_set_ptr(¶ms, "source", source); + + signal_handler_signal(canvas->context.signals, "channel_change", ¶ms); + if (canvas->flags & MAIN) + signal_handler_signal(obs->signals, "channel_change", ¶ms); + + /* For some reason the original implementation allows overriding the source from the callback, + * so just in case support that here as well. This isn't used anywhere in OBS itself. */ + calldata_get_ptr(¶ms, "source", &source); + view->channels[channel] = source; + + calldata_free(¶ms); + pthread_mutex_unlock(&view->channels_mutex); + + if (source) + obs_source_activate(source, view->type); + + if (prev_source) { + obs_source_deactivate(prev_source, view->type); + obs_source_release(prev_source); + } +} + +obs_source_t *obs_canvas_get_channel(obs_canvas_t *canvas, uint32_t channel) +{ + return obs_view_get_source(&canvas->view, channel); +} + +obs_scene_t *obs_canvas_scene_create(obs_canvas_t *canvas, const char *name) +{ + struct obs_source *source = obs_source_create_canvas(canvas, "scene", name, NULL, NULL); + return source->context.data; +} + +void obs_canvas_scene_remove(obs_scene_t *scene) +{ + obs_canvas_remove_source(scene->source); +} + +void obs_canvas_set_name(obs_canvas_t *canvas, const char *name) +{ + if (!name || !*name) + return; + if (canvas->flags & MAIN) /* Do not allow renaming main canvases. */ + return; + if (strcmp(name, canvas->context.name) == 0) + return; + + char *prev_name = bstrdup(canvas->context.name); + + if (canvas->context.private) + obs_context_data_setname(&canvas->context, name); + else + obs_context_data_setname_ht(&canvas->context, name, &obs->data.named_canvases); + + struct calldata data; + calldata_init(&data); + calldata_set_ptr(&data, "canvas", canvas); + calldata_set_string(&data, "new_name", canvas->context.name); + calldata_set_string(&data, "prev_name", prev_name); + signal_handler_signal(canvas->context.signals, "rename", &data); + + if (!canvas->context.private) + signal_handler_signal(obs->signals, "canvas_rename", &data); + + calldata_free(&data); + bfree(prev_name); +} + +const char *obs_canvas_get_name(const obs_canvas_t *canvas) +{ + return canvas->context.name; +} + +const char *obs_canvas_get_uuid(const obs_canvas_t *canvas) +{ + return canvas->context.uuid; +} + +uint32_t obs_canvas_get_flags(const obs_canvas_t *canvas) +{ + return canvas->flags; +} + +static bool enum_move_cb(obs_scene_t *scene, obs_sceneitem_t *item, void *param) +{ + UNUSED_PARAMETER(scene); + + obs_canvas_t *dst = param; + obs_source_t *source = item->source; + + if (obs_source_is_group(source)) { + obs_canvas_remove_source(source); + obs_canvas_insert_source(dst, source); + } + + return true; +} + +void obs_canvas_move_scene(obs_scene_t *scene, obs_canvas_t *dst) +{ + obs_source_t *source = scene->source; + obs_canvas_remove_source(source); + obs_canvas_insert_source(dst, source); + + /* Also move all groups within this scene */ + obs_scene_enum_items(scene, enum_move_cb, dst); +} + +void obs_canvas_remove(obs_canvas_t *canvas) +{ + /* Do not allow removing the main canvas, or canvases already marked as removed. */ + if (canvas->flags & (REMOVED | MAIN)) + return; + + obs_canvas_t *c = obs_canvas_get_ref(canvas); + if (c) { + c->flags |= REMOVED; + canvas_dosignal(c, "canvas_remove", "remove"); + obs_canvas_release(c); + } +} + +bool obs_canvas_removed(obs_canvas_t *canvas) +{ + return (canvas->flags & REMOVED) != 0; +} + +bool obs_canvas_has_video(obs_canvas_t *canvas) +{ + return canvas->mix != NULL; +} + +void obs_canvas_render(obs_canvas_t *canvas) +{ + obs_view_render(&canvas->view); +} diff --git a/libobs/obs-internal.h b/libobs/obs-internal.h index c1a3f2a37..49a864b5c 100644 --- a/libobs/obs-internal.h +++ b/libobs/obs-internal.h @@ -325,6 +325,8 @@ struct obs_core_video_mix { bool encoder_only_mix; long encoder_refs; + + bool mix_audio; }; extern struct obs_core_video_mix *obs_create_video_mix(struct obs_video_info *ovi); @@ -377,7 +379,6 @@ struct obs_core_video { pthread_mutex_t mixes_mutex; DARRAY(struct obs_core_video_mix *) mixes; - struct obs_core_video_mix *main_mix; }; extern void add_ready_encoder_group(obs_encoder_t *encoder); @@ -412,6 +413,9 @@ struct obs_core_data { struct obs_source *sources; /* Lookup by UUID (hh_uuid) */ struct obs_source *public_sources; /* Lookup by name (hh) */ + struct obs_canvas *canvases; /* Lookup by UUID (hh_uuid) */ + struct obs_canvas *named_canvases; /* Lookup by name (hh) */ + /* Linked lists */ struct obs_source *first_audio_source; struct obs_display *first_display; @@ -426,11 +430,13 @@ struct obs_core_data { pthread_mutex_t services_mutex; pthread_mutex_t audio_sources_mutex; pthread_mutex_t draw_callbacks_mutex; + pthread_mutex_t canvases_mutex; DARRAY(struct draw_callback) draw_callbacks; DARRAY(struct rendered_callback) rendered_callbacks; DARRAY(struct tick_callback) tick_callbacks; - struct obs_view main_view; + /* Main canvas, guaranteed to exist for the lifetime of the program */ + struct obs_canvas *main_canvas; long long unnamed_index; @@ -595,8 +601,8 @@ extern void obs_context_data_insert_name(struct obs_context_data *context, pthre extern void obs_context_data_insert_uuid(struct obs_context_data *context, pthread_mutex_t *mutex, void *first_uuid); extern void obs_context_data_remove(struct obs_context_data *context); -extern void obs_context_data_remove_name(struct obs_context_data *context, void *phead); -extern void obs_context_data_remove_uuid(struct obs_context_data *context, void *puuid_head); +extern void obs_context_data_remove_name(struct obs_context_data *context, pthread_mutex_t *mutex, void *phead); +extern void obs_context_data_remove_uuid(struct obs_context_data *context, pthread_mutex_t *mutex, void *puuid_head); extern void obs_context_wait(struct obs_context_data *context); @@ -644,6 +650,40 @@ static inline bool obs_weak_ref_expired(struct obs_weak_ref *ref) return owners < 0; } +/* ------------------------------------------------------------------------- */ +/* canvases */ + +struct obs_weak_canvas { + struct obs_weak_ref ref; + struct obs_canvas *canvas; +}; + +struct obs_canvas { + struct obs_context_data context; + + /* obs_canvas_flags */ + uint32_t flags; + /* Video info for this canvas, FPS ignored */ + struct obs_video_info ovi; + + /* Hash table containing scenes (and groups) associated with this canvas */ + struct obs_source *sources; + pthread_mutex_t sources_mutex; + + /* For now, canvas objects mainly act as a proxy for the existing view and video mix objects, + * though this may change in the future. */ + struct obs_view view; + struct obs_core_video_mix *mix; +}; + +extern obs_canvas_t *obs_create_main_canvas(void); +extern void obs_canvas_destroy(obs_canvas_t *canvas); +extern void obs_canvas_clear_mix(obs_canvas_t *canvas); +extern void obs_free_canvas_mixes(void); +extern bool obs_canvas_reset_video_internal(obs_canvas_t *canvas, struct obs_video_info *ovi); +extern void obs_canvas_insert_source(obs_canvas_t *canvas, obs_source_t *source); +extern void obs_canvas_remove_source(obs_source_t *source); + /* ------------------------------------------------------------------------- */ /* sources */ @@ -894,6 +934,9 @@ struct obs_source { /* private data */ obs_data_t *private_settings; + + /* canvas this source belongs to (only used for scenes) */ + obs_weak_canvas_t *canvas; }; extern struct obs_source_info *get_source_info(const char *id); @@ -912,9 +955,12 @@ struct audio_monitor *audio_monitor_create(obs_source_t *source); void audio_monitor_reset(struct audio_monitor *monitor); extern void audio_monitor_destroy(struct audio_monitor *monitor); -extern obs_source_t *obs_source_create_set_last_ver(const char *id, const char *name, const char *uuid, - obs_data_t *settings, obs_data_t *hotkey_data, +extern obs_source_t *obs_source_create_canvas(obs_canvas_t *canvas, const char *id, const char *name, + obs_data_t *settings, obs_data_t *hotkey_data); +extern obs_source_t *obs_source_create_set_last_ver(obs_canvas_t *canvas, const char *id, const char *name, + const char *uuid, obs_data_t *settings, obs_data_t *hotkey_data, uint32_t last_obs_ver, bool is_private); + extern void obs_source_destroy(struct obs_source *source); extern void obs_source_addref(obs_source_t *source); @@ -931,6 +977,21 @@ static inline void obs_source_dosignal(struct obs_source *source, const char *si signal_handler_signal(source->context.signals, signal_source, &data); } +static inline void obs_source_dosignal_canvas(struct obs_source *source, struct obs_canvas *canvas, + const char *signal_obs, const char *signal_source) +{ + struct calldata data; + uint8_t stack[128]; + + calldata_init_fixed(&data, stack, sizeof(stack)); + calldata_set_ptr(&data, "source", source); + calldata_set_ptr(&data, "canvas", canvas); + if (signal_obs && !source->context.private) + signal_handler_signal(obs->signals, signal_obs, &data); + if (signal_source) + signal_handler_signal(source->context.signals, signal_source, &data); +} + /* maximum timestamp variance in nanoseconds */ #define MAX_TS_VAR 2000000000ULL diff --git a/libobs/obs-scene.c b/libobs/obs-scene.c index 6c661e8ab..97a9743e6 100644 --- a/libobs/obs-scene.c +++ b/libobs/obs-scene.c @@ -338,13 +338,18 @@ void add_alignment(struct vec2 *v, uint32_t align, int cx, int cy) static uint32_t scene_getwidth(void *data); static uint32_t scene_getheight(void *data); +static uint32_t canvas_getwidth(obs_weak_canvas_t *weak); +static uint32_t canvas_getheight(obs_weak_canvas_t *weak); static inline void get_scene_dimensions(const obs_sceneitem_t *item, float *x, float *y) { obs_scene_t *parent = item->parent; - if (!parent || parent->is_group) { - *x = (float)obs->video.main_mix->ovi.base_width; - *y = (float)obs->video.main_mix->ovi.base_height; + if (!parent) { + *x = (float)obs->data.main_canvas->mix->ovi.base_width; + *y = (float)obs->data.main_canvas->mix->ovi.base_height; + } else if (parent->is_group) { + *x = (float)canvas_getwidth(parent->source->canvas); + *y = (float)canvas_getheight(parent->source->canvas); } else { *x = (float)scene_getwidth(parent); *y = (float)scene_getheight(parent); @@ -1455,13 +1460,41 @@ static void scene_save(void *data, obs_data_t *settings) obs_data_array_release(array); } +static uint32_t canvas_getwidth(obs_weak_canvas_t *weak) +{ + uint32_t width = 0; + + obs_canvas_t *canvas = obs_weak_canvas_get_canvas(weak); + if (canvas) { + width = canvas->ovi.base_width; + obs_canvas_release(canvas); + } + + return width; +} + +static uint32_t canvas_getheight(obs_weak_canvas_t *weak) +{ + uint32_t height = 0; + + obs_canvas_t *canvas = obs_weak_canvas_get_canvas(weak); + if (canvas) { + height = canvas->ovi.base_height; + obs_canvas_release(canvas); + } + + return height; +} + static uint32_t scene_getwidth(void *data) { obs_scene_t *scene = data; if (scene->custom_size) return scene->cx; - if (obs->video.main_mix) - return obs->video.main_mix->ovi.base_width; + if (scene->source->canvas) + return canvas_getwidth(scene->source->canvas); + if (obs->data.main_canvas->mix) + return obs->data.main_canvas->mix->ovi.base_width; return 0; } @@ -1470,8 +1503,10 @@ static uint32_t scene_getheight(void *data) obs_scene_t *scene = data; if (scene->custom_size) return scene->cy; - if (obs->video.main_mix) - return obs->video.main_mix->ovi.base_height; + if (scene->source->canvas) + return canvas_getheight(scene->source->canvas); + if (obs->data.main_canvas->mix) + return obs->data.main_canvas->mix->ovi.base_height; return 0; } @@ -1793,7 +1828,7 @@ const struct obs_source_info scene_info = { .id = "scene", .type = OBS_SOURCE_TYPE_SCENE, .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW | OBS_SOURCE_COMPOSITE | OBS_SOURCE_DO_NOT_DUPLICATE | - OBS_SOURCE_SRGB, + OBS_SOURCE_SRGB | OBS_SOURCE_REQUIRES_CANVAS, .get_name = scene_getname, .create = scene_create, .destroy = scene_destroy, @@ -1812,7 +1847,8 @@ const struct obs_source_info scene_info = { const struct obs_source_info group_info = { .id = "group", .type = OBS_SOURCE_TYPE_SCENE, - .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW | OBS_SOURCE_COMPOSITE | OBS_SOURCE_SRGB, + .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW | OBS_SOURCE_COMPOSITE | OBS_SOURCE_SRGB | + OBS_SOURCE_REQUIRES_CANVAS, .get_name = group_getname, .create = scene_create, .destroy = scene_destroy, @@ -1828,9 +1864,9 @@ const struct obs_source_info group_info = { .video_get_color_space = scene_video_get_color_space, }; -static inline obs_scene_t *create_id(const char *id, const char *name) +static inline obs_scene_t *create_id(obs_canvas_t *canvas, const char *id, const char *name) { - struct obs_source *source = obs_source_create(id, name, NULL, NULL); + struct obs_source *source = obs_source_create_canvas(canvas, id, name, NULL, NULL); return source->context.data; } @@ -1842,7 +1878,7 @@ static inline obs_scene_t *create_private_id(const char *id, const char *name) obs_scene_t *obs_scene_create(const char *name) { - return create_id("scene", name); + return create_id(obs->data.main_canvas, "scene", name); } obs_scene_t *obs_scene_create_private(const char *name) @@ -1987,8 +2023,10 @@ obs_scene_t *obs_scene_duplicate(obs_scene_t *scene, const char *name, enum obs_ /* --------------------------------- */ + obs_canvas_t *canvas = obs_weak_canvas_get_canvas(scene->source->canvas); new_scene = make_private ? create_private_id(scene->source->info.id, name) - : create_id(scene->source->info.id, name); + : create_id(canvas, scene->source->info.id, name); + obs_canvas_release(canvas); new_scene->is_group = scene->is_group; new_scene->custom_size = scene->custom_size; @@ -3630,9 +3668,11 @@ obs_sceneitem_t *obs_scene_insert_group(obs_scene_t *scene, const char *name, ob return NULL; } - obs_scene_t *sub_scene = create_id("group", name); - obs_sceneitem_t *last_item = items ? items[count - 1] : NULL; + obs_canvas_t *canvas = obs_weak_canvas_get_canvas(scene->source->canvas); + obs_scene_t *sub_scene = create_id(canvas, group_info.id, name); + obs_canvas_release(canvas); + obs_sceneitem_t *last_item = items ? items[count - 1] : NULL; obs_sceneitem_t *item = obs_scene_add_internal(scene, sub_scene->source, last_item, 0); if (!items || !count) { diff --git a/libobs/obs-source.c b/libobs/obs-source.c index d7719a29f..b162948ac 100644 --- a/libobs/obs-source.c +++ b/libobs/obs-source.c @@ -164,6 +164,11 @@ static inline bool is_composite_source(const struct obs_source *source) return source->info.output_flags & OBS_SOURCE_COMPOSITE; } +static inline bool requires_canvas(const struct obs_source *source) +{ + return source->info.output_flags & OBS_SOURCE_REQUIRES_CANVAS; +} + extern char *find_libobs_data_file(const char *file); /* internal initialization */ @@ -218,7 +223,7 @@ static bool obs_source_init(struct obs_source *source) return true; } -static void obs_source_init_finalize(struct obs_source *source) +static void obs_source_init_finalize(struct obs_source *source, obs_canvas_t *canvas) { if (is_audio_source(source)) { pthread_mutex_lock(&obs->data.audio_sources_mutex); @@ -233,7 +238,12 @@ static void obs_source_init_finalize(struct obs_source *source) } if (!source->context.private) { - obs_context_data_insert_name(&source->context, &obs->data.sources_mutex, &obs->data.public_sources); + if (requires_canvas(source)) { + obs_canvas_insert_source(canvas, source); + } else { + obs_context_data_insert_name(&source->context, &obs->data.sources_mutex, + &obs->data.public_sources); + } } obs_context_data_insert_uuid(&source->context, &obs->data.sources_mutex, &obs->data.sources); } @@ -320,7 +330,7 @@ static void obs_source_init_audio_hotkeys(struct obs_source *source) static obs_source_t *obs_source_create_internal(const char *id, const char *name, const char *uuid, obs_data_t *settings, obs_data_t *hotkey_data, bool private, - uint32_t last_obs_ver) + uint32_t last_obs_ver, obs_canvas_t *canvas) { struct obs_source *source = bzalloc(sizeof(struct obs_source)); @@ -362,6 +372,12 @@ static obs_source_t *obs_source_create_internal(const char *id, const char *name if (!obs_source_init(source)) goto fail; + /* Scenes need canvases, fall back to using default canvas if none provided here. */ + if (requires_canvas(source) && !canvas) { + blog(LOG_WARNING, "Attempted to add Scene without specifying a canvas! Using default canvas instead."); + canvas = obs->data.main_canvas; + } + if (!private) obs_source_init_audio_hotkeys(source); @@ -377,9 +393,12 @@ static obs_source_t *obs_source_create_internal(const char *id, const char *name source->flags = source->default_flags; source->enabled = true; - obs_source_init_finalize(source); + obs_source_init_finalize(source, canvas); if (!private) { - obs_source_dosignal(source, "source_create", NULL); + if (canvas) + obs_source_dosignal_canvas(source, canvas, "source_create_canvas", NULL); + if (!canvas || canvas == obs->data.main_canvas) + obs_source_dosignal(source, "source_create", NULL); } return source; @@ -392,18 +411,25 @@ fail: obs_source_t *obs_source_create(const char *id, const char *name, obs_data_t *settings, obs_data_t *hotkey_data) { - return obs_source_create_internal(id, name, NULL, settings, hotkey_data, false, LIBOBS_API_VER); + return obs_source_create_internal(id, name, NULL, settings, hotkey_data, false, LIBOBS_API_VER, NULL); } obs_source_t *obs_source_create_private(const char *id, const char *name, obs_data_t *settings) { - return obs_source_create_internal(id, name, NULL, settings, NULL, true, LIBOBS_API_VER); + return obs_source_create_internal(id, name, NULL, settings, NULL, true, LIBOBS_API_VER, NULL); } -obs_source_t *obs_source_create_set_last_ver(const char *id, const char *name, const char *uuid, obs_data_t *settings, - obs_data_t *hotkey_data, uint32_t last_obs_ver, bool is_private) +obs_source_t *obs_source_create_canvas(obs_canvas_t *canvas, const char *id, const char *name, obs_data_t *settings, + obs_data_t *hotkey_data) { - return obs_source_create_internal(id, name, uuid, settings, hotkey_data, is_private, last_obs_ver); + return obs_source_create_internal(id, name, NULL, settings, hotkey_data, false, LIBOBS_API_VER, canvas); +} + +obs_source_t *obs_source_create_set_last_ver(obs_canvas_t *canvas, const char *id, const char *name, const char *uuid, + obs_data_t *settings, obs_data_t *hotkey_data, uint32_t last_obs_ver, + bool is_private) +{ + return obs_source_create_internal(id, name, uuid, settings, hotkey_data, is_private, last_obs_ver, canvas); } static char *get_new_filter_name(obs_source_t *dst, const char *name) @@ -612,9 +638,15 @@ void obs_source_destroy(struct obs_source *source) while (source->filters.num) obs_source_filter_remove(source, source->filters.array[0]); - obs_context_data_remove_uuid(&source->context, &obs->data.sources); - if (!source->context.private) - obs_context_data_remove_name(&source->context, &obs->data.public_sources); + obs_context_data_remove_uuid(&source->context, &obs->data.sources_mutex, &obs->data.sources); + if (!source->context.private) { + if (requires_canvas(source)) { + obs_canvas_remove_source(source); + } else { + obs_context_data_remove_name(&source->context, &obs->data.sources_mutex, + &obs->data.public_sources); + } + } source_profiler_remove_source(source); @@ -794,6 +826,10 @@ void obs_source_remove(obs_source_t *source) if (s) { s->removed = true; obs_source_dosignal(s, "source_remove", "remove"); + /* Remove from canvas if there is one. */ + if (source->canvas) + obs_canvas_remove_source(s); + obs_source_release(s); } } @@ -5829,3 +5865,8 @@ uint64_t obs_source_get_last_async_ts(const obs_source_t *source) { return source->async_last_rendered_ts; } + +obs_canvas_t *obs_source_get_canvas(const obs_source_t *source) +{ + return obs_weak_canvas_get_canvas(source->canvas); +} diff --git a/libobs/obs-source.h b/libobs/obs-source.h index 4849d5394..9cfef2166 100644 --- a/libobs/obs-source.h +++ b/libobs/obs-source.h @@ -203,6 +203,11 @@ enum obs_media_state { */ #define OBS_SOURCE_CAP_DONT_SHOW_PROPERTIES (1 << 16) +/** + * Source requires a canvas to operate + */ +#define OBS_SOURCE_REQUIRES_CANVAS (1 << 17) + /** @} */ typedef void (*obs_source_enum_proc_t)(obs_source_t *parent, obs_source_t *child, void *param); diff --git a/libobs/obs-view.c b/libobs/obs-view.c index df91c107f..80c0e8843 100644 --- a/libobs/obs-view.c +++ b/libobs/obs-view.c @@ -150,21 +150,11 @@ static inline size_t find_mix_for_view(obs_view_t *view) return DARRAY_INVALID; } -static inline void set_main_mix() -{ - size_t idx = find_mix_for_view(&obs->data.main_view); - - struct obs_core_video_mix *mix = NULL; - if (idx != DARRAY_INVALID) - mix = obs->video.mixes.array[idx]; - obs->video.main_mix = mix; -} - video_t *obs_view_add(obs_view_t *view) { - if (!obs->video.main_mix) + if (!obs->data.main_canvas->mix) return NULL; - return obs_view_add2(view, &obs->video.main_mix->ovi); + return obs_view_add2(view, &obs->data.main_canvas->mix->ovi); } video_t *obs_view_add2(obs_view_t *view, struct obs_video_info *ovi) @@ -180,7 +170,6 @@ video_t *obs_view_add2(obs_view_t *view, struct obs_video_info *ovi) pthread_mutex_lock(&obs->video.mixes_mutex); da_push_back(obs->video.mixes, &mix); - set_main_mix(); pthread_mutex_unlock(&obs->video.mixes_mutex); return mix->video; @@ -196,7 +185,6 @@ void obs_view_remove(obs_view_t *view) if (obs->video.mixes.array[i]->view == view) obs->video.mixes.array[i]->view = NULL; } - set_main_mix(); pthread_mutex_unlock(&obs->video.mixes_mutex); } diff --git a/libobs/obs.c b/libobs/obs.c index 1146fc160..51fa5dc4c 100644 --- a/libobs/obs.c +++ b/libobs/obs.c @@ -615,8 +615,8 @@ static int obs_init_video_mix(struct obs_video_info *ovi, struct obs_core_video_ * so share FPS settings for aux views */ pthread_mutex_lock(&obs->video.mixes_mutex); size_t num = obs->video.mixes.num; - if (num && obs->video.main_mix) { - struct obs_video_info main_ovi = obs->video.main_mix->ovi; + if (num && obs->data.main_canvas->mix) { + struct obs_video_info main_ovi = obs->data.main_canvas->mix->ovi; video->ovi.fps_num = main_ovi.fps_num; video->ovi.fps_den = main_ovi.fps_den; } @@ -665,6 +665,27 @@ struct obs_core_video_mix *obs_create_video_mix(struct obs_video_info *ovi) return video; } +static bool restore_canvases(void) +{ + bool success = true; + + pthread_mutex_lock(&obs->data.canvases_mutex); + struct obs_context_data *ctx, *tmp; + HASH_ITER (hh, (struct obs_context_data *)obs->data.canvases, ctx, tmp) { + obs_canvas_t *canvas = (obs_canvas_t *)ctx; + if (canvas->flags & MAIN) + continue; + + if (!obs_canvas_reset_video_internal(canvas, NULL)) { + blog(LOG_ERROR, "Failed restoring video mix for canvas '%s'", canvas->context.name); + success = false; + } + } + pthread_mutex_unlock(&obs->data.canvases_mutex); + + return success; +} + static int obs_init_video(struct obs_video_info *ovi) { struct obs_core_video *video = &obs->video; @@ -678,7 +699,11 @@ static int obs_init_video(struct obs_video_info *ovi) if (pthread_mutex_init(&video->mixes_mutex, NULL) < 0) return OBS_VIDEO_FAIL; - if (!obs_view_add2(&obs->data.main_view, ovi)) + /* Reset main canvas mix first so it remains first in the rendering order. */ + if (!obs_canvas_reset_video_internal(obs->data.main_canvas, ovi)) + return OBS_VIDEO_FAIL; + /* Reset mixes for remaining canvases using their existing video info. */ + if (!restore_canvases()) return OBS_VIDEO_FAIL; int errorcode; @@ -939,12 +964,13 @@ static bool obs_init_data(void) goto fail; if (pthread_mutex_init_recursive(&obs->data.draw_callbacks_mutex) != 0) goto fail; - - if (!obs_view_init(&data->main_view)) + if (pthread_mutex_init_recursive(&obs->data.canvases_mutex) != 0) goto fail; data->sources = NULL; data->public_sources = NULL; + data->canvases = NULL; + data->named_canvases = NULL; data->private_data = obs_data_create(); data->valid = true; @@ -993,11 +1019,11 @@ static void obs_free_data(void) data->valid = false; - obs_view_remove(&data->main_view); - obs_main_view_free(&data->main_view); - blog(LOG_INFO, "Freeing OBS context data"); + /* Free main canvas */ + obs_canvas_release(data->main_canvas); + FREE_OBS_LINKED_LIST(output); FREE_OBS_LINKED_LIST(encoder); FREE_OBS_LINKED_LIST(display); @@ -1005,6 +1031,8 @@ static void obs_free_data(void) FREE_OBS_HASH_TABLE(hh, &data->public_sources, source); FREE_OBS_HASH_TABLE(hh_uuid, &data->sources, source); + FREE_OBS_HASH_TABLE(hh, &data->named_canvases, canvas); + FREE_OBS_HASH_TABLE(hh_uuid, &data->canvases, canvas); os_task_queue_wait(obs->destruction_task_thread); @@ -1015,6 +1043,7 @@ static void obs_free_data(void) pthread_mutex_destroy(&data->encoders_mutex); pthread_mutex_destroy(&data->services_mutex); pthread_mutex_destroy(&data->draw_callbacks_mutex); + pthread_mutex_destroy(&data->canvases_mutex); da_free(data->draw_callbacks); da_free(data->rendered_callbacks); da_free(data->tick_callbacks); @@ -1028,6 +1057,7 @@ static void obs_free_data(void) static const char *obs_signals[] = { "void source_create(ptr source)", + "void source_create_canvas(ptr source, ptr canvas)", "void source_destroy(ptr source)", "void source_remove(ptr source)", "void source_update(ptr source)", @@ -1043,8 +1073,7 @@ static const char *obs_signals[] = { "void source_filter_remove(ptr source, ptr filter)", "void source_rename(ptr source, string new_name, string prev_name)", "void source_volume(ptr source, in out float volume)", - "void source_volume_level(ptr source, float level, float magnitude, " - "float peak)", + "void source_volume_level(ptr source, float level, float magnitude, float peak)", "void source_transition_start(ptr source)", "void source_transition_video_stop(ptr source)", "void source_transition_stop(ptr source)", @@ -1056,6 +1085,12 @@ static const char *obs_signals[] = { "void hotkey_unregister(ptr hotkey)", "void hotkey_bindings_changed(ptr hotkey)", + "void canvas_create(ptr canvas)", + "void canvas_remove(ptr canvas)", + "void canvas_destroy(ptr canvas)", + "void canvas_video_reset(ptr canvas)", + "void canvas_rename(ptr canvas, string new_name, string prev_name)", + "void video_reset()", NULL, @@ -1189,6 +1224,11 @@ static bool obs_init(const char *locale, const char *module_config_path, profile if (!obs_init_hotkeys()) return false; + /* Create persistent main canvas. */ + obs->data.main_canvas = obs_create_main_canvas(); + if (!obs->data.main_canvas) + return false; + obs->destruction_task_thread = os_task_queue_create(); if (!obs->destruction_task_thread) return false; @@ -1453,6 +1493,7 @@ int obs_reset_video(struct obs_video_info *ovi) return OBS_VIDEO_INVALID_PARAM; stop_video(); + obs_free_canvas_mixes(); obs_free_video(); /* align to multiple-of-two and SSE alignment sizes */ @@ -1571,10 +1612,10 @@ bool obs_reset_audio(const struct obs_audio_info *oai) bool obs_get_video_info(struct obs_video_info *ovi) { - if (!obs->video.graphics || !obs->video.main_mix) + if (!obs->video.graphics || !obs->data.main_canvas->mix) return false; - *ovi = obs->video.main_mix->ovi; + *ovi = obs->data.main_canvas->mix->ovi; return true; } @@ -1740,49 +1781,17 @@ audio_t *obs_get_audio(void) video_t *obs_get_video(void) { - return obs->video.main_mix->video; + return obs->data.main_canvas->mix->video; } obs_source_t *obs_get_output_source(uint32_t channel) { - return obs_view_get_source(&obs->data.main_view, channel); + return obs_canvas_get_channel(obs->data.main_canvas, channel); } void obs_set_output_source(uint32_t channel, obs_source_t *source) { - assert(channel < MAX_CHANNELS); - - if (channel >= MAX_CHANNELS) - return; - - struct obs_source *prev_source; - struct obs_view *view = &obs->data.main_view; - struct calldata params = {0}; - - pthread_mutex_lock(&view->channels_mutex); - - source = obs_source_get_ref(source); - - prev_source = view->channels[channel]; - - calldata_set_int(¶ms, "channel", channel); - calldata_set_ptr(¶ms, "prev_source", prev_source); - calldata_set_ptr(¶ms, "source", source); - signal_handler_signal(obs->signals, "channel_change", ¶ms); - calldata_get_ptr(¶ms, "source", &source); - calldata_free(¶ms); - - view->channels[channel] = source; - - pthread_mutex_unlock(&view->channels_mutex); - - if (source) - obs_source_activate(source, MAIN_VIEW); - - if (prev_source) { - obs_source_deactivate(prev_source, MAIN_VIEW); - obs_source_release(prev_source); - } + obs_canvas_set_channel(obs->data.main_canvas, channel, source); } void obs_enum_sources(bool (*enum_proc)(void *, obs_source_t *), void *param) @@ -1790,38 +1799,40 @@ void obs_enum_sources(bool (*enum_proc)(void *, obs_source_t *), void *param) obs_source_t *source; pthread_mutex_lock(&obs->data.sources_mutex); - source = obs->data.public_sources; + source = obs->data.sources; while (source) { obs_source_t *s = obs_source_get_ref(source); if (s) { - if (s->info.type == OBS_SOURCE_TYPE_INPUT && !enum_proc(param, s)) { - obs_source_release(s); - break; - } else if (strcmp(s->info.id, group_info.id) == 0 && !enum_proc(param, s)) { - obs_source_release(s); - break; + if (!s->context.private) { + if (s->info.type == OBS_SOURCE_TYPE_INPUT && !enum_proc(param, s)) { + obs_source_release(s); + break; + } else if (strcmp(s->info.id, group_info.id) == 0 && !enum_proc(param, s)) { + obs_source_release(s); + break; + } } obs_source_release(s); } - source = (obs_source_t *)source->context.hh.next; + source = (obs_source_t *)source->context.hh_uuid.next; } pthread_mutex_unlock(&obs->data.sources_mutex); } -void obs_enum_scenes(bool (*enum_proc)(void *, obs_source_t *), void *param) +void obs_canvas_enum_scenes(obs_canvas_t *canvas, bool (*enum_proc)(void *, obs_source_t *), void *param) { obs_source_t *source; - pthread_mutex_lock(&obs->data.sources_mutex); + pthread_mutex_lock(&canvas->sources_mutex); - source = obs->data.public_sources; + source = canvas->sources; while (source) { obs_source_t *s = obs_source_get_ref(source); if (s) { - if (source->info.type == OBS_SOURCE_TYPE_SCENE && !enum_proc(param, s)) { + if (obs_source_is_scene(source) && !enum_proc(param, s)) { obs_source_release(s); break; } @@ -1831,7 +1842,12 @@ void obs_enum_scenes(bool (*enum_proc)(void *, obs_source_t *), void *param) source = (obs_source_t *)source->context.hh.next; } - pthread_mutex_unlock(&obs->data.sources_mutex); + pthread_mutex_unlock(&canvas->sources_mutex); +} + +void obs_enum_scenes(bool (*enum_proc)(void *, obs_source_t *), void *param) +{ + obs_canvas_enum_scenes(obs->data.main_canvas, enum_proc, param); } static inline void obs_enum(void *pstart, pthread_mutex_t *mutex, void *proc, void *param) @@ -1895,6 +1911,22 @@ void obs_enum_services(bool (*enum_proc)(void *, obs_service_t *), void *param) obs_enum(&obs->data.first_service, &obs->data.services_mutex, enum_proc, param); } +void obs_enum_canvases(bool (*enum_proc)(void *, obs_canvas_t *), void *param) +{ + struct obs_context_data *start = (struct obs_context_data *)obs->data.named_canvases; + struct obs_context_data *context, *tmp; + + pthread_mutex_lock(&obs->data.canvases_mutex); + + HASH_ITER (hh, start, context, tmp) { + obs_canvas_t *canvas = (obs_canvas_t *)context; + if (!enum_proc(param, canvas)) + break; + } + + pthread_mutex_unlock(&obs->data.canvases_mutex); +} + static inline void *get_context_by_name(void *vfirst, const char *name, pthread_mutex_t *mutex, void *(*addref)(void *)) { struct obs_context_data **first = vfirst; @@ -1958,9 +1990,21 @@ static inline void *obs_service_addref_safe_(void *ref) return obs_service_get_ref(ref); } +static inline void *obs_canvas_addref_safe_(void *ref) +{ + return obs_canvas_get_ref(ref); +} + obs_source_t *obs_get_source_by_name(const char *name) { - return get_context_by_name(&obs->data.public_sources, name, &obs->data.sources_mutex, obs_source_addref_safe_); + obs_source_t *source = + get_context_by_name(&obs->data.public_sources, name, &obs->data.sources_mutex, obs_source_addref_safe_); + /* For backwards compat: Also look up source name in main canvas's scenes list. */ + if (!source) { + source = get_context_by_name(&obs->data.main_canvas->sources, name, + &obs->data.main_canvas->sources_mutex, obs_source_addref_safe_); + } + return source; } obs_source_t *obs_get_source_by_uuid(const char *uuid) @@ -1968,6 +2012,32 @@ obs_source_t *obs_get_source_by_uuid(const char *uuid) return get_context_by_uuid(&obs->data.sources, uuid, &obs->data.sources_mutex, obs_source_addref_safe_); } +obs_canvas_t *obs_get_canvas_by_name(const char *name) +{ + return get_context_by_name(&obs->data.named_canvases, name, &obs->data.canvases_mutex, obs_canvas_addref_safe_); +} + +obs_canvas_t *obs_get_canvas_by_uuid(const char *uuid) +{ + return get_context_by_uuid(&obs->data.canvases, uuid, &obs->data.canvases_mutex, obs_canvas_addref_safe_); +} + +obs_source_t *obs_canvas_get_source_by_name(obs_canvas_t *canvas, const char *name) +{ + return get_context_by_name(&canvas->sources, name, &canvas->sources_mutex, obs_source_addref_safe_); +} + +obs_scene_t *obs_canvas_get_scene_by_name(obs_canvas_t *canvas, const char *name) +{ + obs_source_t *source = obs_canvas_get_source_by_name(canvas, name); + obs_scene_t *scene = obs_scene_from_source(source); + if (!scene) { + obs_source_release(source); + return NULL; + } + return scene; +} + obs_source_t *obs_get_transition_by_name(const char *name) { struct obs_source **first = &obs->data.sources; @@ -2055,16 +2125,17 @@ proc_handler_t *obs_get_proc_handler(void) return obs->procs; } -static void obs_render_main_texture_internal(enum gs_blend_type src_c, enum gs_blend_type dest_c, - enum gs_blend_type src_a, enum gs_blend_type dest_a) +static void obs_render_canvas_texture_internal(obs_canvas_t *canvas, enum gs_blend_type src_c, + enum gs_blend_type dest_c, enum gs_blend_type src_a, + enum gs_blend_type dest_a) { struct obs_core_video_mix *video; gs_texture_t *tex; gs_effect_t *effect; gs_eparam_t *param; - video = obs->video.main_mix; - if (!video->texture_rendered) + video = canvas->mix; + if (!video || !video->texture_rendered) return; const enum gs_color_space source_space = video->render_space; @@ -2108,19 +2179,32 @@ static void obs_render_main_texture_internal(enum gs_blend_type src_c, enum gs_b void obs_render_main_texture(void) { - obs_render_main_texture_internal(GS_BLEND_ONE, GS_BLEND_INVSRCALPHA, GS_BLEND_ONE, GS_BLEND_INVSRCALPHA); + obs_render_canvas_texture_internal(obs->data.main_canvas, GS_BLEND_ONE, GS_BLEND_INVSRCALPHA, GS_BLEND_ONE, + GS_BLEND_INVSRCALPHA); } void obs_render_main_texture_src_color_only(void) { - obs_render_main_texture_internal(GS_BLEND_ONE, GS_BLEND_ZERO, GS_BLEND_ONE, GS_BLEND_INVSRCALPHA); + obs_render_canvas_texture_internal(obs->data.main_canvas, GS_BLEND_ONE, GS_BLEND_ZERO, GS_BLEND_ONE, + GS_BLEND_INVSRCALPHA); +} + +void obs_render_canvas_texture(obs_canvas_t *canvas) +{ + obs_render_canvas_texture_internal(canvas, GS_BLEND_ONE, GS_BLEND_INVSRCALPHA, GS_BLEND_ONE, + GS_BLEND_INVSRCALPHA); +} + +void obs_render_canvas_texture_src_color_only(obs_canvas_t *canvas) +{ + obs_render_canvas_texture_internal(canvas, GS_BLEND_ONE, GS_BLEND_ZERO, GS_BLEND_ONE, GS_BLEND_INVSRCALPHA); } gs_texture_t *obs_get_main_texture(void) { struct obs_core_video_mix *video; - video = obs->video.main_mix; + video = obs->data.main_canvas->mix; if (!video->texture_rendered) return NULL; @@ -2137,6 +2221,7 @@ static obs_source_t *obs_load_source_type(obs_data_t *source_data, bool is_priva const char *v_id = obs_data_get_string(source_data, "versioned_id"); obs_data_t *settings = obs_data_get_obj(source_data, "settings"); obs_data_t *hotkeys = obs_data_get_obj(source_data, "hotkeys"); + obs_canvas_t *canvas = NULL; double volume; double balance; int64_t sync; @@ -2153,13 +2238,23 @@ static obs_source_t *obs_load_source_type(obs_data_t *source_data, bool is_priva if (!*v_id) v_id = id; - source = obs_source_create_set_last_ver(v_id, name, uuid, settings, hotkeys, prev_ver, is_private); + if (strcmp(id, scene_info.id) == 0 || strcmp(id, group_info.id) == 0) { + const char *canvas_uuid = obs_data_get_string(source_data, "canvas_uuid"); + canvas = obs_get_canvas_by_uuid(canvas_uuid); + /* Fall back to main canvas if canvas cannot be found. */ + if (!canvas) { + canvas = obs_canvas_get_ref(obs->data.main_canvas); + } + } + + source = obs_source_create_set_last_ver(canvas, v_id, name, uuid, settings, hotkeys, prev_ver, is_private); if (source->owns_info_id) { bfree((void *)source->info.unversioned_id); source->info.unversioned_id = bstrdup(id); } + obs_canvas_release(canvas); obs_data_release(hotkeys); caps = obs_source_get_output_flags(source); @@ -2322,6 +2417,7 @@ obs_data_t *obs_save_source(obs_source_t *source) int m_type = (int)obs_source_get_monitoring_type(source); int di_mode = (int)obs_source_get_deinterlace_mode(source); int di_order = (int)obs_source_get_deinterlace_field_order(source); + obs_canvas_t *canvas = obs_source_get_canvas(source); DARRAY(obs_source_t *) filters_copy; obs_source_save(source); @@ -2356,6 +2452,11 @@ obs_data_t *obs_save_source(obs_source_t *source) obs_data_set_int(source_data, "deinterlace_field_order", di_order); obs_data_set_int(source_data, "monitoring_type", m_type); + if (canvas) { + obs_data_set_string(source_data, "canvas_uuid", obs_canvas_get_uuid(canvas)); + obs_canvas_release(canvas); + } + obs_data_set_obj(source_data, "private_settings", source->private_settings); if (source->info.type == OBS_SOURCE_TYPE_TRANSITION) @@ -2403,18 +2504,18 @@ obs_data_array_t *obs_save_sources_filtered(obs_save_source_filter_cb cb, void * pthread_mutex_lock(&data->sources_mutex); - source = data->public_sources; + source = data->sources; while (source) { if ((source->info.type != OBS_SOURCE_TYPE_FILTER) != 0 && !source->removed && !source->temp_removed && - cb(data_, source)) { + !source->context.private && cb(data_, source)) { obs_data_t *source_data = obs_save_source(source); obs_data_array_push_back(array, source_data); obs_data_release(source_data); } - source = (obs_source_t *)source->context.hh.next; + source = (obs_source_t *)source->context.hh_uuid.next; } pthread_mutex_unlock(&data->sources_mutex); @@ -2499,7 +2600,7 @@ static inline bool obs_context_data_init_wrap(struct obs_context_data *context, if (uuid && strlen(uuid) == UUID_STR_LENGTH) context->uuid = bstrdup(uuid); /* Only automatically generate UUIDs for sources */ - else if (type == OBS_OBJ_TYPE_SOURCE) + else if (type == OBS_OBJ_TYPE_SOURCE || type == OBS_OBJ_TYPE_CANVAS) context->uuid = os_generate_uuid(); context->name = dup_name(name, private); @@ -2656,7 +2757,7 @@ void obs_context_data_remove(struct obs_context_data *context) } } -void obs_context_data_remove_name(struct obs_context_data *context, void *phead) +void obs_context_data_remove_name(struct obs_context_data *context, pthread_mutex_t *mutex, void *phead) { struct obs_context_data **head = phead; @@ -2665,12 +2766,12 @@ void obs_context_data_remove_name(struct obs_context_data *context, void *phead) if (!context) return; - pthread_mutex_lock(context->mutex); + pthread_mutex_lock(mutex); HASH_DELETE(hh, *head, context); - pthread_mutex_unlock(context->mutex); + pthread_mutex_unlock(mutex); } -void obs_context_data_remove_uuid(struct obs_context_data *context, void *puuid_head) +void obs_context_data_remove_uuid(struct obs_context_data *context, pthread_mutex_t *mutex, void *puuid_head) { struct obs_context_data **uuid_head = puuid_head; @@ -2679,9 +2780,9 @@ void obs_context_data_remove_uuid(struct obs_context_data *context, void *puuid_ if (!context || !context->uuid || !uuid_head) return; - pthread_mutex_lock(context->mutex); + pthread_mutex_lock(mutex); HASH_DELETE(hh_uuid, *uuid_head, context); - pthread_mutex_unlock(context->mutex); + pthread_mutex_unlock(mutex); } void obs_context_wait(struct obs_context_data *context) @@ -2970,13 +3071,13 @@ void obs_add_raw_video_callback(const struct video_scale_info *conversion, void obs_add_raw_video_callback2(const struct video_scale_info *conversion, uint32_t frame_rate_divisor, void (*callback)(void *param, struct video_data *frame), void *param) { - struct obs_core_video_mix *video = obs->video.main_mix; + struct obs_core_video_mix *video = obs->data.main_canvas->mix; start_raw_video(video->video, conversion, frame_rate_divisor, callback, param); } void obs_remove_raw_video_callback(void (*callback)(void *param, struct video_data *frame), void *param) { - struct obs_core_video_mix *video = obs->video.main_mix; + struct obs_core_video_mix *video = obs->data.main_canvas->mix; stop_raw_video(video->video, callback, param); } @@ -3093,13 +3194,13 @@ bool obs_video_active(void) bool obs_nv12_tex_active(void) { - struct obs_core_video_mix *video = obs->video.main_mix; + struct obs_core_video_mix *video = obs->data.main_canvas->mix; return video->using_nv12_tex; } bool obs_p010_tex_active(void) { - struct obs_core_video_mix *video = obs->video.main_mix; + struct obs_core_video_mix *video = obs->data.main_canvas->mix; return video->using_p010_tex; } @@ -3314,3 +3415,8 @@ bool obs_enum_output_protocols(size_t idx, char **protocol) *protocol = obs->data.protocols.array[idx]; return true; } + +obs_canvas_t *obs_get_main_canvas(void) +{ + return obs_canvas_get_ref(obs->data.main_canvas); +} diff --git a/libobs/obs.h b/libobs/obs.h index 6308c19fc..df8b75443 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -51,6 +51,7 @@ struct obs_service; struct obs_module; struct obs_fader; struct obs_volmeter; +struct obs_canvas; typedef struct obs_context_data obs_object_t; typedef struct obs_display obs_display_t; @@ -65,12 +66,14 @@ typedef struct obs_service obs_service_t; typedef struct obs_module obs_module_t; typedef struct obs_fader obs_fader_t; typedef struct obs_volmeter obs_volmeter_t; +typedef struct obs_canvas obs_canvas_t; typedef struct obs_weak_object obs_weak_object_t; typedef struct obs_weak_source obs_weak_source_t; typedef struct obs_weak_output obs_weak_output_t; typedef struct obs_weak_encoder obs_weak_encoder_t; typedef struct obs_weak_service obs_weak_service_t; +typedef struct obs_weak_canvas obs_weak_canvas_t; #include "obs-missing-files.h" #include "obs-source.h" @@ -688,6 +691,9 @@ EXPORT void obs_enum_encoders(bool (*enum_proc)(void *, obs_encoder_t *), void * /** Enumerates encoders */ EXPORT void obs_enum_services(bool (*enum_proc)(void *, obs_service_t *), void *param); +/** Enumerates canvases */ +EXPORT void obs_enum_canvases(bool (*enum_proc)(void *, obs_canvas_t *), void *param); + /** * Gets a source by its name. * @@ -719,6 +725,11 @@ EXPORT obs_encoder_t *obs_get_encoder_by_name(const char *name); /** Gets an service by its name. */ EXPORT obs_service_t *obs_get_service_by_name(const char *name); +/** Get a canvas by its name. */ +EXPORT obs_canvas_t *obs_get_canvas_by_name(const char *name); +/** Get a canvas by its UUID. */ +EXPORT obs_canvas_t *obs_get_canvas_by_uuid(const char *uuid); + enum obs_base_effect { OBS_EFFECT_DEFAULT, /**< RGB/YUV */ OBS_EFFECT_DEFAULT_RECT, /**< RGB/YUV (using texture_rect) */ @@ -747,6 +758,12 @@ EXPORT void obs_render_main_texture(void); /** Renders the last main output texture ignoring background color */ EXPORT void obs_render_main_texture_src_color_only(void); +/** Renders the last canvas output texture */ +EXPORT void obs_render_canvas_texture(obs_canvas_t *canvas); + +/** Renders the last main output texture ignoring background color */ +EXPORT void obs_render_canvas_texture_src_color_only(obs_canvas_t *canvas); + /** Returns the last main output texture. This can return NULL if the texture * is unavailable. */ EXPORT gs_texture_t *obs_get_main_texture(void); @@ -790,6 +807,7 @@ enum obs_obj_type { OBS_OBJ_TYPE_OUTPUT, OBS_OBJ_TYPE_ENCODER, OBS_OBJ_TYPE_SERVICE, + OBS_OBJ_TYPE_CANVAS, }; EXPORT enum obs_obj_type obs_obj_get_type(void *obj); @@ -1482,6 +1500,9 @@ EXPORT enum obs_media_state obs_source_media_get_state(obs_source_t *source); EXPORT void obs_source_media_started(obs_source_t *source); EXPORT void obs_source_media_ended(obs_source_t *source); +/** Get canvas this source belongs to (reference incremented) */ +EXPORT obs_canvas_t *obs_source_get_canvas(const obs_source_t *source); + /* ------------------------------------------------------------------------- */ /* Transition-specific functions */ enum obs_transition_target { @@ -2484,6 +2505,99 @@ EXPORT void obs_source_frame_copy(struct obs_source_frame *dst, const struct obs /* Get source icon type */ EXPORT enum obs_icon_type obs_source_get_icon_type(const char *id); +/* ------------------------------------------------------------------------- */ +/* Canvases */ + +/* Canvas flags */ +enum obs_canvas_flags { + MAIN = 1 << 0, // Main canvas created by libobs, cannot be renamed or reset, cannot be set by user + ACTIVATE = 1 << 1, // Canvas sources will become active when they are visible + MIX_AUDIO = 1 << 2, // Audio from channels in this canvas will be mixed into the audio output + SCENE_REF = 1 << 3, // Canvas will hold references for scene sources + EPHEMERAL = 1 << 4, // Indicates this canvas is not supposed to be saved + + /* Presets */ + PROGRAM = ACTIVATE | MIX_AUDIO | SCENE_REF, + PREVIEW = EPHEMERAL, + DEVICE = ACTIVATE | EPHEMERAL, +}; + +/** Get a strong reference to the main OBS canvas */ +EXPORT obs_canvas_t *obs_get_main_canvas(void); + +/** Creates a new canvas */ +EXPORT obs_canvas_t *obs_canvas_create(const char *name, struct obs_video_info *ovi, uint32_t flags); +/** Creates a new private canvas */ +EXPORT obs_canvas_t *obs_canvas_create_private(const char *name, struct obs_video_info *ovi, uint32_t flags); + +/** Signal that references to canvas should be released and mark the canvas as removed. */ +EXPORT void obs_canvas_remove(obs_canvas_t *canvas); +/** Returns if a canvas is marked as removed (i.e., should no longer be used). */ +EXPORT bool obs_canvas_removed(obs_canvas_t *canvas); + +/* Canvas properties */ +/** Set canvas name */ +EXPORT void obs_canvas_set_name(obs_canvas_t *canvas, const char *name); +/** Get canvas name */ +EXPORT const char *obs_canvas_get_name(const obs_canvas_t *canvas); +/** Get canvas UUID */ +EXPORT const char *obs_canvas_get_uuid(const obs_canvas_t *canvas); +/** Gets flags set on a canvas */ +EXPORT uint32_t obs_canvas_get_flags(const obs_canvas_t *canvas); + +/* Saving/Loading */ +/** Saves a canvas to settings data */ +EXPORT obs_data_t *obs_save_canvas(obs_canvas_t *source); +/** Loads a canvas from settings data */ +EXPORT obs_canvas_t *obs_load_canvas(obs_data_t *data); + +/* Reference counting */ +/** Add strong reference */ +EXPORT obs_canvas_t *obs_canvas_get_ref(obs_canvas_t *canvas); +/** Release strong reference */ +EXPORT void obs_canvas_release(obs_canvas_t *canvas); +/** Add weak reference */ +EXPORT void obs_weak_canvas_addref(obs_weak_canvas_t *weak); +/** Release weak reference */ +EXPORT void obs_weak_canvas_release(obs_weak_canvas_t *weak); + +/** Get weak reference from strong reference */ +EXPORT obs_weak_canvas_t *obs_canvas_get_weak_canvas(obs_canvas_t *canvas); +/** Get strong reference from weak reference */ +EXPORT obs_canvas_t *obs_weak_canvas_get_canvas(obs_weak_canvas_t *weak); + +/* Channels */ +/** Sets the source to be used for this canvas. */ +EXPORT void obs_canvas_set_channel(obs_canvas_t *canvas, uint32_t channel, obs_source_t *source); +/** Gets the source currently in use for this view context */ +EXPORT obs_source_t *obs_canvas_get_channel(obs_canvas_t *canvas, uint32_t channel); + +/* Canvas sources */ +/** Create scene attached to a canvas */ +EXPORT obs_scene_t *obs_canvas_scene_create(obs_canvas_t *canvas, const char *name); +/** Remove a scene from a canvas */ +EXPORT void obs_canvas_scene_remove(obs_scene_t *scene); +/** Move scene to another canvas, detaching it from the previous one and deduplicating the name if needed */ +EXPORT void obs_canvas_move_scene(obs_scene_t *scene, obs_canvas_t *dst); +/** Enumerates scenes belonging to a canvas */ +EXPORT void obs_canvas_enum_scenes(obs_canvas_t *canvas, bool (*enum_proc)(void *, obs_source_t *), void *param); +/** Get a canvas source by name */ +EXPORT obs_source_t *obs_canvas_get_source_by_name(obs_canvas_t *canvas, const char *name); +/** Get a canvas source by UUID */ +EXPORT obs_scene_t *obs_canvas_get_scene_by_name(obs_canvas_t *canvas, const char *name); + +/* Canvas video */ +/** Reset a canvas's video mix */ +EXPORT bool obs_canvas_reset_video(obs_canvas_t *canvas, struct obs_video_info *ovi); +/** Returns true if the canvas video is configured */ +EXPORT bool obs_canvas_has_video(obs_canvas_t *canvas); +/** Get canvas video output */ +EXPORT video_t *obs_canvas_get_video(const obs_canvas_t *canvas); +/** Get canvas video info (if it exists) */ +EXPORT bool obs_canvas_get_video_info(const obs_canvas_t *canvas, struct obs_video_info *ovi); +/** Renders the sources of this canvas's view context */ +EXPORT void obs_canvas_render(obs_canvas_t *canvas); + #ifdef __cplusplus } #endif