[OBS] 渲染 source,scene,transition,filter

渲染transition或scene:不管是否需要transition,scene的渲染都是通過transition封裝調用的。

transition相關的變量:

struct obs_source {
    uint64_t transition_start_time;
	uint64_t transition_duration;
	pthread_mutex_t transition_tex_mutex;
	gs_texrender_t *transition_texrender[2]; // 兩個scene的畫布
	pthread_mutex_t transition_mutex;
	obs_source_t *transition_sources[2]; // 兩個scene對象
	bool transitioning_video; // video當前是否正在做transition
	bool transitioning_audio;
	bool transition_source_active[2];
	uint32_t transition_alignment;
	uint32_t transition_actual_cx;
	uint32_t transition_actual_cy;
	uint32_t transition_cx;
	uint32_t transition_cy;
	uint32_t transition_fixed_duration;
	bool transition_use_fixed_duration;
	enum obs_transition_mode transition_mode;
	enum obs_transition_scale_type transition_scale_type;
	struct matrix4 transition_matrices[2];

其中transition_sources[0]始終存儲了當前active的scene,當切換scene完成後,也會更新爲新的scene。transition_sources[1]保存transition時需要切換的目標場景,當開始transition時會對其賦值。transition結束時更新[0]並且置空[1]的函數如下:

static void obs_transition_stop(obs_source_t *transition)
{
	obs_source_t *old_child = transition->transition_sources[0];

    // deactive之前的場景源
	if (old_child && transition->transition_source_active[0])
		obs_source_remove_active_child(transition, old_child);
	obs_source_release(old_child);

	transition->transition_source_active[0] = true;
	transition->transition_source_active[1] = false;
	transition->transition_sources[0] = transition->transition_sources[1];
	transition->transition_sources[1] = NULL;
}

transition插件的渲染函數中會調用obs_transition_video_render,並傳遞一個回調函數,比如:

void slide_video_render(void *data, gs_effect_t *effect)
{
	struct slide_info *slide = data;
	obs_transition_video_render(slide->source, slide_callback);
}

如果當前不需要做video transition(即transitioning_video==false)obs_transition_video_render中直接渲染當前激活的scene source:

gs_matrix_push();
gs_matrix_mul(&matrices[0]);
obs_source_video_render(state.s[0]); // render current scene source
gs_matrix_pop();

如果需要做video transition,則把transition_sources的兩個scene source,分別畫到畫布數組transition_texrender中,然後調用transition插件傳遞過來的回調函數,由具體的transition插件完成兩個畫布中的紋理的合併:

