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(&params, stack, sizeof(stack));
68 	calldata_set_ptr(&params, "item", item);
69 
70 	signal_parent(item->parent, "item_remove", &params);
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(&params, stack, sizeof(stack));
436 	calldata_set_ptr(&params, "item", item);
437 	signal_parent(item->parent, "item_transform", &params);
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, &copy, 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(&params, stack, sizeof(stack));
1925 	calldata_set_ptr(&params, "scene", scene);
1926 	calldata_set_ptr(&params, "item", item);
1927 	signal_handler_signal(scene->source->context.signals, "item_add",
1928 			      &params);
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(&params, stack, sizeof(stack));
2225 	calldata_set_ptr(&params, "item", item);
2226 
2227 	signal_parent(item->parent, command, &params);
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(&params, stack, sizeof(stack));
2284 	signal_parent(item->parent, command, &params);
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(&params, stack, sizeof(stack));
2296 	signal_parent(scene, command, &params);
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