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 }