1 #include <obs-module.h>
2 #include <graphics/vec2.h>
3 
4 struct scroll_filter_data {
5 	obs_source_t *context;
6 
7 	gs_effect_t *effect;
8 	gs_eparam_t *param_add;
9 	gs_eparam_t *param_mul;
10 	gs_eparam_t *param_image;
11 
12 	struct vec2 scroll_speed;
13 	gs_samplerstate_t *sampler;
14 	bool limit_cx;
15 	bool limit_cy;
16 	uint32_t cx;
17 	uint32_t cy;
18 
19 	struct vec2 size_i;
20 	struct vec2 offset;
21 
22 	bool loop;
23 };
24 
scroll_filter_get_name(void * unused)25 static const char *scroll_filter_get_name(void *unused)
26 {
27 	UNUSED_PARAMETER(unused);
28 	return obs_module_text("ScrollFilter");
29 }
30 
scroll_filter_create(obs_data_t * settings,obs_source_t * context)31 static void *scroll_filter_create(obs_data_t *settings, obs_source_t *context)
32 {
33 	struct scroll_filter_data *filter = bzalloc(sizeof(*filter));
34 	char *effect_path = obs_module_file("crop_filter.effect");
35 
36 	filter->context = context;
37 
38 	obs_enter_graphics();
39 	filter->effect = gs_effect_create_from_file(effect_path, NULL);
40 	obs_leave_graphics();
41 
42 	bfree(effect_path);
43 
44 	if (!filter->effect) {
45 		bfree(filter);
46 		return NULL;
47 	}
48 
49 	filter->param_add =
50 		gs_effect_get_param_by_name(filter->effect, "add_val");
51 	filter->param_mul =
52 		gs_effect_get_param_by_name(filter->effect, "mul_val");
53 	filter->param_image =
54 		gs_effect_get_param_by_name(filter->effect, "image");
55 
56 	obs_source_update(context, settings);
57 	return filter;
58 }
59 
scroll_filter_destroy(void * data)60 static void scroll_filter_destroy(void *data)
61 {
62 	struct scroll_filter_data *filter = data;
63 
64 	obs_enter_graphics();
65 	gs_effect_destroy(filter->effect);
66 	gs_samplerstate_destroy(filter->sampler);
67 	obs_leave_graphics();
68 
69 	bfree(filter);
70 }
71 
scroll_filter_update(void * data,obs_data_t * settings)72 static void scroll_filter_update(void *data, obs_data_t *settings)
73 {
74 	struct scroll_filter_data *filter = data;
75 
76 	filter->limit_cx = obs_data_get_bool(settings, "limit_cx");
77 	filter->limit_cy = obs_data_get_bool(settings, "limit_cy");
78 	filter->cx = (uint32_t)obs_data_get_int(settings, "cx");
79 	filter->cy = (uint32_t)obs_data_get_int(settings, "cy");
80 
81 	filter->scroll_speed.x =
82 		(float)obs_data_get_double(settings, "speed_x");
83 	filter->scroll_speed.y =
84 		(float)obs_data_get_double(settings, "speed_y");
85 
86 	filter->loop = obs_data_get_bool(settings, "loop");
87 
88 	struct gs_sampler_info sampler_info = {
89 		.filter = GS_FILTER_LINEAR,
90 		.address_u = filter->loop ? GS_ADDRESS_WRAP : GS_ADDRESS_BORDER,
91 		.address_v = filter->loop ? GS_ADDRESS_WRAP : GS_ADDRESS_BORDER,
92 	};
93 
94 	obs_enter_graphics();
95 	gs_samplerstate_destroy(filter->sampler);
96 	filter->sampler = gs_samplerstate_create(&sampler_info);
97 	obs_leave_graphics();
98 
99 	if (filter->scroll_speed.x == 0.0f)
100 		filter->offset.x = 0.0f;
101 	if (filter->scroll_speed.y == 0.0f)
102 		filter->offset.y = 0.0f;
103 }
104 
limit_cx_clicked(obs_properties_t * props,obs_property_t * p,obs_data_t * settings)105 static bool limit_cx_clicked(obs_properties_t *props, obs_property_t *p,
106 			     obs_data_t *settings)
107 {
108 	bool limit_size = obs_data_get_bool(settings, "limit_cx");
109 	obs_property_set_visible(obs_properties_get(props, "cx"), limit_size);
110 
111 	UNUSED_PARAMETER(p);
112 	return true;
113 }
114 
limit_cy_clicked(obs_properties_t * props,obs_property_t * p,obs_data_t * settings)115 static bool limit_cy_clicked(obs_properties_t *props, obs_property_t *p,
116 			     obs_data_t *settings)
117 {
118 	bool limit_size = obs_data_get_bool(settings, "limit_cy");
119 	obs_property_set_visible(obs_properties_get(props, "cy"), limit_size);
120 
121 	UNUSED_PARAMETER(p);
122 	return true;
123 }
124 
scroll_filter_properties(void * data)125 static obs_properties_t *scroll_filter_properties(void *data)
126 {
127 	obs_properties_t *props = obs_properties_create();
128 	obs_property_t *p;
129 
130 	obs_properties_add_float_slider(props, "speed_x",
131 					obs_module_text("ScrollFilter.SpeedX"),
132 					-500.0, 500.0, 1.0);
133 	obs_properties_add_float_slider(props, "speed_y",
134 					obs_module_text("ScrollFilter.SpeedY"),
135 					-500.0, 500.0, 1.0);
136 
137 	p = obs_properties_add_bool(props, "limit_cx",
138 				    obs_module_text("ScrollFilter.LimitWidth"));
139 	obs_property_set_modified_callback(p, limit_cx_clicked);
140 	obs_properties_add_int(props, "cx", obs_module_text("Crop.Width"), 1,
141 			       8192, 1);
142 
143 	p = obs_properties_add_bool(
144 		props, "limit_cy", obs_module_text("ScrollFilter.LimitHeight"));
145 	obs_property_set_modified_callback(p, limit_cy_clicked);
146 	obs_properties_add_int(props, "cy", obs_module_text("Crop.Height"), 1,
147 			       8192, 1);
148 
149 	obs_properties_add_bool(props, "loop",
150 				obs_module_text("ScrollFilter.Loop"));
151 
152 	UNUSED_PARAMETER(data);
153 	return props;
154 }
155 
scroll_filter_defaults(obs_data_t * settings)156 static void scroll_filter_defaults(obs_data_t *settings)
157 {
158 	obs_data_set_default_bool(settings, "limit_size", false);
159 	obs_data_set_default_int(settings, "cx", 100);
160 	obs_data_set_default_int(settings, "cy", 100);
161 	obs_data_set_default_bool(settings, "loop", true);
162 }
163 
scroll_filter_tick(void * data,float seconds)164 static void scroll_filter_tick(void *data, float seconds)
165 {
166 	struct scroll_filter_data *filter = data;
167 
168 	filter->offset.x += filter->size_i.x * filter->scroll_speed.x * seconds;
169 	filter->offset.y += filter->size_i.y * filter->scroll_speed.y * seconds;
170 
171 	if (filter->loop) {
172 		if (filter->offset.x > 1.0f)
173 			filter->offset.x -= 1.0f;
174 		if (filter->offset.y > 1.0f)
175 			filter->offset.y -= 1.0f;
176 	} else {
177 		if (filter->offset.x > 1.0f)
178 			filter->offset.x = 1.0f;
179 		if (filter->offset.y > 1.0f)
180 			filter->offset.y = 1.0f;
181 	}
182 }
183 
scroll_filter_render(void * data,gs_effect_t * effect)184 static void scroll_filter_render(void *data, gs_effect_t *effect)
185 {
186 	struct scroll_filter_data *filter = data;
187 	struct vec2 mul_val;
188 	uint32_t base_cx;
189 	uint32_t base_cy;
190 	uint32_t cx;
191 	uint32_t cy;
192 
193 	obs_source_t *target = obs_filter_get_target(filter->context);
194 	base_cx = obs_source_get_base_width(target);
195 	base_cy = obs_source_get_base_height(target);
196 
197 	cx = filter->limit_cx ? filter->cx : base_cx;
198 	cy = filter->limit_cy ? filter->cy : base_cy;
199 
200 	if (base_cx && base_cy) {
201 		vec2_set(&filter->size_i, 1.0f / (float)base_cx,
202 			 1.0f / (float)base_cy);
203 	} else {
204 		vec2_zero(&filter->size_i);
205 		obs_source_skip_video_filter(filter->context);
206 		return;
207 	}
208 
209 	vec2_set(&mul_val, (float)cx / (float)base_cx,
210 		 (float)cy / (float)base_cy);
211 
212 	if (!obs_source_process_filter_begin(filter->context, GS_RGBA,
213 					     OBS_NO_DIRECT_RENDERING))
214 		return;
215 
216 	gs_effect_set_vec2(filter->param_add, &filter->offset);
217 	gs_effect_set_vec2(filter->param_mul, &mul_val);
218 
219 	gs_effect_set_next_sampler(filter->param_image, filter->sampler);
220 
221 	gs_blend_state_push();
222 	gs_blend_function(GS_BLEND_ONE, GS_BLEND_INVSRCALPHA);
223 
224 	obs_source_process_filter_end(filter->context, filter->effect, cx, cy);
225 
226 	gs_blend_state_pop();
227 
228 	UNUSED_PARAMETER(effect);
229 }
230 
scroll_filter_width(void * data)231 static uint32_t scroll_filter_width(void *data)
232 {
233 	struct scroll_filter_data *filter = data;
234 	obs_source_t *target = obs_filter_get_target(filter->context);
235 
236 	return filter->limit_cx ? filter->cx
237 				: obs_source_get_base_width(target);
238 }
239 
scroll_filter_height(void * data)240 static uint32_t scroll_filter_height(void *data)
241 {
242 	struct scroll_filter_data *filter = data;
243 	obs_source_t *target = obs_filter_get_target(filter->context);
244 
245 	return filter->limit_cy ? filter->cy
246 				: obs_source_get_base_height(target);
247 }
248 
scroll_filter_show(void * data)249 static void scroll_filter_show(void *data)
250 {
251 	struct scroll_filter_data *filter = data;
252 	filter->offset.x = 0.0f;
253 	filter->offset.y = 0.0f;
254 }
255 
256 struct obs_source_info scroll_filter = {
257 	.id = "scroll_filter",
258 	.type = OBS_SOURCE_TYPE_FILTER,
259 	.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_SRGB,
260 	.get_name = scroll_filter_get_name,
261 	.create = scroll_filter_create,
262 	.destroy = scroll_filter_destroy,
263 	.update = scroll_filter_update,
264 	.get_properties = scroll_filter_properties,
265 	.get_defaults = scroll_filter_defaults,
266 	.video_tick = scroll_filter_tick,
267 	.video_render = scroll_filter_render,
268 	.get_width = scroll_filter_width,
269 	.get_height = scroll_filter_height,
270 	.show = scroll_filter_show,
271 };
272