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