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-box.hpp"
19 #include <cmath>
20 #include <memory>
21 #include <stdexcept>
22 #include "obs/gs/gs-helper.hpp"
23 #include "plugin.hpp"
24 
25 #ifdef _MSC_VER
26 #pragma warning(push)
27 #pragma warning(disable : 4201)
28 #endif
29 #include <obs.h>
30 #include <obs-module.h>
31 #ifdef _MSC_VER
32 #pragma warning(pop)
33 #endif
34 
35 #define MAX_BLUR_SIZE 128 // Also change this in box.effect if modified.
36 
box_data()37 gfx::blur::box_data::box_data()
38 {
39 	auto gctx = gs::context();
40 	try {
41 		_effect = gs::effect::create(streamfx::data_file_path("effects/blur/box.effect").u8string());
42 	} catch (...) {
43 		DLOG_ERROR("<gfx::blur::box> Failed to load _effect.");
44 	}
45 }
46 
~box_data()47 gfx::blur::box_data::~box_data()
48 {
49 	auto gctx = gs::context();
50 	_effect.reset();
51 }
52 
get_effect()53 gs::effect gfx::blur::box_data::get_effect()
54 {
55 	return _effect;
56 }
57 
box_factory()58 gfx::blur::box_factory::box_factory() {}
59 
~box_factory()60 gfx::blur::box_factory::~box_factory() {}
61 
is_type_supported(::gfx::blur::type type)62 bool gfx::blur::box_factory::is_type_supported(::gfx::blur::type type)
63 {
64 	switch (type) {
65 	case ::gfx::blur::type::Area:
66 		return true;
67 	case ::gfx::blur::type::Directional:
68 		return true;
69 	case ::gfx::blur::type::Rotational:
70 		return true;
71 	case ::gfx::blur::type::Zoom:
72 		return true;
73 	default:
74 		return false;
75 	}
76 }
77 
create(::gfx::blur::type type)78 std::shared_ptr<::gfx::blur::base> gfx::blur::box_factory::create(::gfx::blur::type type)
79 {
80 	switch (type) {
81 	case ::gfx::blur::type::Area:
82 		return std::make_shared<::gfx::blur::box>();
83 	case ::gfx::blur::type::Directional:
84 		return std::static_pointer_cast<::gfx::blur::box>(std::make_shared<::gfx::blur::box_directional>());
85 	case ::gfx::blur::type::Rotational:
86 		return std::make_shared<::gfx::blur::box_rotational>();
87 	case ::gfx::blur::type::Zoom:
88 		return std::make_shared<::gfx::blur::box_zoom>();
89 	default:
90 		throw std::runtime_error("Invalid type.");
91 	}
92 }
93 
get_min_size(::gfx::blur::type)94 double_t gfx::blur::box_factory::get_min_size(::gfx::blur::type)
95 {
96 	return double_t(1.0);
97 }
98 
get_step_size(::gfx::blur::type)99 double_t gfx::blur::box_factory::get_step_size(::gfx::blur::type)
100 {
101 	return double_t(1.0);
102 }
103 
get_max_size(::gfx::blur::type)104 double_t gfx::blur::box_factory::get_max_size(::gfx::blur::type)
105 {
106 	return double_t(MAX_BLUR_SIZE);
107 }
108 
get_min_angle(::gfx::blur::type v)109 double_t gfx::blur::box_factory::get_min_angle(::gfx::blur::type v)
110 {
111 	switch (v) {
112 	case ::gfx::blur::type::Directional:
113 	case ::gfx::blur::type::Rotational:
114 		return -180.0;
115 	default:
116 		return 0;
117 	}
118 }
119 
get_step_angle(::gfx::blur::type)120 double_t gfx::blur::box_factory::get_step_angle(::gfx::blur::type)
121 {
122 	return double_t(0.01);
123 }
124 
get_max_angle(::gfx::blur::type v)125 double_t gfx::blur::box_factory::get_max_angle(::gfx::blur::type v)
126 {
127 	switch (v) {
128 	case ::gfx::blur::type::Directional:
129 	case ::gfx::blur::type::Rotational:
130 		return 180.0;
131 	default:
132 		return 0;
133 	}
134 }
135 
is_step_scale_supported(::gfx::blur::type v)136 bool gfx::blur::box_factory::is_step_scale_supported(::gfx::blur::type v)
137 {
138 	switch (v) {
139 	case ::gfx::blur::type::Area:
140 	case ::gfx::blur::type::Zoom:
141 	case ::gfx::blur::type::Directional:
142 		return true;
143 	default:
144 		return false;
145 	}
146 }
147 
get_min_step_scale_x(::gfx::blur::type)148 double_t gfx::blur::box_factory::get_min_step_scale_x(::gfx::blur::type)
149 {
150 	return double_t(0.01);
151 }
152 
get_step_step_scale_x(::gfx::blur::type)153 double_t gfx::blur::box_factory::get_step_step_scale_x(::gfx::blur::type)
154 {
155 	return double_t(0.01);
156 }
157 
get_max_step_scale_x(::gfx::blur::type)158 double_t gfx::blur::box_factory::get_max_step_scale_x(::gfx::blur::type)
159 {
160 	return double_t(1000.0);
161 }
162 
get_min_step_scale_y(::gfx::blur::type)163 double_t gfx::blur::box_factory::get_min_step_scale_y(::gfx::blur::type)
164 {
165 	return double_t(0.01);
166 }
167 
get_step_step_scale_y(::gfx::blur::type)168 double_t gfx::blur::box_factory::get_step_step_scale_y(::gfx::blur::type)
169 {
170 	return double_t(0.01);
171 }
172 
get_max_step_scale_y(::gfx::blur::type)173 double_t gfx::blur::box_factory::get_max_step_scale_y(::gfx::blur::type)
174 {
175 	return double_t(1000.0);
176 }
177 
data()178 std::shared_ptr<::gfx::blur::box_data> gfx::blur::box_factory::data()
179 {
180 	std::unique_lock<std::mutex>           ulock(_data_lock);
181 	std::shared_ptr<::gfx::blur::box_data> data = _data.lock();
182 	if (!data) {
183 		data  = std::make_shared<::gfx::blur::box_data>();
184 		_data = data;
185 	}
186 	return data;
187 }
188 
get()189 ::gfx::blur::box_factory& gfx::blur::box_factory::get()
190 {
191 	static ::gfx::blur::box_factory instance;
192 	return instance;
193 }
194 
box()195 gfx::blur::box::box() : _data(::gfx::blur::box_factory::get().data()), _size(1.), _step_scale({1., 1.})
196 {
197 	auto gctx      = gs::context();
198 	_rendertarget  = std::make_shared<::gs::rendertarget>(GS_RGBA, GS_ZS_NONE);
199 	_rendertarget2 = std::make_shared<::gs::rendertarget>(GS_RGBA, GS_ZS_NONE);
200 }
201 
~box()202 gfx::blur::box::~box() {}
203 
set_input(std::shared_ptr<::gs::texture> texture)204 void gfx::blur::box::set_input(std::shared_ptr<::gs::texture> texture)
205 {
206 	_input_texture = texture;
207 }
208 
get_type()209 ::gfx::blur::type gfx::blur::box::get_type()
210 {
211 	return ::gfx::blur::type::Area;
212 }
213 
get_size()214 double_t gfx::blur::box::get_size()
215 {
216 	return _size;
217 }
218 
set_size(double_t width)219 void gfx::blur::box::set_size(double_t width)
220 {
221 	_size = width;
222 	if (_size < 1.0) {
223 		_size = 1.0;
224 	}
225 	if (_size > MAX_BLUR_SIZE) {
226 		_size = MAX_BLUR_SIZE;
227 	}
228 }
229 
set_step_scale(double_t x,double_t y)230 void gfx::blur::box::set_step_scale(double_t x, double_t y)
231 {
232 	_step_scale = {x, y};
233 }
234 
get_step_scale(double_t & x,double_t & y)235 void gfx::blur::box::get_step_scale(double_t& x, double_t& y)
236 {
237 	x = _step_scale.first;
238 	y = _step_scale.second;
239 }
240 
get_step_scale_x()241 double_t gfx::blur::box::get_step_scale_x()
242 {
243 	return _step_scale.first;
244 }
245 
get_step_scale_y()246 double_t gfx::blur::box::get_step_scale_y()
247 {
248 	return _step_scale.second;
249 }
250 
render()251 std::shared_ptr<::gs::texture> gfx::blur::box::render()
252 {
253 	auto gctx = gs::context();
254 
255 #ifdef ENABLE_PROFILING
256 	auto gdmp = gs::debug_marker(gs::debug_color_azure_radiance, "Box Blur");
257 #endif
258 
259 	float_t width  = float_t(_input_texture->get_width());
260 	float_t height = float_t(_input_texture->get_height());
261 
262 	gs_set_cull_mode(GS_NEITHER);
263 	gs_enable_color(true, true, true, true);
264 	gs_enable_depth_test(false);
265 	gs_depth_function(GS_ALWAYS);
266 	gs_blend_state_push();
267 	gs_reset_blend_state();
268 	gs_enable_blending(false);
269 	gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO);
270 	gs_enable_stencil_test(false);
271 	gs_enable_stencil_write(false);
272 	gs_stencil_function(GS_STENCIL_BOTH, GS_ALWAYS);
273 	gs_stencil_op(GS_STENCIL_BOTH, GS_ZERO, GS_ZERO, GS_ZERO);
274 
275 	// Two Pass Blur
276 	gs::effect effect = _data->get_effect();
277 	if (effect) {
278 		// Pass 1
279 		effect.get_parameter("pImage").set_texture(_input_texture);
280 		effect.get_parameter("pImageTexel").set_float2(float_t(1.f / width), 0.f);
281 		effect.get_parameter("pStepScale").set_float2(float_t(_step_scale.first), float_t(_step_scale.second));
282 		effect.get_parameter("pSize").set_float(float_t(_size));
283 		effect.get_parameter("pSizeInverseMul").set_float(float_t(1.0f / (float_t(_size) * 2.0f + 1.0f)));
284 
285 		{
286 #ifdef ENABLE_PROFILING
287 			auto gdm = gs::debug_marker(gs::debug_color_azure_radiance, "Horizontal");
288 #endif
289 
290 			auto op = _rendertarget2->render(uint32_t(width), uint32_t(height));
291 			gs_ortho(0, 1., 0, 1., 0, 1.);
292 			while (gs_effect_loop(effect.get_object(), "Draw")) {
293 				streamfx::gs_draw_fullscreen_tri();
294 			}
295 		}
296 
297 		// Pass 2
298 		effect.get_parameter("pImage").set_texture(_rendertarget2->get_texture());
299 		effect.get_parameter("pImageTexel").set_float2(0.f, float_t(1.f / height));
300 
301 		{
302 #ifdef ENABLE_PROFILING
303 			auto gdm = gs::debug_marker(gs::debug_color_azure_radiance, "Vertical");
304 #endif
305 
306 			auto op = _rendertarget->render(uint32_t(width), uint32_t(height));
307 			gs_ortho(0, 1., 0, 1., 0, 1.);
308 			while (gs_effect_loop(effect.get_object(), "Draw")) {
309 				streamfx::gs_draw_fullscreen_tri();
310 			}
311 		}
312 	}
313 
314 	gs_blend_state_pop();
315 
316 	return _rendertarget->get_texture();
317 }
318 
get()319 std::shared_ptr<::gs::texture> gfx::blur::box::get()
320 {
321 	return _rendertarget->get_texture();
322 }
323 
box_directional()324 gfx::blur::box_directional::box_directional() : _angle(0) {}
325 
get_type()326 ::gfx::blur::type gfx::blur::box_directional::get_type()
327 {
328 	return ::gfx::blur::type::Directional;
329 }
330 
get_angle()331 double_t gfx::blur::box_directional::get_angle()
332 {
333 	return D_RAD_TO_DEG(_angle);
334 }
335 
set_angle(double_t angle)336 void gfx::blur::box_directional::set_angle(double_t angle)
337 {
338 	_angle = D_DEG_TO_RAD(angle);
339 }
340 
render()341 std::shared_ptr<::gs::texture> gfx::blur::box_directional::render()
342 {
343 	auto gctx = gs::context();
344 
345 #ifdef ENABLE_PROFILING
346 	auto gdmp = gs::debug_marker(gs::debug_color_azure_radiance, "Box Directional Blur");
347 #endif
348 
349 	float_t width  = float_t(_input_texture->get_width());
350 	float_t height = float_t(_input_texture->get_height());
351 
352 	gs_blend_state_push();
353 	gs_reset_blend_state();
354 	gs_enable_color(true, true, true, true);
355 	gs_enable_blending(false);
356 	gs_enable_depth_test(false);
357 	gs_enable_stencil_test(false);
358 	gs_enable_stencil_write(false);
359 	gs_set_cull_mode(GS_NEITHER);
360 	gs_depth_function(GS_ALWAYS);
361 	gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO);
362 	gs_stencil_function(GS_STENCIL_BOTH, GS_ALWAYS);
363 	gs_stencil_op(GS_STENCIL_BOTH, GS_ZERO, GS_ZERO, GS_ZERO);
364 
365 	// One Pass Blur
366 	gs::effect effect = _data->get_effect();
367 	if (effect) {
368 		effect.get_parameter("pImage").set_texture(_input_texture);
369 		effect.get_parameter("pImageTexel")
370 			.set_float2(float_t(1. / width * cos(_angle)), float_t(1.f / height * sin(_angle)));
371 		effect.get_parameter("pStepScale").set_float2(float_t(_step_scale.first), float_t(_step_scale.second));
372 		effect.get_parameter("pSize").set_float(float_t(_size));
373 		effect.get_parameter("pSizeInverseMul").set_float(float_t(1.0f / (float_t(_size) * 2.0f + 1.0f)));
374 
375 		{
376 			auto op = _rendertarget->render(uint32_t(width), uint32_t(height));
377 			gs_ortho(0, 1., 0, 1., 0, 1.);
378 			while (gs_effect_loop(effect.get_object(), "Draw")) {
379 				streamfx::gs_draw_fullscreen_tri();
380 			}
381 		}
382 	}
383 
384 	gs_blend_state_pop();
385 
386 	return _rendertarget->get_texture();
387 }
388 
get_type()389 ::gfx::blur::type gfx::blur::box_rotational::get_type()
390 {
391 	return ::gfx::blur::type::Rotational;
392 }
393 
set_center(double_t x,double_t y)394 void gfx::blur::box_rotational::set_center(double_t x, double_t y)
395 {
396 	_center.first  = x;
397 	_center.second = y;
398 }
399 
get_center(double_t & x,double_t & y)400 void gfx::blur::box_rotational::get_center(double_t& x, double_t& y)
401 {
402 	x = _center.first;
403 	y = _center.second;
404 }
405 
get_angle()406 double_t gfx::blur::box_rotational::get_angle()
407 {
408 	return D_RAD_TO_DEG(_angle);
409 }
410 
set_angle(double_t angle)411 void gfx::blur::box_rotational::set_angle(double_t angle)
412 {
413 	_angle = D_DEG_TO_RAD(angle);
414 }
415 
render()416 std::shared_ptr<::gs::texture> gfx::blur::box_rotational::render()
417 {
418 	auto gctx = gs::context();
419 
420 #ifdef ENABLE_PROFILING
421 	auto gdmp = gs::debug_marker(gs::debug_color_azure_radiance, "Box Rotational Blur");
422 #endif
423 
424 	float_t width  = float_t(_input_texture->get_width());
425 	float_t height = float_t(_input_texture->get_height());
426 
427 	gs_blend_state_push();
428 	gs_reset_blend_state();
429 	gs_enable_color(true, true, true, true);
430 	gs_enable_blending(false);
431 	gs_enable_depth_test(false);
432 	gs_enable_stencil_test(false);
433 	gs_enable_stencil_write(false);
434 	gs_set_cull_mode(GS_NEITHER);
435 	gs_depth_function(GS_ALWAYS);
436 	gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO);
437 	gs_stencil_function(GS_STENCIL_BOTH, GS_ALWAYS);
438 	gs_stencil_op(GS_STENCIL_BOTH, GS_ZERO, GS_ZERO, GS_ZERO);
439 
440 	// One Pass Blur
441 	gs::effect effect = _data->get_effect();
442 	if (effect) {
443 		effect.get_parameter("pImage").set_texture(_input_texture);
444 		effect.get_parameter("pImageTexel").set_float2(float_t(1.f / width), float_t(1.f / height));
445 		effect.get_parameter("pStepScale").set_float2(float_t(_step_scale.first), float_t(_step_scale.second));
446 		effect.get_parameter("pSize").set_float(float_t(_size));
447 		effect.get_parameter("pSizeInverseMul").set_float(float_t(1.0f / (float_t(_size) * 2.0f + 1.0f)));
448 		effect.get_parameter("pAngle").set_float(float_t(_angle / _size));
449 		effect.get_parameter("pCenter").set_float2(float_t(_center.first), float_t(_center.second));
450 
451 		{
452 			auto op = _rendertarget->render(uint32_t(width), uint32_t(height));
453 			gs_ortho(0, 1., 0, 1., 0, 1.);
454 			while (gs_effect_loop(effect.get_object(), "Rotate")) {
455 				streamfx::gs_draw_fullscreen_tri();
456 			}
457 		}
458 	}
459 
460 	gs_blend_state_pop();
461 
462 	return _rendertarget->get_texture();
463 }
464 
get_type()465 ::gfx::blur::type gfx::blur::box_zoom::get_type()
466 {
467 	return ::gfx::blur::type::Zoom;
468 }
469 
set_center(double_t x,double_t y)470 void gfx::blur::box_zoom::set_center(double_t x, double_t y)
471 {
472 	_center.first  = x;
473 	_center.second = y;
474 }
475 
get_center(double_t & x,double_t & y)476 void gfx::blur::box_zoom::get_center(double_t& x, double_t& y)
477 {
478 	x = _center.first;
479 	y = _center.second;
480 }
481 
render()482 std::shared_ptr<::gs::texture> gfx::blur::box_zoom::render()
483 {
484 	auto gctx = gs::context();
485 
486 #ifdef ENABLE_PROFILING
487 	auto gdmp = gs::debug_marker(gs::debug_color_azure_radiance, "Box Zoom Blur");
488 #endif
489 
490 	float_t width  = float_t(_input_texture->get_width());
491 	float_t height = float_t(_input_texture->get_height());
492 
493 	gs_blend_state_push();
494 	gs_reset_blend_state();
495 	gs_enable_color(true, true, true, true);
496 	gs_enable_blending(false);
497 	gs_enable_depth_test(false);
498 	gs_enable_stencil_test(false);
499 	gs_enable_stencil_write(false);
500 	gs_set_cull_mode(GS_NEITHER);
501 	gs_depth_function(GS_ALWAYS);
502 	gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO);
503 	gs_stencil_function(GS_STENCIL_BOTH, GS_ALWAYS);
504 	gs_stencil_op(GS_STENCIL_BOTH, GS_ZERO, GS_ZERO, GS_ZERO);
505 
506 	// One Pass Blur
507 	gs::effect effect = _data->get_effect();
508 	if (effect) {
509 		effect.get_parameter("pImage").set_texture(_input_texture);
510 		effect.get_parameter("pImageTexel").set_float2(float_t(1.f / width), float_t(1.f / height));
511 		effect.get_parameter("pStepScale").set_float2(float_t(_step_scale.first), float_t(_step_scale.second));
512 		effect.get_parameter("pSize").set_float(float_t(_size));
513 		effect.get_parameter("pSizeInverseMul").set_float(float_t(1.0f / (float_t(_size) * 2.0f + 1.0f)));
514 		effect.get_parameter("pCenter").set_float2(float_t(_center.first), float_t(_center.second));
515 
516 		{
517 			auto op = _rendertarget->render(uint32_t(width), uint32_t(height));
518 			gs_ortho(0, 1., 0, 1., 0, 1.);
519 			while (gs_effect_loop(effect.get_object(), "Zoom")) {
520 				streamfx::gs_draw_fullscreen_tri();
521 			}
522 		}
523 	}
524 
525 	gs_blend_state_pop();
526 
527 	return _rendertarget->get_texture();
528 }
529