1 // Modern effects for a modern Streamer
2 // Copyright (C) 2019 Michael Fabian Dirks
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, write to the Free Software
16 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
17 
18 #include "gfx-blur-gaussian-linear.hpp"
19 #include <stdexcept>
20 #include "obs/gs/gs-helper.hpp"
21 
22 #ifdef _MSC_VER
23 #pragma warning(push)
24 #pragma warning(disable : 4201)
25 #endif
26 #include <obs.h>
27 #include <obs-module.h>
28 #ifdef _MSC_VER
29 #pragma warning(pop)
30 #endif
31 
32 // FIXME: This breaks when MAX_KERNEL_SIZE is changed, due to the way the Gaussian
33 //  function first goes up at the point, and then once we pass the critical point
34 //  will go down again and it is not handled well. This is a pretty basic
35 //  approximation anyway at the moment.
36 #define MAX_KERNEL_SIZE 128
37 #define MAX_BLUR_SIZE (MAX_KERNEL_SIZE - 1)
38 #define SEARCH_DENSITY double_t(1. / 500.)
39 #define SEARCH_THRESHOLD double_t(1. / (MAX_KERNEL_SIZE * 5))
40 #define SEARCH_EXTENSION 1
41 #define SEARCH_RANGE MAX_KERNEL_SIZE * 2
42 
gaussian_linear_data()43 gfx::blur::gaussian_linear_data::gaussian_linear_data()
44 {
45 	auto gctx = gs::context();
46 	_effect   = gs::effect::create(streamfx::data_file_path("effects/blur/gaussian-linear.effect").u8string());
47 
48 	// Precalculate Kernels
49 	for (std::size_t kernel_size = 1; kernel_size <= MAX_BLUR_SIZE; kernel_size++) {
50 		std::vector<double_t> kernel_math(MAX_KERNEL_SIZE);
51 		std::vector<float_t>  kernel_data(MAX_KERNEL_SIZE);
52 		double_t              actual_width = 1.;
53 
54 		// Find actual kernel width.
55 		for (double_t h = SEARCH_DENSITY; h < SEARCH_RANGE; h += SEARCH_DENSITY) {
56 			if (util::math::gaussian<double_t>(double_t(kernel_size + SEARCH_EXTENSION), h) > SEARCH_THRESHOLD) {
57 				actual_width = h;
58 				break;
59 			}
60 		}
61 
62 		// Calculate and normalize
63 		double_t sum = 0;
64 		for (std::size_t p = 0; p <= kernel_size; p++) {
65 			kernel_math[p] = util::math::gaussian<double_t>(double_t(p), actual_width);
66 			sum += kernel_math[p] * (p > 0 ? 2 : 1);
67 		}
68 
69 		// Normalize to fill the entire 0..1 range over the width.
70 		double_t inverse_sum = 1.0 / sum;
71 		for (std::size_t p = 0; p <= kernel_size; p++) {
72 			kernel_data.at(p) = float_t(kernel_math[p] * inverse_sum);
73 		}
74 
75 		_kernels.push_back(std::move(kernel_data));
76 	}
77 }
78 
~gaussian_linear_data()79 gfx::blur::gaussian_linear_data::~gaussian_linear_data()
80 {
81 	_effect.reset();
82 }
83 
get_effect()84 gs::effect gfx::blur::gaussian_linear_data::get_effect()
85 {
86 	return _effect;
87 }
88 
get_kernel(std::size_t width)89 std::vector<float_t> const& gfx::blur::gaussian_linear_data::get_kernel(std::size_t width)
90 {
91 	if (width < 1)
92 		width = 1;
93 	if (width > MAX_BLUR_SIZE)
94 		width = MAX_BLUR_SIZE;
95 	width -= 1;
96 	return _kernels[width];
97 }
98 
gaussian_linear_factory()99 gfx::blur::gaussian_linear_factory::gaussian_linear_factory() {}
100 
~gaussian_linear_factory()101 gfx::blur::gaussian_linear_factory::~gaussian_linear_factory() {}
102 
is_type_supported(::gfx::blur::type v)103 bool gfx::blur::gaussian_linear_factory::is_type_supported(::gfx::blur::type v)
104 {
105 	switch (v) {
106 	case ::gfx::blur::type::Area:
107 		return true;
108 	case ::gfx::blur::type::Directional:
109 		return true;
110 	default:
111 		return false;
112 	}
113 }
114 
create(::gfx::blur::type v)115 std::shared_ptr<::gfx::blur::base> gfx::blur::gaussian_linear_factory::create(::gfx::blur::type v)
116 {
117 	switch (v) {
118 	case ::gfx::blur::type::Area:
119 		return std::make_shared<::gfx::blur::gaussian_linear>();
120 	case ::gfx::blur::type::Directional:
121 		return std::static_pointer_cast<::gfx::blur::gaussian_linear>(
122 			std::make_shared<::gfx::blur::gaussian_linear_directional>());
123 	default:
124 		throw std::runtime_error("Invalid type.");
125 	}
126 }
127 
get_min_size(::gfx::blur::type)128 double_t gfx::blur::gaussian_linear_factory::get_min_size(::gfx::blur::type)
129 {
130 	return double_t(1.0);
131 }
132 
get_step_size(::gfx::blur::type)133 double_t gfx::blur::gaussian_linear_factory::get_step_size(::gfx::blur::type)
134 {
135 	return double_t(1.0);
136 }
137 
get_max_size(::gfx::blur::type)138 double_t gfx::blur::gaussian_linear_factory::get_max_size(::gfx::blur::type)
139 {
140 	return double_t(MAX_BLUR_SIZE);
141 }
142 
get_min_angle(::gfx::blur::type v)143 double_t gfx::blur::gaussian_linear_factory::get_min_angle(::gfx::blur::type v)
144 {
145 	switch (v) {
146 	case ::gfx::blur::type::Directional:
147 	case ::gfx::blur::type::Rotational:
148 		return -180.0;
149 	default:
150 		return 0;
151 	}
152 }
153 
get_step_angle(::gfx::blur::type)154 double_t gfx::blur::gaussian_linear_factory::get_step_angle(::gfx::blur::type)
155 {
156 	return double_t(0.01);
157 }
158 
get_max_angle(::gfx::blur::type v)159 double_t gfx::blur::gaussian_linear_factory::get_max_angle(::gfx::blur::type v)
160 {
161 	switch (v) {
162 	case ::gfx::blur::type::Directional:
163 	case ::gfx::blur::type::Rotational:
164 		return 180.0;
165 	default:
166 		return 0;
167 	}
168 }
169 
is_step_scale_supported(::gfx::blur::type v)170 bool gfx::blur::gaussian_linear_factory::is_step_scale_supported(::gfx::blur::type v)
171 {
172 	switch (v) {
173 	case ::gfx::blur::type::Area:
174 	case ::gfx::blur::type::Zoom:
175 	case ::gfx::blur::type::Directional:
176 		return true;
177 	default:
178 		return false;
179 	}
180 }
181 
get_min_step_scale_x(::gfx::blur::type)182 double_t gfx::blur::gaussian_linear_factory::get_min_step_scale_x(::gfx::blur::type)
183 {
184 	return double_t(0.01);
185 }
186 
get_step_step_scale_x(::gfx::blur::type)187 double_t gfx::blur::gaussian_linear_factory::get_step_step_scale_x(::gfx::blur::type)
188 {
189 	return double_t(0.01);
190 }
191 
get_max_step_scale_x(::gfx::blur::type)192 double_t gfx::blur::gaussian_linear_factory::get_max_step_scale_x(::gfx::blur::type)
193 {
194 	return double_t(1000.0);
195 }
196 
get_min_step_scale_y(::gfx::blur::type)197 double_t gfx::blur::gaussian_linear_factory::get_min_step_scale_y(::gfx::blur::type)
198 {
199 	return double_t(0.01);
200 }
201 
get_step_step_scale_y(::gfx::blur::type)202 double_t gfx::blur::gaussian_linear_factory::get_step_step_scale_y(::gfx::blur::type)
203 {
204 	return double_t(0.01);
205 }
206 
get_max_step_scale_y(::gfx::blur::type)207 double_t gfx::blur::gaussian_linear_factory::get_max_step_scale_y(::gfx::blur::type)
208 {
209 	return double_t(1000.0);
210 }
211 
data()212 std::shared_ptr<::gfx::blur::gaussian_linear_data> gfx::blur::gaussian_linear_factory::data()
213 {
214 	std::unique_lock<std::mutex>                       ulock(_data_lock);
215 	std::shared_ptr<::gfx::blur::gaussian_linear_data> data = _data.lock();
216 	if (!data) {
217 		data  = std::make_shared<::gfx::blur::gaussian_linear_data>();
218 		_data = data;
219 	}
220 	return data;
221 }
222 
get()223 ::gfx::blur::gaussian_linear_factory& gfx::blur::gaussian_linear_factory::get()
224 {
225 	static ::gfx::blur::gaussian_linear_factory instance;
226 	return instance;
227 }
228 
gaussian_linear()229 gfx::blur::gaussian_linear::gaussian_linear()
230 	: _data(::gfx::blur::gaussian_linear_factory::get().data()), _size(1.), _step_scale({1., 1.})
231 {
232 	auto gctx = gs::context();
233 
234 	_rendertarget  = std::make_shared<gs::rendertarget>(GS_RGBA, GS_ZS_NONE);
235 	_rendertarget2 = std::make_shared<gs::rendertarget>(GS_RGBA, GS_ZS_NONE);
236 }
237 
~gaussian_linear()238 gfx::blur::gaussian_linear::~gaussian_linear() {}
239 
set_input(std::shared_ptr<::gs::texture> texture)240 void gfx::blur::gaussian_linear::set_input(std::shared_ptr<::gs::texture> texture)
241 {
242 	_input_texture = texture;
243 }
244 
get_type()245 ::gfx::blur::type gfx::blur::gaussian_linear::get_type()
246 {
247 	return ::gfx::blur::type::Area;
248 }
249 
get_size()250 double_t gfx::blur::gaussian_linear::get_size()
251 {
252 	return _size;
253 }
254 
set_size(double_t width)255 void gfx::blur::gaussian_linear::set_size(double_t width)
256 {
257 	if (width < 1.)
258 		width = 1.;
259 	if (width > MAX_BLUR_SIZE)
260 		width = MAX_BLUR_SIZE;
261 	_size = width;
262 }
263 
set_step_scale(double_t x,double_t y)264 void gfx::blur::gaussian_linear::set_step_scale(double_t x, double_t y)
265 {
266 	_step_scale.first  = x;
267 	_step_scale.second = y;
268 }
269 
get_step_scale(double_t & x,double_t & y)270 void gfx::blur::gaussian_linear::get_step_scale(double_t& x, double_t& y)
271 {
272 	x = _step_scale.first;
273 	y = _step_scale.second;
274 }
275 
get_step_scale_x()276 double_t gfx::blur::gaussian_linear::get_step_scale_x()
277 {
278 	return _step_scale.first;
279 }
280 
get_step_scale_y()281 double_t gfx::blur::gaussian_linear::get_step_scale_y()
282 {
283 	return _step_scale.second;
284 }
285 
render()286 std::shared_ptr<::gs::texture> gfx::blur::gaussian_linear::render()
287 {
288 	auto gctx = gs::context();
289 
290 #ifdef ENABLE_PROFILING
291 	auto gdmp = gs::debug_marker(gs::debug_color_azure_radiance, "Gaussian Linear Blur");
292 #endif
293 
294 	gs::effect effect = _data->get_effect();
295 	auto       kernel = _data->get_kernel(size_t(_size));
296 
297 	if (!effect || ((_step_scale.first + _step_scale.second) < std::numeric_limits<double_t>::epsilon())) {
298 		return _input_texture;
299 	}
300 
301 	float_t width  = float_t(_input_texture->get_width());
302 	float_t height = float_t(_input_texture->get_height());
303 
304 	// Setup
305 	gs_set_cull_mode(GS_NEITHER);
306 	gs_enable_color(true, true, true, true);
307 	gs_enable_depth_test(false);
308 	gs_depth_function(GS_ALWAYS);
309 	gs_blend_state_push();
310 	gs_reset_blend_state();
311 	gs_enable_blending(false);
312 	gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO);
313 	gs_enable_stencil_test(false);
314 	gs_enable_stencil_write(false);
315 	gs_stencil_function(GS_STENCIL_BOTH, GS_ALWAYS);
316 	gs_stencil_op(GS_STENCIL_BOTH, GS_ZERO, GS_ZERO, GS_ZERO);
317 
318 	effect.get_parameter("pImage").set_texture(_input_texture);
319 	effect.get_parameter("pStepScale").set_float2(float_t(_step_scale.first), float_t(_step_scale.second));
320 	effect.get_parameter("pSize").set_float(float_t(_size));
321 	effect.get_parameter("pKernel").set_value(kernel.data(), MAX_KERNEL_SIZE);
322 
323 	// First Pass
324 	if (_step_scale.first > std::numeric_limits<double_t>::epsilon()) {
325 		effect.get_parameter("pImageTexel").set_float2(float_t(1.f / width), 0.f);
326 
327 		{
328 #ifdef ENABLE_PROFILING
329 			auto gdm = gs::debug_marker(gs::debug_color_azure_radiance, "Horizontal");
330 #endif
331 
332 			auto op = _rendertarget2->render(uint32_t(width), uint32_t(height));
333 			gs_ortho(0, 1., 0, 1., 0, 1.);
334 			while (gs_effect_loop(effect.get_object(), "Draw")) {
335 				streamfx::gs_draw_fullscreen_tri();
336 			}
337 		}
338 
339 		std::swap(_rendertarget, _rendertarget2);
340 		effect.get_parameter("pImage").set_texture(_rendertarget->get_texture());
341 	}
342 
343 	// Second Pass
344 	if (_step_scale.second > std::numeric_limits<double_t>::epsilon()) {
345 		effect.get_parameter("pImageTexel").set_float2(0.f, float_t(1.f / height));
346 
347 		{
348 #ifdef ENABLE_PROFILING
349 			auto gdm = gs::debug_marker(gs::debug_color_azure_radiance, "Vertical");
350 #endif
351 
352 			auto op = _rendertarget2->render(uint32_t(width), uint32_t(height));
353 			gs_ortho(0, 1., 0, 1., 0, 1.);
354 			while (gs_effect_loop(effect.get_object(), "Draw")) {
355 				streamfx::gs_draw_fullscreen_tri();
356 			}
357 		}
358 
359 		std::swap(_rendertarget, _rendertarget2);
360 	}
361 
362 	gs_blend_state_pop();
363 
364 	return this->get();
365 }
366 
get()367 std::shared_ptr<::gs::texture> gfx::blur::gaussian_linear::get()
368 {
369 	return _rendertarget->get_texture();
370 }
371 
gaussian_linear_directional()372 gfx::blur::gaussian_linear_directional::gaussian_linear_directional() : _angle(0.) {}
373 
~gaussian_linear_directional()374 gfx::blur::gaussian_linear_directional::~gaussian_linear_directional() {}
375 
get_type()376 ::gfx::blur::type gfx::blur::gaussian_linear_directional::get_type()
377 {
378 	return ::gfx::blur::type::Directional;
379 }
380 
get_angle()381 double_t gfx::blur::gaussian_linear_directional::get_angle()
382 {
383 	return D_RAD_TO_DEG(_angle);
384 }
385 
set_angle(double_t angle)386 void gfx::blur::gaussian_linear_directional::set_angle(double_t angle)
387 {
388 	_angle = D_DEG_TO_RAD(angle);
389 }
390 
render()391 std::shared_ptr<::gs::texture> gfx::blur::gaussian_linear_directional::render()
392 {
393 	auto gctx = gs::context();
394 
395 #ifdef ENABLE_PROFILING
396 	auto gdmp = gs::debug_marker(gs::debug_color_azure_radiance, "Gaussian Linear Directional Blur");
397 #endif
398 
399 	gs::effect effect = _data->get_effect();
400 	auto       kernel = _data->get_kernel(size_t(_size));
401 
402 	if (!effect || ((_step_scale.first + _step_scale.second) < std::numeric_limits<double_t>::epsilon())) {
403 		return _input_texture;
404 	}
405 
406 	float_t width  = float_t(_input_texture->get_width());
407 	float_t height = float_t(_input_texture->get_height());
408 
409 	// Setup
410 	gs_set_cull_mode(GS_NEITHER);
411 	gs_enable_color(true, true, true, true);
412 	gs_enable_depth_test(false);
413 	gs_depth_function(GS_ALWAYS);
414 	gs_blend_state_push();
415 	gs_reset_blend_state();
416 	gs_enable_blending(false);
417 	gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO);
418 	gs_enable_stencil_test(false);
419 	gs_enable_stencil_write(false);
420 	gs_stencil_function(GS_STENCIL_BOTH, GS_ALWAYS);
421 	gs_stencil_op(GS_STENCIL_BOTH, GS_ZERO, GS_ZERO, GS_ZERO);
422 
423 	effect.get_parameter("pImage").set_texture(_input_texture);
424 	effect.get_parameter("pImageTexel")
425 		.set_float2(float_t(1.f / width * cos(_angle)), float_t(1.f / height * sin(_angle)));
426 	effect.get_parameter("pStepScale").set_float2(float_t(_step_scale.first), float_t(_step_scale.second));
427 	effect.get_parameter("pSize").set_float(float_t(_size));
428 	effect.get_parameter("pKernel").set_value(kernel.data(), MAX_KERNEL_SIZE);
429 
430 	// First Pass
431 	{
432 		auto op = _rendertarget->render(uint32_t(width), uint32_t(height));
433 		gs_ortho(0, 1., 0, 1., 0, 1.);
434 		while (gs_effect_loop(effect.get_object(), "Draw")) {
435 			streamfx::gs_draw_fullscreen_tri();
436 		}
437 	}
438 
439 	gs_blend_state_pop();
440 
441 	return this->get();
442 }
443