GCC Code Coverage Report


./
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
0 of 381, 0 excluded
0.0%
Functions:
0 of 40, 0 excluded
0.0%
Branches:
0 of 468, 0 excluded
0.0%

libs/render/src/eu/render/postproc.cc
Line Branch Exec Source
1 #include "eu/render/postproc.h"
2
3 #include "eu/assert/assert.h"
4
5 #include "eu/log/log.h"
6
7 #include "eu/render/opengl_utils.h"
8 #include "eu/render/postproc.internal.h"
9 #include "eu/render/renderer.h"
10 #include "eu/render/renderer.pimpl.h"
11 #include "eu/render/shader_resource.h"
12 #include "eu/render/shader.h"
13 #include "eu/render/state.h"
14 #include "eu/render/camera.h"
15 #include "eu/render/shadow.h"
16
17 #if FF_HAS(EU_DEBUG_RUNNER)
18 #include "eu/imgui/ui.h"
19 #include "dear_imgui/imgui.h"
20 #endif
21
22
23 namespace eu::render
24 {
25
26
27 ///////////////////////////////////////////////////////////////////////////////////////////////////
28 // Effect
29
30 bool Effect::enabled() const
31 {
32 return is_enabled;
33 }
34
35 void Effect::set_enabled(bool n)
36 {
37 if (is_enabled == n)
38 {
39 return;
40 }
41
42 is_enabled = n;
43 if (owner != nullptr)
44 {
45 owner->dirty = true;
46 }
47 }
48
49
50 ///////////////////////////////////////////////////////////////////////////////////////////////////
51 // RenderWorld
52
53 // todo(Gustav): should this be a user config option? evaluate higher/lower bits, probably 16 or 32 since it needs to be floating point for hdr
54 constexpr ColorBitsPerPixel render_world_color_bits_per_pixel = ColorBitsPerPixel::use_16;
55
56 static std::optional<BloomRender> build_bloom(const Size& size, ExtractShader* sh, PingPongBlurShader* ping_sh)
57 {
58 if (sh == nullptr) { return std::nullopt; }
59
60 auto bloom_buffer = build_simple_framebuffer(USE_DEBUG_LABEL_MANY("bloom extraction buffer") size);
61
62 auto ping_one = build_simple_framebuffer(USE_DEBUG_LABEL_MANY("ping-pong bloom buffer one") size);
63 auto ping_two = build_simple_framebuffer(USE_DEBUG_LABEL_MANY("ping-pong bloom buffer two") size);
64
65 return BloomRender
66 {
67 .extract_shader = sh,
68 .bloom_buffer = bloom_buffer,
69 .ping_pong_shader = ping_sh,
70 .ping_pong_buffer = {ping_one, ping_two}
71 };
72 }
73
74 RenderWorld::RenderWorld(const Size& size, RealizeShader* re_sh, ExtractShader* ex_sh, PingPongBlurShader* ping_sh, int msaa_samples, bool* h, float* e)
75 : window_size(size)
76 , use_hdr(h)
77 , exposure(e)
78 , msaa_buffer(build_msaa_framebuffer(USE_DEBUG_LABEL_MANY("msaa buffer")
79 size, msaa_samples, render_world_color_bits_per_pixel))
80 , realized_buffer(build_hdr_floating_framebuffer(USE_DEBUG_LABEL_MANY("realized msaa buffer") size, render_world_color_bits_per_pixel))
81 , realize_shader(re_sh)
82 , bloom_render(build_bloom(size, ex_sh, ping_sh))
83 , last_bloom_blur_index(0)
84 {
85 ASSERT(use_hdr);
86 ASSERT(exposure);
87 }
88
89 void RenderWorld::update(const PostProcArg& arg)
90 {
91 // render shadow buffer
92 const auto shadow_size = arg.renderer->settings.shadow_map_resolution;
93 {
94 bool update_shadow_buffer = shadow_buffer == nullptr;
95 if (shadow_buffer && shadow_buffer->size != shadow_size)
96 {
97 update_shadow_buffer = true;
98 }
99
100 if (update_shadow_buffer)
101 {
102 shadow_buffer = build_shadow_framebuffer(USE_DEBUG_LABEL_MANY("shadow buffer") shadow_size);
103 }
104 }
105
106 std::optional<CompiledCamera> opt_compiled_shadow_camera;
107
108 if (arg.world->lights.directional_lights.empty() == false)
109 {
110 SCOPED_DEBUG_GROUP("render shadow buffer"sv);
111
112 const auto compiled_shadow_camera = compile_the_shadow_camera(
113 *arg.camera, window_size, arg.world->lights.directional_lights[0], arg.renderer->settings, *arg.world
114 );
115
116 auto bound = BoundFbo{shadow_buffer};
117 set_gl_viewport({
118 .width = shadow_buffer->size.width,
119 .height = shadow_buffer->size.height
120 });
121 arg.renderer->render_shadows(shadow_size, *arg.world, compiled_shadow_camera);
122 }
123
124 // render into msaa buffer
125 {
126 SCOPED_DEBUG_GROUP("rendering into msaa buffer"sv);
127
128 const ShadowContext shadow_context = opt_compiled_shadow_camera ? ShadowContext{
129 .directional_shadow_map = shadow_buffer.get(),
130 .directional_shadow_clip_from_world = opt_compiled_shadow_camera->clip_from_view * opt_compiled_shadow_camera->view_from_world
131 } : ShadowContext{
132 .directional_shadow_map = nullptr,
133 .directional_shadow_clip_from_world = m4_identity
134 };
135
136 auto bound = BoundFbo{msaa_buffer};
137 set_gl_viewport({
138 .width = msaa_buffer->size.width,
139 .height = msaa_buffer->size.height
140 });
141 arg.renderer->render_world(window_size, *arg.world, compile(*arg.camera, window_size), shadow_context);
142 }
143
144 // copy msaa buffer to realized
145 {
146 SCOPED_DEBUG_GROUP("resolving msaa buffer"sv);
147 resolve_multisampled_buffer(*msaa_buffer, realized_buffer.get());
148 }
149
150 // extract overexposed
151 if (bloom_render.has_value())
152 {
153 // extract overexposed pixels
154 {
155 SCOPED_DEBUG_GROUP("extracting to overexposed buffer"sv);
156 StateChanger{arg.renderer->pimpl->states}
157 .cull_face(false)
158 .stencil_test(false)
159 .depth_test(false)
160 .depth_mask(false)
161 .blending(false);
162
163 glClearColor(0, 0, 0, 1.0f);
164 glClear(GL_COLOR_BUFFER_BIT);
165
166 // todo(Gustav): why bind this late... shouldn't this be first???
167 auto bound = BoundFbo{bloom_render->bloom_buffer};
168
169 {
170 const auto& container = bloom_render->extract_shader;
171 const auto& shader = container->shader;
172 const auto& program = shader->program;
173 program->use();
174 program->set_float(container->cutoff_uniform, arg.renderer->settings.bloom_cutoff);
175 program->set_float(container->softness_uniform, arg.renderer->settings.bloom_softness);
176 bind_texture_2d(arg.renderer->pimpl->states, shader->tex_input_uniform, *realized_buffer);
177
178 render_geom(*arg.renderer->pimpl->full_screen_geom);
179 }
180 }
181
182 // blur the buffers
183 {
184 SCOPED_DEBUG_GROUP("blur extracted pixels for bloom"sv);
185 bool is_horizontal = true;
186 for (int blur_iteration = 0; blur_iteration < arg.renderer->settings.bloom_blur_steps; blur_iteration += 1)
187 {
188 const auto is_first_iteration = blur_iteration == 0;
189
190 const std::size_t source_index = is_horizontal ? 1 : 0;
191 const std::size_t target_index = is_horizontal ? 0 : 1;
192 last_bloom_blur_index = target_index;
193
194 SCOPED_DEBUG_GROUP(
195 fmt::format("blur pass #{} ({}, {})",
196 blur_iteration,
197 (is_horizontal ? "hori" : "vert"),
198 (is_first_iteration ? "first" : "after"))
199 );
200 auto bound = BoundFbo{bloom_render->ping_pong_buffer[target_index]};
201 StateChanger{arg.renderer->pimpl->states}
202 .cull_face(false)
203 .stencil_test(false)
204 .depth_test(false)
205 .depth_mask(false)
206 .blending(false);
207
208 glClearColor(0, 0, 0, 1.0f);
209 glClear(GL_COLOR_BUFFER_BIT);
210
211 {
212 const auto& container = bloom_render->ping_pong_shader;
213 const auto& shader = container->shader;
214 const auto& program = shader->program;
215 program->use();
216 program->set_bool(container->is_horizontal_uniform, is_horizontal);
217
218 auto& src_texture = is_first_iteration ? bloom_render->bloom_buffer
219 : bloom_render->ping_pong_buffer[source_index];
220 bind_texture_2d(arg.renderer->pimpl->states, shader->tex_input_uniform, *src_texture);
221
222 render_geom(*arg.renderer->pimpl->full_screen_geom);
223 }
224
225 is_horizontal = ! is_horizontal;
226 }
227 }
228 }
229
230 // blur overexposed using ping-pong gaussian blur
231 }
232
233 void RenderWorld::render(const PostProcArg& arg)
234 {
235 // render realized (with shader)
236 SCOPED_DEBUG_GROUP("render realized msaa buffer"sv);
237 StateChanger{arg.renderer->pimpl->states}
238 .cull_face(false)
239 .stencil_test(false)
240 .depth_test(false)
241 .depth_mask(false)
242 .blending(false);
243
244 glClearColor(0, 0, 0, 1.0f);
245 glClear(GL_COLOR_BUFFER_BIT);
246
247 ASSERT(use_hdr);
248 ASSERT(exposure);
249
250 {
251 const auto& container = realize_shader;
252 const auto& program = container->program;
253 program->use();
254 program->set_float(container->gamma_uniform, arg.renderer->settings.gamma);
255 program->set_float(container->exposure_uniform, *use_hdr ? *exposure : -1.0f);
256 program->set_bool(container->use_blur_uniform, bloom_render.has_value());
257 bind_texture_2d(arg.renderer->pimpl->states, container->tex_input_uniform, *realized_buffer);
258 if (bloom_render.has_value())
259 {
260 bind_texture_2d(arg.renderer->pimpl->states, container->tex_blurred_bloom_uniform, *bloom_render->ping_pong_buffer[last_bloom_blur_index]);
261 }
262
263 render_geom(*arg.renderer->pimpl->full_screen_geom);
264 }
265 }
266
267
268
269 void RenderWorld::gui(imgui::ImguiShaderCache* cache)
270 {
271 // todo(Gustav): refactor
272 #if FF_HAS(EU_DEBUG_RUNNER)
273 if (bloom_render && bloom_render->bloom_buffer)
274 {
275 imgui::imgui_image("bloom buffer", *bloom_render->bloom_buffer, cache, imgui::ImageShader::tonemap_and_gamma);
276
277 imgui::imgui_image("ping-pong A", *bloom_render->ping_pong_buffer[0], cache, imgui::ImageShader::tonemap_and_gamma);
278 imgui::imgui_image("ping-pong B", *bloom_render->ping_pong_buffer[1], cache, imgui::ImageShader::tonemap_and_gamma);
279 }
280 #endif
281 }
282
283
284 ///////////////////////////////////////////////////////////////////////////////////////////////////
285 // RenderTextureWithShader
286
287 RenderTextureWithShader::RenderTextureWithShader(std::string n, std::shared_ptr<RenderSource> s, std::shared_ptr<FrameBuffer> f, ShaderPropertyProvider* e)
288 : name(std::move(n))
289 , source(std::move(s))
290 , fbo(std::move(f))
291 , effect(e)
292 {
293 ASSERT(effect);
294 }
295
296 void RenderTextureWithShader::render(const PostProcArg& arg)
297 {
298 ASSERT(effect);
299
300 SCOPED_DEBUG_GROUP(fmt::format("Rendering task {}", name));
301
302 StateChanger{arg.renderer->pimpl->states}
303 .cull_face(false)
304 .stencil_test(false)
305 .depth_test(false)
306 .depth_mask(false)
307 .blending(false);
308
309 glClearColor(0, 0, 0, 1.0f);
310 glClear(GL_COLOR_BUFFER_BIT);
311
312 effect->use_shader(arg, *fbo);
313 render_geom(*arg.renderer->pimpl->full_screen_geom);
314 }
315
316 void RenderTextureWithShader::update(const PostProcArg& arg)
317 {
318 SCOPED_DEBUG_GROUP(fmt::format("Updating task {}", name));
319 auto bound = BoundFbo{fbo};
320 set_gl_viewport(fbo->size);
321 source->render(arg);
322 }
323
324
325 ///////////////////////////////////////////////////////////////////////////////////////////////////
326 // EffectStack
327
328 void EffectStack::update(float dt) const
329 {
330 for (const auto& e: effects)
331 {
332 if (e->is_enabled == false)
333 {
334 continue;
335 }
336
337 e->update(dt);
338 }
339 }
340
341 void EffectStack::render(const PostProcArg& arg)
342 {
343 // todo(Gustav): owners are only used when we have added an effect and the effect can change dirty flag
344 // make this a requirement when creating the effect instead and remove this loop?
345 for (const auto& e: effects)
346 {
347 if (e->owner != this)
348 {
349 e->owner = this;
350 dirty = true;
351 }
352 }
353
354 if (render_world == nullptr)
355 {
356 dirty = true;
357 }
358
359 if (window_size.has_value() == false || *window_size != arg.window_size)
360 {
361 window_size = arg.window_size;
362 dirty = true;
363 }
364
365 {
366 const auto latest_msaa = arg.renderer->settings.msaa;
367 if (current_msaa_setting != latest_msaa)
368 {
369 current_msaa_setting = latest_msaa;
370 dirty = true;
371 }
372 }
373
374 const auto should_use_bloom = arg.renderer->settings.use_bloom;
375 {
376 const auto is_currently_using_bloom = render_world && render_world->bloom_render.has_value();
377 if (is_currently_using_bloom != should_use_bloom)
378 {
379 dirty = true;
380 }
381 }
382
383 // if dirty, update the compiled state
384 if (dirty)
385 {
386 dirty = false;
387 LOG_INFO("Building effects stack");
388
389 compiled.targets.clear();
390
391 auto created_world = std::make_shared<RenderWorld>(
392 arg.window_size,
393 &arg.renderer->pimpl->shaders_resources.pp_realize,
394 should_use_bloom ? &arg.renderer->pimpl->shaders_resources.pp_extract : nullptr,
395 should_use_bloom ? &arg.renderer->pimpl->shaders_resources.pp_ping : nullptr,
396 current_msaa_setting,
397 &use_hdr,
398 &exposure
399 );
400 compiled.last_source = created_world;
401 render_world = created_world;
402
403 for (auto& e: effects)
404 {
405 if (e->is_enabled)
406 {
407 e->build({
408 .builder = &compiled,
409 .window_size = arg.window_size
410 });
411 }
412 }
413 }
414
415 // the stack is now compiled
416 // before rendering, update all targets/fbos and present
417 {
418 SCOPED_DEBUG_GROUP("updating postproc render_world"sv);
419 render_world->update(arg);
420 }
421
422 {
423 SCOPED_DEBUG_GROUP("rendering postproc actions"sv);
424 for (const auto& action: compiled.targets)
425 {
426 action->update(arg);
427 }
428 }
429
430 // render the final image to the screen
431 {
432 SCOPED_DEBUG_GROUP("rendering postproc to screen"sv);
433 set_gl_viewport(arg.final_rect);
434 compiled.last_source->render(arg);
435 }
436 }
437
438 void EffectStack::gui(imgui::ImguiShaderCache* cache)
439 {
440 #if FF_HAS(EU_DEBUG_RUNNER)
441 ImGui::Checkbox("HDR", &use_hdr);
442 ImGui::SliderFloat("Exposure", &exposure, 0.01f, 20.0f);
443
444 if (render_world)
445 {
446 render_world->gui(cache);
447 }
448
449 int index = 0;
450 for (const auto& e: effects)
451 {
452 ImGui::PushID(index);
453 e->gui();
454 ImGui::PopID();
455 index += 1;
456 }
457 #endif
458 }
459
460
461 ///////////////////////////////////////////////////////////////////////////////////////////////////
462 // FactorEffect
463
464 FactorEffect::FactorEffect()
465 {
466 set_enabled(false);
467 }
468
469 float FactorEffect::get_factor() const
470 {
471 return factor;
472 }
473
474 void FactorEffect::set_factor(float f)
475 {
476 factor = f;
477 set_enabled(factor > kk::almost_zero);
478 }
479
480
481
482 ///////////////////////////////////////////////////////////////////////////////////////////////////
483 // FloatDragShaderProp
484
485 FloatDragShaderProp::FloatDragShaderProp(const LoadedPostProcShader& shader, const std::string& n, float v, float s)
486 : uniform(shader.program->get_uniform(n))
487 , name(n)
488 , value(v)
489 , speed(s)
490 {
491 }
492
493 void FloatDragShaderProp::use(const PostProcArg&, ShaderProgram& shader)
494 {
495 shader.set_float(uniform, value);
496 }
497
498 void FloatDragShaderProp::gui()
499 {
500 #if FF_HAS(EU_DEBUG_RUNNER)
501 ImGui::DragFloat(name.c_str(), &value, speed);
502 #endif
503 }
504
505
506
507 ///////////////////////////////////////////////////////////////////////////////////////////////////
508 // FloatSliderShaderProp
509
510 FloatSliderShaderProp::FloatSliderShaderProp(
511 const LoadedPostProcShader& shader, const std::string& n, float v, float mi, float ma
512 )
513 : uniform(shader.program->get_uniform(n))
514 , name(n)
515 , value(v)
516 , min(mi)
517 , max(ma)
518 {
519 }
520
521 void FloatSliderShaderProp::use(const PostProcArg&, ShaderProgram& shader)
522 {
523 shader.set_float(uniform, value);
524 }
525
526 void FloatSliderShaderProp::gui()
527 {
528 #if FF_HAS(EU_DEBUG_RUNNER)
529 ImGui::SliderFloat(name.c_str(), &value, min, max);
530 #endif
531 }
532
533
534 ///////////////////////////////////////////////////////////////////////////////////////////////////
535 // SimpleEffect
536
537 SimpleEffect::SimpleEffect(std::string n, std::shared_ptr<LoadedPostProcShader> s)
538 : name(std::move(n))
539 , shader(std::move(s))
540 {
541 ASSERT(shader->factor_uni.has_value());
542 }
543
544 void SimpleEffect::add_float_drag_prop(const std::string& prop_name, float value, float speed)
545 {
546 properties.emplace_back(std::make_shared<FloatDragShaderProp>(*shader, prop_name, value, speed));
547 }
548
549 void SimpleEffect::add_float_slider_prop(const std::string& prop_name, float value, float min, float max)
550 {
551 properties.emplace_back(std::make_shared<FloatSliderShaderProp>(*shader, prop_name, value, min, max));
552 }
553
554 void SimpleEffect::gui()
555 {
556 #if FF_HAS(EU_DEBUG_RUNNER)
557 if (properties.empty())
558 {
559 return;
560 }
561
562 int index = 0;
563
564 if (ImGui::TreeNode(name.c_str()) == false)
565 {
566 return;
567 }
568
569 for (auto& p: properties)
570 {
571 ImGui::PushID(index);
572 p->gui();
573 ImGui::PopID();
574 index += 1;
575 }
576
577 ImGui::TreePop();
578 #endif
579 }
580
581 void SimpleEffect::update(float dt)
582 {
583 time += dt;
584 }
585
586 static v2 v2_from_size(const Size& s)
587 {
588 return { float_from_int(s.width), float_from_int(s.height) };
589 }
590
591 void SimpleEffect::use_shader(const PostProcArg& a, const FrameBuffer& t)
592 {
593 shader->program->use();
594
595 const auto& shader_factor = shader->factor_uni;
596 const auto& shader_resolution = shader->resolution_uni;
597 const auto& shader_time = shader->time_uni;
598
599 if (shader_factor)
600 {
601 shader->program->set_float(*shader_factor, get_factor());
602 }
603 if (shader_resolution)
604 {
605 shader->program->set_vec2(*shader_resolution, v2_from_size(a.window_size));
606 }
607 if (shader_time)
608 {
609 shader->program->set_float(*shader_time, time);
610 }
611 for (auto& p: properties)
612 {
613 p->use(a, *shader->program);
614 }
615 bind_texture_2d(a.renderer->pimpl->states, shader->tex_input_uniform, t);
616 }
617
618 void SimpleEffect::build(const BuildArg& arg)
619 {
620 time = 0.0f;
621
622 // todo(Gustav): reuse buffers created from an earlier postproc build
623 // todo(Gustav): reuse buffers from earlier in the postproc stack, that aren't in use
624 auto fbo = build_simple_framebuffer(USE_DEBUG_LABEL_MANY(fmt::format("fbo for {}", name)) arg.window_size);
625
626 auto src = arg.builder->last_source;
627 auto target = std::make_shared<RenderTextureWithShader>(name, src, fbo, this);
628
629 arg.builder->targets.emplace_back(target);
630 arg.builder->last_source = target;
631 }
632
633
634
635 ///////////////////////////////////////////////////////////////////////////////////////////////////
636 // VertProvider
637
638 BlurVerticalProvider::BlurVerticalProvider(BlurEffect* b)
639 : blur(b)
640 {
641 }
642
643
644 void BlurVerticalProvider::use_shader(const PostProcArg& a, const FrameBuffer& t)
645 {
646 blur->use_vert_shader(a, t);
647 }
648
649
650
651 ///////////////////////////////////////////////////////////////////////////////////////////////////
652 // HoriProvider
653
654 BurHorizontalProvider::BurHorizontalProvider(BlurEffect* b)
655 : blur(b)
656 {
657 }
658
659 void BurHorizontalProvider::use_shader(const PostProcArg& a, const FrameBuffer& t)
660 {
661 blur->use_hori_shader(a, t);
662 }
663
664
665
666
667 ///////////////////////////////////////////////////////////////////////////////////////////////////
668 // BlurEffect
669
670
671 BlurEffect::BlurEffect(std::string n, std::shared_ptr<LoadedPostProcShader> v, std::shared_ptr<LoadedPostProcShader> h)
672 : name(std::move(n))
673 , vert_p(this)
674 , hori_p(this)
675 , vert(std::move(v))
676 , hori(std::move(h))
677 , blur_size_v(vert->program->get_uniform("u_blur_size"))
678 , blur_size_h(hori->program->get_uniform("u_blur_size"))
679 #if FF_HAS(BLUR_USE_GAUSS)
680 , std_dev_v(vert->program->get_uniform("u_std_dev"))
681 , std_dev_h(hori->program->get_uniform("u_std_dev"))
682 #endif
683 {
684 ASSERT(vert->factor_uni.has_value());
685 ASSERT(hori->factor_uni.has_value());
686 }
687
688 void BlurEffect::gui()
689 {
690 #if FF_HAS(EU_DEBUG_RUNNER)
691 if (ImGui::TreeNode(name.c_str()) == false)
692 {
693 return;
694 }
695
696 ImGui::SliderFloat("Blur size", &blur_size, 0.0f, 0.2f);
697 #if FF_HAS(BLUR_USE_GAUSS)
698 ImGui::SliderFloat("Standard deviation", &std_dev, 0.0f, 0.1f);
699 #endif
700
701 ImGui::TreePop();
702 #endif
703 }
704
705 void BlurEffect::update(float)
706 {
707 // no update needed
708 }
709
710 void BlurEffect::use_vert_shader(const PostProcArg& a, const FrameBuffer& t) const
711 {
712 const auto& factor_uniform = vert->factor_uni;
713
714 vert->program->use();
715 ASSERT(factor_uniform);
716 if (factor_uniform)
717 {
718 vert->program->set_float(*factor_uniform, get_factor());
719 }
720 vert->program->set_float(blur_size_v, blur_size);
721 #if FF_HAS(BLUR_USE_GAUSS)
722 vert->program->set_float(std_dev_v, std_dev);
723 #endif
724 bind_texture_2d(a.renderer->pimpl->states, vert->tex_input_uniform, t);
725 }
726
727 void BlurEffect::use_hori_shader(const PostProcArg& a, const FrameBuffer& t)
728 {
729 const auto& factor_uniform = hori->factor_uni;
730 const auto& resolution_uniform = hori->resolution_uni;
731
732 hori->program->use();
733 ASSERT(factor_uniform);
734 if (factor_uniform)
735 {
736 hori->program->set_float(*factor_uniform, get_factor());
737 }
738 ASSERT(resolution_uniform);
739 if (resolution_uniform)
740 {
741 hori->program->set_vec2(*resolution_uniform, v2_from_size(a.window_size));
742 }
743 hori->program->set_float(blur_size_h, blur_size);
744 #if FF_HAS(BLUR_USE_GAUSS)
745 hori->program->set_float(std_dev_h, std_dev);
746 #endif
747 bind_texture_2d(a.renderer->pimpl->states, hori->tex_input_uniform, t);
748 }
749
750 void BlurEffect::build(const BuildArg& arg)
751 {
752 auto src = arg.builder->last_source;
753
754 // todo(Gustav): modify resolution to get better blur and at a lower cost!
755
756 // step 1: vertical
757 auto fbo_v = build_simple_framebuffer(USE_DEBUG_LABEL_MANY("blur vertical") arg.window_size);
758 auto target_v = std::make_shared<RenderTextureWithShader>("blur vertical", src, fbo_v, &vert_p);
759 arg.builder->targets.emplace_back(target_v);
760
761 // step 2: horizontal
762 auto fbo_h = build_simple_framebuffer(USE_DEBUG_LABEL_MANY("blur horizontal") arg.window_size);
763 auto target_h = std::make_shared<RenderTextureWithShader>("blur horizontal", target_v, fbo_h, &hori_p);
764 arg.builder->targets.emplace_back(target_h);
765
766 // done
767 arg.builder->last_source = target_h;
768 }
769
770 } // namespace eu::render
771