1 /** 2 The widget you must inherit from for a PBR background UI (though it isn't mandatory). 3 4 Copyright: Copyright Auburn Sounds 2015-2017. 5 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 Authors: Guillaume Piolat 7 */ 8 module dplug.pbrwidgets.pbrbackgroundgui; 9 10 import dplug.math.box; 11 import dplug.core.nogc; 12 import dplug.core.file; 13 14 // Note: this dependency exist because Key is defined in dplug:window 15 import dplug.window.window; 16 17 import dplug.gui.graphics; 18 import dplug.gui.element; 19 import dplug.gui.compositor; 20 import dplug.gui.legacypbr; 21 public import dplug.gui.sizeconstraints; 22 23 import gamut; 24 25 26 // `decompressImagesLazily` cause JPEG and PNG to be decompressed on the fly on resize, instead 27 // of ahead of time and staying in memory. 28 // This wins 17mb of RAM on Panagement. 29 // However, this also disable live reload of images for UI development. Hence, it is disabled for debug builds, in order 30 // to reload background with the RETURN key. 31 debug {} 32 else 33 { 34 version = decompressImagesLazily; 35 } 36 37 /// PBRBackgroundGUI provides a PBR background loaded from PNG or JPEG images. 38 /// It's very practical while in development because it let's you reload the six 39 /// images used with the press of ENTER. 40 /// The path of each of these images (given as a template parameter) must be 41 /// in your "stringImportPaths" settings. 42 class PBRBackgroundGUI(string baseColorPath, 43 string emissivePath, 44 string materialPath, 45 string depthPath, 46 string skyboxPath, 47 string absoluteGfxDirectory // for UI development only 48 ) : GUIGraphics 49 { 50 public: 51 nothrow: 52 @nogc: 53 54 this(int width, int height) 55 { 56 this(makeSizeConstraintsFixed(width, height)); 57 } 58 59 this(SizeConstraints sizeConstraints) 60 { 61 super(sizeConstraints, flagPBR | flagAnimated | flagDrawAlonePBR); 62 63 _diffuseResized = mallocNew!(OwnedImage!RGBA); 64 _materialResized = mallocNew!(OwnedImage!RGBA); 65 _depthResized = mallocNew!(OwnedImage!L16); 66 67 version(decompressImagesLazily) 68 {} 69 else 70 { 71 loadBackgroundImagesFromStaticData(); 72 } 73 74 auto skyboxData = cast(ubyte[])(import(skyboxPath)); 75 loadSkybox(skyboxData); 76 } 77 78 ~this() 79 { 80 freeBackgroundImages(); 81 _diffuseResized.destroyFree(); 82 _materialResized.destroyFree(); 83 _depthResized.destroyFree(); 84 } 85 86 // Development purposes. 87 // In debug mode, pressing ENTER reload the backgrounds 88 debug 89 { 90 override bool onKeyDown(Key key) 91 { 92 if (super.onKeyDown(key)) 93 return true; 94 95 version(decompressImagesLazily) 96 { 97 } 98 else 99 { 100 if (key == Key.enter) 101 { 102 reloadImagesAtRuntime(); 103 return true; 104 } 105 } 106 107 return false; 108 } 109 } 110 111 override void onDrawPBR(ImageRef!RGBA diffuseMap, ImageRef!L16 depthMap, ImageRef!RGBA materialMap, box2i[] dirtyRects) 112 { 113 // Resize resource to match _position 114 { 115 int W = position.width; 116 int H = position.height; 117 118 if (_forceResizeUpdate || _diffuseResized.w != W || _diffuseResized.h != H) 119 { 120 // Decompress images lazily for this size 121 122 version (decompressImagesLazily) 123 { 124 assert(_diffuse is null); 125 loadBackgroundImagesFromStaticData(); 126 } 127 128 _diffuseResized.size(W, H); 129 _materialResized.size(W, H); 130 _depthResized.size(W, H); 131 132 // Potentially resize all 3 backgrounds in parallel 133 void resizeOneImage(int i, int threadIndex) nothrow @nogc 134 { 135 ImageResizer resizer; 136 if (i == 0) 137 { 138 version(Dplug_ProfileUI) context.profiler.begin("resize Diffuse background"); 139 resizer.resizeImageDiffuse(_diffuse.toRef, _diffuseResized.toRef); 140 version(Dplug_ProfileUI) context.profiler.end; 141 } 142 if (i == 1) 143 { 144 version(Dplug_ProfileUI) context.profiler.begin("resize Material background"); 145 resizer.resizeImageMaterial(_material.toRef, _materialResized.toRef); 146 version(Dplug_ProfileUI) context.profiler.end; 147 } 148 if (i == 2) 149 { 150 version(Dplug_ProfileUI) context.profiler.begin("resize Depth background"); 151 resizer.resizeImageDepth(_depth.toRef, _depthResized.toRef); 152 version(Dplug_ProfileUI) context.profiler.end; 153 } 154 } 155 context.globalThreadPool.parallelFor(3, &resizeOneImage); 156 157 _forceResizeUpdate = false; 158 159 version (decompressImagesLazily) 160 { 161 freeBackgroundImages(); 162 assert(_diffuse is null); 163 } 164 } 165 } 166 167 // Just blit backgrounds into dirtyRects. 168 foreach(dirtyRect; dirtyRects) 169 { 170 auto croppedDiffuseIn = _diffuseResized.toRef().cropImageRef(dirtyRect); 171 auto croppedDiffuseOut = diffuseMap.cropImageRef(dirtyRect); 172 173 auto croppedDepthIn = _depthResized.toRef().cropImageRef(dirtyRect); 174 auto croppedDepthOut = depthMap.cropImageRef(dirtyRect); 175 176 auto croppedMaterialIn = _materialResized.toRef().cropImageRef(dirtyRect); 177 auto croppedMaterialOut = materialMap.cropImageRef(dirtyRect); 178 179 croppedDiffuseIn.blitTo(croppedDiffuseOut); 180 croppedDepthIn.blitTo(croppedDepthOut); 181 croppedMaterialIn.blitTo(croppedMaterialOut); 182 } 183 } 184 185 override void reflow() 186 { 187 } 188 189 private: 190 191 final void loadBackgroundImagesFromStaticData() 192 { 193 auto basecolorData = cast(ubyte[])(import(baseColorPath)); 194 auto emissiveData = cast(ubyte[])(import(emissivePath)); 195 auto materialData = cast(ubyte[])(import(materialPath)); 196 auto depthData = cast(ubyte[])(import(depthPath)); 197 loadBackgroundImages(basecolorData, emissiveData, materialData, depthData); 198 } 199 200 // CTFE used here so we are allowed to use ~ 201 static immutable string baseColorPathAbs = absoluteGfxDirectory ~ baseColorPath; 202 static immutable string emissivePathAbs = absoluteGfxDirectory ~ emissivePath; 203 static immutable string materialPathAbs = absoluteGfxDirectory ~ materialPath; 204 static immutable string depthPathAbs = absoluteGfxDirectory ~ depthPath; 205 static immutable string skyboxPathAbs = absoluteGfxDirectory ~ skyboxPath; 206 207 OwnedImage!RGBA _diffuse; 208 OwnedImage!RGBA _material; 209 OwnedImage!L16 _depth; 210 211 OwnedImage!RGBA _diffuseResized; 212 OwnedImage!RGBA _materialResized; 213 OwnedImage!L16 _depthResized; 214 215 /// Where pixel data is taken in the image, expressed in _background coordinates. 216 box2i _sourceRect; 217 218 /// Where it is deposited. Same size than _sourceRect. Expressed in _position coordinates. 219 box2i _destRect; 220 221 /// Offset from source to dest. 222 vec2i _offset; 223 224 /// Force resize of source image in order to display changes while editing files. 225 bool _forceResizeUpdate; 226 227 void freeBackgroundImages() 228 { 229 if (_diffuse) 230 { 231 _diffuse.destroyFree(); 232 _diffuse = null; 233 } 234 235 if (_depth) 236 { 237 _depth.destroyFree(); 238 _depth = null; 239 } 240 241 if (_material) 242 { 243 _material.destroyFree(); 244 _material = null; 245 } 246 } 247 248 version(decompressImagesLazily) 249 { 250 } 251 else 252 { 253 // Reloads images for UI development, avoid long compile round trips 254 // This saves up hours. 255 void reloadImagesAtRuntime() 256 { 257 // reading images with an absolute path since we don't know 258 // which is the current directory from the host 259 ubyte[] basecolorData = readFile(baseColorPathAbs); 260 ubyte[] emissiveData = readFile(emissivePathAbs); 261 ubyte[] materialData = readFile(materialPathAbs); 262 ubyte[] depthData = readFile(depthPathAbs); 263 ubyte[] skyboxData = readFile(skyboxPathAbs); 264 265 if (basecolorData && emissiveData && materialData 266 && depthData && skyboxData) // all valid? 267 { 268 // Reload images from disk and update the UI 269 freeBackgroundImages(); 270 loadBackgroundImages(basecolorData, emissiveData, materialData, depthData); 271 loadSkybox(skyboxData); 272 _forceResizeUpdate = true; 273 setDirtyWhole(); 274 } 275 else 276 { 277 // Note: if you fail here, the absolute path you provided in your gui.d was incorrect. 278 // The background files cannot be loaded at runtime, and you have to fix your pathes. 279 assert(false); 280 } 281 282 // Release copy of file contents 283 freeSlice(basecolorData); 284 freeSlice(emissiveData); 285 freeSlice(materialData); 286 freeSlice(depthData); 287 freeSlice(skyboxData); 288 } 289 } 290 291 void loadBackgroundImages(ubyte[] basecolorData, ubyte[] emissiveData, 292 ubyte[] materialData, ubyte[] depthData) 293 { 294 version(Dplug_ProfileUI) context.profiler.category("image").begin("load Diffuse background"); 295 _diffuse = loadImageSeparateAlpha(basecolorData, emissiveData); 296 version(Dplug_ProfileUI) context.profiler.end; 297 298 version(Dplug_ProfileUI) context.profiler.begin("load Material background"); 299 _material = loadOwnedImage(materialData); 300 version(Dplug_ProfileUI) context.profiler.end; 301 302 version(Dplug_ProfileUI) context.profiler.begin("load Depth background"); 303 _depth = loadOwnedImageDepth(depthData); 304 version(Dplug_ProfileUI) context.profiler.end; 305 } 306 307 void loadSkybox(ubyte[] skyboxData) 308 { 309 // Search for a pass of type PassSkyboxReflections 310 if (auto mpc = cast(MultipassCompositor) compositor()) 311 { 312 foreach(pass; mpc.passes()) 313 { 314 if (auto skyreflPass = cast(PassSkyboxReflections)pass) 315 { 316 version(Dplug_ProfileUI) context.profiler.category("image").begin("load Skybox"); 317 OwnedImage!RGBA skybox = loadOwnedImage(skyboxData); 318 skyreflPass.setSkybox(skybox); 319 version(Dplug_ProfileUI) context.profiler.end; 320 } 321 } 322 } 323 } 324 } 325