1 /** @file bloom.cpp
2  *
3  * @authors Copyright (c) 2014-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4  *
5  * @par License
6  * GPL: http://www.gnu.org/licenses/gpl.html
7  *
8  * <small>This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by the
10  * Free Software Foundation; either version 2 of the License, or (at your
11  * option) any later version. This program is distributed in the hope that it
12  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
14  * Public License for more details. You should have received a copy of the GNU
15  * General Public License along with this program; if not, see:
16  * http://www.gnu.org/licenses</small>
17  */
18 
19 #include "render/fx/bloom.h"
20 #include "clientapp.h"
21 #include "world/clientserverworld.h"
22 
23 #include <doomsday/console/var.h>
24 #include <de/Drawable>
25 #include <de/GLTextureFramebuffer>
26 #include <de/WindowTransform>
27 
28 using namespace de;
29 
30 namespace fx {
31 
32 static int   bloomEnabled    = true;
33 static float bloomIntensity  = .65f;
34 static float bloomThreshold  = .35f;
35 static float bloomDispersion = 1;
36 static int   bloomComplexity = 1;
37 
DENG2_PIMPL(Bloom)38 DENG2_PIMPL(Bloom)
39 {
40     typedef GLBufferT<Vertex2Tex> VBuf;
41 
42     Drawable bloom;
43     GLTextureFramebuffer workFB;
44     GLUniform uMvpMatrix;
45     GLUniform uTex;
46     GLUniform uColor;
47     GLUniform uBlurStep;
48     GLUniform uWindow;
49     GLUniform uThreshold;
50     GLUniform uIntensity;
51 
52     Impl(Public *i)
53         : Base(i)
54         , uMvpMatrix("uMvpMatrix", GLUniform::Mat4)
55         , uTex      ("uTex",       GLUniform::Sampler2D)
56         , uColor    ("uColor",     GLUniform::Vec4)
57         , uBlurStep ("uBlurStep",  GLUniform::Vec2)
58         , uWindow   ("uWindow",    GLUniform::Vec4)
59         , uThreshold("uThreshold", GLUniform::Float)
60         , uIntensity("uIntensity", GLUniform::Float)
61     {}
62 
63     void glInit()
64     {
65         // Geometry for drawing with: a single quad.
66         VBuf *buf = new VBuf;
67         buf->setVertices(gl::TriangleStrip,
68                          VBuf::Builder().makeQuad(
69                              Rectanglef(0, 0, 1, 1),
70                              Rectanglef(0, 0, 1, 1)),
71                          gl::Static);
72         bloom.addBuffer(buf);
73 
74         // The work buffer does not need alpha because the result will be additively
75         // blended back to the framebuffer.
76         workFB.setColorFormat(Image::RGB_888);
77         workFB.setSampleCount(1);
78         workFB.glInit();
79 
80         ClientApp::shaders().build(bloom.program(), "fx.bloom.horizontal")
81                 << uMvpMatrix << uTex << uBlurStep << uWindow << uThreshold << uIntensity;
82 
83         bloom.addProgram("vert");
84         ClientApp::shaders().build(bloom.program("vert"), "fx.bloom.vertical")
85                 << uMvpMatrix << uTex << uBlurStep << uWindow;
86 
87         uMvpMatrix = Matrix4f::ortho(0, 1, 0, 1);
88     }
89 
90     void glDeinit()
91     {
92         bloom.clear();
93         workFB.glDeinit();
94     }
95 
96     /**
97      * Takes the current rendered frame buffer contents and applies bloom on it.
98      */
99     void draw()
100     {
101         GLFramebuffer &target = GLState::current().target();
102         GLTexture *colorTex = target.attachedTexture(GLFramebuffer::Color);
103 
104         //qDebug() << "bloom with" << colorTex;
105 
106         // Must have access to the color texture containing the frame buffer contents.
107         if (!colorTex) return;
108 
109         // Determine the dimensions of the viewport and the target.
110         //Rectanglef const rectf(0, 0, 1, 1); //= GLState::current().normalizedViewport();
111         Vector2ui const targetSize = colorTex->size(); // (rectf.size() * target.rectInUse().size()).toVector2ui();
112 
113         // Quarter resolution is used for better efficiency (without significant loss
114         // of quality).
115         Vector2ui blurSize = (targetSize / 4).max(Vector2ui(1, 1));
116 
117         // Update the size of the work buffer if needed. Also ensure linear filtering
118         // is used for better-quality blurring.
119         workFB.resize(blurSize);
120         workFB.colorTexture().setFilter(gl::Linear, gl::Linear, gl::MipNone);
121 
122         GLState::push()
123                 .setDepthWrite(false) // don't mess with depth information
124                 .setDepthTest(false);
125 
126         switch (bloomComplexity)
127         {
128         case 1:
129             // Two passes result in a better glow effect: combining multiple Gaussian curves
130             // ensures that the middle peak is higher/sharper.
131             drawBloomPass(*colorTex, .5f, .75f);
132             drawBloomPass(*colorTex, 1.f, 1.f);
133             break;
134 
135         default:
136             // Single-pass for HW with slow fill rate.
137             drawBloomPass(*colorTex, 1.f, 1.75f);
138             break;
139         }
140 
141         GLState::pop();
142     }
143 
144     /**
145      * Draws a bloom pass that takes the contents of the framebuffer, applies blurring
146      * and thresholding, and blends the result additively back to the framebuffer.
147      *
148      * @param rectf        Normalized viewport rectangle within the target.
149      * @param targetSize   Size of the actual area in pixels (affected by target
150      *                     active rectangle and viewport).
151      * @param colorTarget  Texture containing the frame buffer colors.
152      * @param bloomSize    Size factor for the effect: at most 1.0; smaller values
153      *                     cause more blurring/less quality as the work resolution
154      *                     reduces.
155      * @param weight       Weight factor for intensity.
156      * @param targetOp     Blending factor (should be gl::One unless debugging).
157      */
158     void drawBloomPass(//Rectanglef const &rectf, //Vector2ui const &/*targetSize*/,
159                        GLTexture &colorTarget, float bloomSize, float weight,
160                        gl::Blend targetOp = gl::One)
161     {
162         uThreshold = bloomThreshold * (1 + bloomSize) / 2.f;
163         uIntensity = bloomIntensity * weight;
164 
165         // Initialize the work buffer for this pass.
166         workFB.clear(GLFramebuffer::Color);
167 
168         // Divert rendering to the work area (full or partial area used).
169         //GLFramebuffer &target = GLState::current().target();
170         Vector2ui const workSize = workFB.size() * bloomSize;
171         GLState::push()
172                 .setTarget(workFB)
173                 .setViewport(Rectangleui::fromSize(workSize));
174 
175         // Normalized active rectangle of the target.
176         /*Vector4f const active(target.activeRectScale(),
177                               target.activeRectNormalizedOffset());*/
178 
179         /*
180          * Draw step #1: thresholding and horizontal blur.
181          */
182         uTex = colorTarget;
183 
184         // Window in the color buffer: area occupied by the viewport. Top needs to
185         // be flipped because the shader uses the bottom left corner as UV origin.
186         // Also need to apply the active rectangle as it affects where the viewport
187         // ends up inside the frame buffer.
188         uWindow = Vector4f(0, 0, 1, 1); /*Vector4f(rectf.left() * active.x        + active.z,
189                            1 - (rectf.bottom() * active.y + active.w),
190                            rectf.width()  * active.x,
191                            rectf.height() * active.y);*/
192 
193         // Spread out or contract the texture sampling of the Gaussian blur kernel.
194         // If dispersion is too large, the quality of the blur will suffer.
195         uBlurStep = Vector2f(bloomDispersion / workFB.size().x,
196                              bloomDispersion / workFB.size().y);
197 
198         bloom.setProgram(bloom.program()); // horizontal shader
199         bloom.draw();
200 
201         GLState::pop();
202 
203         workFB.resolveSamples();
204 
205         /*
206          * Draw step #2: vertical blur and blending back to the real framebuffer.
207          */
208         GLState::push()
209                 .setBlend(true)
210                 .setBlendFunc(gl::One, targetOp);
211 
212         // Use the work buffer's texture as the source.
213         uTex    = workFB.colorTexture();
214         uWindow = Vector4f(0, 1 - bloomSize, bloomSize, bloomSize);
215 
216         bloom.setProgram("vert"); // vertical shader
217         bloom.draw();
218 
219         GLState::pop();
220     }
221 };
222 
Bloom(int console)223 Bloom::Bloom(int console)
224     : ConsoleEffect(console)
225     , d(new Impl(this))
226 {}
227 
glInit()228 void Bloom::glInit()
229 {
230     d->glInit();
231     ConsoleEffect::glInit();
232 }
233 
glDeinit()234 void Bloom::glDeinit()
235 {
236     ConsoleEffect::glDeinit();
237     d->glDeinit();
238 }
239 
draw()240 void Bloom::draw()
241 {
242     if (!ClientApp::world().hasMap())
243     {
244         return;
245     }
246 
247     if (!bloomEnabled || bloomIntensity <= 0)
248     {
249         return;
250     }
251 
252     d->draw();
253 }
254 
consoleRegister()255 void Bloom::consoleRegister()
256 {
257     C_VAR_INT  ("rend-bloom",            &bloomEnabled,    0, 0, 1);
258     C_VAR_FLOAT("rend-bloom-threshold",  &bloomThreshold,  0, 0, 1);
259     C_VAR_FLOAT("rend-bloom-intensity",  &bloomIntensity,  0, 0, 10);
260     C_VAR_FLOAT("rend-bloom-dispersion", &bloomDispersion, 0, 0, 3.5f);
261     C_VAR_INT  ("rend-bloom-complexity", &bloomComplexity, 0, 0, 1);
262 }
263 
isEnabled()264 bool Bloom::isEnabled() // static
265 {
266     return bloomEnabled;
267 }
268 
intensity()269 float Bloom::intensity()
270 {
271     return bloomIntensity;
272 }
273 
274 } // namespace fx
275