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 gfm.math.box; 11 import dplug.core.nogc; 12 import dplug.core.file; 13 import dplug.graphics.color; 14 import dplug.graphics.image; 15 import dplug.graphics.view; 16 import dplug.graphics.drawex; 17 18 // Note: this dependency exist because Key is defined in dplug:window 19 import dplug.window.window; 20 21 import dplug.gui.graphics; 22 23 /// PBRBackgroundGUI provides a PBR background loaded from PNG or JPEG images. 24 /// It's very practical while in development because it let's you reload the six 25 /// images used with the press of ENTER. 26 /// The path of each of these images (given as a template parameter) must be 27 /// in your "stringImportPaths" settings. 28 class PBRBackgroundGUI(string baseColorPath, 29 string emissivePath, 30 string materialPath, 31 string physicalPath, 32 string depthPath, 33 string skyboxPath, 34 string absoluteGfxDirectory // for UI development only 35 ) : GUIGraphics 36 { 37 public: 38 nothrow: 39 @nogc: 40 41 this(int width, int height) 42 { 43 super(width, height); 44 auto basecolorData = cast(ubyte[])(import(baseColorPath)); 45 auto emissiveData = cast(ubyte[])(import(emissivePath)); 46 auto materialData = cast(ubyte[])(import(materialPath)); 47 auto physicalData = cast(ubyte[])(import(physicalPath)); 48 auto depthData = cast(ubyte[])(import(depthPath)); 49 auto skyboxData = cast(ubyte[])(import(skyboxPath)); 50 loadImages(basecolorData, emissiveData, materialData, physicalData, depthData, skyboxData); 51 } 52 53 ~this() 54 { 55 freeImages(); 56 } 57 58 // Development purposes. 59 // In debug mode, pressing ENTER reload the backgrounds 60 debug 61 { 62 override bool onKeyDown(Key key) 63 { 64 if (super.onKeyDown(key)) 65 return true; 66 67 if (key == Key.enter) 68 { 69 reloadImagesAtRuntime(); 70 return true; 71 } 72 73 return false; 74 } 75 } 76 77 override void reflow(box2i availableSpace) 78 { 79 _position = availableSpace; 80 } 81 82 override void onDraw(ImageRef!RGBA diffuseMap, ImageRef!L16 depthMap, ImageRef!RGBA materialMap, box2i[] dirtyRects) 83 { 84 // Just blit backgrounds into dirtyRects. 85 foreach(dirtyRect; dirtyRects) 86 { 87 auto croppedDiffuseIn = _diffuse.crop(dirtyRect); 88 auto croppedDiffuseOut = diffuseMap.crop(dirtyRect); 89 90 auto croppedDepthIn = _depth.crop(dirtyRect); 91 auto croppedDepthOut = depthMap.crop(dirtyRect); 92 93 auto croppedMaterialIn = _material.crop(dirtyRect); 94 auto croppedMaterialOut = materialMap.crop(dirtyRect); 95 96 croppedDiffuseIn.blitTo(croppedDiffuseOut); 97 croppedDepthIn.blitTo(croppedDepthOut); 98 croppedMaterialIn.blitTo(croppedMaterialOut); 99 } 100 } 101 102 private: 103 104 // CTFE used here so we are allowed to use ~ 105 static immutable string baseColorPathAbs = absoluteGfxDirectory ~ baseColorPath; 106 static immutable string emissivePathAbs = absoluteGfxDirectory ~ emissivePath; 107 static immutable string materialPathAbs = absoluteGfxDirectory ~ materialPath; 108 static immutable string physicalPathAbs = absoluteGfxDirectory ~ physicalPath; 109 static immutable string depthPathAbs = absoluteGfxDirectory ~ depthPath; 110 static immutable string skyboxPathAbs = absoluteGfxDirectory ~ skyboxPath; 111 112 113 OwnedImage!RGBA _diffuse; 114 OwnedImage!RGBA _material; 115 OwnedImage!L16 _depth; 116 117 void freeImages() 118 { 119 if (_diffuse) 120 _diffuse.destroyFree(); 121 if (_depth) 122 _depth.destroyFree(); 123 if (_material) 124 _material.destroyFree(); 125 } 126 127 // Reloads images for UI development, avoid long compile round trips 128 // This saves up hours. 129 void reloadImagesAtRuntime() 130 { 131 // reading images with an absolute path since we don't know 132 // which is the current directory from the host 133 ubyte[] basecolorData = readFile(baseColorPathAbs); 134 ubyte[] emissiveData = readFile(emissivePathAbs); 135 ubyte[] materialData = readFile(materialPathAbs); 136 ubyte[] physicalData = readFile(physicalPathAbs); 137 ubyte[] depthData = readFile(depthPathAbs); 138 ubyte[] skyboxData = readFile(skyboxPathAbs); 139 140 if (basecolorData && emissiveData && materialData 141 && physicalData && depthData && skyboxData) // all valid? 142 { 143 // Reload images from disk and update the UI 144 freeImages(); 145 loadImages(basecolorData, emissiveData, materialData, physicalData, depthData, skyboxData); 146 setDirtyWhole(); 147 } 148 149 // Release copy of file contents 150 freeSlice(basecolorData); 151 freeSlice(emissiveData); 152 freeSlice(materialData); 153 freeSlice(physicalData); 154 freeSlice(depthData); 155 freeSlice(skyboxData); 156 } 157 158 void loadImages(ubyte[] basecolorData, ubyte[] emissiveData, 159 ubyte[] materialData, ubyte[] physicalData, 160 ubyte[] depthData, ubyte[] skyboxData) 161 { 162 _diffuse = loadImageSeparateAlpha(basecolorData, emissiveData); 163 _material = loadImageSeparateAlpha(materialData, physicalData); 164 OwnedImage!RGBA depthRGBA = loadOwnedImage(depthData); 165 scope(exit) depthRGBA.destroyFree(); 166 assert(_diffuse.w == _material.w); 167 assert(_diffuse.h == _material.h); 168 assert(_diffuse.w == depthRGBA.w); 169 assert(_diffuse.h == depthRGBA.h); 170 171 int width = depthRGBA.w; 172 int height = depthRGBA.h; 173 174 _depth = mallocNew!(OwnedImage!L16)(width, height); 175 176 for (int j = 0; j < height; ++j) 177 { 178 RGBA[] inDepth = depthRGBA.scanline(j); 179 L16[] outDepth = _depth.scanline(j); 180 for (int i = 0; i < width; ++i) 181 { 182 RGBA v = inDepth[i]; 183 184 float d = 0.5f + 257 * (v.g + v.r + v.b) / 3; 185 186 outDepth[i].l = cast(ushort)(d); 187 } 188 } 189 190 OwnedImage!RGBA skybox = loadOwnedImage(skyboxData); 191 context.setSkybox(skybox); 192 } 193 }