1 /******************************************************************************
2 Copyright (C) 2013-2015 by Hugh Bailey <obs.jim@gmail.com>
3 Philippe Groarke <philippe.groarke@gmail.com>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 2 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 ******************************************************************************/
18
19 #include "util/threading.h"
20 #include "util/util_uint64.h"
21 #include "graphics/math-defs.h"
22 #include "obs-scene.h"
23 #include "obs-internal.h"
24
25 const struct obs_source_info group_info;
26
27 static void resize_group(obs_sceneitem_t *group);
28 static void resize_scene(obs_scene_t *scene);
29 static void signal_parent(obs_scene_t *parent, const char *name,
30 calldata_t *params);
31 static void get_ungrouped_transform(obs_sceneitem_t *group, struct vec2 *pos,
32 struct vec2 *scale, float *rot);
33 static inline bool crop_enabled(const struct obs_sceneitem_crop *crop);
34 static inline bool item_texture_enabled(const struct obs_scene_item *item);
35 static void init_hotkeys(obs_scene_t *scene, obs_sceneitem_t *item,
36 const char *name);
37
38 /* NOTE: For proper mutex lock order (preventing mutual cross-locks), never
39 * lock the graphics mutex inside either of the scene mutexes.
40 *
41 * Another thing that must be done to prevent that cross-lock (and improve
42 * performance), is to not create/release/update sources within the scene
43 * mutexes.
44 *
45 * It's okay to lock the graphics mutex before locking either of the scene
46 * mutexes, but not after.
47 */
48
49 static const char *obs_scene_signals[] = {
50 "void item_add(ptr scene, ptr item)",
51 "void item_remove(ptr scene, ptr item)",
52 "void reorder(ptr scene)",
53 "void refresh(ptr scene)",
54 "void item_visible(ptr scene, ptr item, bool visible)",
55 "void item_select(ptr scene, ptr item)",
56 "void item_deselect(ptr scene, ptr item)",
57 "void item_transform(ptr scene, ptr item)",
58 "void item_locked(ptr scene, ptr item, bool locked)",
59 NULL,
60 };
61
signal_item_remove(struct obs_scene_item * item)62 static inline void signal_item_remove(struct obs_scene_item *item)
63 {
64 struct calldata params;
65 uint8_t stack[128];
66
67 calldata_init_fixed(¶ms, stack, sizeof(stack));
68 calldata_set_ptr(¶ms, "item", item);
69
70 signal_parent(item->parent, "item_remove", ¶ms);
71 }
72
scene_getname(void * unused)73 static const char *scene_getname(void *unused)
74 {
75 UNUSED_PARAMETER(unused);
76 return "Scene";
77 }
78
group_getname(void * unused)79 static const char *group_getname(void *unused)
80 {
81 UNUSED_PARAMETER(unused);
82 return "Group";
83 }
84
scene_create(obs_data_t * settings,struct obs_source * source)85 static void *scene_create(obs_data_t *settings, struct obs_source *source)
86 {
87 struct obs_scene *scene = bzalloc(sizeof(struct obs_scene));
88 scene->source = source;
89
90 if (strcmp(source->info.id, group_info.id) == 0) {
91 scene->is_group = true;
92 scene->custom_size = true;
93 scene->cx = 0;
94 scene->cy = 0;
95 }
96
97 signal_handler_add_array(obs_source_get_signal_handler(source),
98 obs_scene_signals);
99
100 if (pthread_mutex_init_recursive(&scene->audio_mutex) != 0) {
101 blog(LOG_ERROR, "scene_create: Couldn't initialize audio "
102 "mutex");
103 goto fail;
104 }
105 if (pthread_mutex_init_recursive(&scene->video_mutex) != 0) {
106 blog(LOG_ERROR, "scene_create: Couldn't initialize video "
107 "mutex");
108 goto fail;
109 }
110
111 UNUSED_PARAMETER(settings);
112 return scene;
113
114 fail:
115 bfree(scene);
116 return NULL;
117 }
118
119 #define audio_lock(scene) pthread_mutex_lock(&scene->audio_mutex)
120 #define video_lock(scene) pthread_mutex_lock(&scene->video_mutex)
121 #define audio_unlock(scene) pthread_mutex_unlock(&scene->audio_mutex)
122 #define video_unlock(scene) pthread_mutex_unlock(&scene->video_mutex)
123
full_lock(struct obs_scene * scene)124 static inline void full_lock(struct obs_scene *scene)
125 {
126 video_lock(scene);
127 audio_lock(scene);
128 }
129
full_unlock(struct obs_scene * scene)130 static inline void full_unlock(struct obs_scene *scene)
131 {
132 audio_unlock(scene);
133 video_unlock(scene);
134 }
135
136 static void set_visibility(struct obs_scene_item *item, bool vis);
137 static inline void detach_sceneitem(struct obs_scene_item *item);
138
remove_without_release(struct obs_scene_item * item)139 static inline void remove_without_release(struct obs_scene_item *item)
140 {
141 item->removed = true;
142 set_visibility(item, false);
143 signal_item_remove(item);
144 detach_sceneitem(item);
145 }
146
remove_all_items(struct obs_scene * scene)147 static void remove_all_items(struct obs_scene *scene)
148 {
149 struct obs_scene_item *item;
150 DARRAY(struct obs_scene_item *) items;
151
152 da_init(items);
153
154 full_lock(scene);
155
156 item = scene->first_item;
157
158 while (item) {
159 struct obs_scene_item *del_item = item;
160 item = item->next;
161
162 remove_without_release(del_item);
163 da_push_back(items, &del_item);
164 }
165
166 full_unlock(scene);
167
168 for (size_t i = 0; i < items.num; i++)
169 obs_sceneitem_release(items.array[i]);
170 da_free(items);
171 }
172
scene_destroy(void * data)173 static void scene_destroy(void *data)
174 {
175 struct obs_scene *scene = data;
176
177 remove_all_items(scene);
178
179 pthread_mutex_destroy(&scene->video_mutex);
180 pthread_mutex_destroy(&scene->audio_mutex);
181 bfree(scene);
182 }
183
transition_active(obs_source_t * transition)184 static inline bool transition_active(obs_source_t *transition)
185 {
186 return transition && (transition->transitioning_audio ||
187 transition->transitioning_video);
188 }
189
scene_enum_sources(void * data,obs_source_enum_proc_t enum_callback,void * param,bool active)190 static void scene_enum_sources(void *data, obs_source_enum_proc_t enum_callback,
191 void *param, bool active)
192 {
193 struct obs_scene *scene = data;
194 struct obs_scene_item *item;
195 struct obs_scene_item *next;
196
197 full_lock(scene);
198 item = scene->first_item;
199
200 while (item) {
201 next = item->next;
202
203 obs_sceneitem_addref(item);
204 if (active) {
205 if (item->visible &&
206 transition_active(item->show_transition))
207 enum_callback(scene->source,
208 item->show_transition, param);
209 else if (!item->visible &&
210 transition_active(item->hide_transition))
211 enum_callback(scene->source,
212 item->hide_transition, param);
213 else if (os_atomic_load_long(&item->active_refs) > 0)
214 enum_callback(scene->source, item->source,
215 param);
216 } else {
217 if (item->show_transition)
218 enum_callback(scene->source,
219 item->show_transition, param);
220 if (item->hide_transition)
221 enum_callback(scene->source,
222 item->hide_transition, param);
223 enum_callback(scene->source, item->source, param);
224 }
225
226 obs_sceneitem_release(item);
227
228 item = next;
229 }
230
231 full_unlock(scene);
232 }
233
scene_enum_active_sources(void * data,obs_source_enum_proc_t enum_callback,void * param)234 static void scene_enum_active_sources(void *data,
235 obs_source_enum_proc_t enum_callback,
236 void *param)
237 {
238 scene_enum_sources(data, enum_callback, param, true);
239 }
240
scene_enum_all_sources(void * data,obs_source_enum_proc_t enum_callback,void * param)241 static void scene_enum_all_sources(void *data,
242 obs_source_enum_proc_t enum_callback,
243 void *param)
244 {
245 scene_enum_sources(data, enum_callback, param, false);
246 }
247
detach_sceneitem(struct obs_scene_item * item)248 static inline void detach_sceneitem(struct obs_scene_item *item)
249 {
250 if (item->prev)
251 item->prev->next = item->next;
252 else
253 item->parent->first_item = item->next;
254
255 if (item->next)
256 item->next->prev = item->prev;
257
258 item->parent = NULL;
259 }
260
attach_sceneitem(struct obs_scene * parent,struct obs_scene_item * item,struct obs_scene_item * prev)261 static inline void attach_sceneitem(struct obs_scene *parent,
262 struct obs_scene_item *item,
263 struct obs_scene_item *prev)
264 {
265 item->prev = prev;
266 item->parent = parent;
267
268 if (prev) {
269 item->next = prev->next;
270 if (prev->next)
271 prev->next->prev = item;
272 prev->next = item;
273 } else {
274 item->next = parent->first_item;
275 if (parent->first_item)
276 parent->first_item->prev = item;
277 parent->first_item = item;
278 }
279 }
280
add_alignment(struct vec2 * v,uint32_t align,int cx,int cy)281 void add_alignment(struct vec2 *v, uint32_t align, int cx, int cy)
282 {
283 if (align & OBS_ALIGN_RIGHT)
284 v->x += (float)cx;
285 else if ((align & OBS_ALIGN_LEFT) == 0)
286 v->x += (float)(cx / 2);
287
288 if (align & OBS_ALIGN_BOTTOM)
289 v->y += (float)cy;
290 else if ((align & OBS_ALIGN_TOP) == 0)
291 v->y += (float)(cy / 2);
292 }
293
calculate_bounds_data(struct obs_scene_item * item,struct vec2 * origin,struct vec2 * scale,uint32_t * cx,uint32_t * cy)294 static void calculate_bounds_data(struct obs_scene_item *item,
295 struct vec2 *origin, struct vec2 *scale,
296 uint32_t *cx, uint32_t *cy)
297 {
298 float width = (float)(*cx) * fabsf(scale->x);
299 float height = (float)(*cy) * fabsf(scale->y);
300 float item_aspect = width / height;
301 float bounds_aspect = item->bounds.x / item->bounds.y;
302 uint32_t bounds_type = item->bounds_type;
303 float width_diff, height_diff;
304
305 if (item->bounds_type == OBS_BOUNDS_MAX_ONLY)
306 if (width > item->bounds.x || height > item->bounds.y)
307 bounds_type = OBS_BOUNDS_SCALE_INNER;
308
309 if (bounds_type == OBS_BOUNDS_SCALE_INNER ||
310 bounds_type == OBS_BOUNDS_SCALE_OUTER) {
311 bool use_width = (bounds_aspect < item_aspect);
312 float mul;
313
314 if (item->bounds_type == OBS_BOUNDS_SCALE_OUTER)
315 use_width = !use_width;
316
317 mul = use_width ? item->bounds.x / width
318 : item->bounds.y / height;
319
320 vec2_mulf(scale, scale, mul);
321
322 } else if (bounds_type == OBS_BOUNDS_SCALE_TO_WIDTH) {
323 vec2_mulf(scale, scale, item->bounds.x / width);
324
325 } else if (bounds_type == OBS_BOUNDS_SCALE_TO_HEIGHT) {
326 vec2_mulf(scale, scale, item->bounds.y / height);
327
328 } else if (bounds_type == OBS_BOUNDS_STRETCH) {
329 scale->x = item->bounds.x / (float)(*cx);
330 scale->y = item->bounds.y / (float)(*cy);
331 }
332
333 width = (float)(*cx) * scale->x;
334 height = (float)(*cy) * scale->y;
335 width_diff = item->bounds.x - width;
336 height_diff = item->bounds.y - height;
337 *cx = (uint32_t)item->bounds.x;
338 *cy = (uint32_t)item->bounds.y;
339
340 add_alignment(origin, item->bounds_align, (int)-width_diff,
341 (int)-height_diff);
342 }
343
calc_cx(const struct obs_scene_item * item,uint32_t width)344 static inline uint32_t calc_cx(const struct obs_scene_item *item,
345 uint32_t width)
346 {
347 uint32_t crop_cx = item->crop.left + item->crop.right;
348 return (crop_cx > width) ? 2 : (width - crop_cx);
349 }
350
calc_cy(const struct obs_scene_item * item,uint32_t height)351 static inline uint32_t calc_cy(const struct obs_scene_item *item,
352 uint32_t height)
353 {
354 uint32_t crop_cy = item->crop.top + item->crop.bottom;
355 return (crop_cy > height) ? 2 : (height - crop_cy);
356 }
357
update_item_transform(struct obs_scene_item * item,bool update_tex)358 static void update_item_transform(struct obs_scene_item *item, bool update_tex)
359 {
360 uint32_t width;
361 uint32_t height;
362 uint32_t cx;
363 uint32_t cy;
364 struct vec2 base_origin;
365 struct vec2 origin;
366 struct vec2 scale;
367 struct calldata params;
368 uint8_t stack[128];
369
370 if (os_atomic_load_long(&item->defer_update) > 0)
371 return;
372
373 width = obs_source_get_width(item->source);
374 height = obs_source_get_height(item->source);
375 cx = calc_cx(item, width);
376 cy = calc_cy(item, height);
377 scale = item->scale;
378 item->last_width = width;
379 item->last_height = height;
380
381 width = cx;
382 height = cy;
383
384 vec2_zero(&base_origin);
385 vec2_zero(&origin);
386
387 /* ----------------------- */
388
389 if (item->bounds_type != OBS_BOUNDS_NONE) {
390 calculate_bounds_data(item, &origin, &scale, &cx, &cy);
391 } else {
392 cx = (uint32_t)((float)cx * scale.x);
393 cy = (uint32_t)((float)cy * scale.y);
394 }
395
396 add_alignment(&origin, item->align, (int)cx, (int)cy);
397
398 matrix4_identity(&item->draw_transform);
399 matrix4_scale3f(&item->draw_transform, &item->draw_transform, scale.x,
400 scale.y, 1.0f);
401 matrix4_translate3f(&item->draw_transform, &item->draw_transform,
402 -origin.x, -origin.y, 0.0f);
403 matrix4_rotate_aa4f(&item->draw_transform, &item->draw_transform, 0.0f,
404 0.0f, 1.0f, RAD(item->rot));
405 matrix4_translate3f(&item->draw_transform, &item->draw_transform,
406 item->pos.x, item->pos.y, 0.0f);
407
408 item->output_scale = scale;
409
410 /* ----------------------- */
411
412 if (item->bounds_type != OBS_BOUNDS_NONE) {
413 vec2_copy(&scale, &item->bounds);
414 } else {
415 scale.x = (float)width * item->scale.x;
416 scale.y = (float)height * item->scale.y;
417 }
418
419 item->box_scale = scale;
420
421 add_alignment(&base_origin, item->align, (int)scale.x, (int)scale.y);
422
423 matrix4_identity(&item->box_transform);
424 matrix4_scale3f(&item->box_transform, &item->box_transform, scale.x,
425 scale.y, 1.0f);
426 matrix4_translate3f(&item->box_transform, &item->box_transform,
427 -base_origin.x, -base_origin.y, 0.0f);
428 matrix4_rotate_aa4f(&item->box_transform, &item->box_transform, 0.0f,
429 0.0f, 1.0f, RAD(item->rot));
430 matrix4_translate3f(&item->box_transform, &item->box_transform,
431 item->pos.x, item->pos.y, 0.0f);
432
433 /* ----------------------- */
434
435 calldata_init_fixed(¶ms, stack, sizeof(stack));
436 calldata_set_ptr(¶ms, "item", item);
437 signal_parent(item->parent, "item_transform", ¶ms);
438
439 if (!update_tex)
440 return;
441
442 if (item->item_render && !item_texture_enabled(item)) {
443 obs_enter_graphics();
444 gs_texrender_destroy(item->item_render);
445 item->item_render = NULL;
446 obs_leave_graphics();
447
448 } else if (!item->item_render && item_texture_enabled(item)) {
449 obs_enter_graphics();
450 item->item_render = gs_texrender_create(GS_RGBA, GS_ZS_NONE);
451 obs_leave_graphics();
452 }
453
454 os_atomic_set_bool(&item->update_transform, false);
455 }
456
source_size_changed(struct obs_scene_item * item)457 static inline bool source_size_changed(struct obs_scene_item *item)
458 {
459 uint32_t width = obs_source_get_width(item->source);
460 uint32_t height = obs_source_get_height(item->source);
461
462 return item->last_width != width || item->last_height != height;
463 }
464
crop_enabled(const struct obs_sceneitem_crop * crop)465 static inline bool crop_enabled(const struct obs_sceneitem_crop *crop)
466 {
467 return crop->left || crop->right || crop->top || crop->bottom;
468 }
469
scale_filter_enabled(const struct obs_scene_item * item)470 static inline bool scale_filter_enabled(const struct obs_scene_item *item)
471 {
472 return item->scale_filter != OBS_SCALE_DISABLE;
473 }
474
item_is_scene(const struct obs_scene_item * item)475 static inline bool item_is_scene(const struct obs_scene_item *item)
476 {
477 return item->source && item->source->info.type == OBS_SOURCE_TYPE_SCENE;
478 }
479
item_texture_enabled(const struct obs_scene_item * item)480 static inline bool item_texture_enabled(const struct obs_scene_item *item)
481 {
482 return crop_enabled(&item->crop) || scale_filter_enabled(item) ||
483 (item_is_scene(item) && !item->is_group);
484 }
485
render_item_texture(struct obs_scene_item * item)486 static void render_item_texture(struct obs_scene_item *item)
487 {
488 gs_texture_t *tex = gs_texrender_get_texture(item->item_render);
489 if (!tex) {
490 return;
491 }
492
493 GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_ITEM_TEXTURE,
494 "render_item_texture");
495
496 gs_effect_t *effect = obs->video.default_effect;
497 enum obs_scale_type type = item->scale_filter;
498 uint32_t cx = gs_texture_get_width(tex);
499 uint32_t cy = gs_texture_get_height(tex);
500 const char *tech = "Draw";
501
502 if (type != OBS_SCALE_DISABLE) {
503 if (type == OBS_SCALE_POINT) {
504 gs_eparam_t *image =
505 gs_effect_get_param_by_name(effect, "image");
506 gs_effect_set_next_sampler(image,
507 obs->video.point_sampler);
508
509 } else if (!close_float(item->output_scale.x, 1.0f, EPSILON) ||
510 !close_float(item->output_scale.y, 1.0f, EPSILON)) {
511 gs_eparam_t *scale_param;
512 gs_eparam_t *scale_i_param;
513
514 if (item->output_scale.x < 0.5f ||
515 item->output_scale.y < 0.5f) {
516 effect = obs->video.bilinear_lowres_effect;
517 } else if (type == OBS_SCALE_BICUBIC) {
518 effect = obs->video.bicubic_effect;
519 } else if (type == OBS_SCALE_LANCZOS) {
520 effect = obs->video.lanczos_effect;
521 } else if (type == OBS_SCALE_AREA) {
522 effect = obs->video.area_effect;
523 if ((item->output_scale.x >= 1.0f) &&
524 (item->output_scale.y >= 1.0f))
525 tech = "DrawUpscale";
526 }
527
528 scale_param = gs_effect_get_param_by_name(
529 effect, "base_dimension");
530 if (scale_param) {
531 struct vec2 base_res = {(float)cx, (float)cy};
532
533 gs_effect_set_vec2(scale_param, &base_res);
534 }
535
536 scale_i_param = gs_effect_get_param_by_name(
537 effect, "base_dimension_i");
538 if (scale_i_param) {
539 struct vec2 base_res_i = {1.0f / (float)cx,
540 1.0f / (float)cy};
541
542 gs_effect_set_vec2(scale_i_param, &base_res_i);
543 }
544 }
545 }
546
547 gs_blend_state_push();
548 gs_blend_function(GS_BLEND_ONE, GS_BLEND_INVSRCALPHA);
549
550 while (gs_effect_loop(effect, tech))
551 obs_source_draw(tex, 0, 0, 0, 0, 0);
552
553 gs_blend_state_pop();
554
555 GS_DEBUG_MARKER_END();
556 }
557
are_texcoords_centered(struct matrix4 * m)558 static bool are_texcoords_centered(struct matrix4 *m)
559 {
560 static const struct matrix4 identity = {
561 {1.0f, 0.0f, 0.0f, 0.0f},
562 {0.0f, 1.0f, 0.0f, 0.0f},
563 {0.0f, 0.0f, 1.0f, 0.0f},
564 {0.0f, 0.0f, 0.0f, 1.0f},
565 };
566 struct matrix4 copy = identity;
567 copy.t.x = floorf(m->t.x);
568 copy.t.y = floorf(m->t.y);
569 return memcmp(m, ©, sizeof(*m)) == 0;
570 }
571
render_item(struct obs_scene_item * item)572 static inline void render_item(struct obs_scene_item *item)
573 {
574 GS_DEBUG_MARKER_BEGIN_FORMAT(GS_DEBUG_COLOR_ITEM, "Item: %s",
575 obs_source_get_name(item->source));
576
577 if (item->item_render) {
578 uint32_t width = obs_source_get_width(item->source);
579 uint32_t height = obs_source_get_height(item->source);
580
581 if (!width || !height) {
582 goto cleanup;
583 }
584
585 uint32_t cx = calc_cx(item, width);
586 uint32_t cy = calc_cy(item, height);
587
588 if (cx && cy && gs_texrender_begin(item->item_render, cx, cy)) {
589 float cx_scale = (float)width / (float)cx;
590 float cy_scale = (float)height / (float)cy;
591 struct vec4 clear_color;
592
593 vec4_zero(&clear_color);
594 gs_clear(GS_CLEAR_COLOR, &clear_color, 0.0f, 0);
595 gs_ortho(0.0f, (float)width, 0.0f, (float)height,
596 -100.0f, 100.0f);
597
598 gs_matrix_scale3f(cx_scale, cy_scale, 1.0f);
599 gs_matrix_translate3f(-(float)item->crop.left,
600 -(float)item->crop.top, 0.0f);
601
602 if (item->user_visible &&
603 transition_active(item->show_transition)) {
604 const int cx =
605 obs_source_get_width(item->source);
606 const int cy =
607 obs_source_get_height(item->source);
608 obs_transition_set_size(item->show_transition,
609 cx, cy);
610 obs_source_video_render(item->show_transition);
611 } else if (!item->user_visible &&
612 transition_active(item->hide_transition)) {
613 const int cx =
614 obs_source_get_width(item->source);
615 const int cy =
616 obs_source_get_height(item->source);
617 obs_transition_set_size(item->hide_transition,
618 cx, cy);
619 obs_source_video_render(item->hide_transition);
620 } else {
621 obs_source_set_texcoords_centered(item->source,
622 true);
623 obs_source_video_render(item->source);
624 obs_source_set_texcoords_centered(item->source,
625 false);
626 }
627
628 gs_texrender_end(item->item_render);
629 }
630 }
631
632 const bool previous = gs_set_linear_srgb(true);
633 gs_matrix_push();
634 gs_matrix_mul(&item->draw_transform);
635 if (item->item_render) {
636 render_item_texture(item);
637 } else if (item->user_visible &&
638 transition_active(item->show_transition)) {
639 const int cx = obs_source_get_width(item->source);
640 const int cy = obs_source_get_height(item->source);
641 obs_transition_set_size(item->show_transition, cx, cy);
642 obs_source_video_render(item->show_transition);
643 } else if (!item->user_visible &&
644 transition_active(item->hide_transition)) {
645 const int cx = obs_source_get_width(item->source);
646 const int cy = obs_source_get_height(item->source);
647 obs_transition_set_size(item->hide_transition, cx, cy);
648 obs_source_video_render(item->hide_transition);
649 } else {
650 const bool centered =
651 are_texcoords_centered(&item->draw_transform);
652 obs_source_set_texcoords_centered(item->source, centered);
653 obs_source_video_render(item->source);
654 obs_source_set_texcoords_centered(item->source, false);
655 }
656 gs_matrix_pop();
657 gs_set_linear_srgb(previous);
658
659 cleanup:
660 GS_DEBUG_MARKER_END();
661 }
662
scene_video_tick(void * data,float seconds)663 static void scene_video_tick(void *data, float seconds)
664 {
665 struct obs_scene *scene = data;
666 struct obs_scene_item *item;
667
668 video_lock(scene);
669 item = scene->first_item;
670 while (item) {
671 if (item->item_render)
672 gs_texrender_reset(item->item_render);
673 item = item->next;
674 }
675 video_unlock(scene);
676
677 UNUSED_PARAMETER(seconds);
678 }
679
680 /* assumes video lock */
681 static void
update_transforms_and_prune_sources(obs_scene_t * scene,struct darray * remove_items,obs_sceneitem_t * group_sceneitem)682 update_transforms_and_prune_sources(obs_scene_t *scene,
683 struct darray *remove_items,
684 obs_sceneitem_t *group_sceneitem)
685 {
686 struct obs_scene_item *item = scene->first_item;
687 bool rebuild_group =
688 group_sceneitem &&
689 os_atomic_load_bool(&group_sceneitem->update_group_resize);
690
691 while (item) {
692 if (obs_source_removed(item->source)) {
693 struct obs_scene_item *del_item = item;
694 item = item->next;
695
696 remove_without_release(del_item);
697 darray_push_back(sizeof(struct obs_scene_item *),
698 remove_items, &del_item);
699 rebuild_group = true;
700 continue;
701 }
702
703 if (item->is_group) {
704 obs_scene_t *group_scene = item->source->context.data;
705
706 video_lock(group_scene);
707 update_transforms_and_prune_sources(group_scene,
708 remove_items, item);
709 video_unlock(group_scene);
710 }
711
712 if (os_atomic_load_bool(&item->update_transform) ||
713 source_size_changed(item)) {
714
715 update_item_transform(item, true);
716 rebuild_group = true;
717 }
718
719 item = item->next;
720 }
721
722 if (rebuild_group && group_sceneitem)
723 resize_group(group_sceneitem);
724 }
725
scene_video_render(void * data,gs_effect_t * effect)726 static void scene_video_render(void *data, gs_effect_t *effect)
727 {
728 DARRAY(struct obs_scene_item *) remove_items;
729 struct obs_scene *scene = data;
730 struct obs_scene_item *item;
731
732 da_init(remove_items);
733
734 video_lock(scene);
735
736 if (!scene->is_group) {
737 update_transforms_and_prune_sources(scene, &remove_items.da,
738 NULL);
739 }
740
741 gs_blend_state_push();
742 gs_reset_blend_state();
743
744 item = scene->first_item;
745 while (item) {
746 if (item->user_visible ||
747 transition_active(item->hide_transition))
748 render_item(item);
749
750 item = item->next;
751 }
752
753 gs_blend_state_pop();
754
755 video_unlock(scene);
756
757 for (size_t i = 0; i < remove_items.num; i++)
758 obs_sceneitem_release(remove_items.array[i]);
759 da_free(remove_items);
760
761 UNUSED_PARAMETER(effect);
762 }
763
set_visibility(struct obs_scene_item * item,bool vis)764 static void set_visibility(struct obs_scene_item *item, bool vis)
765 {
766 pthread_mutex_lock(&item->actions_mutex);
767
768 da_resize(item->audio_actions, 0);
769
770 if (os_atomic_load_long(&item->active_refs) > 0) {
771 if (!vis)
772 obs_source_remove_active_child(item->parent->source,
773 item->source);
774 } else if (vis) {
775 obs_source_add_active_child(item->parent->source, item->source);
776 }
777
778 os_atomic_set_long(&item->active_refs, vis ? 1 : 0);
779 item->visible = vis;
780 item->user_visible = vis;
781
782 pthread_mutex_unlock(&item->actions_mutex);
783 }
784
785 static void scene_load(void *data, obs_data_t *settings);
786
scene_load_item(struct obs_scene * scene,obs_data_t * item_data)787 static void scene_load_item(struct obs_scene *scene, obs_data_t *item_data)
788 {
789 const char *name = obs_data_get_string(item_data, "name");
790 obs_source_t *source;
791 const char *scale_filter_str;
792 struct obs_scene_item *item;
793 bool visible;
794 bool lock;
795
796 if (obs_data_get_bool(item_data, "group_item_backup"))
797 return;
798
799 source = obs_get_source_by_name(name);
800 if (!source) {
801 blog(LOG_WARNING,
802 "[scene_load_item] Source %s not "
803 "found!",
804 name);
805 return;
806 }
807
808 item = obs_scene_add(scene, source);
809 if (!item) {
810 blog(LOG_WARNING,
811 "[scene_load_item] Could not add source '%s' "
812 "to scene '%s'!",
813 name, obs_source_get_name(scene->source));
814
815 obs_source_release(source);
816 return;
817 }
818
819 item->is_group = strcmp(source->info.id, group_info.id) == 0;
820
821 obs_data_set_default_int(item_data, "align",
822 OBS_ALIGN_TOP | OBS_ALIGN_LEFT);
823
824 if (obs_data_has_user_value(item_data, "id"))
825 item->id = obs_data_get_int(item_data, "id");
826
827 item->rot = (float)obs_data_get_double(item_data, "rot");
828 item->align = (uint32_t)obs_data_get_int(item_data, "align");
829 visible = obs_data_get_bool(item_data, "visible");
830 lock = obs_data_get_bool(item_data, "locked");
831 obs_data_get_vec2(item_data, "pos", &item->pos);
832 obs_data_get_vec2(item_data, "scale", &item->scale);
833
834 obs_data_release(item->private_settings);
835 item->private_settings =
836 obs_data_get_obj(item_data, "private_settings");
837 if (!item->private_settings)
838 item->private_settings = obs_data_create();
839
840 set_visibility(item, visible);
841 obs_sceneitem_set_locked(item, lock);
842
843 item->bounds_type = (enum obs_bounds_type)obs_data_get_int(
844 item_data, "bounds_type");
845 item->bounds_align =
846 (uint32_t)obs_data_get_int(item_data, "bounds_align");
847 obs_data_get_vec2(item_data, "bounds", &item->bounds);
848
849 item->crop.left = (uint32_t)obs_data_get_int(item_data, "crop_left");
850 item->crop.top = (uint32_t)obs_data_get_int(item_data, "crop_top");
851 item->crop.right = (uint32_t)obs_data_get_int(item_data, "crop_right");
852 item->crop.bottom =
853 (uint32_t)obs_data_get_int(item_data, "crop_bottom");
854
855 scale_filter_str = obs_data_get_string(item_data, "scale_filter");
856 item->scale_filter = OBS_SCALE_DISABLE;
857
858 if (scale_filter_str) {
859 if (astrcmpi(scale_filter_str, "point") == 0)
860 item->scale_filter = OBS_SCALE_POINT;
861 else if (astrcmpi(scale_filter_str, "bilinear") == 0)
862 item->scale_filter = OBS_SCALE_BILINEAR;
863 else if (astrcmpi(scale_filter_str, "bicubic") == 0)
864 item->scale_filter = OBS_SCALE_BICUBIC;
865 else if (astrcmpi(scale_filter_str, "lanczos") == 0)
866 item->scale_filter = OBS_SCALE_LANCZOS;
867 else if (astrcmpi(scale_filter_str, "area") == 0)
868 item->scale_filter = OBS_SCALE_AREA;
869 }
870
871 obs_data_t *show_data = obs_data_get_obj(item_data, "show_transition");
872 if (show_data) {
873 obs_sceneitem_transition_load(item, show_data, true);
874 obs_data_release(show_data);
875 }
876
877 obs_data_t *hide_data = obs_data_get_obj(item_data, "hide_transition");
878 if (hide_data) {
879 obs_sceneitem_transition_load(item, hide_data, false);
880 obs_data_release(hide_data);
881 }
882
883 if (item->item_render && !item_texture_enabled(item)) {
884 obs_enter_graphics();
885 gs_texrender_destroy(item->item_render);
886 item->item_render = NULL;
887 obs_leave_graphics();
888
889 } else if (!item->item_render && item_texture_enabled(item)) {
890 obs_enter_graphics();
891 item->item_render = gs_texrender_create(GS_RGBA, GS_ZS_NONE);
892 obs_leave_graphics();
893 }
894
895 obs_source_release(source);
896
897 update_item_transform(item, false);
898 }
899
scene_load(void * data,obs_data_t * settings)900 static void scene_load(void *data, obs_data_t *settings)
901 {
902 struct obs_scene *scene = data;
903 obs_data_array_t *items = obs_data_get_array(settings, "items");
904 size_t count, i;
905
906 remove_all_items(scene);
907
908 if (!items)
909 return;
910
911 count = obs_data_array_count(items);
912
913 for (i = 0; i < count; i++) {
914 obs_data_t *item_data = obs_data_array_item(items, i);
915 scene_load_item(scene, item_data);
916 obs_data_release(item_data);
917 }
918
919 if (obs_data_has_user_value(settings, "id_counter"))
920 scene->id_counter = obs_data_get_int(settings, "id_counter");
921
922 if (obs_data_get_bool(settings, "custom_size")) {
923 scene->cx = (uint32_t)obs_data_get_int(settings, "cx");
924 scene->cy = (uint32_t)obs_data_get_int(settings, "cy");
925 scene->custom_size = true;
926 }
927
928 obs_data_array_release(items);
929 }
930
931 static void scene_save(void *data, obs_data_t *settings);
932
scene_save_item(obs_data_array_t * array,struct obs_scene_item * item,struct obs_scene_item * backup_group)933 static void scene_save_item(obs_data_array_t *array,
934 struct obs_scene_item *item,
935 struct obs_scene_item *backup_group)
936 {
937 obs_data_t *item_data = obs_data_create();
938 const char *name = obs_source_get_name(item->source);
939 const char *scale_filter;
940 struct vec2 pos = item->pos;
941 struct vec2 scale = item->scale;
942 float rot = item->rot;
943
944 if (backup_group) {
945 get_ungrouped_transform(backup_group, &pos, &scale, &rot);
946 }
947
948 obs_data_set_string(item_data, "name", name);
949 obs_data_set_bool(item_data, "visible", item->user_visible);
950 obs_data_set_bool(item_data, "locked", item->locked);
951 obs_data_set_double(item_data, "rot", rot);
952 obs_data_set_vec2(item_data, "pos", &pos);
953 obs_data_set_vec2(item_data, "scale", &scale);
954 obs_data_set_int(item_data, "align", (int)item->align);
955 obs_data_set_int(item_data, "bounds_type", (int)item->bounds_type);
956 obs_data_set_int(item_data, "bounds_align", (int)item->bounds_align);
957 obs_data_set_vec2(item_data, "bounds", &item->bounds);
958 obs_data_set_int(item_data, "crop_left", (int)item->crop.left);
959 obs_data_set_int(item_data, "crop_top", (int)item->crop.top);
960 obs_data_set_int(item_data, "crop_right", (int)item->crop.right);
961 obs_data_set_int(item_data, "crop_bottom", (int)item->crop.bottom);
962 obs_data_set_int(item_data, "id", item->id);
963 obs_data_set_bool(item_data, "group_item_backup", !!backup_group);
964
965 if (item->is_group) {
966 obs_scene_t *group_scene = item->source->context.data;
967 obs_sceneitem_t *group_item;
968
969 /* save group items as part of main scene, but ignored.
970 * causes an automatic ungroup if scene collection file
971 * is loaded in previous versions. */
972 full_lock(group_scene);
973
974 group_item = group_scene->first_item;
975 while (group_item) {
976 scene_save_item(array, group_item, item);
977 group_item = group_item->next;
978 }
979
980 full_unlock(group_scene);
981 }
982
983 if (item->scale_filter == OBS_SCALE_POINT)
984 scale_filter = "point";
985 else if (item->scale_filter == OBS_SCALE_BILINEAR)
986 scale_filter = "bilinear";
987 else if (item->scale_filter == OBS_SCALE_BICUBIC)
988 scale_filter = "bicubic";
989 else if (item->scale_filter == OBS_SCALE_LANCZOS)
990 scale_filter = "lanczos";
991 else if (item->scale_filter == OBS_SCALE_AREA)
992 scale_filter = "area";
993 else
994 scale_filter = "disable";
995
996 obs_data_set_string(item_data, "scale_filter", scale_filter);
997
998 obs_data_t *show_data = obs_sceneitem_transition_save(item, true);
999 obs_data_set_obj(item_data, "show_transition", show_data);
1000 obs_data_release(show_data);
1001
1002 obs_data_t *hide_data = obs_sceneitem_transition_save(item, false);
1003 obs_data_set_obj(item_data, "hide_transition", hide_data);
1004 obs_data_release(hide_data);
1005
1006 obs_data_set_obj(item_data, "private_settings", item->private_settings);
1007
1008 obs_data_array_push_back(array, item_data);
1009 obs_data_release(item_data);
1010 }
1011
scene_save(void * data,obs_data_t * settings)1012 static void scene_save(void *data, obs_data_t *settings)
1013 {
1014 struct obs_scene *scene = data;
1015 obs_data_array_t *array = obs_data_array_create();
1016 struct obs_scene_item *item;
1017
1018 full_lock(scene);
1019
1020 item = scene->first_item;
1021 while (item) {
1022 scene_save_item(array, item, NULL);
1023 item = item->next;
1024 }
1025
1026 obs_data_set_int(settings, "id_counter", scene->id_counter);
1027 obs_data_set_bool(settings, "custom_size", scene->custom_size);
1028 if (scene->custom_size) {
1029 obs_data_set_int(settings, "cx", scene->cx);
1030 obs_data_set_int(settings, "cy", scene->cy);
1031 }
1032
1033 full_unlock(scene);
1034
1035 obs_data_set_array(settings, "items", array);
1036 obs_data_array_release(array);
1037 }
1038
scene_getwidth(void * data)1039 static uint32_t scene_getwidth(void *data)
1040 {
1041 obs_scene_t *scene = data;
1042 return scene->custom_size ? scene->cx : obs->video.base_width;
1043 }
1044
scene_getheight(void * data)1045 static uint32_t scene_getheight(void *data)
1046 {
1047 obs_scene_t *scene = data;
1048 return scene->custom_size ? scene->cy : obs->video.base_height;
1049 }
1050
apply_scene_item_audio_actions(struct obs_scene_item * item,float * buf,uint64_t ts,size_t sample_rate)1051 static void apply_scene_item_audio_actions(struct obs_scene_item *item,
1052 float *buf, uint64_t ts,
1053 size_t sample_rate)
1054 {
1055 bool cur_visible = item->visible;
1056 uint64_t frame_num = 0;
1057 size_t deref_count = 0;
1058
1059 pthread_mutex_lock(&item->actions_mutex);
1060
1061 for (size_t i = 0; i < item->audio_actions.num; i++) {
1062 struct item_action action = item->audio_actions.array[i];
1063 uint64_t timestamp = action.timestamp;
1064 uint64_t new_frame_num;
1065
1066 if (timestamp < ts)
1067 timestamp = ts;
1068
1069 new_frame_num = util_mul_div64(timestamp - ts, sample_rate,
1070 1000000000ULL);
1071
1072 if (ts && new_frame_num >= AUDIO_OUTPUT_FRAMES)
1073 break;
1074
1075 da_erase(item->audio_actions, i--);
1076
1077 item->visible = action.visible;
1078 if (!item->visible)
1079 deref_count++;
1080
1081 if (buf && new_frame_num > frame_num) {
1082 for (; frame_num < new_frame_num; frame_num++)
1083 buf[frame_num] = cur_visible ? 1.0f : 0.0f;
1084 }
1085
1086 cur_visible = item->visible;
1087 }
1088
1089 if (buf) {
1090 for (; frame_num < AUDIO_OUTPUT_FRAMES; frame_num++)
1091 buf[frame_num] = cur_visible ? 1.0f : 0.0f;
1092 }
1093
1094 pthread_mutex_unlock(&item->actions_mutex);
1095
1096 while (deref_count--) {
1097 if (os_atomic_dec_long(&item->active_refs) == 0) {
1098 obs_source_remove_active_child(item->parent->source,
1099 item->source);
1100 }
1101 }
1102 }
1103
apply_scene_item_volume(struct obs_scene_item * item,float * buf,uint64_t ts,size_t sample_rate)1104 static bool apply_scene_item_volume(struct obs_scene_item *item, float *buf,
1105 uint64_t ts, size_t sample_rate)
1106 {
1107 bool actions_pending;
1108 struct item_action action;
1109
1110 pthread_mutex_lock(&item->actions_mutex);
1111
1112 actions_pending = item->audio_actions.num > 0;
1113 if (actions_pending)
1114 action = item->audio_actions.array[0];
1115
1116 pthread_mutex_unlock(&item->actions_mutex);
1117
1118 if (actions_pending) {
1119 uint64_t duration = util_mul_div64(AUDIO_OUTPUT_FRAMES,
1120 1000000000ULL, sample_rate);
1121
1122 if (!ts || action.timestamp < (ts + duration)) {
1123 apply_scene_item_audio_actions(item, buf, ts,
1124 sample_rate);
1125 return true;
1126 }
1127 }
1128
1129 return false;
1130 }
1131
process_all_audio_actions(struct obs_scene_item * item,size_t sample_rate)1132 static void process_all_audio_actions(struct obs_scene_item *item,
1133 size_t sample_rate)
1134 {
1135 while (apply_scene_item_volume(item, NULL, 0, sample_rate))
1136 ;
1137 }
1138
mix_audio_with_buf(float * p_out,float * p_in,float * buf_in,size_t pos,size_t count)1139 static void mix_audio_with_buf(float *p_out, float *p_in, float *buf_in,
1140 size_t pos, size_t count)
1141 {
1142 register float *out = p_out;
1143 register float *buf = buf_in + pos;
1144 register float *in = p_in + pos;
1145 register float *end = in + count;
1146
1147 while (in < end)
1148 *(out++) += *(in++) * *(buf++);
1149 }
1150
mix_audio(float * p_out,float * p_in,size_t pos,size_t count)1151 static inline void mix_audio(float *p_out, float *p_in, size_t pos,
1152 size_t count)
1153 {
1154 register float *out = p_out;
1155 register float *in = p_in + pos;
1156 register float *end = in + count;
1157
1158 while (in < end)
1159 *(out++) += *(in++);
1160 }
1161
scene_audio_render(void * data,uint64_t * ts_out,struct obs_source_audio_mix * audio_output,uint32_t mixers,size_t channels,size_t sample_rate)1162 static bool scene_audio_render(void *data, uint64_t *ts_out,
1163 struct obs_source_audio_mix *audio_output,
1164 uint32_t mixers, size_t channels,
1165 size_t sample_rate)
1166 {
1167 uint64_t timestamp = 0;
1168 float buf[AUDIO_OUTPUT_FRAMES];
1169 struct obs_source_audio_mix child_audio;
1170 struct obs_scene *scene = data;
1171 struct obs_scene_item *item;
1172
1173 audio_lock(scene);
1174
1175 item = scene->first_item;
1176 while (item) {
1177 struct obs_source *source;
1178 if (item->visible && transition_active(item->show_transition))
1179 source = item->show_transition;
1180 else if (!item->visible &&
1181 transition_active(item->hide_transition))
1182 source = item->hide_transition;
1183 else
1184 source = item->source;
1185 if (!obs_source_audio_pending(source) &&
1186 (item->visible ||
1187 transition_active(item->hide_transition))) {
1188 uint64_t source_ts =
1189 obs_source_get_audio_timestamp(source);
1190
1191 if (source_ts && (!timestamp || source_ts < timestamp))
1192 timestamp = source_ts;
1193 }
1194
1195 item = item->next;
1196 }
1197
1198 if (!timestamp) {
1199 /* just process all pending audio actions if no audio playing,
1200 * otherwise audio actions will just never be processed */
1201 item = scene->first_item;
1202 while (item) {
1203 process_all_audio_actions(item, sample_rate);
1204 item = item->next;
1205 }
1206
1207 audio_unlock(scene);
1208 return false;
1209 }
1210
1211 item = scene->first_item;
1212 while (item) {
1213 uint64_t source_ts;
1214 size_t pos, count;
1215 bool apply_buf;
1216 struct obs_source *source;
1217 if (item->visible && transition_active(item->show_transition))
1218 source = item->show_transition;
1219 else if (!item->visible &&
1220 transition_active(item->hide_transition))
1221 source = item->hide_transition;
1222 else
1223 source = item->source;
1224
1225 apply_buf = apply_scene_item_volume(item, buf, timestamp,
1226 sample_rate);
1227
1228 if (obs_source_audio_pending(source)) {
1229 item = item->next;
1230 continue;
1231 }
1232
1233 source_ts = obs_source_get_audio_timestamp(source);
1234 if (!source_ts) {
1235 item = item->next;
1236 continue;
1237 }
1238
1239 pos = (size_t)ns_to_audio_frames(sample_rate,
1240 source_ts - timestamp);
1241 count = AUDIO_OUTPUT_FRAMES - pos;
1242
1243 if (!apply_buf && !item->visible &&
1244 !transition_active(item->hide_transition)) {
1245 item = item->next;
1246 continue;
1247 }
1248
1249 obs_source_get_audio_mix(source, &child_audio);
1250
1251 for (size_t mix = 0; mix < MAX_AUDIO_MIXES; mix++) {
1252 if ((mixers & (1 << mix)) == 0)
1253 continue;
1254
1255 for (size_t ch = 0; ch < channels; ch++) {
1256 float *out = audio_output->output[mix].data[ch];
1257 float *in = child_audio.output[mix].data[ch];
1258
1259 if (apply_buf)
1260 mix_audio_with_buf(out, in, buf, pos,
1261 count);
1262 else
1263 mix_audio(out, in, pos, count);
1264 }
1265 }
1266
1267 item = item->next;
1268 }
1269
1270 *ts_out = timestamp;
1271 audio_unlock(scene);
1272
1273 return true;
1274 }
1275
1276 const struct obs_source_info scene_info = {
1277 .id = "scene",
1278 .type = OBS_SOURCE_TYPE_SCENE,
1279 .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW |
1280 OBS_SOURCE_COMPOSITE | OBS_SOURCE_DO_NOT_DUPLICATE |
1281 OBS_SOURCE_SRGB,
1282 .get_name = scene_getname,
1283 .create = scene_create,
1284 .destroy = scene_destroy,
1285 .video_tick = scene_video_tick,
1286 .video_render = scene_video_render,
1287 .audio_render = scene_audio_render,
1288 .get_width = scene_getwidth,
1289 .get_height = scene_getheight,
1290 .load = scene_load,
1291 .save = scene_save,
1292 .enum_active_sources = scene_enum_active_sources,
1293 .enum_all_sources = scene_enum_all_sources};
1294
1295 const struct obs_source_info group_info = {
1296 .id = "group",
1297 .type = OBS_SOURCE_TYPE_SCENE,
1298 .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW |
1299 OBS_SOURCE_COMPOSITE | OBS_SOURCE_SRGB,
1300 .get_name = group_getname,
1301 .create = scene_create,
1302 .destroy = scene_destroy,
1303 .video_tick = scene_video_tick,
1304 .video_render = scene_video_render,
1305 .audio_render = scene_audio_render,
1306 .get_width = scene_getwidth,
1307 .get_height = scene_getheight,
1308 .load = scene_load,
1309 .save = scene_save,
1310 .enum_active_sources = scene_enum_active_sources,
1311 .enum_all_sources = scene_enum_all_sources};
1312
create_id(const char * id,const char * name)1313 static inline obs_scene_t *create_id(const char *id, const char *name)
1314 {
1315 struct obs_source *source = obs_source_create(id, name, NULL, NULL);
1316 return source->context.data;
1317 }
1318
create_private_id(const char * id,const char * name)1319 static inline obs_scene_t *create_private_id(const char *id, const char *name)
1320 {
1321 struct obs_source *source = obs_source_create_private(id, name, NULL);
1322 return source->context.data;
1323 }
1324
obs_scene_create(const char * name)1325 obs_scene_t *obs_scene_create(const char *name)
1326 {
1327 return create_id("scene", name);
1328 }
1329
obs_scene_create_private(const char * name)1330 obs_scene_t *obs_scene_create_private(const char *name)
1331 {
1332 return create_private_id("scene", name);
1333 }
1334
get_child_at_idx(obs_scene_t * scene,size_t idx)1335 static obs_source_t *get_child_at_idx(obs_scene_t *scene, size_t idx)
1336 {
1337 struct obs_scene_item *item = scene->first_item;
1338
1339 while (item && idx--)
1340 item = item->next;
1341 return item ? item->source : NULL;
1342 }
1343
dup_child(struct darray * array,size_t idx,obs_scene_t * new_scene,bool private)1344 static inline obs_source_t *dup_child(struct darray *array, size_t idx,
1345 obs_scene_t *new_scene, bool private)
1346 {
1347 DARRAY(struct obs_scene_item *) old_items;
1348 obs_source_t *source;
1349
1350 old_items.da = *array;
1351
1352 source = old_items.array[idx]->source;
1353
1354 /* if the old item is referenced more than once in the old scene,
1355 * make sure they're referenced similarly in the new scene to reduce
1356 * load times */
1357 for (size_t i = 0; i < idx; i++) {
1358 struct obs_scene_item *item = old_items.array[i];
1359 if (item->source == source) {
1360 source = get_child_at_idx(new_scene, i);
1361 obs_source_addref(source);
1362 return source;
1363 }
1364 }
1365
1366 return obs_source_duplicate(
1367 source, private ? obs_source_get_name(source) : NULL, private);
1368 }
1369
new_ref(obs_source_t * source)1370 static inline obs_source_t *new_ref(obs_source_t *source)
1371 {
1372 obs_source_addref(source);
1373 return source;
1374 }
1375
duplicate_item_data(struct obs_scene_item * dst,struct obs_scene_item * src,bool defer_texture_update,bool duplicate_hotkeys,bool duplicate_private_data)1376 static inline void duplicate_item_data(struct obs_scene_item *dst,
1377 struct obs_scene_item *src,
1378 bool defer_texture_update,
1379 bool duplicate_hotkeys,
1380 bool duplicate_private_data)
1381 {
1382 struct obs_scene *dst_scene = dst->parent;
1383
1384 if (!src->user_visible)
1385 set_visibility(dst, false);
1386
1387 dst->selected = src->selected;
1388 dst->pos = src->pos;
1389 dst->rot = src->rot;
1390 dst->scale = src->scale;
1391 dst->align = src->align;
1392 dst->last_width = src->last_width;
1393 dst->last_height = src->last_height;
1394 dst->output_scale = src->output_scale;
1395 dst->scale_filter = src->scale_filter;
1396 dst->box_transform = src->box_transform;
1397 dst->box_scale = src->box_scale;
1398 dst->draw_transform = src->draw_transform;
1399 dst->bounds_type = src->bounds_type;
1400 dst->bounds_align = src->bounds_align;
1401 dst->bounds = src->bounds;
1402
1403 if (src->show_transition) {
1404 obs_source_t *transition = obs_source_duplicate(
1405 src->show_transition,
1406 obs_source_get_name(src->show_transition), true);
1407 obs_sceneitem_set_show_transition(dst, transition);
1408 obs_source_release(transition);
1409 }
1410 if (src->hide_transition) {
1411 obs_source_t *transition = obs_source_duplicate(
1412 src->hide_transition,
1413 obs_source_get_name(src->hide_transition), true);
1414 obs_sceneitem_set_hide_transition(dst, transition);
1415 obs_source_release(transition);
1416 }
1417 dst->show_transition_duration = src->show_transition_duration;
1418 dst->hide_transition_duration = src->hide_transition_duration;
1419
1420 if (duplicate_hotkeys && !dst_scene->source->context.private) {
1421 obs_data_array_t *data0 = NULL;
1422 obs_data_array_t *data1 = NULL;
1423
1424 obs_hotkey_pair_save(src->toggle_visibility, &data0, &data1);
1425 obs_hotkey_pair_load(dst->toggle_visibility, data0, data1);
1426
1427 obs_data_array_release(data0);
1428 obs_data_array_release(data1);
1429 }
1430
1431 obs_sceneitem_set_crop(dst, &src->crop);
1432 obs_sceneitem_set_locked(dst, src->locked);
1433
1434 if (defer_texture_update) {
1435 os_atomic_set_bool(&dst->update_transform, true);
1436 } else {
1437 if (!dst->item_render && item_texture_enabled(dst)) {
1438 obs_enter_graphics();
1439 dst->item_render =
1440 gs_texrender_create(GS_RGBA, GS_ZS_NONE);
1441 obs_leave_graphics();
1442 }
1443 }
1444
1445 if (duplicate_private_data) {
1446 obs_data_apply(dst->private_settings, src->private_settings);
1447 }
1448 }
1449
obs_scene_duplicate(obs_scene_t * scene,const char * name,enum obs_scene_duplicate_type type)1450 obs_scene_t *obs_scene_duplicate(obs_scene_t *scene, const char *name,
1451 enum obs_scene_duplicate_type type)
1452 {
1453 bool make_unique = ((int)type & (1 << 0)) != 0;
1454 bool make_private = ((int)type & (1 << 1)) != 0;
1455 DARRAY(struct obs_scene_item *) items;
1456 struct obs_scene *new_scene;
1457 struct obs_scene_item *item;
1458 struct obs_source *source;
1459
1460 da_init(items);
1461
1462 if (!obs_ptr_valid(scene, "obs_scene_duplicate"))
1463 return NULL;
1464
1465 /* --------------------------------- */
1466
1467 full_lock(scene);
1468
1469 item = scene->first_item;
1470 while (item) {
1471 da_push_back(items, &item);
1472 obs_sceneitem_addref(item);
1473 item = item->next;
1474 }
1475
1476 full_unlock(scene);
1477
1478 /* --------------------------------- */
1479
1480 new_scene = make_private
1481 ? create_private_id(scene->source->info.id, name)
1482 : create_id(scene->source->info.id, name);
1483
1484 obs_source_copy_filters(new_scene->source, scene->source);
1485
1486 obs_data_apply(new_scene->source->private_settings,
1487 scene->source->private_settings);
1488
1489 /* never duplicate sub-items for groups */
1490 if (scene->is_group)
1491 make_unique = false;
1492
1493 for (size_t i = 0; i < items.num; i++) {
1494 item = items.array[i];
1495 source = make_unique ? dup_child(&items.da, i, new_scene,
1496 make_private)
1497 : new_ref(item->source);
1498
1499 if (source) {
1500 struct obs_scene_item *new_item =
1501 obs_scene_add(new_scene, source);
1502
1503 if (!new_item) {
1504 obs_source_release(source);
1505 continue;
1506 }
1507
1508 duplicate_item_data(new_item, item, false, false,
1509 false);
1510
1511 obs_source_release(source);
1512 }
1513 }
1514
1515 for (size_t i = 0; i < items.num; i++)
1516 obs_sceneitem_release(items.array[i]);
1517
1518 if (new_scene->is_group)
1519 resize_scene(new_scene);
1520
1521 da_free(items);
1522 return new_scene;
1523 }
1524
obs_scene_addref(obs_scene_t * scene)1525 void obs_scene_addref(obs_scene_t *scene)
1526 {
1527 if (scene)
1528 obs_source_addref(scene->source);
1529 }
1530
obs_scene_release(obs_scene_t * scene)1531 void obs_scene_release(obs_scene_t *scene)
1532 {
1533 if (scene)
1534 obs_source_release(scene->source);
1535 }
1536
obs_scene_get_source(const obs_scene_t * scene)1537 obs_source_t *obs_scene_get_source(const obs_scene_t *scene)
1538 {
1539 return scene ? scene->source : NULL;
1540 }
1541
obs_scene_from_source(const obs_source_t * source)1542 obs_scene_t *obs_scene_from_source(const obs_source_t *source)
1543 {
1544 if (!source || strcmp(source->info.id, scene_info.id) != 0)
1545 return NULL;
1546
1547 return source->context.data;
1548 }
1549
obs_group_from_source(const obs_source_t * source)1550 obs_scene_t *obs_group_from_source(const obs_source_t *source)
1551 {
1552 if (!source || strcmp(source->info.id, group_info.id) != 0)
1553 return NULL;
1554
1555 return source->context.data;
1556 }
1557
obs_scene_find_source(obs_scene_t * scene,const char * name)1558 obs_sceneitem_t *obs_scene_find_source(obs_scene_t *scene, const char *name)
1559 {
1560 struct obs_scene_item *item;
1561
1562 if (!scene)
1563 return NULL;
1564
1565 full_lock(scene);
1566
1567 item = scene->first_item;
1568 while (item) {
1569 if (strcmp(item->source->context.name, name) == 0)
1570 break;
1571
1572 item = item->next;
1573 }
1574
1575 full_unlock(scene);
1576
1577 return item;
1578 }
1579
obs_scene_find_source_recursive(obs_scene_t * scene,const char * name)1580 obs_sceneitem_t *obs_scene_find_source_recursive(obs_scene_t *scene,
1581 const char *name)
1582 {
1583 struct obs_scene_item *item;
1584
1585 if (!scene)
1586 return NULL;
1587
1588 full_lock(scene);
1589
1590 item = scene->first_item;
1591 while (item) {
1592 if (strcmp(item->source->context.name, name) == 0)
1593 break;
1594
1595 if (item->is_group) {
1596 obs_scene_t *group = item->source->context.data;
1597 obs_sceneitem_t *child =
1598 obs_scene_find_source(group, name);
1599 if (child) {
1600 item = child;
1601 break;
1602 }
1603 }
1604
1605 item = item->next;
1606 }
1607
1608 full_unlock(scene);
1609
1610 return item;
1611 }
1612
1613 struct sceneitem_check {
1614 obs_source_t *source_in;
1615 obs_sceneitem_t *item_out;
1616 };
1617
check_sceneitem_exists(obs_scene_t * scene,obs_sceneitem_t * item,void * vp_check)1618 bool check_sceneitem_exists(obs_scene_t *scene, obs_sceneitem_t *item,
1619 void *vp_check)
1620 {
1621 UNUSED_PARAMETER(scene);
1622 struct sceneitem_check *check = (struct sceneitem_check *)vp_check;
1623 if (obs_sceneitem_get_source(item) == check->source_in) {
1624 check->item_out = item;
1625 obs_sceneitem_addref(item);
1626 return false;
1627 }
1628
1629 return true;
1630 }
1631
obs_scene_sceneitem_from_source(obs_scene_t * scene,obs_source_t * source)1632 obs_sceneitem_t *obs_scene_sceneitem_from_source(obs_scene_t *scene,
1633 obs_source_t *source)
1634 {
1635 struct sceneitem_check check = {source, NULL};
1636 obs_scene_enum_items(scene, check_sceneitem_exists, (void *)&check);
1637 return check.item_out;
1638 }
1639
obs_scene_find_sceneitem_by_id(obs_scene_t * scene,int64_t id)1640 obs_sceneitem_t *obs_scene_find_sceneitem_by_id(obs_scene_t *scene, int64_t id)
1641 {
1642 struct obs_scene_item *item;
1643
1644 if (!scene)
1645 return NULL;
1646
1647 full_lock(scene);
1648
1649 item = scene->first_item;
1650 while (item) {
1651 if (item->id == id)
1652 break;
1653
1654 item = item->next;
1655 }
1656
1657 full_unlock(scene);
1658
1659 return item;
1660 }
1661
obs_scene_enum_items(obs_scene_t * scene,bool (* callback)(obs_scene_t *,obs_sceneitem_t *,void *),void * param)1662 void obs_scene_enum_items(obs_scene_t *scene,
1663 bool (*callback)(obs_scene_t *, obs_sceneitem_t *,
1664 void *),
1665 void *param)
1666 {
1667 struct obs_scene_item *item;
1668
1669 if (!scene || !callback)
1670 return;
1671
1672 full_lock(scene);
1673
1674 item = scene->first_item;
1675 while (item) {
1676 struct obs_scene_item *next = item->next;
1677
1678 obs_sceneitem_addref(item);
1679
1680 if (!callback(scene, item, param)) {
1681 obs_sceneitem_release(item);
1682 break;
1683 }
1684
1685 obs_sceneitem_release(item);
1686
1687 item = next;
1688 }
1689
1690 full_unlock(scene);
1691 }
1692
sceneitem_get_ref(obs_sceneitem_t * si)1693 static obs_sceneitem_t *sceneitem_get_ref(obs_sceneitem_t *si)
1694 {
1695 long owners = os_atomic_load_long(&si->ref);
1696 while (owners > 0) {
1697 if (os_atomic_compare_exchange_long(&si->ref, &owners,
1698 owners + 1)) {
1699 return si;
1700 }
1701 }
1702 return NULL;
1703 }
1704
hotkey_show_sceneitem(void * data,obs_hotkey_pair_id id,obs_hotkey_t * hotkey,bool pressed)1705 static bool hotkey_show_sceneitem(void *data, obs_hotkey_pair_id id,
1706 obs_hotkey_t *hotkey, bool pressed)
1707 {
1708 UNUSED_PARAMETER(id);
1709 UNUSED_PARAMETER(hotkey);
1710
1711 obs_sceneitem_t *si = sceneitem_get_ref(data);
1712 if (pressed && si && !si->user_visible) {
1713 obs_sceneitem_set_visible(si, true);
1714 obs_sceneitem_release(si);
1715 return true;
1716 }
1717
1718 obs_sceneitem_release(si);
1719 return false;
1720 }
1721
hotkey_hide_sceneitem(void * data,obs_hotkey_pair_id id,obs_hotkey_t * hotkey,bool pressed)1722 static bool hotkey_hide_sceneitem(void *data, obs_hotkey_pair_id id,
1723 obs_hotkey_t *hotkey, bool pressed)
1724 {
1725 UNUSED_PARAMETER(id);
1726 UNUSED_PARAMETER(hotkey);
1727
1728 obs_sceneitem_t *si = sceneitem_get_ref(data);
1729 if (pressed && si && si->user_visible) {
1730 obs_sceneitem_set_visible(si, false);
1731 obs_sceneitem_release(si);
1732 return true;
1733 }
1734
1735 obs_sceneitem_release(si);
1736 return false;
1737 }
1738
init_hotkeys(obs_scene_t * scene,obs_sceneitem_t * item,const char * name)1739 static void init_hotkeys(obs_scene_t *scene, obs_sceneitem_t *item,
1740 const char *name)
1741 {
1742 struct dstr show = {0};
1743 struct dstr hide = {0};
1744 struct dstr show_desc = {0};
1745 struct dstr hide_desc = {0};
1746
1747 dstr_copy(&show, "libobs.show_scene_item.%1");
1748 dstr_replace(&show, "%1", name);
1749 dstr_copy(&hide, "libobs.hide_scene_item.%1");
1750 dstr_replace(&hide, "%1", name);
1751
1752 dstr_copy(&show_desc, obs->hotkeys.sceneitem_show);
1753 dstr_replace(&show_desc, "%1", name);
1754 dstr_copy(&hide_desc, obs->hotkeys.sceneitem_hide);
1755 dstr_replace(&hide_desc, "%1", name);
1756
1757 item->toggle_visibility = obs_hotkey_pair_register_source(
1758 scene->source, show.array, show_desc.array, hide.array,
1759 hide_desc.array, hotkey_show_sceneitem, hotkey_hide_sceneitem,
1760 item, item);
1761
1762 dstr_free(&show);
1763 dstr_free(&hide);
1764 dstr_free(&show_desc);
1765 dstr_free(&hide_desc);
1766 }
1767
sceneitem_rename_hotkey(const obs_sceneitem_t * scene_item,const char * new_name)1768 static void sceneitem_rename_hotkey(const obs_sceneitem_t *scene_item,
1769 const char *new_name)
1770 {
1771 struct dstr show = {0};
1772 struct dstr hide = {0};
1773 struct dstr show_desc = {0};
1774 struct dstr hide_desc = {0};
1775
1776 dstr_copy(&show, "libobs.show_scene_item.%1");
1777 dstr_replace(&show, "%1", new_name);
1778 dstr_copy(&hide, "libobs.hide_scene_item.%1");
1779 dstr_replace(&hide, "%1", new_name);
1780
1781 obs_hotkey_pair_set_names(scene_item->toggle_visibility, show.array,
1782 hide.array);
1783
1784 dstr_copy(&show_desc, obs->hotkeys.sceneitem_show);
1785 dstr_replace(&show_desc, "%1", new_name);
1786 dstr_copy(&hide_desc, obs->hotkeys.sceneitem_hide);
1787 dstr_replace(&hide_desc, "%1", new_name);
1788
1789 obs_hotkey_pair_set_descriptions(scene_item->toggle_visibility,
1790 show_desc.array, hide_desc.array);
1791
1792 dstr_free(&show);
1793 dstr_free(&hide);
1794 dstr_free(&show_desc);
1795 dstr_free(&hide_desc);
1796 }
1797
sceneitem_renamed(void * param,calldata_t * data)1798 static void sceneitem_renamed(void *param, calldata_t *data)
1799 {
1800 obs_sceneitem_t *scene_item = param;
1801 const char *name = calldata_string(data, "new_name");
1802
1803 sceneitem_rename_hotkey(scene_item, name);
1804 }
1805
source_has_audio(obs_source_t * source)1806 static inline bool source_has_audio(obs_source_t *source)
1807 {
1808 return (source->info.output_flags &
1809 (OBS_SOURCE_AUDIO | OBS_SOURCE_COMPOSITE)) != 0;
1810 }
1811
obs_scene_add_internal(obs_scene_t * scene,obs_source_t * source,obs_sceneitem_t * insert_after,bool create_texture)1812 static obs_sceneitem_t *obs_scene_add_internal(obs_scene_t *scene,
1813 obs_source_t *source,
1814 obs_sceneitem_t *insert_after,
1815 bool create_texture)
1816 {
1817 struct obs_scene_item *last;
1818 struct obs_scene_item *item;
1819 pthread_mutex_t mutex;
1820
1821 struct item_action action = {.visible = true,
1822 .timestamp = os_gettime_ns()};
1823
1824 if (!scene)
1825 return NULL;
1826
1827 if (!source) {
1828 blog(LOG_ERROR, "Tried to add a NULL source to a scene");
1829 return NULL;
1830 }
1831
1832 if (source->removed) {
1833 blog(LOG_WARNING, "Tried to add a removed source to a scene");
1834 return NULL;
1835 }
1836
1837 if (pthread_mutex_init(&mutex, NULL) != 0) {
1838 blog(LOG_WARNING, "Failed to create scene item mutex");
1839 return NULL;
1840 }
1841
1842 if (!obs_source_add_active_child(scene->source, source)) {
1843 blog(LOG_WARNING, "Failed to add source to scene due to "
1844 "infinite source recursion");
1845 pthread_mutex_destroy(&mutex);
1846 return NULL;
1847 }
1848
1849 item = bzalloc(sizeof(struct obs_scene_item));
1850 item->source = source;
1851 item->id = ++scene->id_counter;
1852 item->parent = scene;
1853 item->ref = 1;
1854 item->align = OBS_ALIGN_TOP | OBS_ALIGN_LEFT;
1855 item->actions_mutex = mutex;
1856 item->user_visible = true;
1857 item->locked = false;
1858 item->is_group = strcmp(source->info.id, group_info.id) == 0;
1859 item->private_settings = obs_data_create();
1860 item->toggle_visibility = OBS_INVALID_HOTKEY_PAIR_ID;
1861 os_atomic_set_long(&item->active_refs, 1);
1862 vec2_set(&item->scale, 1.0f, 1.0f);
1863 matrix4_identity(&item->draw_transform);
1864 matrix4_identity(&item->box_transform);
1865
1866 obs_source_addref(source);
1867
1868 if (source_has_audio(source)) {
1869 item->visible = false;
1870 da_push_back(item->audio_actions, &action);
1871 } else {
1872 item->visible = true;
1873 }
1874
1875 if (create_texture && item_texture_enabled(item)) {
1876 obs_enter_graphics();
1877 item->item_render = gs_texrender_create(GS_RGBA, GS_ZS_NONE);
1878 obs_leave_graphics();
1879 }
1880
1881 full_lock(scene);
1882
1883 if (insert_after) {
1884 obs_sceneitem_t *next = insert_after->next;
1885 if (next)
1886 next->prev = item;
1887 item->next = insert_after->next;
1888 item->prev = insert_after;
1889 insert_after->next = item;
1890 } else {
1891 last = scene->first_item;
1892 if (!last) {
1893 scene->first_item = item;
1894 } else {
1895 while (last->next)
1896 last = last->next;
1897
1898 last->next = item;
1899 item->prev = last;
1900 }
1901 }
1902
1903 full_unlock(scene);
1904
1905 if (!scene->source->context.private)
1906 init_hotkeys(scene, item, obs_source_get_name(source));
1907
1908 signal_handler_connect(obs_source_get_signal_handler(source), "rename",
1909 sceneitem_renamed, item);
1910
1911 return item;
1912 }
1913
obs_scene_add(obs_scene_t * scene,obs_source_t * source)1914 obs_sceneitem_t *obs_scene_add(obs_scene_t *scene, obs_source_t *source)
1915 {
1916 obs_sceneitem_t *item =
1917 obs_scene_add_internal(scene, source, NULL, true);
1918 struct calldata params;
1919 uint8_t stack[128];
1920
1921 if (!item)
1922 return NULL;
1923
1924 calldata_init_fixed(¶ms, stack, sizeof(stack));
1925 calldata_set_ptr(¶ms, "scene", scene);
1926 calldata_set_ptr(¶ms, "item", item);
1927 signal_handler_signal(scene->source->context.signals, "item_add",
1928 ¶ms);
1929 return item;
1930 }
1931
obs_sceneitem_destroy(obs_sceneitem_t * item)1932 static void obs_sceneitem_destroy(obs_sceneitem_t *item)
1933 {
1934 if (item) {
1935 if (item->item_render) {
1936 obs_enter_graphics();
1937 gs_texrender_destroy(item->item_render);
1938 obs_leave_graphics();
1939 }
1940 obs_data_release(item->private_settings);
1941 obs_hotkey_pair_unregister(item->toggle_visibility);
1942 pthread_mutex_destroy(&item->actions_mutex);
1943 signal_handler_disconnect(
1944 obs_source_get_signal_handler(item->source), "rename",
1945 sceneitem_renamed, item);
1946 if (item->show_transition)
1947 obs_source_release(item->show_transition);
1948 if (item->hide_transition)
1949 obs_source_release(item->hide_transition);
1950 if (item->source)
1951 obs_source_release(item->source);
1952 da_free(item->audio_actions);
1953 bfree(item);
1954 }
1955 }
1956
obs_sceneitem_addref(obs_sceneitem_t * item)1957 void obs_sceneitem_addref(obs_sceneitem_t *item)
1958 {
1959 if (item)
1960 os_atomic_inc_long(&item->ref);
1961 }
1962
obs_sceneitem_release(obs_sceneitem_t * item)1963 void obs_sceneitem_release(obs_sceneitem_t *item)
1964 {
1965 if (!item)
1966 return;
1967
1968 if (os_atomic_dec_long(&item->ref) == 0)
1969 obs_sceneitem_destroy(item);
1970 }
1971
obs_sceneitem_remove(obs_sceneitem_t * item)1972 void obs_sceneitem_remove(obs_sceneitem_t *item)
1973 {
1974 obs_scene_t *scene;
1975
1976 if (!item)
1977 return;
1978
1979 scene = item->parent;
1980
1981 full_lock(scene);
1982
1983 if (item->removed) {
1984 if (scene)
1985 full_unlock(scene);
1986 return;
1987 }
1988
1989 item->removed = true;
1990
1991 assert(scene != NULL);
1992 assert(scene->source != NULL);
1993
1994 set_visibility(item, false);
1995
1996 signal_item_remove(item);
1997 detach_sceneitem(item);
1998
1999 full_unlock(scene);
2000
2001 obs_sceneitem_set_show_transition(item, NULL);
2002 obs_sceneitem_set_hide_transition(item, NULL);
2003
2004 obs_sceneitem_release(item);
2005 }
2006
obs_sceneitem_save(obs_sceneitem_t * item,obs_data_array_t * arr)2007 void obs_sceneitem_save(obs_sceneitem_t *item, obs_data_array_t *arr)
2008 {
2009 scene_save_item(arr, item, NULL);
2010 }
2011
sceneitem_restore(obs_data_t * data,void * vp)2012 void sceneitem_restore(obs_data_t *data, void *vp)
2013 {
2014 obs_scene_t *scene = (obs_scene_t *)vp;
2015 scene_load_item(scene, data);
2016 }
2017
obs_sceneitems_add(obs_scene_t * scene,obs_data_array_t * data)2018 void obs_sceneitems_add(obs_scene_t *scene, obs_data_array_t *data)
2019 {
2020 obs_data_array_enum(data, sceneitem_restore, scene);
2021 }
2022
obs_sceneitem_get_scene(const obs_sceneitem_t * item)2023 obs_scene_t *obs_sceneitem_get_scene(const obs_sceneitem_t *item)
2024 {
2025 return item ? item->parent : NULL;
2026 }
2027
obs_sceneitem_get_source(const obs_sceneitem_t * item)2028 obs_source_t *obs_sceneitem_get_source(const obs_sceneitem_t *item)
2029 {
2030 return item ? item->source : NULL;
2031 }
2032
signal_parent(obs_scene_t * parent,const char * command,calldata_t * params)2033 static void signal_parent(obs_scene_t *parent, const char *command,
2034 calldata_t *params)
2035 {
2036 calldata_set_ptr(params, "scene", parent);
2037 signal_handler_signal(parent->source->context.signals, command, params);
2038 }
2039
2040 struct passthrough {
2041 obs_data_array_t *ids;
2042 obs_data_array_t *scenes_and_groups;
2043 bool all_items;
2044 };
2045
save_transform_states(obs_scene_t * scene,obs_sceneitem_t * item,void * vp_pass)2046 bool save_transform_states(obs_scene_t *scene, obs_sceneitem_t *item,
2047 void *vp_pass)
2048 {
2049 struct passthrough *pass = (struct passthrough *)vp_pass;
2050 if (obs_sceneitem_selected(item) || pass->all_items) {
2051 obs_data_t *temp = obs_data_create();
2052 obs_data_array_t *item_ids = (obs_data_array_t *)pass->ids;
2053
2054 struct obs_transform_info info;
2055 struct obs_sceneitem_crop crop;
2056 obs_sceneitem_get_info(item, &info);
2057 obs_sceneitem_get_crop(item, &crop);
2058
2059 struct vec2 pos = info.pos;
2060 struct vec2 scale = info.scale;
2061 float rot = info.rot;
2062 uint32_t alignment = info.alignment;
2063 uint32_t bounds_type = info.bounds_type;
2064 uint32_t bounds_alignment = info.bounds_alignment;
2065 struct vec2 bounds = info.bounds;
2066
2067 obs_data_set_int(temp, "id", obs_sceneitem_get_id(item));
2068 obs_data_set_vec2(temp, "pos", &pos);
2069 obs_data_set_vec2(temp, "scale", &scale);
2070 obs_data_set_double(temp, "rot", rot);
2071 obs_data_set_int(temp, "alignment", alignment);
2072 obs_data_set_int(temp, "bounds_type", bounds_type);
2073 obs_data_set_vec2(temp, "bounds", &bounds);
2074 obs_data_set_int(temp, "bounds_alignment", bounds_alignment);
2075 obs_data_set_int(temp, "top", crop.top);
2076 obs_data_set_int(temp, "bottom", crop.bottom);
2077 obs_data_set_int(temp, "left", crop.left);
2078 obs_data_set_int(temp, "right", crop.right);
2079
2080 obs_data_array_push_back(item_ids, temp);
2081
2082 obs_data_release(temp);
2083 }
2084
2085 obs_source_t *item_source = obs_sceneitem_get_source(item);
2086
2087 if (obs_source_is_group(item_source)) {
2088 obs_data_t *temp = obs_data_create();
2089 obs_data_array_t *nids = obs_data_array_create();
2090
2091 obs_data_set_string(temp, "scene_name",
2092 obs_source_get_name(item_source));
2093 obs_data_set_bool(temp, "is_group", true);
2094 obs_data_set_string(
2095 temp, "group_parent",
2096 obs_source_get_name(obs_scene_get_source(scene)));
2097
2098 struct passthrough npass = {nids, pass->scenes_and_groups,
2099 pass->all_items};
2100 obs_sceneitem_group_enum_items(item, save_transform_states,
2101 (void *)&npass);
2102
2103 obs_data_set_array(temp, "items", nids);
2104
2105 obs_data_array_push_back(pass->scenes_and_groups, temp);
2106
2107 obs_data_release(temp);
2108 obs_data_array_release(nids);
2109 }
2110
2111 UNUSED_PARAMETER(scene);
2112 return true;
2113 }
2114
obs_scene_save_transform_states(obs_scene_t * scene,bool all_items)2115 obs_data_t *obs_scene_save_transform_states(obs_scene_t *scene, bool all_items)
2116 {
2117 obs_data_t *wrapper = obs_data_create();
2118 obs_data_array_t *scenes_and_groups = obs_data_array_create();
2119 obs_data_array_t *item_ids = obs_data_array_create();
2120
2121 struct passthrough pass = {item_ids, scenes_and_groups, all_items};
2122
2123 obs_data_t *temp = obs_data_create();
2124
2125 obs_data_set_string(temp, "scene_name",
2126 obs_source_get_name(obs_scene_get_source(scene)));
2127 obs_data_set_bool(temp, "is_group", false);
2128
2129 obs_scene_enum_items(scene, save_transform_states, (void *)&pass);
2130
2131 obs_data_set_array(temp, "items", item_ids);
2132 obs_data_array_push_back(scenes_and_groups, temp);
2133
2134 obs_data_set_array(wrapper, "scenes_and_groups", scenes_and_groups);
2135
2136 obs_data_array_release(item_ids);
2137 obs_data_array_release(scenes_and_groups);
2138 obs_data_release(temp);
2139
2140 return wrapper;
2141 }
2142
load_transform_states(obs_data_t * temp,void * vp_scene)2143 void load_transform_states(obs_data_t *temp, void *vp_scene)
2144 {
2145 obs_scene_t *scene = (obs_scene_t *)vp_scene;
2146 int64_t id = obs_data_get_int(temp, "id");
2147 obs_sceneitem_t *item = obs_scene_find_sceneitem_by_id(scene, id);
2148
2149 struct obs_transform_info info;
2150 struct obs_sceneitem_crop crop;
2151 obs_data_get_vec2(temp, "pos", &info.pos);
2152 obs_data_get_vec2(temp, "scale", &info.scale);
2153 info.rot = (float)obs_data_get_double(temp, "rot");
2154 info.alignment = (uint32_t)obs_data_get_int(temp, "alignment");
2155 info.bounds_type =
2156 (enum obs_bounds_type)obs_data_get_int(temp, "bounds_type");
2157 info.bounds_alignment =
2158 (uint32_t)obs_data_get_int(temp, "bounds_alignment");
2159 obs_data_get_vec2(temp, "bounds", &info.bounds);
2160 crop.top = (int)obs_data_get_int(temp, "top");
2161 crop.bottom = (int)obs_data_get_int(temp, "bottom");
2162 crop.left = (int)obs_data_get_int(temp, "left");
2163 crop.right = (int)obs_data_get_int(temp, "right");
2164
2165 obs_sceneitem_defer_update_begin(item);
2166
2167 obs_sceneitem_set_info(item, &info);
2168 obs_sceneitem_set_crop(item, &crop);
2169
2170 obs_sceneitem_defer_update_end(item);
2171 }
2172
iterate_scenes_and_groups_transform_states(obs_data_t * data,void * vp)2173 void iterate_scenes_and_groups_transform_states(obs_data_t *data, void *vp)
2174 {
2175 obs_data_array_t *items = obs_data_get_array(data, "items");
2176 obs_source_t *scene_source =
2177 obs_get_source_by_name(obs_data_get_string(data, "scene_name"));
2178 obs_scene_t *scene = obs_scene_from_source(scene_source);
2179
2180 if (obs_data_get_bool(data, "is_group")) {
2181 obs_source_t *parent_source = obs_get_source_by_name(
2182 obs_data_get_string(data, "group_parent"));
2183 obs_scene_t *parent = obs_scene_from_source(parent_source);
2184 obs_sceneitem_t *group = obs_scene_get_group(
2185 parent, obs_data_get_string(data, "scene_name"));
2186 scene = obs_sceneitem_group_get_scene(group);
2187
2188 obs_source_release(parent_source);
2189 }
2190
2191 obs_data_array_enum(items, load_transform_states, (void *)scene);
2192
2193 UNUSED_PARAMETER(vp);
2194
2195 obs_data_array_release(items);
2196 obs_source_release(scene_source);
2197 }
2198
obs_scene_load_transform_states(const char * data)2199 void obs_scene_load_transform_states(const char *data)
2200 {
2201 obs_data_t *dat = obs_data_create_from_json(data);
2202
2203 obs_data_array_t *scenes_and_groups =
2204 obs_data_get_array(dat, "scenes_and_groups");
2205
2206 obs_data_array_enum(scenes_and_groups,
2207 iterate_scenes_and_groups_transform_states, NULL);
2208
2209 obs_data_release(dat);
2210 obs_data_array_release(scenes_and_groups);
2211 }
2212
obs_sceneitem_select(obs_sceneitem_t * item,bool select)2213 void obs_sceneitem_select(obs_sceneitem_t *item, bool select)
2214 {
2215 struct calldata params;
2216 uint8_t stack[128];
2217 const char *command = select ? "item_select" : "item_deselect";
2218
2219 if (!item || item->selected == select || !item->parent)
2220 return;
2221
2222 item->selected = select;
2223
2224 calldata_init_fixed(¶ms, stack, sizeof(stack));
2225 calldata_set_ptr(¶ms, "item", item);
2226
2227 signal_parent(item->parent, command, ¶ms);
2228 }
2229
obs_sceneitem_selected(const obs_sceneitem_t * item)2230 bool obs_sceneitem_selected(const obs_sceneitem_t *item)
2231 {
2232 return item ? item->selected : false;
2233 }
2234
2235 #define do_update_transform(item) \
2236 do { \
2237 if (!item->parent || item->parent->is_group) \
2238 os_atomic_set_bool(&item->update_transform, true); \
2239 else \
2240 update_item_transform(item, false); \
2241 } while (false)
2242
obs_sceneitem_set_pos(obs_sceneitem_t * item,const struct vec2 * pos)2243 void obs_sceneitem_set_pos(obs_sceneitem_t *item, const struct vec2 *pos)
2244 {
2245 if (item) {
2246 vec2_copy(&item->pos, pos);
2247 do_update_transform(item);
2248 }
2249 }
2250
obs_sceneitem_set_rot(obs_sceneitem_t * item,float rot)2251 void obs_sceneitem_set_rot(obs_sceneitem_t *item, float rot)
2252 {
2253 if (item) {
2254 item->rot = rot;
2255 do_update_transform(item);
2256 }
2257 }
2258
obs_sceneitem_set_scale(obs_sceneitem_t * item,const struct vec2 * scale)2259 void obs_sceneitem_set_scale(obs_sceneitem_t *item, const struct vec2 *scale)
2260 {
2261 if (item) {
2262 vec2_copy(&item->scale, scale);
2263 do_update_transform(item);
2264 }
2265 }
2266
obs_sceneitem_set_alignment(obs_sceneitem_t * item,uint32_t alignment)2267 void obs_sceneitem_set_alignment(obs_sceneitem_t *item, uint32_t alignment)
2268 {
2269 if (item) {
2270 item->align = alignment;
2271 do_update_transform(item);
2272 }
2273 }
2274
signal_reorder(struct obs_scene_item * item)2275 static inline void signal_reorder(struct obs_scene_item *item)
2276 {
2277 const char *command = NULL;
2278 struct calldata params;
2279 uint8_t stack[128];
2280
2281 command = "reorder";
2282
2283 calldata_init_fixed(¶ms, stack, sizeof(stack));
2284 signal_parent(item->parent, command, ¶ms);
2285 }
2286
signal_refresh(obs_scene_t * scene)2287 static inline void signal_refresh(obs_scene_t *scene)
2288 {
2289 const char *command = NULL;
2290 struct calldata params;
2291 uint8_t stack[128];
2292
2293 command = "refresh";
2294
2295 calldata_init_fixed(¶ms, stack, sizeof(stack));
2296 signal_parent(scene, command, ¶ms);
2297 }
2298
obs_sceneitem_set_order(obs_sceneitem_t * item,enum obs_order_movement movement)2299 void obs_sceneitem_set_order(obs_sceneitem_t *item,
2300 enum obs_order_movement movement)
2301 {
2302 if (!item)
2303 return;
2304
2305 struct obs_scene_item *next, *prev;
2306 struct obs_scene *scene = item->parent;
2307
2308 obs_scene_addref(scene);
2309 full_lock(scene);
2310
2311 next = item->next;
2312 prev = item->prev;
2313
2314 detach_sceneitem(item);
2315
2316 if (movement == OBS_ORDER_MOVE_DOWN) {
2317 attach_sceneitem(scene, item, prev ? prev->prev : NULL);
2318
2319 } else if (movement == OBS_ORDER_MOVE_UP) {
2320 attach_sceneitem(scene, item, next ? next : prev);
2321
2322 } else if (movement == OBS_ORDER_MOVE_TOP) {
2323 struct obs_scene_item *last = next;
2324 if (!last) {
2325 last = prev;
2326 } else {
2327 while (last->next)
2328 last = last->next;
2329 }
2330
2331 attach_sceneitem(scene, item, last);
2332
2333 } else if (movement == OBS_ORDER_MOVE_BOTTOM) {
2334 attach_sceneitem(scene, item, NULL);
2335 }
2336
2337 full_unlock(scene);
2338
2339 signal_reorder(item);
2340 obs_scene_release(scene);
2341 }
2342
obs_sceneitem_get_order_position(obs_sceneitem_t * item)2343 int obs_sceneitem_get_order_position(obs_sceneitem_t *item)
2344 {
2345 struct obs_scene *scene = item->parent;
2346 struct obs_scene_item *next = scene->first_item;
2347
2348 full_lock(scene);
2349
2350 int index = 0;
2351 while (next && next != item) {
2352 next = next->next;
2353 ++index;
2354 }
2355
2356 full_unlock(scene);
2357
2358 return index;
2359 }
2360
obs_sceneitem_set_order_position(obs_sceneitem_t * item,int position)2361 void obs_sceneitem_set_order_position(obs_sceneitem_t *item, int position)
2362 {
2363 if (!item)
2364 return;
2365
2366 struct obs_scene *scene = item->parent;
2367 struct obs_scene_item *next;
2368
2369 obs_scene_addref(scene);
2370 full_lock(scene);
2371
2372 detach_sceneitem(item);
2373 next = scene->first_item;
2374
2375 if (position == 0) {
2376 attach_sceneitem(scene, item, NULL);
2377 } else {
2378 for (int i = position; i > 1; --i) {
2379 if (next->next == NULL)
2380 break;
2381 next = next->next;
2382 }
2383
2384 attach_sceneitem(scene, item, next);
2385 }
2386
2387 full_unlock(scene);
2388
2389 signal_reorder(item);
2390 obs_scene_release(scene);
2391 }
2392
obs_sceneitem_set_bounds_type(obs_sceneitem_t * item,enum obs_bounds_type type)2393 void obs_sceneitem_set_bounds_type(obs_sceneitem_t *item,
2394 enum obs_bounds_type type)
2395 {
2396 if (item) {
2397 item->bounds_type = type;
2398 do_update_transform(item);
2399 }
2400 }
2401
obs_sceneitem_set_bounds_alignment(obs_sceneitem_t * item,uint32_t alignment)2402 void obs_sceneitem_set_bounds_alignment(obs_sceneitem_t *item,
2403 uint32_t alignment)
2404 {
2405 if (item) {
2406 item->bounds_align = alignment;
2407 do_update_transform(item);
2408 }
2409 }
2410
obs_sceneitem_set_bounds(obs_sceneitem_t * item,const struct vec2 * bounds)2411 void obs_sceneitem_set_bounds(obs_sceneitem_t *item, const struct vec2 *bounds)
2412 {
2413 if (item) {
2414 item->bounds = *bounds;
2415 do_update_transform(item);
2416 }
2417 }
2418
obs_sceneitem_get_pos(const obs_sceneitem_t * item,struct vec2 * pos)2419 void obs_sceneitem_get_pos(const obs_sceneitem_t *item, struct vec2 *pos)
2420 {
2421 if (item)
2422 vec2_copy(pos, &item->pos);
2423 }
2424
obs_sceneitem_get_rot(const obs_sceneitem_t * item)2425 float obs_sceneitem_get_rot(const obs_sceneitem_t *item)
2426 {
2427 return item ? item->rot : 0.0f;
2428 }
2429
obs_sceneitem_get_scale(const obs_sceneitem_t * item,struct vec2 * scale)2430 void obs_sceneitem_get_scale(const obs_sceneitem_t *item, struct vec2 *scale)
2431 {
2432 if (item)
2433 vec2_copy(scale, &item->scale);
2434 }
2435
obs_sceneitem_get_alignment(const obs_sceneitem_t * item)2436 uint32_t obs_sceneitem_get_alignment(const obs_sceneitem_t *item)
2437 {
2438 return item ? item->align : 0;
2439 }
2440
obs_sceneitem_get_bounds_type(const obs_sceneitem_t * item)2441 enum obs_bounds_type obs_sceneitem_get_bounds_type(const obs_sceneitem_t *item)
2442 {
2443 return item ? item->bounds_type : OBS_BOUNDS_NONE;
2444 }
2445
obs_sceneitem_get_bounds_alignment(const obs_sceneitem_t * item)2446 uint32_t obs_sceneitem_get_bounds_alignment(const obs_sceneitem_t *item)
2447 {
2448 return item ? item->bounds_align : 0;
2449 }
2450
obs_sceneitem_get_bounds(const obs_sceneitem_t * item,struct vec2 * bounds)2451 void obs_sceneitem_get_bounds(const obs_sceneitem_t *item, struct vec2 *bounds)
2452 {
2453 if (item)
2454 *bounds = item->bounds;
2455 }
2456
obs_sceneitem_get_info(const obs_sceneitem_t * item,struct obs_transform_info * info)2457 void obs_sceneitem_get_info(const obs_sceneitem_t *item,
2458 struct obs_transform_info *info)
2459 {
2460 if (item && info) {
2461 info->pos = item->pos;
2462 info->rot = item->rot;
2463 info->scale = item->scale;
2464 info->alignment = item->align;
2465 info->bounds_type = item->bounds_type;
2466 info->bounds_alignment = item->bounds_align;
2467 info->bounds = item->bounds;
2468 }
2469 }
2470
obs_sceneitem_set_info(obs_sceneitem_t * item,const struct obs_transform_info * info)2471 void obs_sceneitem_set_info(obs_sceneitem_t *item,
2472 const struct obs_transform_info *info)
2473 {
2474 if (item && info) {
2475 item->pos = info->pos;
2476 item->rot = info->rot;
2477 item->scale = info->scale;
2478 item->align = info->alignment;
2479 item->bounds_type = info->bounds_type;
2480 item->bounds_align = info->bounds_alignment;
2481 item->bounds = info->bounds;
2482 do_update_transform(item);
2483 }
2484 }
2485
obs_sceneitem_get_draw_transform(const obs_sceneitem_t * item,struct matrix4 * transform)2486 void obs_sceneitem_get_draw_transform(const obs_sceneitem_t *item,
2487 struct matrix4 *transform)
2488 {
2489 if (item)
2490 matrix4_copy(transform, &item->draw_transform);
2491 }
2492
obs_sceneitem_get_box_transform(const obs_sceneitem_t * item,struct matrix4 * transform)2493 void obs_sceneitem_get_box_transform(const obs_sceneitem_t *item,
2494 struct matrix4 *transform)
2495 {
2496 if (item)
2497 matrix4_copy(transform, &item->box_transform);
2498 }
2499
obs_sceneitem_get_box_scale(const obs_sceneitem_t * item,struct vec2 * scale)2500 void obs_sceneitem_get_box_scale(const obs_sceneitem_t *item,
2501 struct vec2 *scale)
2502 {
2503 if (item)
2504 *scale = item->box_scale;
2505 }
2506
obs_sceneitem_visible(const obs_sceneitem_t * item)2507 bool obs_sceneitem_visible(const obs_sceneitem_t *item)
2508 {
2509 return item ? item->user_visible : false;
2510 }
2511
group_item_transition(obs_scene_t * scene,obs_sceneitem_t * item,void * param)2512 static bool group_item_transition(obs_scene_t *scene, obs_sceneitem_t *item,
2513 void *param)
2514 {
2515 if (!param || !item)
2516 return true;
2517 const bool visible = *(bool *)param;
2518 if (obs_sceneitem_visible(item))
2519 obs_sceneitem_do_transition(item, visible);
2520 UNUSED_PARAMETER(scene);
2521 return true;
2522 }
2523
obs_sceneitem_set_visible(obs_sceneitem_t * item,bool visible)2524 bool obs_sceneitem_set_visible(obs_sceneitem_t *item, bool visible)
2525 {
2526 struct calldata cd;
2527 uint8_t stack[256];
2528 struct item_action action = {.visible = visible,
2529 .timestamp = os_gettime_ns()};
2530
2531 if (!item)
2532 return false;
2533
2534 if (item->user_visible == visible)
2535 return false;
2536
2537 if (!item->parent)
2538 return false;
2539
2540 obs_sceneitem_do_transition(item, visible);
2541 if (obs_sceneitem_is_group(item))
2542 obs_sceneitem_group_enum_items(item, group_item_transition,
2543 &visible);
2544
2545 item->user_visible = visible;
2546
2547 if (visible) {
2548 if (os_atomic_inc_long(&item->active_refs) == 1) {
2549 if (!obs_source_add_active_child(item->parent->source,
2550 item->source)) {
2551 os_atomic_dec_long(&item->active_refs);
2552 return false;
2553 }
2554 }
2555 }
2556
2557 calldata_init_fixed(&cd, stack, sizeof(stack));
2558 calldata_set_ptr(&cd, "item", item);
2559 calldata_set_bool(&cd, "visible", visible);
2560
2561 signal_parent(item->parent, "item_visible", &cd);
2562
2563 if (source_has_audio(item->source)) {
2564 pthread_mutex_lock(&item->actions_mutex);
2565 da_push_back(item->audio_actions, &action);
2566 pthread_mutex_unlock(&item->actions_mutex);
2567 } else {
2568 set_visibility(item, visible);
2569 }
2570 return true;
2571 }
2572
obs_sceneitem_locked(const obs_sceneitem_t * item)2573 bool obs_sceneitem_locked(const obs_sceneitem_t *item)
2574 {
2575 return item ? item->locked : false;
2576 }
2577
obs_sceneitem_set_locked(obs_sceneitem_t * item,bool lock)2578 bool obs_sceneitem_set_locked(obs_sceneitem_t *item, bool lock)
2579 {
2580 struct calldata cd;
2581 uint8_t stack[256];
2582
2583 if (!item)
2584 return false;
2585
2586 if (item->locked == lock)
2587 return false;
2588
2589 if (!item->parent)
2590 return false;
2591
2592 item->locked = lock;
2593
2594 calldata_init_fixed(&cd, stack, sizeof(stack));
2595 calldata_set_ptr(&cd, "item", item);
2596 calldata_set_bool(&cd, "locked", lock);
2597
2598 signal_parent(item->parent, "item_locked", &cd);
2599
2600 return true;
2601 }
2602
sceneitems_match(obs_scene_t * scene,obs_sceneitem_t * const * items,size_t size,bool * order_matches)2603 static bool sceneitems_match(obs_scene_t *scene, obs_sceneitem_t *const *items,
2604 size_t size, bool *order_matches)
2605 {
2606 obs_sceneitem_t *item = scene->first_item;
2607
2608 size_t count = 0;
2609 while (item) {
2610 bool found = false;
2611 for (size_t i = 0; i < size; i++) {
2612 if (items[i] != item)
2613 continue;
2614
2615 if (count != i)
2616 *order_matches = false;
2617
2618 found = true;
2619 break;
2620 }
2621
2622 if (!found)
2623 return false;
2624
2625 item = item->next;
2626 count += 1;
2627 }
2628
2629 return count == size;
2630 }
2631
obs_scene_reorder_items(obs_scene_t * scene,obs_sceneitem_t * const * item_order,size_t item_order_size)2632 bool obs_scene_reorder_items(obs_scene_t *scene,
2633 obs_sceneitem_t *const *item_order,
2634 size_t item_order_size)
2635 {
2636 if (!scene || !item_order_size)
2637 return false;
2638
2639 obs_scene_addref(scene);
2640 full_lock(scene);
2641
2642 bool order_matches = true;
2643 if (!sceneitems_match(scene, item_order, item_order_size,
2644 &order_matches) ||
2645 order_matches) {
2646 full_unlock(scene);
2647 obs_scene_release(scene);
2648 return false;
2649 }
2650
2651 scene->first_item = item_order[0];
2652
2653 obs_sceneitem_t *prev = NULL;
2654 for (size_t i = 0; i < item_order_size; i++) {
2655 item_order[i]->prev = prev;
2656 item_order[i]->next = NULL;
2657
2658 if (prev)
2659 prev->next = item_order[i];
2660
2661 prev = item_order[i];
2662 }
2663
2664 full_unlock(scene);
2665
2666 signal_reorder(scene->first_item);
2667 obs_scene_release(scene);
2668 return true;
2669 }
2670
obs_scene_atomic_update(obs_scene_t * scene,obs_scene_atomic_update_func func,void * data)2671 void obs_scene_atomic_update(obs_scene_t *scene,
2672 obs_scene_atomic_update_func func, void *data)
2673 {
2674 if (!scene)
2675 return;
2676
2677 obs_scene_addref(scene);
2678 full_lock(scene);
2679 func(data, scene);
2680 full_unlock(scene);
2681 obs_scene_release(scene);
2682 }
2683
crop_equal(const struct obs_sceneitem_crop * crop1,const struct obs_sceneitem_crop * crop2)2684 static inline bool crop_equal(const struct obs_sceneitem_crop *crop1,
2685 const struct obs_sceneitem_crop *crop2)
2686 {
2687 return crop1->left == crop2->left && crop1->right == crop2->right &&
2688 crop1->top == crop2->top && crop1->bottom == crop2->bottom;
2689 }
2690
obs_sceneitem_set_crop(obs_sceneitem_t * item,const struct obs_sceneitem_crop * crop)2691 void obs_sceneitem_set_crop(obs_sceneitem_t *item,
2692 const struct obs_sceneitem_crop *crop)
2693 {
2694 if (!obs_ptr_valid(item, "obs_sceneitem_set_crop"))
2695 return;
2696 if (!obs_ptr_valid(crop, "obs_sceneitem_set_crop"))
2697 return;
2698 if (crop_equal(crop, &item->crop))
2699 return;
2700
2701 memcpy(&item->crop, crop, sizeof(*crop));
2702
2703 if (item->crop.left < 0)
2704 item->crop.left = 0;
2705 if (item->crop.right < 0)
2706 item->crop.right = 0;
2707 if (item->crop.top < 0)
2708 item->crop.top = 0;
2709 if (item->crop.bottom < 0)
2710 item->crop.bottom = 0;
2711
2712 os_atomic_set_bool(&item->update_transform, true);
2713 }
2714
obs_sceneitem_get_crop(const obs_sceneitem_t * item,struct obs_sceneitem_crop * crop)2715 void obs_sceneitem_get_crop(const obs_sceneitem_t *item,
2716 struct obs_sceneitem_crop *crop)
2717 {
2718 if (!obs_ptr_valid(item, "obs_sceneitem_get_crop"))
2719 return;
2720 if (!obs_ptr_valid(crop, "obs_sceneitem_get_crop"))
2721 return;
2722
2723 memcpy(crop, &item->crop, sizeof(*crop));
2724 }
2725
obs_sceneitem_set_scale_filter(obs_sceneitem_t * item,enum obs_scale_type filter)2726 void obs_sceneitem_set_scale_filter(obs_sceneitem_t *item,
2727 enum obs_scale_type filter)
2728 {
2729 if (!obs_ptr_valid(item, "obs_sceneitem_set_scale_filter"))
2730 return;
2731
2732 item->scale_filter = filter;
2733
2734 os_atomic_set_bool(&item->update_transform, true);
2735 }
2736
obs_sceneitem_get_scale_filter(obs_sceneitem_t * item)2737 enum obs_scale_type obs_sceneitem_get_scale_filter(obs_sceneitem_t *item)
2738 {
2739 return obs_ptr_valid(item, "obs_sceneitem_get_scale_filter")
2740 ? item->scale_filter
2741 : OBS_SCALE_DISABLE;
2742 }
2743
obs_sceneitem_defer_update_begin(obs_sceneitem_t * item)2744 void obs_sceneitem_defer_update_begin(obs_sceneitem_t *item)
2745 {
2746 if (!obs_ptr_valid(item, "obs_sceneitem_defer_update_begin"))
2747 return;
2748
2749 os_atomic_inc_long(&item->defer_update);
2750 }
2751
obs_sceneitem_defer_update_end(obs_sceneitem_t * item)2752 void obs_sceneitem_defer_update_end(obs_sceneitem_t *item)
2753 {
2754 if (!obs_ptr_valid(item, "obs_sceneitem_defer_update_end"))
2755 return;
2756
2757 if (os_atomic_dec_long(&item->defer_update) == 0)
2758 do_update_transform(item);
2759 }
2760
obs_sceneitem_defer_group_resize_begin(obs_sceneitem_t * item)2761 void obs_sceneitem_defer_group_resize_begin(obs_sceneitem_t *item)
2762 {
2763 if (!obs_ptr_valid(item, "obs_sceneitem_defer_group_resize_begin"))
2764 return;
2765
2766 os_atomic_inc_long(&item->defer_group_resize);
2767 }
2768
obs_sceneitem_defer_group_resize_end(obs_sceneitem_t * item)2769 void obs_sceneitem_defer_group_resize_end(obs_sceneitem_t *item)
2770 {
2771 if (!obs_ptr_valid(item, "obs_sceneitem_defer_group_resize_end"))
2772 return;
2773
2774 if (os_atomic_dec_long(&item->defer_group_resize) == 0)
2775 os_atomic_set_bool(&item->update_group_resize, true);
2776 }
2777
obs_sceneitem_get_id(const obs_sceneitem_t * item)2778 int64_t obs_sceneitem_get_id(const obs_sceneitem_t *item)
2779 {
2780 if (!obs_ptr_valid(item, "obs_sceneitem_get_id"))
2781 return 0;
2782
2783 return item->id;
2784 }
2785
obs_sceneitem_set_id(obs_sceneitem_t * item,int64_t id)2786 void obs_sceneitem_set_id(obs_sceneitem_t *item, int64_t id)
2787 {
2788 item->id = id;
2789 }
2790
obs_sceneitem_get_private_settings(obs_sceneitem_t * item)2791 obs_data_t *obs_sceneitem_get_private_settings(obs_sceneitem_t *item)
2792 {
2793 if (!obs_ptr_valid(item, "obs_sceneitem_get_private_settings"))
2794 return NULL;
2795
2796 obs_data_addref(item->private_settings);
2797 return item->private_settings;
2798 }
2799
transform_val(struct vec2 * v2,struct matrix4 * transform)2800 static inline void transform_val(struct vec2 *v2, struct matrix4 *transform)
2801 {
2802 struct vec3 v;
2803 vec3_set(&v, v2->x, v2->y, 0.0f);
2804 vec3_transform(&v, &v, transform);
2805 v2->x = v.x;
2806 v2->y = v.y;
2807 }
2808
get_ungrouped_transform(obs_sceneitem_t * group,struct vec2 * pos,struct vec2 * scale,float * rot)2809 static void get_ungrouped_transform(obs_sceneitem_t *group, struct vec2 *pos,
2810 struct vec2 *scale, float *rot)
2811 {
2812 struct matrix4 transform;
2813 struct matrix4 mat;
2814 struct vec4 x_base;
2815
2816 vec4_set(&x_base, 1.0f, 0.0f, 0.0f, 0.0f);
2817
2818 matrix4_copy(&transform, &group->draw_transform);
2819
2820 transform_val(pos, &transform);
2821 vec4_set(&transform.t, 0.0f, 0.0f, 0.0f, 1.0f);
2822
2823 vec4_set(&mat.x, scale->x, 0.0f, 0.0f, 0.0f);
2824 vec4_set(&mat.y, 0.0f, scale->y, 0.0f, 0.0f);
2825 vec4_set(&mat.z, 0.0f, 0.0f, 1.0f, 0.0f);
2826 vec4_set(&mat.t, 0.0f, 0.0f, 0.0f, 1.0f);
2827 matrix4_mul(&mat, &mat, &transform);
2828
2829 scale->x = vec4_len(&mat.x) * (scale->x > 0.0f ? 1.0f : -1.0f);
2830 scale->y = vec4_len(&mat.y) * (scale->y > 0.0f ? 1.0f : -1.0f);
2831 *rot += group->rot;
2832 }
2833
remove_group_transform(obs_sceneitem_t * group,obs_sceneitem_t * item)2834 static void remove_group_transform(obs_sceneitem_t *group,
2835 obs_sceneitem_t *item)
2836 {
2837 obs_scene_t *parent = item->parent;
2838 if (!parent || !group)
2839 return;
2840
2841 get_ungrouped_transform(group, &item->pos, &item->scale, &item->rot);
2842
2843 update_item_transform(item, false);
2844 }
2845
apply_group_transform(obs_sceneitem_t * item,obs_sceneitem_t * group)2846 static void apply_group_transform(obs_sceneitem_t *item, obs_sceneitem_t *group)
2847 {
2848 struct matrix4 transform;
2849 struct matrix4 mat;
2850 struct vec4 x_base;
2851
2852 vec4_set(&x_base, 1.0f, 0.0f, 0.0f, 0.0f);
2853
2854 matrix4_inv(&transform, &group->draw_transform);
2855
2856 transform_val(&item->pos, &transform);
2857 vec4_set(&transform.t, 0.0f, 0.0f, 0.0f, 1.0f);
2858
2859 vec4_set(&mat.x, item->scale.x, 0.0f, 0.0f, 0.0f);
2860 vec4_set(&mat.y, 0.0f, item->scale.y, 0.0f, 0.0f);
2861 vec4_set(&mat.z, 0.0f, 0.0f, 1.0f, 0.0f);
2862 vec4_set(&mat.t, 0.0f, 0.0f, 0.0f, 1.0f);
2863 matrix4_mul(&mat, &mat, &transform);
2864
2865 item->scale.x =
2866 vec4_len(&mat.x) * (item->scale.x > 0.0f ? 1.0f : -1.0f);
2867 item->scale.y =
2868 vec4_len(&mat.y) * (item->scale.y > 0.0f ? 1.0f : -1.0f);
2869 item->rot -= group->rot;
2870
2871 update_item_transform(item, false);
2872 }
2873
resize_scene_base(obs_scene_t * scene,struct vec2 * minv,struct vec2 * maxv,struct vec2 * scale)2874 static bool resize_scene_base(obs_scene_t *scene, struct vec2 *minv,
2875 struct vec2 *maxv, struct vec2 *scale)
2876 {
2877 vec2_set(minv, M_INFINITE, M_INFINITE);
2878 vec2_set(maxv, -M_INFINITE, -M_INFINITE);
2879
2880 obs_sceneitem_t *item = scene->first_item;
2881 if (!item) {
2882 scene->cx = 0;
2883 scene->cy = 0;
2884 return false;
2885 }
2886
2887 while (item) {
2888 #define get_min_max(x_val, y_val) \
2889 do { \
2890 struct vec3 v; \
2891 vec3_set(&v, x_val, y_val, 0.0f); \
2892 vec3_transform(&v, &v, &item->box_transform); \
2893 if (v.x < minv->x) \
2894 minv->x = v.x; \
2895 if (v.y < minv->y) \
2896 minv->y = v.y; \
2897 if (v.x > maxv->x) \
2898 maxv->x = v.x; \
2899 if (v.y > maxv->y) \
2900 maxv->y = v.y; \
2901 } while (false)
2902
2903 get_min_max(0.0f, 0.0f);
2904 get_min_max(1.0f, 0.0f);
2905 get_min_max(0.0f, 1.0f);
2906 get_min_max(1.0f, 1.0f);
2907 #undef get_min_max
2908
2909 item = item->next;
2910 }
2911
2912 item = scene->first_item;
2913 while (item) {
2914 vec2_sub(&item->pos, &item->pos, minv);
2915 update_item_transform(item, false);
2916 item = item->next;
2917 }
2918
2919 vec2_sub(scale, maxv, minv);
2920 scene->cx = (uint32_t)ceilf(scale->x);
2921 scene->cy = (uint32_t)ceilf(scale->y);
2922 return true;
2923 }
2924
resize_scene(obs_scene_t * scene)2925 static void resize_scene(obs_scene_t *scene)
2926 {
2927 struct vec2 minv;
2928 struct vec2 maxv;
2929 struct vec2 scale;
2930 resize_scene_base(scene, &minv, &maxv, &scale);
2931 }
2932
2933 /* assumes group scene and parent scene is locked */
resize_group(obs_sceneitem_t * group)2934 static void resize_group(obs_sceneitem_t *group)
2935 {
2936 obs_scene_t *scene = group->source->context.data;
2937 struct vec2 minv;
2938 struct vec2 maxv;
2939 struct vec2 scale;
2940
2941 if (os_atomic_load_long(&group->defer_group_resize) > 0)
2942 return;
2943
2944 if (!resize_scene_base(scene, &minv, &maxv, &scale))
2945 return;
2946
2947 if (group->bounds_type == OBS_BOUNDS_NONE) {
2948 struct vec2 new_pos;
2949
2950 if ((group->align & OBS_ALIGN_LEFT) != 0)
2951 new_pos.x = minv.x;
2952 else if ((group->align & OBS_ALIGN_RIGHT) != 0)
2953 new_pos.x = maxv.x;
2954 else
2955 new_pos.x = (maxv.x - minv.x) * 0.5f + minv.x;
2956
2957 if ((group->align & OBS_ALIGN_TOP) != 0)
2958 new_pos.y = minv.y;
2959 else if ((group->align & OBS_ALIGN_BOTTOM) != 0)
2960 new_pos.y = maxv.y;
2961 else
2962 new_pos.y = (maxv.y - minv.y) * 0.5f + minv.y;
2963
2964 transform_val(&new_pos, &group->draw_transform);
2965 vec2_copy(&group->pos, &new_pos);
2966 }
2967
2968 os_atomic_set_bool(&group->update_group_resize, false);
2969
2970 update_item_transform(group, false);
2971 }
2972
obs_scene_add_group(obs_scene_t * scene,const char * name)2973 obs_sceneitem_t *obs_scene_add_group(obs_scene_t *scene, const char *name)
2974 {
2975 return obs_scene_insert_group(scene, name, NULL, 0);
2976 }
2977
obs_scene_add_group2(obs_scene_t * scene,const char * name,bool signal)2978 obs_sceneitem_t *obs_scene_add_group2(obs_scene_t *scene, const char *name,
2979 bool signal)
2980 {
2981 return obs_scene_insert_group2(scene, name, NULL, 0, signal);
2982 }
2983
obs_scene_insert_group(obs_scene_t * scene,const char * name,obs_sceneitem_t ** items,size_t count)2984 obs_sceneitem_t *obs_scene_insert_group(obs_scene_t *scene, const char *name,
2985 obs_sceneitem_t **items, size_t count)
2986 {
2987 if (!scene)
2988 return NULL;
2989
2990 /* don't allow groups or sub-items of other groups */
2991 for (size_t i = count; i > 0; i--) {
2992 obs_sceneitem_t *item = items[i - 1];
2993 if (item->parent != scene || item->is_group)
2994 return NULL;
2995 }
2996
2997 obs_scene_t *sub_scene = create_id("group", name);
2998 obs_sceneitem_t *last_item = items ? items[count - 1] : NULL;
2999
3000 obs_sceneitem_t *item = obs_scene_add_internal(scene, sub_scene->source,
3001 last_item, true);
3002
3003 obs_scene_release(sub_scene);
3004
3005 if (!items || !count)
3006 return item;
3007
3008 /* ------------------------- */
3009
3010 full_lock(scene);
3011 full_lock(sub_scene);
3012 sub_scene->first_item = items[0];
3013
3014 for (size_t i = count; i > 0; i--) {
3015 size_t idx = i - 1;
3016 remove_group_transform(item, items[idx]);
3017 detach_sceneitem(items[idx]);
3018 }
3019 for (size_t i = 0; i < count; i++) {
3020 size_t idx = i;
3021 if (idx != (count - 1)) {
3022 size_t next_idx = idx + 1;
3023 items[idx]->next = items[next_idx];
3024 items[next_idx]->prev = items[idx];
3025 } else {
3026 items[idx]->next = NULL;
3027 }
3028 items[idx]->parent = sub_scene;
3029 apply_group_transform(items[idx], item);
3030 }
3031 items[0]->prev = NULL;
3032 resize_group(item);
3033 full_unlock(sub_scene);
3034 full_unlock(scene);
3035
3036 /* ------------------------- */
3037
3038 return item;
3039 }
3040
obs_scene_insert_group2(obs_scene_t * scene,const char * name,obs_sceneitem_t ** items,size_t count,bool signal)3041 obs_sceneitem_t *obs_scene_insert_group2(obs_scene_t *scene, const char *name,
3042 obs_sceneitem_t **items, size_t count,
3043 bool signal)
3044 {
3045 obs_sceneitem_t *item =
3046 obs_scene_insert_group(scene, name, items, count);
3047 if (signal && item)
3048 signal_refresh(scene);
3049 return item;
3050 }
3051
obs_scene_get_group(obs_scene_t * scene,const char * name)3052 obs_sceneitem_t *obs_scene_get_group(obs_scene_t *scene, const char *name)
3053 {
3054 if (!scene || !name || !*name) {
3055 return NULL;
3056 }
3057
3058 obs_sceneitem_t *group = NULL;
3059 obs_sceneitem_t *item;
3060
3061 full_lock(scene);
3062
3063 item = scene->first_item;
3064 while (item) {
3065 if (item->is_group && item->source->context.name) {
3066 if (strcmp(item->source->context.name, name) == 0) {
3067 group = item;
3068 break;
3069 }
3070 }
3071
3072 item = item->next;
3073 }
3074
3075 full_unlock(scene);
3076
3077 return group;
3078 }
3079
obs_sceneitem_is_group(obs_sceneitem_t * item)3080 bool obs_sceneitem_is_group(obs_sceneitem_t *item)
3081 {
3082 return item && item->is_group;
3083 }
3084
obs_sceneitem_group_get_scene(const obs_sceneitem_t * item)3085 obs_scene_t *obs_sceneitem_group_get_scene(const obs_sceneitem_t *item)
3086 {
3087 return (item && item->is_group) ? item->source->context.data : NULL;
3088 }
3089
obs_sceneitem_group_ungroup(obs_sceneitem_t * item)3090 void obs_sceneitem_group_ungroup(obs_sceneitem_t *item)
3091 {
3092 if (!item || !item->is_group)
3093 return;
3094
3095 obs_scene_t *scene = item->parent;
3096 obs_scene_t *subscene = item->source->context.data;
3097 obs_sceneitem_t *insert_after = item;
3098 obs_sceneitem_t *first;
3099 obs_sceneitem_t *last;
3100
3101 full_lock(scene);
3102
3103 /* ------------------------- */
3104
3105 full_lock(subscene);
3106 first = subscene->first_item;
3107 last = first;
3108 while (last) {
3109 obs_sceneitem_t *dst;
3110
3111 remove_group_transform(item, last);
3112 dst = obs_scene_add_internal(scene, last->source, insert_after,
3113 false);
3114 duplicate_item_data(dst, last, true, true, true);
3115 apply_group_transform(last, item);
3116
3117 if (!last->next)
3118 break;
3119
3120 insert_after = dst;
3121 last = last->next;
3122 }
3123 full_unlock(subscene);
3124
3125 /* ------------------------- */
3126
3127 detach_sceneitem(item);
3128 full_unlock(scene);
3129
3130 obs_sceneitem_release(item);
3131 }
3132
obs_sceneitem_group_ungroup2(obs_sceneitem_t * item,bool signal)3133 void obs_sceneitem_group_ungroup2(obs_sceneitem_t *item, bool signal)
3134 {
3135 obs_scene_t *scene = item->parent;
3136 obs_sceneitem_group_ungroup(item);
3137 if (signal)
3138 signal_refresh(scene);
3139 }
3140
obs_sceneitem_group_add_item(obs_sceneitem_t * group,obs_sceneitem_t * item)3141 void obs_sceneitem_group_add_item(obs_sceneitem_t *group, obs_sceneitem_t *item)
3142 {
3143 if (!group || !group->is_group || !item)
3144 return;
3145
3146 obs_scene_t *scene = group->parent;
3147 obs_scene_t *groupscene = group->source->context.data;
3148
3149 if (item->parent != scene)
3150 return;
3151
3152 if (item->parent == groupscene)
3153 return;
3154
3155 /* ------------------------- */
3156
3157 full_lock(scene);
3158 full_lock(groupscene);
3159
3160 remove_group_transform(group, item);
3161
3162 detach_sceneitem(item);
3163 attach_sceneitem(groupscene, item, NULL);
3164
3165 apply_group_transform(item, group);
3166
3167 resize_group(group);
3168
3169 full_unlock(groupscene);
3170 full_unlock(scene);
3171
3172 /* ------------------------- */
3173
3174 signal_refresh(scene);
3175 }
3176
obs_sceneitem_group_remove_item(obs_sceneitem_t * group,obs_sceneitem_t * item)3177 void obs_sceneitem_group_remove_item(obs_sceneitem_t *group,
3178 obs_sceneitem_t *item)
3179 {
3180 if (!item || !group || !group->is_group)
3181 return;
3182
3183 obs_scene_t *groupscene = item->parent;
3184 obs_scene_t *scene = group->parent;
3185
3186 /* ------------------------- */
3187
3188 full_lock(scene);
3189 full_lock(groupscene);
3190
3191 remove_group_transform(group, item);
3192
3193 detach_sceneitem(item);
3194 attach_sceneitem(scene, item, NULL);
3195
3196 resize_group(group);
3197
3198 full_unlock(groupscene);
3199 full_unlock(scene);
3200
3201 /* ------------------------- */
3202
3203 signal_refresh(scene);
3204 }
3205
3206 static void
build_current_order_info(obs_scene_t * scene,struct obs_sceneitem_order_info ** items_out,size_t * size_out)3207 build_current_order_info(obs_scene_t *scene,
3208 struct obs_sceneitem_order_info **items_out,
3209 size_t *size_out)
3210 {
3211 DARRAY(struct obs_sceneitem_order_info) items;
3212 da_init(items);
3213
3214 obs_sceneitem_t *item = scene->first_item;
3215 while (item) {
3216 struct obs_sceneitem_order_info info = {0};
3217 info.item = item;
3218 da_push_back(items, &info);
3219
3220 if (item->is_group) {
3221 obs_scene_t *sub_scene = item->source->context.data;
3222
3223 full_lock(sub_scene);
3224
3225 obs_sceneitem_t *sub_item = sub_scene->first_item;
3226
3227 while (sub_item) {
3228 info.group = item;
3229 info.item = sub_item;
3230 da_push_back(items, &info);
3231
3232 sub_item = sub_item->next;
3233 }
3234
3235 full_unlock(sub_scene);
3236 }
3237
3238 item = item->next;
3239 }
3240
3241 *items_out = items.array;
3242 *size_out = items.num;
3243 }
3244
sceneitems_match2(obs_scene_t * scene,struct obs_sceneitem_order_info * items,size_t size)3245 static bool sceneitems_match2(obs_scene_t *scene,
3246 struct obs_sceneitem_order_info *items,
3247 size_t size)
3248 {
3249 struct obs_sceneitem_order_info *cur_items;
3250 size_t cur_size;
3251
3252 build_current_order_info(scene, &cur_items, &cur_size);
3253 if (cur_size != size) {
3254 bfree(cur_items);
3255 return false;
3256 }
3257
3258 for (size_t i = 0; i < size; i++) {
3259 struct obs_sceneitem_order_info *new = &items[i];
3260 struct obs_sceneitem_order_info *old = &cur_items[i];
3261
3262 if (new->group != old->group || new->item != old->item) {
3263 bfree(cur_items);
3264 return false;
3265 }
3266 }
3267
3268 bfree(cur_items);
3269 return true;
3270 }
3271
3272 static obs_sceneitem_t *
get_sceneitem_parent_group(obs_scene_t * scene,obs_sceneitem_t * group_subitem)3273 get_sceneitem_parent_group(obs_scene_t *scene, obs_sceneitem_t *group_subitem)
3274 {
3275 if (group_subitem->is_group)
3276 return NULL;
3277
3278 obs_sceneitem_t *item = scene->first_item;
3279 while (item) {
3280 if (item->is_group &&
3281 item->source->context.data == group_subitem->parent)
3282 return item;
3283 item = item->next;
3284 }
3285
3286 return NULL;
3287 }
3288
obs_scene_reorder_items2(obs_scene_t * scene,struct obs_sceneitem_order_info * item_order,size_t item_order_size)3289 bool obs_scene_reorder_items2(obs_scene_t *scene,
3290 struct obs_sceneitem_order_info *item_order,
3291 size_t item_order_size)
3292 {
3293 if (!scene || !item_order_size || !item_order)
3294 return false;
3295
3296 obs_scene_addref(scene);
3297 full_lock(scene);
3298
3299 if (sceneitems_match2(scene, item_order, item_order_size)) {
3300 full_unlock(scene);
3301 obs_scene_release(scene);
3302 return false;
3303 }
3304
3305 for (size_t i = 0; i < item_order_size; i++) {
3306 struct obs_sceneitem_order_info *info = &item_order[i];
3307 if (!info->item->is_group) {
3308 obs_sceneitem_t *group =
3309 get_sceneitem_parent_group(scene, info->item);
3310 remove_group_transform(group, info->item);
3311 }
3312 }
3313
3314 scene->first_item = item_order[0].item;
3315
3316 obs_sceneitem_t *prev = NULL;
3317 for (size_t i = 0; i < item_order_size; i++) {
3318 struct obs_sceneitem_order_info *info = &item_order[i];
3319 obs_sceneitem_t *item = info->item;
3320
3321 if (info->item->is_group) {
3322 obs_sceneitem_t *sub_prev = NULL;
3323 obs_scene_t *sub_scene =
3324 info->item->source->context.data;
3325
3326 sub_scene->first_item = NULL;
3327
3328 obs_scene_addref(sub_scene);
3329 full_lock(sub_scene);
3330
3331 for (i++; i < item_order_size; i++) {
3332 struct obs_sceneitem_order_info *sub_info =
3333 &item_order[i];
3334 obs_sceneitem_t *sub_item = sub_info->item;
3335
3336 if (sub_info->group != info->item) {
3337 i--;
3338 break;
3339 }
3340
3341 if (!sub_scene->first_item)
3342 sub_scene->first_item = sub_item;
3343
3344 sub_item->prev = sub_prev;
3345 sub_item->next = NULL;
3346 sub_item->parent = sub_scene;
3347
3348 if (sub_prev)
3349 sub_prev->next = sub_item;
3350
3351 apply_group_transform(sub_info->item,
3352 sub_info->group);
3353
3354 sub_prev = sub_item;
3355 }
3356
3357 resize_group(info->item);
3358 full_unlock(sub_scene);
3359 obs_scene_release(sub_scene);
3360 }
3361
3362 item->prev = prev;
3363 item->next = NULL;
3364 item->parent = scene;
3365
3366 if (prev)
3367 prev->next = item;
3368
3369 prev = item;
3370 }
3371
3372 full_unlock(scene);
3373
3374 signal_reorder(scene->first_item);
3375 obs_scene_release(scene);
3376 return true;
3377 }
3378
obs_sceneitem_get_group(obs_scene_t * scene,obs_sceneitem_t * group_subitem)3379 obs_sceneitem_t *obs_sceneitem_get_group(obs_scene_t *scene,
3380 obs_sceneitem_t *group_subitem)
3381 {
3382 if (!scene || !group_subitem || group_subitem->is_group)
3383 return NULL;
3384
3385 full_lock(scene);
3386 obs_sceneitem_t *group =
3387 get_sceneitem_parent_group(scene, group_subitem);
3388 full_unlock(scene);
3389
3390 return group;
3391 }
3392
obs_source_is_group(const obs_source_t * source)3393 bool obs_source_is_group(const obs_source_t *source)
3394 {
3395 return source && strcmp(source->info.id, group_info.id) == 0;
3396 }
3397
obs_source_is_scene(const obs_source_t * source)3398 bool obs_source_is_scene(const obs_source_t *source)
3399 {
3400 return source && strcmp(source->info.id, scene_info.id) == 0;
3401 }
3402
obs_scene_is_group(const obs_scene_t * scene)3403 bool obs_scene_is_group(const obs_scene_t *scene)
3404 {
3405 return scene ? scene->is_group : false;
3406 }
3407
obs_sceneitem_group_enum_items(obs_sceneitem_t * group,bool (* callback)(obs_scene_t *,obs_sceneitem_t *,void *),void * param)3408 void obs_sceneitem_group_enum_items(obs_sceneitem_t *group,
3409 bool (*callback)(obs_scene_t *,
3410 obs_sceneitem_t *, void *),
3411 void *param)
3412 {
3413 if (!group || !group->is_group)
3414 return;
3415
3416 obs_scene_t *scene = group->source->context.data;
3417 if (scene)
3418 obs_scene_enum_items(scene, callback, param);
3419 }
3420
obs_sceneitem_force_update_transform(obs_sceneitem_t * item)3421 void obs_sceneitem_force_update_transform(obs_sceneitem_t *item)
3422 {
3423 if (!item)
3424 return;
3425
3426 if (os_atomic_set_bool(&item->update_transform, false))
3427 update_item_transform(item, false);
3428 }
3429
obs_sceneitem_set_show_transition(obs_sceneitem_t * item,obs_source_t * transition)3430 void obs_sceneitem_set_show_transition(obs_sceneitem_t *item,
3431 obs_source_t *transition)
3432 {
3433 if (!item)
3434 return;
3435 if (item->show_transition)
3436 obs_source_release(item->show_transition);
3437
3438 item->show_transition = transition;
3439 if (item->show_transition)
3440 obs_source_addref(item->show_transition);
3441 }
3442
obs_sceneitem_set_show_transition_duration(obs_sceneitem_t * item,uint32_t duration_ms)3443 void obs_sceneitem_set_show_transition_duration(obs_sceneitem_t *item,
3444 uint32_t duration_ms)
3445 {
3446 if (!item)
3447 return;
3448 item->show_transition_duration = duration_ms;
3449 }
3450
obs_sceneitem_get_show_transition(obs_sceneitem_t * item)3451 obs_source_t *obs_sceneitem_get_show_transition(obs_sceneitem_t *item)
3452 {
3453 if (!item)
3454 return NULL;
3455 return item->show_transition;
3456 }
3457
obs_sceneitem_get_show_transition_duration(obs_sceneitem_t * item)3458 uint32_t obs_sceneitem_get_show_transition_duration(obs_sceneitem_t *item)
3459 {
3460 if (!item)
3461 return 0;
3462 return item->show_transition_duration;
3463 }
3464
obs_sceneitem_set_hide_transition(obs_sceneitem_t * item,obs_source_t * transition)3465 void obs_sceneitem_set_hide_transition(obs_sceneitem_t *item,
3466 obs_source_t *transition)
3467 {
3468 if (!item)
3469 return;
3470 if (item->hide_transition)
3471 obs_source_release(item->hide_transition);
3472
3473 item->hide_transition = transition;
3474 if (item->hide_transition)
3475 obs_source_addref(item->hide_transition);
3476 }
3477
obs_sceneitem_set_hide_transition_duration(obs_sceneitem_t * item,uint32_t duration_ms)3478 void obs_sceneitem_set_hide_transition_duration(obs_sceneitem_t *item,
3479 uint32_t duration_ms)
3480 {
3481 if (!item)
3482 return;
3483 item->hide_transition_duration = duration_ms;
3484 }
3485
obs_sceneitem_get_hide_transition(obs_sceneitem_t * item)3486 obs_source_t *obs_sceneitem_get_hide_transition(obs_sceneitem_t *item)
3487 {
3488 if (!item)
3489 return NULL;
3490 return item->hide_transition;
3491 }
3492
obs_sceneitem_get_hide_transition_duration(obs_sceneitem_t * item)3493 uint32_t obs_sceneitem_get_hide_transition_duration(obs_sceneitem_t *item)
3494 {
3495 if (!item)
3496 return 0;
3497 return item->hide_transition_duration;
3498 }
3499
obs_sceneitem_transition_stop(void * data,calldata_t * calldata)3500 void obs_sceneitem_transition_stop(void *data, calldata_t *calldata)
3501 {
3502 obs_source_t *parent = data;
3503 obs_source_t *transition;
3504 calldata_get_ptr(calldata, "source", &transition);
3505 obs_source_remove_active_child(parent, transition);
3506 signal_handler_t *sh = obs_source_get_signal_handler(transition);
3507 if (sh)
3508 signal_handler_disconnect(sh, "transition_stop",
3509 obs_sceneitem_transition_stop,
3510 parent);
3511 }
3512
obs_sceneitem_do_transition(obs_sceneitem_t * item,bool visible)3513 void obs_sceneitem_do_transition(obs_sceneitem_t *item, bool visible)
3514 {
3515 if (!item)
3516 return;
3517
3518 if (transition_active(item->show_transition))
3519 obs_transition_force_stop(item->show_transition);
3520
3521 if (transition_active(item->hide_transition))
3522 obs_transition_force_stop(item->hide_transition);
3523
3524 obs_source_t *transition =
3525 visible ? obs_sceneitem_get_show_transition(item)
3526 : obs_sceneitem_get_hide_transition(item);
3527 if (!transition)
3528 return;
3529
3530 int duration =
3531 (int)(visible ? obs_sceneitem_get_show_transition_duration(item)
3532 : obs_sceneitem_get_hide_transition_duration(
3533 item));
3534
3535 const int cx = obs_source_get_width(item->source);
3536 const int cy = obs_source_get_height(item->source);
3537 obs_transition_set_size(transition, cx, cy);
3538 obs_transition_set_alignment(transition, OBS_ALIGN_CENTER);
3539 obs_transition_set_scale_type(transition, OBS_TRANSITION_SCALE_ASPECT);
3540
3541 if (duration == 0)
3542 duration = 300;
3543
3544 obs_scene_t *scene = obs_sceneitem_get_scene(item);
3545 obs_source_t *parent = obs_scene_get_source(scene);
3546 obs_source_add_active_child(parent, transition);
3547
3548 signal_handler_t *sh = obs_source_get_signal_handler(transition);
3549 if (sh)
3550 signal_handler_connect(sh, "transition_stop",
3551 obs_sceneitem_transition_stop, parent);
3552
3553 if (!visible) {
3554 obs_transition_set(transition, item->source);
3555 obs_transition_start(transition, OBS_TRANSITION_MODE_AUTO,
3556 duration, NULL);
3557 } else {
3558 obs_transition_set(transition, NULL);
3559 obs_transition_start(transition, OBS_TRANSITION_MODE_AUTO,
3560 duration, item->source);
3561 }
3562 }
3563
obs_sceneitem_transition_load(struct obs_scene_item * item,obs_data_t * data,bool show)3564 void obs_sceneitem_transition_load(struct obs_scene_item *item,
3565 obs_data_t *data, bool show)
3566 {
3567 if (!item || !data)
3568 return;
3569 const char *id = obs_data_get_string(data, "id");
3570 if (id && strlen(id)) {
3571 const char *tn = obs_data_get_string(data, "name");
3572 obs_data_t *s = obs_data_get_obj(data, "transition");
3573 obs_source_t *t = obs_source_create_private(id, tn, s);
3574 if (show)
3575 obs_sceneitem_set_show_transition(item, t);
3576 else
3577 obs_sceneitem_set_hide_transition(item, t);
3578 obs_source_release(t);
3579 obs_data_release(s);
3580 }
3581 if (show)
3582 item->show_transition_duration =
3583 (uint32_t)obs_data_get_int(data, "duration");
3584 else
3585 item->hide_transition_duration =
3586 (uint32_t)obs_data_get_int(data, "duration");
3587 }
3588
obs_sceneitem_transition_save(struct obs_scene_item * item,bool show)3589 obs_data_t *obs_sceneitem_transition_save(struct obs_scene_item *item,
3590 bool show)
3591 {
3592 obs_data_t *data = obs_data_create();
3593 struct obs_source *transition = show ? item->show_transition
3594 : item->hide_transition;
3595 if (transition) {
3596 obs_data_set_string(data, "id",
3597 obs_source_get_unversioned_id(transition));
3598 obs_data_set_string(data, "versioned_id",
3599 obs_source_get_id(transition));
3600 obs_data_set_string(data, "name",
3601 obs_source_get_name(transition));
3602 obs_data_t *s = obs_source_get_settings(transition);
3603 obs_data_set_obj(data, "transition", s);
3604 obs_data_release(s);
3605 }
3606 obs_data_set_int(data, "duration",
3607 show ? item->show_transition_duration
3608 : item->hide_transition_duration);
3609 return data;
3610 }
3611