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     override void setDirty(box2i rect, UILayer layer = UILayer.guessFromFlags)
176     {
177         super.setDirty(rect, layer);
178         _mustBeRedrawn = true; // the content of the cached buffer will change, need to be redrawn
179     }
180 
181     override void setDirtyWhole(UILayer layer = UILayer.guessFromFlags)
182     {
183         super.setDirtyWhole(layer);
184         _mustBeRedrawn = true; // the content of the cached buffer will change, need to be redrawn
185     }
186 
187     final override void onDrawRaw(ImageRef!RGBA rawMap, box2i[] dirtyRects)
188     {
189         // Did the element's size changed?
190         int currentWidth = _rawBuf.w;
191         int currentHeight = _rawBuf.h;
192         int newWidth = _position.width;
193         int newHeight = _position.height;
194         bool sizeChanged = (currentWidth != newWidth) || (currentHeight != newHeight);
195         if (sizeChanged)
196         {
197             // If the widget size changed, we must redraw it even if it was not dirtied
198             _mustBeRedrawn = true;
199 
200             // Change size of buffers
201             _opacityBuf.size(newWidth, newHeight);
202             _rawBuf.size(newWidth, newHeight);
203         }
204 
205         if (_mustBeRedrawn)
206         {
207             // opacity buffer originally filled with zeroes
208             _opacityBuf.fillAll(opacityFullyTransparent);
209 
210             // RGBA buffer originally filled with black
211             _rawBuf.fillAll(RGBA(0, 0, 0, 255));
212 
213             onDrawBufferedRaw(_rawBuf.toRef(), _opacityBuf.toRef());
214 
215             // For debug purpose            
216             //_opacityBuf.fill(opacityFullyOpaque);
217 
218             _mustBeRedrawn = false;
219         }
220 
221         // Blend cached render to given targets
222         foreach(dirtyRect; dirtyRects)
223         {
224             auto sourceRaw = _rawBuf.toRef().cropImageRef(dirtyRect);
225             auto destRaw = rawMap.cropImageRef(dirtyRect);
226             sourceRaw.blendWithAlpha(destRaw, _opacityBuf.toRef().cropImageRef(dirtyRect));
227         }
228     }
229 
230     /// Redraws the whole widget without consideration for drawing only in dirty rects.
231     abstract void onDrawBufferedRaw(ImageRef!RGBA rawMap, ImageRef!L8 opacity);
232 
233 private:
234     OwnedImage!RGBA _rawBuf;
235     OwnedImage!L8 _opacityBuf;
236     bool _mustBeRedrawn;
237 }