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