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 }