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