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 }