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 /// Extending the UIElement with an owned drawing buffer.
18 /// This is intended to have easier dirtyrect-compliant widgets.
19 /// Also caches expensive drawing, but it's not free at all.
20 ///
21 /// No less than three additional opacity channels must be filled to be able to blend the widgets explicitely.
22 /// The semantic of the opacity channels are:
23 ///   opacity left at 0 => pixel untouched
24 ///   opacity > 0       => pixel is touched, blending will occur
25 class UIBufferedElement : UIElement
26 {
27 public:
28 nothrow:
29 @nogc:
30 
31     this(UIContext context)
32     {
33         super(context);
34         _diffuseBuf = mallocNew!(OwnedImage!RGBA)();
35         _depthBuf = mallocNew!(OwnedImage!L16)();
36         _materialBuf = mallocNew!(OwnedImage!RGBA)();
37 
38         _diffuseOpacityBuf = mallocNew!(OwnedImage!L8)();
39         _depthOpacityBuf = mallocNew!(OwnedImage!L8)();
40         _materialOpacityBuf = mallocNew!(OwnedImage!L8)();
41     }
42 
43     ~this()
44     {
45         _diffuseBuf.destroyFree();
46         _depthBuf.destroyFree();
47         _materialBuf.destroyFree();
48 
49         _diffuseOpacityBuf.destroyFree();
50         _depthOpacityBuf.destroyFree();
51         _materialOpacityBuf.destroyFree();
52     }
53     
54     override void setDirty(box2i rect) nothrow @nogc 
55     {
56         super.setDirty(rect);
57         _mustBeRedrawn = true; // the content of the cached buffer will change, need to be redrawn
58     }
59 
60     override void setDirtyWhole() nothrow @nogc 
61     {
62         super.setDirtyWhole();
63         _mustBeRedrawn = true; // the content of the cached buffer will change, need to be redrawn
64     }
65 
66     final override void onDraw(ImageRef!RGBA diffuseMap, ImageRef!L16 depthMap, ImageRef!RGBA materialMap, box2i[] dirtyRects) nothrow @nogc
67     {
68         // Did the element's size changed?
69         int currentWidth = _diffuseBuf.w;
70         int currentHeight = _diffuseBuf.h;
71         int newWidth = _position.width;
72         int newHeight = _position.height;
73         bool sizeChanged = (currentWidth != newWidth) || (currentHeight != newHeight);
74         if (sizeChanged)
75         {
76             // If the widget size changed, we must redraw it even if it was not dirtied
77             _mustBeRedrawn = true;
78 
79             // Change size of buffers
80             _diffuseBuf.size(newWidth, newHeight);
81             _depthBuf.size(newWidth, newHeight);
82             _materialBuf.size(newWidth, newHeight);
83 
84             _diffuseOpacityBuf.size(newWidth, newHeight);
85             _depthOpacityBuf.size(newWidth, newHeight);
86             _materialOpacityBuf.size(newWidth, newHeight);
87         }
88 
89         if (_mustBeRedrawn)
90         {
91             // opacity buffer originally filled with zeroes
92             _diffuseOpacityBuf.fillAll(opacityFullyTransparent);
93             _depthOpacityBuf.fillAll(opacityFullyTransparent);
94             _materialOpacityBuf.fillAll(opacityFullyTransparent);
95 
96             _diffuseBuf.fillAll(RGBA(128, 128, 128, 0));
97             _depthBuf.fillAll(L16(defaultDepth));
98             _materialBuf.fillAll(RGBA(defaultRoughness, defaultMetalnessMetal, defaultSpecular, defaultPhysical));
99 
100             onDrawBuffered(_diffuseBuf.toRef(), _depthBuf.toRef(), _materialBuf.toRef(), 
101                            _diffuseOpacityBuf.toRef(),
102                            _depthOpacityBuf.toRef(),
103                            _materialOpacityBuf.toRef());
104 
105             // For debug purpose            
106             //_diffuseOpacityBuf.fill(opacityFullyOpaque);
107             //_depthOpacityBuf.fill(opacityFullyOpaque);
108             //_materialOpacityBuf.fill(opacityFullyOpaque);
109 
110             _mustBeRedrawn = false;
111         }
112 
113         // Blend cached render to given targets
114         foreach(dirtyRect; dirtyRects)
115         {
116             auto sourceDiffuse = _diffuseBuf.toRef().cropImageRef(dirtyRect);
117             auto sourceDepth = _depthBuf.toRef().cropImageRef(dirtyRect);
118             auto sourceMaterial = _materialBuf.toRef().cropImageRef(dirtyRect);
119             auto destDiffuse = diffuseMap.cropImageRef(dirtyRect);
120             auto destDepth = depthMap.cropImageRef(dirtyRect);
121             auto destMaterial = materialMap.cropImageRef(dirtyRect);
122 
123             sourceDiffuse.blendWithAlpha(destDiffuse, _diffuseOpacityBuf.toRef().cropImageRef(dirtyRect));
124             sourceDepth.blendWithAlpha(destDepth, _depthOpacityBuf.toRef().cropImageRef(dirtyRect));
125             sourceMaterial.blendWithAlpha(destMaterial, _materialOpacityBuf.toRef().cropImageRef(dirtyRect));
126         }
127     }
128 
129     /// Redraws the whole widget without consideration for drawing only in dirty rects.
130     /// That is a lot of maps to fill. On the plus side, this happen quite infrequently.
131     abstract void onDrawBuffered(ImageRef!RGBA diffuseMap, 
132                                  ImageRef!L16 depthMap, 
133                                  ImageRef!RGBA materialMap, 
134                                  ImageRef!L8 diffuseOpacity,
135                                  ImageRef!L8 depthOpacity,
136                                  ImageRef!L8 materialOpacity) nothrow @nogc;
137 
138 private:
139     OwnedImage!RGBA _diffuseBuf;
140     OwnedImage!L16 _depthBuf;
141     OwnedImage!RGBA _materialBuf;
142     OwnedImage!L8 _diffuseOpacityBuf;
143     OwnedImage!L8 _depthOpacityBuf;
144     OwnedImage!L8 _materialOpacityBuf;
145     bool _mustBeRedrawn;
146 }
147