1 /**
2 * Home of `UIBufferedElement`, for non-opaque widgets.
3 *
4 * Copyright: Copyright Auburn Sounds 2015-2016.
5 * License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
6 * Authors:   Guillaume Piolat
7 */
8 module dplug.gui.bufferedelement;
9 
10 import dplug.core.nogc;
11 public import dplug.gui.element;
12 
13 // Important values for opacity.
14 enum L8 opacityFullyOpaque = L8(255);
15 enum L8 opacityFullyTransparent = L8(0);
16 
17 
18 /// Extending the UIElement with an owned drawing buffer.
19 /// This is intended to have easier dirtyrect-compliant widgets.
20 /// Also caches expensive drawing, but it's not free at all.
21 ///
22 /// No less than three additional opacity channels must be filled to be able to blend the widgets explicitely.
23 /// The semantic of the opacity channels are:
24 ///   opacity left at 0 => pixel untouched
25 ///   opacity > 0       => pixel is touched, blending will occur
26 class UIBufferedElementPBR : UIElement
27 {
28 public:
29 nothrow:
30 @nogc:
31 
32     this(UIContext context, uint flags)
33     {
34         super(context, flags);
35 
36         // It makes no sense to bufferize the PBR impact of an UIElement which would not draw there
37         assert(drawsToPBR());
38 
39         _diffuseBuf = mallocNew!(OwnedImage!RGBA)();
40         _depthBuf = mallocNew!(OwnedImage!L16)();
41         _materialBuf = mallocNew!(OwnedImage!RGBA)();
42         _diffuseOpacityBuf = mallocNew!(OwnedImage!L8)();
43         _depthOpacityBuf = mallocNew!(OwnedImage!L8)();
44         _materialOpacityBuf = mallocNew!(OwnedImage!L8)();
45     }
46 
47     ~this()
48     {
49         _diffuseBuf.destroyFree();
50         _depthBuf.destroyFree();
51         _materialBuf.destroyFree();
52         _diffuseOpacityBuf.destroyFree();
53         _depthOpacityBuf.destroyFree();
54         _materialOpacityBuf.destroyFree();
55     }
56 
57     override void setDirty(box2i rect, UILayer layer = UILayer.guessFromFlags) nothrow @nogc 
58     {
59         super.setDirty(rect, layer);
60         _mustBeRedrawn = true; // the content of the cached buffer will change, need to be redrawn
61     }
62 
63     override void setDirtyWhole(UILayer layer = UILayer.guessFromFlags) nothrow @nogc 
64     {
65         super.setDirtyWhole(layer);
66         _mustBeRedrawn = true; // the content of the cached buffer will change, need to be redrawn
67     }
68 
69     final override void onDrawPBR(ImageRef!RGBA diffuseMap, ImageRef!L16 depthMap, ImageRef!RGBA materialMap, box2i[] dirtyRects) nothrow @nogc
70     {
71         // Did the element's size changed?
72         int currentWidth = _diffuseBuf.w;
73         int currentHeight = _diffuseBuf.h;
74         int newWidth = _position.width;
75         int newHeight = _position.height;
76         bool sizeChanged = (currentWidth != newWidth) || (currentHeight != newHeight);
77         if (sizeChanged)
78         {
79             // If the widget size changed, we must redraw it even if it was not dirtied
80             _mustBeRedrawn = true;
81 
82             // Change size of buffers
83             _diffuseBuf.size(newWidth, newHeight);
84             _depthBuf.size(newWidth, newHeight);
85             _materialBuf.size(newWidth, newHeight);
86 
87             _diffuseOpacityBuf.size(newWidth, newHeight);
88             _depthOpacityBuf.size(newWidth, newHeight);
89             _materialOpacityBuf.size(newWidth, newHeight);
90         }
91 
92         if (_mustBeRedrawn)
93         {
94             // opacity buffer originally filled with zeroes
95             _diffuseOpacityBuf.fillAll(opacityFullyTransparent);
96             _depthOpacityBuf.fillAll(opacityFullyTransparent);
97             _materialOpacityBuf.fillAll(opacityFullyTransparent);
98 
99             _diffuseBuf.fillAll(RGBA(128, 128, 128, 0));
100             _depthBuf.fillAll(L16(defaultDepth));
101             _materialBuf.fillAll(RGBA(defaultRoughness, defaultMetalnessMetal, defaultSpecular, 255));
102 
103             onDrawBufferedPBR(_diffuseBuf.toRef(), _depthBuf.toRef(), _materialBuf.toRef(), 
104                               _diffuseOpacityBuf.toRef(),
105                               _depthOpacityBuf.toRef(),
106                               _materialOpacityBuf.toRef());
107 
108             // For debug purpose            
109             //_diffuseOpacityBuf.fill(opacityFullyOpaque);
110             //_depthOpacityBuf.fill(opacityFullyOpaque);
111             //_materialOpacityBuf.fill(opacityFullyOpaque);
112 
113             _mustBeRedrawn = false;
114         }
115 
116         // Blend cached render to given targets
117         foreach(dirtyRect; dirtyRects)
118         {
119             auto sourceDiffuse = _diffuseBuf.toRef().cropImageRef(dirtyRect);
120             auto sourceDepth = _depthBuf.toRef().cropImageRef(dirtyRect);
121             auto sourceMaterial = _materialBuf.toRef().cropImageRef(dirtyRect);
122             auto destDiffuse = diffuseMap.cropImageRef(dirtyRect);
123             auto destDepth = depthMap.cropImageRef(dirtyRect);
124             auto destMaterial = materialMap.cropImageRef(dirtyRect);
125 
126             sourceDiffuse.blendWithAlpha(destDiffuse, _diffuseOpacityBuf.toRef().cropImageRef(dirtyRect));
127             sourceDepth.blendWithAlpha(destDepth, _depthOpacityBuf.toRef().cropImageRef(dirtyRect));
128             sourceMaterial.blendWithAlpha(destMaterial, _materialOpacityBuf.toRef().cropImageRef(dirtyRect));
129         }
130     }
131 
132     /// Redraws the whole widget without consideration for drawing only in dirty rects.
133     /// That is a lot of maps to fill. On the plus side, this happen quite infrequently.
134     abstract void onDrawBufferedPBR(ImageRef!RGBA diffuseMap, 
135                                     ImageRef!L16 depthMap, 
136                                     ImageRef!RGBA materialMap, 
137                                     ImageRef!L8 diffuseOpacity,
138                                     ImageRef!L8 depthOpacity,
139                                     ImageRef!L8 materialOpacity) nothrow @nogc;
140 
141 private:
142     OwnedImage!RGBA _diffuseBuf;
143     OwnedImage!L16 _depthBuf;
144     OwnedImage!RGBA _materialBuf;
145     OwnedImage!L8 _diffuseOpacityBuf;
146     OwnedImage!L8 _depthOpacityBuf;
147     OwnedImage!L8 _materialOpacityBuf;
148     bool _mustBeRedrawn;
149 }
150 
151 ///ditto
152 class UIBufferedElementRaw : UIElement
153 {
154 public:
155 nothrow:
156 @nogc:
157 
158     this(UIContext context, uint flags)
159     {
160         super(context, flags);
161 
162         // It makes no sense to bufferize the Raw impact of an UIElement which would not draw there
163         assert(drawsToRaw());
164 
165         _rawBuf = mallocNew!(OwnedImage!RGBA)();
166         _opacityBuf = mallocNew!(OwnedImage!L8)();
167     }
168 
169     ~this()
170     {
171         _rawBuf.destroyFree();
172         _opacityBuf.destroyFree();
173     }
174 
175     /// Does not initialize buffers.
176     /// `onDrawBufferedRaw` will be returned the same content, 
177     /// unless the buffer size has changed.
178     /// This is needed for widgets might want their own Raw 
179     /// and Opacity buffer to stay unchanged, if their size
180     /// didn't change. This is typically an optimization.
181     void doNotClearBuffers()
182     {
183         _preClearBuffers = false;
184     }
185 
186     override void setDirty(box2i rect, UILayer layer = UILayer.guessFromFlags)
187     {
188         super.setDirty(rect, layer);
189         _mustBeRedrawn = true; // the content of the cached buffer will change, need to be redrawn
190     }
191 
192     override void setDirtyWhole(UILayer layer = UILayer.guessFromFlags)
193     {
194         super.setDirtyWhole(layer);
195         _mustBeRedrawn = true; // the content of the cached buffer will change, need to be redrawn
196     }
197 
198     final override void onDrawRaw(ImageRef!RGBA rawMap, box2i[] dirtyRects)
199     {
200         // Did the element's size changed?
201         int currentWidth = _rawBuf.w;
202         int currentHeight = _rawBuf.h;
203         int newWidth = _position.width;
204         int newHeight = _position.height;
205         bool sizeChanged = (currentWidth != newWidth) || (currentHeight != newHeight);
206 
207         bool preClear = _preClearBuffers;
208         if (sizeChanged)
209         {
210             // If the widget size changed, we must redraw it even if it was not dirtied
211             _mustBeRedrawn = true;
212 
213             // Change size of buffers
214             _opacityBuf.size(newWidth, newHeight);
215             _rawBuf.size(newWidth, newHeight);
216 
217             preClear = true;
218         }
219 
220         if (_mustBeRedrawn)
221         {
222             if (preClear)
223             {
224                 // opacity buffer originally filled with zeroes
225                 _opacityBuf.fillAll(opacityFullyTransparent);
226 
227                 // RGBA buffer originally filled with black
228                 _rawBuf.fillAll(RGBA(0, 0, 0, 255));
229             }
230 
231             onDrawBufferedRaw(_rawBuf.toRef(), _opacityBuf.toRef());
232 
233             // For debug purpose
234             //_opacityBuf.fillAll(opacityFullyOpaque);
235 
236             _mustBeRedrawn = false;
237         }
238 
239         // Blend cached render to given targets
240         foreach(dirtyRect; dirtyRects)
241         {
242             auto sourceRaw = _rawBuf.toRef().cropImageRef(dirtyRect);
243             auto destRaw = rawMap.cropImageRef(dirtyRect);
244             sourceRaw.blendWithAlpha(destRaw, _opacityBuf.toRef().cropImageRef(dirtyRect));
245         }
246     }
247 
248     /// Redraws the whole widget without consideration for drawing only in dirty rects.
249     abstract void onDrawBufferedRaw(ImageRef!RGBA rawMap, ImageRef!L8 opacity);
250 
251 private:
252     OwnedImage!RGBA _rawBuf;
253     OwnedImage!L8 _opacityBuf;
254     bool _mustBeRedrawn;
255 
256     // Buffers are pre-cleared at every draw.
257     // This allows to draw only non-transparent part, 
258     // but can also be a CPU cost.
259     // Default = true.
260     bool _preClearBuffers = true;
261 }