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