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