	if (state.transitioning_video && locked && callback)
	{
		gs_texture_t *tex[2] = {NULL, NULL};
		for (size_t i = 0; i < 2; i++)
		{
			if (state.s[i])
			{
				render_child(transition, state.s[i], i);
				tex[i] = get_texture(transition, i); // get transition_texrender
				if (!tex[i])
					tex[i] = obs->video.transparent_texture;
			}
			else
			{
				tex[i] = obs->video.transparent_texture;
			}
		}
		 
		uint32_t cx = get_cx(transition);
		uint32_t cy = get_cy(transition);
		if (cx && cy)
		{
			gs_blend_state_push();
			gs_blend_function(GS_BLEND_ONE, GS_BLEND_INVSRCALPHA);

			callback(transition->context.data, tex[0], tex[1], t, cx, cy);

			gs_blend_state_pop();
		}

	} 

1. 渲染沒有filter的text source的調用堆棧(從transition source開始執行render函數):

2. 當input source有filter時,會在void render_video(obs_source_t *source)中先渲染filter:

// 如果當前Source有filter,則遞歸渲染filterSource,遞歸前後,inputsource的rendering_filter會被賦值爲true和false
// 此處的source是input source
if (source->filters.num && !source->rendering_filter)
	obs_source_render_filters(source); 

渲染filter的順序時,先渲染索引爲0的filter(即最後添加的filter,因爲每次新添加的filter都是放到索引0的位置),然後會調用到filterSource的video_render函數,在該函數中會先調用obs_source_process_filter_begin,其中又會獲取filter鏈路中的上一個source::filter_target , 會對filter_target調用obs_source_video_render(filter_target),在自己的畫布上渲染上一個filter或source,獲取的輸出紋理作爲當前filter的source texture。然後再調用obs_source_process_filter_begin,在主畫布上使用當前filter的shader渲染獲取的source texture.

 

添加filter時,設置filter_target的邏輯(可以把filter_target理解爲sourceTexture, 即當前filter要處理的源紋理):

filter->filter_parent = source; // 關聯的input source
if (source->filters.num > 0)
{
     // 設爲上一個filterSource
    filter->filter_target = source->filters.array[0];
}
else
{
    filter->filter_target = source;
}
da_insert(source->filters, 0, &filter); // 新的filter放到0的位置

對一個source 先添加Color Key,再添加Scroll,其渲染的遞歸調用順序如下:

filterPlugin的video_render函數,實現的僞代碼通常是:

obs_source_process_filter_begin()

// 設置shader參數
gs_effect_set_vec2(filter->param_add, &filter->offset);
gs_effect_set_vec2(filter->param_mul, &mul_val);
gs_effect_set_next_sampler(filter->param_image, filter->sampler);

obs_source_process_filter_end(filter->filterSource, filter->effect, cx, cy);

obs_source_process_filter_begin代碼備註

	if (!filter->filter_texrender) // 創建一個紋理作爲臨時畫布
		filter->filter_texrender = gs_texrender_create(format, GS_ZS_NONE);

	gs_blend_state_push(); // 存儲之前的狀態
	gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO);

	// 將自己的紋理設爲畫布,保存之前的畫布和深度模板
	if (gs_texrender_begin(filter->filter_texrender, cx, cy)) 
	{
		bool custom_draw = (parent_flags & OBS_SOURCE_CUSTOM_DRAW) != 0;
		bool async = (parent_flags & OBS_SOURCE_ASYNC) != 0;
		struct vec4 clear_color;

		vec4_zero(&clear_color);
		gs_clear(GS_CLEAR_COLOR, &clear_color, 0.0f, 0);
		gs_ortho(0.0f, (float)cx, 0.0f, (float)cy, -100.0f, 100.0f);

		if (target == parent && !custom_draw && !async)
		{
			obs_source_default_render(target);
		}
		else
		{
// 將filter鏈路的上一個source(可能是filterSource或inputSource)的輸出紋理,
// 畫到自己的臨時畫布上
			obs_source_video_render(target);
		}

            // 恢復之前的矩陣和畫布
		gs_texrender_end(filter->filter_texrender); 
	}

	gs_blend_state_pop(); // 恢復之前的狀態
	return true;

obs_source_process_filter_end代碼備註

void obs_source_process_filter_end(obs_source_t *filter, gs_effect_t *effect,
				   uint32_t width, uint32_t height)
{
	obs_source_t *target, *parent;
	gs_texture_t *texture;
	uint32_t parent_flags;

	if (!obs_ptr_valid(filter, "obs_source_process_filter_end"))
		return;

	target = obs_filter_get_target(filter);
	parent = obs_filter_get_parent(filter);
	parent_flags = parent->info.output_flags;

	if (can_bypass(target, parent, parent_flags, filter->allow_direct))
	{
		render_filter_bypass(target, effect, "Draw");
	}
	else
	{
		// 獲取自己臨時畫布的紋理(inputSource的紋理已經畫到臨時畫布了)
		texture = gs_texrender_get_texture(filter->filter_texrender); 
		if (texture)
		{
			// 將臨時畫布的紋理,使用filterShader畫到目標畫布上
			render_filter_tex(texture, effect, width, height, "Draw");
		}
	}
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章