1 /******************************************************************************
2     Copyright (C) 2013-2014 by Hugh Bailey <obs.jim@gmail.com>
3 
4     This program is free software: you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation, either version 2 of the License, or
7     (at your option) any later version.
8 
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13 
14     You should have received a copy of the GNU General Public License
15     along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 ******************************************************************************/
17 
18 #include "obs-internal.h"
19 #include "util/util_uint64.h"
20 #include "graphics/math-extra.h"
21 
22 #define lock_transition(transition) \
23 	pthread_mutex_lock(&transition->transition_mutex);
24 #define unlock_transition(transition) \
25 	pthread_mutex_unlock(&transition->transition_mutex);
26 
27 #define trylock_textures(transition) \
28 	pthread_mutex_trylock(&transition->transition_tex_mutex)
29 #define lock_textures(transition) \
30 	pthread_mutex_lock(&transition->transition_tex_mutex)
31 #define unlock_textures(transition) \
32 	pthread_mutex_unlock(&transition->transition_tex_mutex)
33 
transition_valid(const obs_source_t * transition,const char * func)34 static inline bool transition_valid(const obs_source_t *transition,
35 				    const char *func)
36 {
37 	if (!obs_ptr_valid(transition, func))
38 		return false;
39 	else if (transition->info.type != OBS_SOURCE_TYPE_TRANSITION)
40 		return false;
41 
42 	return true;
43 }
44 
obs_transition_init(obs_source_t * transition)45 bool obs_transition_init(obs_source_t *transition)
46 {
47 	pthread_mutex_init_value(&transition->transition_mutex);
48 	pthread_mutex_init_value(&transition->transition_tex_mutex);
49 	if (pthread_mutex_init(&transition->transition_mutex, NULL) != 0)
50 		return false;
51 	if (pthread_mutex_init(&transition->transition_tex_mutex, NULL) != 0)
52 		return false;
53 
54 	transition->transition_alignment = OBS_ALIGN_LEFT | OBS_ALIGN_TOP;
55 	transition->transition_texrender[0] =
56 		gs_texrender_create(GS_RGBA, GS_ZS_NONE);
57 	transition->transition_texrender[1] =
58 		gs_texrender_create(GS_RGBA, GS_ZS_NONE);
59 	transition->transition_source_active[0] = true;
60 
61 	return transition->transition_texrender[0] != NULL &&
62 	       transition->transition_texrender[1] != NULL;
63 }
64 
obs_transition_free(obs_source_t * transition)65 void obs_transition_free(obs_source_t *transition)
66 {
67 	pthread_mutex_destroy(&transition->transition_mutex);
68 	pthread_mutex_destroy(&transition->transition_tex_mutex);
69 
70 	gs_enter_context(obs->video.graphics);
71 	gs_texrender_destroy(transition->transition_texrender[0]);
72 	gs_texrender_destroy(transition->transition_texrender[1]);
73 	gs_leave_context();
74 }
75 
obs_transition_clear(obs_source_t * transition)76 void obs_transition_clear(obs_source_t *transition)
77 {
78 	obs_source_t *s[2];
79 	bool active[2];
80 
81 	if (!transition_valid(transition, "obs_transition_clear"))
82 		return;
83 
84 	lock_transition(transition);
85 	for (size_t i = 0; i < 2; i++) {
86 		s[i] = transition->transition_sources[i];
87 		active[i] = transition->transition_source_active[i];
88 		transition->transition_sources[i] = NULL;
89 		transition->transition_source_active[i] = false;
90 	}
91 	transition->transitioning_video = false;
92 	transition->transitioning_audio = false;
93 	unlock_transition(transition);
94 
95 	for (size_t i = 0; i < 2; i++) {
96 		if (s[i] && active[i])
97 			obs_source_remove_active_child(transition, s[i]);
98 		obs_source_release(s[i]);
99 	}
100 }
101 
102 void add_alignment(struct vec2 *v, uint32_t align, int cx, int cy);
103 
get_cx(obs_source_t * tr)104 static inline uint32_t get_cx(obs_source_t *tr)
105 {
106 	return tr->transition_cx ? tr->transition_cx : tr->transition_actual_cx;
107 }
108 
get_cy(obs_source_t * tr)109 static inline uint32_t get_cy(obs_source_t *tr)
110 {
111 	return tr->transition_cy ? tr->transition_cy : tr->transition_actual_cy;
112 }
113 
recalculate_transition_matrix(obs_source_t * tr,size_t idx)114 static void recalculate_transition_matrix(obs_source_t *tr, size_t idx)
115 {
116 	obs_source_t *child;
117 	struct matrix4 mat;
118 	struct vec2 pos;
119 	struct vec2 scale;
120 	float tr_cx = (float)get_cx(tr);
121 	float tr_cy = (float)get_cy(tr);
122 	float source_cx;
123 	float source_cy;
124 	float tr_aspect = tr_cx / tr_cy;
125 	float source_aspect;
126 	enum obs_transition_scale_type scale_type = tr->transition_scale_type;
127 
128 	lock_transition(tr);
129 
130 	child = tr->transition_sources[idx];
131 	if (!child) {
132 		unlock_transition(tr);
133 		return;
134 	}
135 
136 	source_cx = (float)obs_source_get_width(child);
137 	source_cy = (float)obs_source_get_height(child);
138 	unlock_transition(tr);
139 
140 	if (source_cx == 0.0f || source_cy == 0.0f)
141 		return;
142 
143 	source_aspect = source_cx / source_cy;
144 
145 	if (scale_type == OBS_TRANSITION_SCALE_MAX_ONLY) {
146 		if (source_cx > tr_cx || source_cy > tr_cy) {
147 			scale_type = OBS_TRANSITION_SCALE_ASPECT;
148 		} else {
149 			scale.x = 1.0f;
150 			scale.y = 1.0f;
151 		}
152 	}
153 
154 	if (scale_type == OBS_TRANSITION_SCALE_ASPECT) {
155 		bool use_width = tr_aspect < source_aspect;
156 		scale.x = scale.y = use_width ? tr_cx / source_cx
157 					      : tr_cy / source_cy;
158 
159 	} else if (scale_type == OBS_TRANSITION_SCALE_STRETCH) {
160 		scale.x = tr_cx / source_cx;
161 		scale.y = tr_cy / source_cy;
162 	}
163 
164 	source_cx *= scale.x;
165 	source_cy *= scale.y;
166 
167 	vec2_zero(&pos);
168 	add_alignment(&pos, tr->transition_alignment, (int)(tr_cx - source_cx),
169 		      (int)(tr_cy - source_cy));
170 
171 	matrix4_identity(&mat);
172 	matrix4_scale3f(&mat, &mat, scale.x, scale.y, 1.0f);
173 	matrix4_translate3f(&mat, &mat, pos.x, pos.y, 0.0f);
174 	matrix4_copy(&tr->transition_matrices[idx], &mat);
175 }
176 
recalculate_transition_matrices(obs_source_t * transition)177 static inline void recalculate_transition_matrices(obs_source_t *transition)
178 {
179 	recalculate_transition_matrix(transition, 0);
180 	recalculate_transition_matrix(transition, 1);
181 }
182 
recalculate_transition_size(obs_source_t * transition)183 static void recalculate_transition_size(obs_source_t *transition)
184 {
185 	uint32_t cx = 0, cy = 0;
186 	obs_source_t *child;
187 
188 	lock_transition(transition);
189 
190 	for (size_t i = 0; i < 2; i++) {
191 		child = transition->transition_sources[i];
192 		if (child) {
193 			uint32_t new_cx = obs_source_get_width(child);
194 			uint32_t new_cy = obs_source_get_height(child);
195 			if (new_cx > cx)
196 				cx = new_cx;
197 			if (new_cy > cy)
198 				cy = new_cy;
199 		}
200 	}
201 
202 	unlock_transition(transition);
203 
204 	transition->transition_actual_cx = cx;
205 	transition->transition_actual_cy = cy;
206 }
207 
obs_transition_tick(obs_source_t * transition,float t)208 void obs_transition_tick(obs_source_t *transition, float t)
209 {
210 	recalculate_transition_size(transition);
211 	recalculate_transition_matrices(transition);
212 
213 	if (transition->transition_mode == OBS_TRANSITION_MODE_MANUAL) {
214 		if (transition->transition_manual_torque == 0.0f) {
215 			transition->transition_manual_val =
216 				transition->transition_manual_target;
217 		} else {
218 			transition->transition_manual_val = calc_torquef(
219 				transition->transition_manual_val,
220 				transition->transition_manual_target,
221 				transition->transition_manual_torque,
222 				transition->transition_manual_clamp, t);
223 		}
224 	}
225 
226 	if (trylock_textures(transition) == 0) {
227 		gs_texrender_reset(transition->transition_texrender[0]);
228 		gs_texrender_reset(transition->transition_texrender[1]);
229 		unlock_textures(transition);
230 	}
231 }
232 
233 static void
set_source(obs_source_t * transition,enum obs_transition_target target,obs_source_t * new_child,bool (* callback)(obs_source_t * t,size_t idx,obs_source_t * c))234 set_source(obs_source_t *transition, enum obs_transition_target target,
235 	   obs_source_t *new_child,
236 	   bool (*callback)(obs_source_t *t, size_t idx, obs_source_t *c))
237 {
238 	size_t idx = (size_t)target;
239 	obs_source_t *old_child;
240 	bool add_success = true;
241 	bool already_active;
242 
243 	if (new_child)
244 		obs_source_addref(new_child);
245 
246 	lock_transition(transition);
247 
248 	old_child = transition->transition_sources[idx];
249 
250 	if (new_child == old_child) {
251 		unlock_transition(transition);
252 		obs_source_release(new_child);
253 		return;
254 	}
255 
256 	already_active = transition->transition_source_active[idx];
257 
258 	if (already_active) {
259 		if (new_child)
260 			add_success = obs_source_add_active_child(transition,
261 								  new_child);
262 		if (old_child && add_success)
263 			obs_source_remove_active_child(transition, old_child);
264 	}
265 
266 	if (callback && add_success)
267 		add_success = callback(transition, idx, new_child);
268 
269 	transition->transition_sources[idx] = add_success ? new_child : NULL;
270 
271 	unlock_transition(transition);
272 
273 	if (add_success) {
274 		if (transition->transition_cx == 0 ||
275 		    transition->transition_cy == 0) {
276 			recalculate_transition_size(transition);
277 			recalculate_transition_matrices(transition);
278 		}
279 	} else {
280 		obs_source_release(new_child);
281 	}
282 
283 	obs_source_release(old_child);
284 }
285 
obs_transition_get_source(obs_source_t * transition,enum obs_transition_target target)286 obs_source_t *obs_transition_get_source(obs_source_t *transition,
287 					enum obs_transition_target target)
288 {
289 	size_t idx = (size_t)target;
290 	obs_source_t *ret;
291 
292 	if (!transition_valid(transition, "obs_transition_get_source"))
293 		return NULL;
294 
295 	lock_transition(transition);
296 	ret = transition->transition_sources[idx];
297 	obs_source_addref(ret);
298 	unlock_transition(transition);
299 
300 	return ret;
301 }
302 
obs_transition_get_active_source(obs_source_t * transition)303 obs_source_t *obs_transition_get_active_source(obs_source_t *transition)
304 {
305 	obs_source_t *ret;
306 
307 	if (!transition_valid(transition, "obs_transition_get_source"))
308 		return NULL;
309 
310 	lock_transition(transition);
311 	if (transition->transitioning_audio || transition->transitioning_video)
312 		ret = transition->transition_sources[1];
313 	else
314 		ret = transition->transition_sources[0];
315 	obs_source_addref(ret);
316 	unlock_transition(transition);
317 
318 	return ret;
319 }
320 
activate_child(obs_source_t * transition,size_t idx)321 static inline bool activate_child(obs_source_t *transition, size_t idx)
322 {
323 	bool success = true;
324 
325 	lock_transition(transition);
326 
327 	if (transition->transition_sources[idx] &&
328 	    !transition->transition_source_active[idx]) {
329 
330 		success = obs_source_add_active_child(
331 			transition, transition->transition_sources[idx]);
332 		if (success)
333 			transition->transition_source_active[idx] = true;
334 	}
335 
336 	unlock_transition(transition);
337 
338 	return success;
339 }
340 
activate_transition(obs_source_t * transition,size_t idx,obs_source_t * child)341 static bool activate_transition(obs_source_t *transition, size_t idx,
342 				obs_source_t *child)
343 {
344 	if (!transition->transition_source_active[idx]) {
345 		if (!obs_source_add_active_child(transition, child))
346 			return false;
347 
348 		transition->transition_source_active[idx] = true;
349 	}
350 
351 	transition->transitioning_video = true;
352 	transition->transitioning_audio = true;
353 	return true;
354 }
355 
transition_active(obs_source_t * transition)356 static inline bool transition_active(obs_source_t *transition)
357 {
358 	return transition->transitioning_audio ||
359 	       transition->transitioning_video;
360 }
361 
obs_transition_start(obs_source_t * transition,enum obs_transition_mode mode,uint32_t duration_ms,obs_source_t * dest)362 bool obs_transition_start(obs_source_t *transition,
363 			  enum obs_transition_mode mode, uint32_t duration_ms,
364 			  obs_source_t *dest)
365 {
366 	bool active;
367 	bool same_as_source;
368 	bool same_as_dest;
369 	bool same_mode;
370 
371 	if (!transition_valid(transition, "obs_transition_start"))
372 		return false;
373 
374 	lock_transition(transition);
375 	same_as_source = dest == transition->transition_sources[0];
376 	same_as_dest = dest == transition->transition_sources[1];
377 	same_mode = mode == transition->transition_mode;
378 	active = transition_active(transition);
379 	unlock_transition(transition);
380 
381 	if (same_as_source && !active)
382 		return false;
383 	if (active && mode == OBS_TRANSITION_MODE_MANUAL && same_mode &&
384 	    same_as_dest)
385 		return true;
386 
387 	lock_transition(transition);
388 	transition->transition_mode = mode;
389 	transition->transition_manual_val = 0.0f;
390 	transition->transition_manual_target = 0.0f;
391 	unlock_transition(transition);
392 
393 	if (transition->info.transition_start)
394 		transition->info.transition_start(transition->context.data);
395 
396 	if (transition->transition_use_fixed_duration)
397 		duration_ms = transition->transition_fixed_duration;
398 
399 	if (!active || (!same_as_dest && !same_as_source)) {
400 		transition->transition_start_time = os_gettime_ns();
401 		transition->transition_duration =
402 			(uint64_t)duration_ms * 1000000ULL;
403 	}
404 
405 	set_source(transition, OBS_TRANSITION_SOURCE_B, dest,
406 		   activate_transition);
407 	if (dest == NULL && same_as_dest && !same_as_source) {
408 		transition->transitioning_video = true;
409 		transition->transitioning_audio = true;
410 	}
411 
412 	obs_source_dosignal(transition, "source_transition_start",
413 			    "transition_start");
414 
415 	recalculate_transition_size(transition);
416 	recalculate_transition_matrices(transition);
417 
418 	return true;
419 }
420 
obs_transition_set_manual_torque(obs_source_t * transition,float torque,float clamp)421 void obs_transition_set_manual_torque(obs_source_t *transition, float torque,
422 				      float clamp)
423 {
424 	lock_transition(transition);
425 	transition->transition_manual_torque = torque;
426 	transition->transition_manual_clamp = clamp;
427 	unlock_transition(transition);
428 }
429 
obs_transition_set_manual_time(obs_source_t * transition,float t)430 void obs_transition_set_manual_time(obs_source_t *transition, float t)
431 {
432 	lock_transition(transition);
433 	transition->transition_manual_target = t;
434 	unlock_transition(transition);
435 }
436 
obs_transition_set(obs_source_t * transition,obs_source_t * source)437 void obs_transition_set(obs_source_t *transition, obs_source_t *source)
438 {
439 	obs_source_t *s[2];
440 	bool active[2];
441 
442 	if (!transition_valid(transition, "obs_transition_clear"))
443 		return;
444 
445 	obs_source_addref(source);
446 
447 	lock_transition(transition);
448 	for (size_t i = 0; i < 2; i++) {
449 		s[i] = transition->transition_sources[i];
450 		active[i] = transition->transition_source_active[i];
451 		transition->transition_sources[i] = NULL;
452 		transition->transition_source_active[i] = false;
453 	}
454 	transition->transition_source_active[0] = true;
455 	transition->transition_sources[0] = source;
456 	transition->transitioning_video = false;
457 	transition->transitioning_audio = false;
458 	transition->transition_manual_val = 0.0f;
459 	transition->transition_manual_target = 0.0f;
460 	unlock_transition(transition);
461 
462 	for (size_t i = 0; i < 2; i++) {
463 		if (s[i] && active[i])
464 			obs_source_remove_active_child(transition, s[i]);
465 		obs_source_release(s[i]);
466 	}
467 
468 	if (source)
469 		obs_source_add_active_child(transition, source);
470 }
471 
calc_time(obs_source_t * transition,uint64_t ts)472 static float calc_time(obs_source_t *transition, uint64_t ts)
473 {
474 	if (transition->transition_mode == OBS_TRANSITION_MODE_MANUAL)
475 		return transition->transition_manual_val;
476 
477 	uint64_t end;
478 
479 	if (ts <= transition->transition_start_time)
480 		return 0.0f;
481 
482 	end = transition->transition_duration;
483 	ts -= transition->transition_start_time;
484 	if (ts >= end || end == 0)
485 		return 1.0f;
486 
487 	return (float)((long double)ts / (long double)end);
488 }
489 
get_video_time(obs_source_t * transition)490 static inline float get_video_time(obs_source_t *transition)
491 {
492 	uint64_t ts = obs->video.video_time;
493 	return calc_time(transition, ts);
494 }
495 
obs_transition_get_time(obs_source_t * transition)496 float obs_transition_get_time(obs_source_t *transition)
497 {
498 	return get_video_time(transition);
499 }
500 
get_texture(obs_source_t * transition,enum obs_transition_target target)501 static inline gs_texture_t *get_texture(obs_source_t *transition,
502 					enum obs_transition_target target)
503 {
504 	size_t idx = (size_t)target;
505 	return gs_texrender_get_texture(transition->transition_texrender[idx]);
506 }
507 
obs_transition_set_scale_type(obs_source_t * transition,enum obs_transition_scale_type type)508 void obs_transition_set_scale_type(obs_source_t *transition,
509 				   enum obs_transition_scale_type type)
510 {
511 	if (!transition_valid(transition, "obs_transition_set_scale_type"))
512 		return;
513 
514 	transition->transition_scale_type = type;
515 }
516 
517 enum obs_transition_scale_type
obs_transition_get_scale_type(const obs_source_t * transition)518 obs_transition_get_scale_type(const obs_source_t *transition)
519 {
520 	return transition_valid(transition, "obs_transition_get_scale_type")
521 		       ? transition->transition_scale_type
522 		       : OBS_TRANSITION_SCALE_MAX_ONLY;
523 }
524 
obs_transition_set_alignment(obs_source_t * transition,uint32_t alignment)525 void obs_transition_set_alignment(obs_source_t *transition, uint32_t alignment)
526 {
527 	if (!transition_valid(transition, "obs_transition_set_alignment"))
528 		return;
529 
530 	transition->transition_alignment = alignment;
531 }
532 
obs_transition_get_alignment(const obs_source_t * transition)533 uint32_t obs_transition_get_alignment(const obs_source_t *transition)
534 {
535 	return transition_valid(transition, "obs_transition_get_alignment")
536 		       ? transition->transition_alignment
537 		       : 0;
538 }
539 
obs_transition_set_size(obs_source_t * transition,uint32_t cx,uint32_t cy)540 void obs_transition_set_size(obs_source_t *transition, uint32_t cx, uint32_t cy)
541 {
542 	if (!transition_valid(transition, "obs_transition_set_size"))
543 		return;
544 
545 	transition->transition_cx = cx;
546 	transition->transition_cy = cy;
547 }
548 
obs_transition_get_size(const obs_source_t * transition,uint32_t * cx,uint32_t * cy)549 void obs_transition_get_size(const obs_source_t *transition, uint32_t *cx,
550 			     uint32_t *cy)
551 {
552 	if (!transition_valid(transition, "obs_transition_set_size")) {
553 		*cx = 0;
554 		*cy = 0;
555 		return;
556 	}
557 
558 	*cx = transition->transition_cx;
559 	*cy = transition->transition_cy;
560 }
561 
obs_transition_save(obs_source_t * tr,obs_data_t * data)562 void obs_transition_save(obs_source_t *tr, obs_data_t *data)
563 {
564 	obs_source_t *child;
565 
566 	lock_transition(tr);
567 	child = transition_active(tr) ? tr->transition_sources[1]
568 				      : tr->transition_sources[0];
569 
570 	obs_data_set_string(data, "transition_source_a",
571 			    child ? child->context.name : "");
572 	obs_data_set_int(data, "transition_alignment",
573 			 tr->transition_alignment);
574 	obs_data_set_int(data, "transition_mode", (int64_t)tr->transition_mode);
575 	obs_data_set_int(data, "transition_scale_type",
576 			 (int64_t)tr->transition_scale_type);
577 	obs_data_set_int(data, "transition_cx", tr->transition_cx);
578 	obs_data_set_int(data, "transition_cy", tr->transition_cy);
579 	unlock_transition(tr);
580 }
581 
obs_transition_load(obs_source_t * tr,obs_data_t * data)582 void obs_transition_load(obs_source_t *tr, obs_data_t *data)
583 {
584 	const char *name = obs_data_get_string(data, "transition_source_a");
585 	int64_t alignment = obs_data_get_int(data, "transition_alignment");
586 	int64_t mode = obs_data_get_int(data, "transition_mode");
587 	int64_t scale_type = obs_data_get_int(data, "transition_scale_type");
588 	int64_t cx = obs_data_get_int(data, "transition_cx");
589 	int64_t cy = obs_data_get_int(data, "transition_cy");
590 	obs_source_t *source = NULL;
591 
592 	if (name) {
593 		source = obs_get_source_by_name(name);
594 		if (source) {
595 			if (!obs_source_add_active_child(tr, source)) {
596 				blog(LOG_WARNING,
597 				     "Cannot set transition '%s' "
598 				     "to source '%s' due to "
599 				     "infinite recursion",
600 				     tr->context.name, name);
601 				obs_source_release(source);
602 				source = NULL;
603 			}
604 		} else {
605 			blog(LOG_WARNING,
606 			     "Failed to find source '%s' for "
607 			     "transition '%s'",
608 			     name, tr->context.name);
609 		}
610 	}
611 
612 	lock_transition(tr);
613 	tr->transition_sources[0] = source;
614 	tr->transition_source_active[0] = true;
615 	tr->transition_alignment = (uint32_t)alignment;
616 	tr->transition_mode = (enum obs_transition_mode)mode;
617 	tr->transition_scale_type = (enum obs_transition_scale_type)scale_type;
618 	tr->transition_cx = (uint32_t)cx;
619 	tr->transition_cy = (uint32_t)cy;
620 	unlock_transition(tr);
621 
622 	recalculate_transition_size(tr);
623 	recalculate_transition_matrices(tr);
624 }
625 
626 struct transition_state {
627 	obs_source_t *s[2];
628 	bool transitioning_video;
629 	bool transitioning_audio;
630 };
631 
copy_transition_state(obs_source_t * transition,struct transition_state * state)632 static inline void copy_transition_state(obs_source_t *transition,
633 					 struct transition_state *state)
634 {
635 	state->s[0] = transition->transition_sources[0];
636 	state->s[1] = transition->transition_sources[1];
637 	obs_source_addref(state->s[0]);
638 	obs_source_addref(state->s[1]);
639 
640 	state->transitioning_video = transition->transitioning_video;
641 	state->transitioning_audio = transition->transitioning_audio;
642 }
643 
enum_child(obs_source_t * tr,obs_source_t * child,obs_source_enum_proc_t enum_callback,void * param)644 static inline void enum_child(obs_source_t *tr, obs_source_t *child,
645 			      obs_source_enum_proc_t enum_callback, void *param)
646 {
647 	if (!child)
648 		return;
649 
650 	if (child->context.data && child->info.enum_active_sources)
651 		child->info.enum_active_sources(child->context.data,
652 						enum_callback, param);
653 
654 	enum_callback(tr, child, param);
655 }
656 
obs_transition_enum_sources(obs_source_t * transition,obs_source_enum_proc_t cb,void * param)657 void obs_transition_enum_sources(obs_source_t *transition,
658 				 obs_source_enum_proc_t cb, void *param)
659 {
660 	lock_transition(transition);
661 	for (size_t i = 0; i < 2; i++) {
662 		if (transition->transition_sources[i])
663 			cb(transition, transition->transition_sources[i],
664 			   param);
665 	}
666 	unlock_transition(transition);
667 }
668 
render_child(obs_source_t * transition,obs_source_t * child,size_t idx)669 static inline void render_child(obs_source_t *transition, obs_source_t *child,
670 				size_t idx)
671 {
672 	uint32_t cx = get_cx(transition);
673 	uint32_t cy = get_cy(transition);
674 	struct vec4 blank;
675 	if (!child)
676 		return;
677 
678 	if (gs_texrender_begin(transition->transition_texrender[idx], cx, cy)) {
679 		vec4_zero(&blank);
680 		gs_clear(GS_CLEAR_COLOR, &blank, 0.0f, 0);
681 		gs_ortho(0.0f, (float)cx, 0.0f, (float)cy, -100.0f, 100.0f);
682 
683 		gs_matrix_push();
684 		gs_matrix_mul(&transition->transition_matrices[idx]);
685 		obs_source_video_render(child);
686 		gs_matrix_pop();
687 
688 		gs_texrender_end(transition->transition_texrender[idx]);
689 	}
690 }
691 
obs_transition_stop(obs_source_t * transition)692 static void obs_transition_stop(obs_source_t *transition)
693 {
694 	obs_source_t *old_child = transition->transition_sources[0];
695 
696 	if (old_child && transition->transition_source_active[0])
697 		obs_source_remove_active_child(transition, old_child);
698 	obs_source_release(old_child);
699 
700 	transition->transition_source_active[0] = true;
701 	transition->transition_source_active[1] = false;
702 	transition->transition_sources[0] = transition->transition_sources[1];
703 	transition->transition_sources[1] = NULL;
704 }
705 
handle_stop(obs_source_t * transition)706 static inline void handle_stop(obs_source_t *transition)
707 {
708 	if (transition->info.transition_stop)
709 		transition->info.transition_stop(transition->context.data);
710 	obs_source_dosignal(transition, "source_transition_stop",
711 			    "transition_stop");
712 }
713 
obs_transition_force_stop(obs_source_t * transition)714 void obs_transition_force_stop(obs_source_t *transition)
715 {
716 	handle_stop(transition);
717 }
718 
obs_transition_video_render(obs_source_t * transition,obs_transition_video_render_callback_t callback)719 void obs_transition_video_render(obs_source_t *transition,
720 				 obs_transition_video_render_callback_t callback)
721 {
722 	struct transition_state state;
723 	struct matrix4 matrices[2];
724 	bool locked = false;
725 	bool stopped = false;
726 	bool video_stopped = false;
727 	float t;
728 
729 	if (!transition_valid(transition, "obs_transition_video_render"))
730 		return;
731 
732 	t = get_video_time(transition);
733 
734 	lock_transition(transition);
735 
736 	if (t >= 1.0f && transition->transitioning_video) {
737 		transition->transitioning_video = false;
738 		video_stopped = true;
739 
740 		if (!transition->transitioning_audio) {
741 			obs_transition_stop(transition);
742 			stopped = true;
743 		}
744 	}
745 	copy_transition_state(transition, &state);
746 	matrices[0] = transition->transition_matrices[0];
747 	matrices[1] = transition->transition_matrices[1];
748 
749 	unlock_transition(transition);
750 
751 	if (state.transitioning_video)
752 		locked = trylock_textures(transition) == 0;
753 
754 	if (state.transitioning_video && locked && callback) {
755 		gs_texture_t *tex[2];
756 		uint32_t cx;
757 		uint32_t cy;
758 
759 		for (size_t i = 0; i < 2; i++) {
760 			if (state.s[i]) {
761 				render_child(transition, state.s[i], i);
762 				tex[i] = get_texture(transition, i);
763 				if (!tex[i])
764 					tex[i] = obs->video.transparent_texture;
765 			} else {
766 				tex[i] = obs->video.transparent_texture;
767 			}
768 		}
769 
770 		cx = get_cx(transition);
771 		cy = get_cy(transition);
772 		if (cx && cy) {
773 			gs_blend_state_push();
774 			gs_blend_function(GS_BLEND_ONE, GS_BLEND_INVSRCALPHA);
775 
776 			callback(transition->context.data, tex[0], tex[1], t,
777 				 cx, cy);
778 
779 			gs_blend_state_pop();
780 		}
781 
782 	} else if (state.transitioning_audio) {
783 		if (state.s[1]) {
784 			gs_matrix_push();
785 			gs_matrix_mul(&matrices[1]);
786 			obs_source_video_render(state.s[1]);
787 			gs_matrix_pop();
788 		}
789 	} else {
790 		if (state.s[0]) {
791 			gs_matrix_push();
792 			gs_matrix_mul(&matrices[0]);
793 			obs_source_video_render(state.s[0]);
794 			gs_matrix_pop();
795 		}
796 	}
797 
798 	if (locked)
799 		unlock_textures(transition);
800 
801 	obs_source_release(state.s[0]);
802 	obs_source_release(state.s[1]);
803 
804 	if (video_stopped)
805 		obs_source_dosignal(transition, "source_transition_video_stop",
806 				    "transition_video_stop");
807 	if (stopped)
808 		handle_stop(transition);
809 }
810 
obs_transition_video_render_direct(obs_source_t * transition,enum obs_transition_target target)811 bool obs_transition_video_render_direct(obs_source_t *transition,
812 					enum obs_transition_target target)
813 {
814 	struct transition_state state;
815 	struct matrix4 matrices[2];
816 	bool stopped = false;
817 	bool video_stopped = false;
818 	bool render_b = target == OBS_TRANSITION_SOURCE_B;
819 	bool transitioning;
820 	float t;
821 
822 	if (!transition_valid(transition, "obs_transition_video_render"))
823 		return false;
824 
825 	t = get_video_time(transition);
826 
827 	lock_transition(transition);
828 
829 	if (t >= 1.0f && transition->transitioning_video) {
830 		transition->transitioning_video = false;
831 		video_stopped = true;
832 
833 		if (!transition->transitioning_audio) {
834 			obs_transition_stop(transition);
835 			stopped = true;
836 		}
837 	}
838 	copy_transition_state(transition, &state);
839 	transitioning = state.transitioning_audio || state.transitioning_video;
840 	matrices[0] = transition->transition_matrices[0];
841 	matrices[1] = transition->transition_matrices[1];
842 
843 	unlock_transition(transition);
844 
845 	int idx = (transitioning && render_b) ? 1 : 0;
846 	if (state.s[idx]) {
847 		gs_matrix_push();
848 		gs_matrix_mul(&matrices[idx]);
849 		obs_source_video_render(state.s[idx]);
850 		gs_matrix_pop();
851 	}
852 
853 	obs_source_release(state.s[0]);
854 	obs_source_release(state.s[1]);
855 
856 	if (video_stopped)
857 		obs_source_dosignal(transition, "source_transition_video_stop",
858 				    "transition_video_stop");
859 	if (stopped)
860 		handle_stop(transition);
861 
862 	return transitioning;
863 }
864 
get_sample_time(obs_source_t * transition,size_t sample_rate,size_t sample,uint64_t ts)865 static inline float get_sample_time(obs_source_t *transition,
866 				    size_t sample_rate, size_t sample,
867 				    uint64_t ts)
868 {
869 	uint64_t sample_ts_offset =
870 		util_mul_div64(sample, 1000000000ULL, sample_rate);
871 	uint64_t i_ts = ts + sample_ts_offset;
872 	return calc_time(transition, i_ts);
873 }
874 
mix_child(obs_source_t * transition,float * out,float * in,size_t count,size_t sample_rate,uint64_t ts,obs_transition_audio_mix_callback_t mix)875 static inline void mix_child(obs_source_t *transition, float *out, float *in,
876 			     size_t count, size_t sample_rate, uint64_t ts,
877 			     obs_transition_audio_mix_callback_t mix)
878 {
879 	void *context_data = transition->context.data;
880 
881 	for (size_t i = 0; i < count; i++) {
882 		float t = get_sample_time(transition, sample_rate, i, ts);
883 		out[i] += in[i] * mix(context_data, t);
884 	}
885 }
886 
process_audio(obs_source_t * transition,obs_source_t * child,struct obs_source_audio_mix * audio,uint64_t min_ts,uint32_t mixers,size_t channels,size_t sample_rate,obs_transition_audio_mix_callback_t mix)887 static void process_audio(obs_source_t *transition, obs_source_t *child,
888 			  struct obs_source_audio_mix *audio, uint64_t min_ts,
889 			  uint32_t mixers, size_t channels, size_t sample_rate,
890 			  obs_transition_audio_mix_callback_t mix)
891 {
892 	bool valid = child && !child->audio_pending && child->audio_ts;
893 	struct obs_source_audio_mix child_audio;
894 	uint64_t ts;
895 	size_t pos;
896 
897 	if (!valid)
898 		return;
899 
900 	ts = child->audio_ts;
901 	obs_source_get_audio_mix(child, &child_audio);
902 	pos = (size_t)ns_to_audio_frames(sample_rate, ts - min_ts);
903 
904 	if (pos > AUDIO_OUTPUT_FRAMES)
905 		return;
906 
907 	for (size_t mix_idx = 0; mix_idx < MAX_AUDIO_MIXES; mix_idx++) {
908 		struct audio_output_data *output = &audio->output[mix_idx];
909 		struct audio_output_data *input = &child_audio.output[mix_idx];
910 
911 		if ((mixers & (1 << mix_idx)) == 0)
912 			continue;
913 
914 		for (size_t ch = 0; ch < channels; ch++) {
915 			float *out = output->data[ch];
916 			float *in = input->data[ch];
917 
918 			mix_child(transition, out + pos, in,
919 				  AUDIO_OUTPUT_FRAMES - pos, sample_rate, ts,
920 				  mix);
921 		}
922 	}
923 }
924 
calc_min_ts(obs_source_t * sources[2])925 static inline uint64_t calc_min_ts(obs_source_t *sources[2])
926 {
927 	uint64_t min_ts = 0;
928 
929 	for (size_t i = 0; i < 2; i++) {
930 		if (sources[i] && !sources[i]->audio_pending &&
931 		    sources[i]->audio_ts) {
932 			if (!min_ts || sources[i]->audio_ts < min_ts)
933 				min_ts = sources[i]->audio_ts;
934 		}
935 	}
936 
937 	return min_ts;
938 }
939 
stop_audio(obs_source_t * transition)940 static inline bool stop_audio(obs_source_t *transition)
941 {
942 	transition->transitioning_audio = false;
943 	if (!transition->transitioning_video) {
944 		obs_transition_stop(transition);
945 		return true;
946 	}
947 
948 	return false;
949 }
950 
obs_transition_audio_render(obs_source_t * transition,uint64_t * ts_out,struct obs_source_audio_mix * audio,uint32_t mixers,size_t channels,size_t sample_rate,obs_transition_audio_mix_callback_t mix_a,obs_transition_audio_mix_callback_t mix_b)951 bool obs_transition_audio_render(obs_source_t *transition, uint64_t *ts_out,
952 				 struct obs_source_audio_mix *audio,
953 				 uint32_t mixers, size_t channels,
954 				 size_t sample_rate,
955 				 obs_transition_audio_mix_callback_t mix_a,
956 				 obs_transition_audio_mix_callback_t mix_b)
957 {
958 	obs_source_t *sources[2];
959 	struct transition_state state = {0};
960 	bool stopped = false;
961 	uint64_t min_ts;
962 	float t;
963 
964 	if (!transition_valid(transition, "obs_transition_audio_render"))
965 		return false;
966 
967 	lock_transition(transition);
968 
969 	sources[0] = transition->transition_sources[0];
970 	sources[1] = transition->transition_sources[1];
971 
972 	min_ts = calc_min_ts(sources);
973 
974 	if (min_ts) {
975 		t = calc_time(transition, min_ts);
976 
977 		if (t >= 1.0f && transition->transitioning_audio)
978 			stopped = stop_audio(transition);
979 
980 		sources[0] = transition->transition_sources[0];
981 		sources[1] = transition->transition_sources[1];
982 		min_ts = calc_min_ts(sources);
983 		if (min_ts)
984 			copy_transition_state(transition, &state);
985 
986 	} else if (!transition->transitioning_video &&
987 		   transition->transitioning_audio) {
988 		stopped = stop_audio(transition);
989 	}
990 
991 	unlock_transition(transition);
992 
993 	if (min_ts) {
994 		if (state.transitioning_audio) {
995 			if (state.s[0])
996 				process_audio(transition, state.s[0], audio,
997 					      min_ts, mixers, channels,
998 					      sample_rate, mix_a);
999 			if (state.s[1])
1000 				process_audio(transition, state.s[1], audio,
1001 					      min_ts, mixers, channels,
1002 					      sample_rate, mix_b);
1003 		} else if (state.s[0]) {
1004 			memcpy(audio->output[0].data[0],
1005 			       state.s[0]->audio_output_buf[0][0],
1006 			       TOTAL_AUDIO_SIZE);
1007 		}
1008 
1009 		obs_source_release(state.s[0]);
1010 		obs_source_release(state.s[1]);
1011 	}
1012 
1013 	if (stopped)
1014 		handle_stop(transition);
1015 
1016 	*ts_out = min_ts;
1017 	return !!min_ts;
1018 }
1019 
obs_transition_enable_fixed(obs_source_t * transition,bool enable,uint32_t duration)1020 void obs_transition_enable_fixed(obs_source_t *transition, bool enable,
1021 				 uint32_t duration)
1022 {
1023 	if (!transition_valid(transition, "obs_transition_enable_fixed"))
1024 		return;
1025 
1026 	transition->transition_use_fixed_duration = enable;
1027 	transition->transition_fixed_duration = duration;
1028 }
1029 
obs_transition_fixed(obs_source_t * transition)1030 bool obs_transition_fixed(obs_source_t *transition)
1031 {
1032 	return transition_valid(transition, "obs_transition_fixed")
1033 		       ? transition->transition_use_fixed_duration
1034 		       : false;
1035 }
1036 
1037 static inline obs_source_t *
copy_source_state(obs_source_t * tr_dest,obs_source_t * tr_source,size_t idx)1038 copy_source_state(obs_source_t *tr_dest, obs_source_t *tr_source, size_t idx)
1039 {
1040 	obs_source_t *old_child = tr_dest->transition_sources[idx];
1041 	obs_source_t *new_child = tr_source->transition_sources[idx];
1042 	bool active = tr_source->transition_source_active[idx];
1043 
1044 	if (old_child && tr_dest->transition_source_active[idx])
1045 		obs_source_remove_active_child(tr_dest, old_child);
1046 
1047 	tr_dest->transition_sources[idx] = new_child;
1048 	tr_dest->transition_source_active[idx] = active;
1049 
1050 	if (active && new_child)
1051 		obs_source_add_active_child(tr_dest, new_child);
1052 	obs_source_addref(new_child);
1053 
1054 	return old_child;
1055 }
1056 
obs_transition_swap_begin(obs_source_t * tr_dest,obs_source_t * tr_source)1057 void obs_transition_swap_begin(obs_source_t *tr_dest, obs_source_t *tr_source)
1058 {
1059 	obs_source_t *old_children[2];
1060 
1061 	if (tr_dest == tr_source)
1062 		return;
1063 
1064 	lock_textures(tr_source);
1065 	lock_textures(tr_dest);
1066 
1067 	lock_transition(tr_source);
1068 	lock_transition(tr_dest);
1069 
1070 	for (size_t i = 0; i < 2; i++)
1071 		old_children[i] = copy_source_state(tr_dest, tr_source, i);
1072 
1073 	unlock_transition(tr_dest);
1074 	unlock_transition(tr_source);
1075 
1076 	for (size_t i = 0; i < 2; i++)
1077 		obs_source_release(old_children[i]);
1078 }
1079 
obs_transition_swap_end(obs_source_t * tr_dest,obs_source_t * tr_source)1080 void obs_transition_swap_end(obs_source_t *tr_dest, obs_source_t *tr_source)
1081 {
1082 	if (tr_dest == tr_source)
1083 		return;
1084 
1085 	obs_transition_clear(tr_source);
1086 
1087 	for (size_t i = 0; i < 2; i++) {
1088 		gs_texrender_t *dest = tr_dest->transition_texrender[i];
1089 		gs_texrender_t *source = tr_source->transition_texrender[i];
1090 
1091 		tr_dest->transition_texrender[i] = source;
1092 		tr_source->transition_texrender[i] = dest;
1093 	}
1094 
1095 	unlock_textures(tr_dest);
1096 	unlock_textures(tr_source);
1097 }
1098