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