A game about forced loneliness, made by TACStudios
1using UnityEngine.Rendering;
2using UnityEngine.Rendering.LookDev;
3using UnityEditorInternal;
4using UnityEngine;
5using System.Linq;
6
7namespace UnityEditor.Rendering.LookDev
8{
9 /// <summary>
10 /// Main entry point for scripting LookDev
11 /// </summary>
12 public static class LookDev
13 {
14 const string lastRenderingDataSavePath = "Library/LookDevConfig.asset";
15
16 //TODO: ensure only one displayer at time for the moment
17 static IViewDisplayer s_ViewDisplayer;
18 static IEnvironmentDisplayer s_EnvironmentDisplayer;
19 static Compositer s_Compositor;
20 static StageCache s_Stages;
21 static Context s_CurrentContext;
22
23 internal static IDataProvider dataProvider
24 => RenderPipelineManager.currentPipeline as IDataProvider;
25
26 /// <summary>
27 /// Get all the data used in LookDev currently (views, layout, debug... )
28 /// </summary>
29 internal static Context currentContext
30 {
31 //Lazy init: load it when needed instead in static even if you do not support lookdev
32 get
33 {
34 if (s_CurrentContext == null || s_CurrentContext.Equals(null))
35 {
36 // In case the context is still alive somewhere but the static reference has been lost, we can find the object back with this
37 s_CurrentContext = TryFindCurrentContext();
38 if (s_CurrentContext != null)
39 return s_CurrentContext;
40
41 s_CurrentContext = LoadConfigInternal();
42 if (s_CurrentContext == null)
43 s_CurrentContext = defaultContext;
44
45 ReloadStage(false);
46 }
47 return s_CurrentContext;
48 }
49 private set => s_CurrentContext = value;
50 }
51
52 static Context TryFindCurrentContext()
53 {
54 Context context = s_CurrentContext;
55
56 if (context != null)
57 return context;
58
59 return Resources.FindObjectsOfTypeAll<Context>().FirstOrDefault();
60 }
61
62 static Context defaultContext
63 {
64 get
65 {
66 var context = UnityEngine.ScriptableObject.CreateInstance<Context>();
67 context.hideFlags = HideFlags.HideAndDontSave;
68 context.Init();
69 return context;
70 }
71 }
72
73 //[TODO: not compatible with multiple displayer. To rework if needed]
74 internal static IViewDisplayer currentViewDisplayer => s_ViewDisplayer;
75 internal static IEnvironmentDisplayer currentEnvironmentDisplayer => s_EnvironmentDisplayer;
76
77 [MenuItem("Window/Rendering/Look Dev", false, 10001)]
78 static void OpenLookDev() => Open();
79
80 [MenuItem("Window/Rendering/Look Dev", true, 10001)]
81 static bool LookDevAvailable() => supported;
82
83 internal static bool waitingConfigure { get; private set; } = true;
84
85 /// <summary>State of the LookDev window</summary>
86 public static bool open { get; private set; }
87
88 /// <summary>
89 /// Does LookDev is supported with the current render pipeline?
90 /// </summary>
91 public static bool supported => dataProvider != null;
92
93 /// <summary>
94 /// Reset all LookDevs datas to the default configuration
95 /// </summary>
96 public static void ResetConfig()
97 => currentContext = defaultContext;
98
99 static Context LoadConfigInternal(string path = lastRenderingDataSavePath)
100 {
101 var objs = InternalEditorUtility.LoadSerializedFileAndForget(path);
102 Context context = (objs.Length > 0 ? objs[0] : null) as Context;
103 if (context != null && !context.Equals(null))
104 context.Init();
105 return context;
106 }
107
108 /// <summary>
109 /// Load a different set of datas
110 /// </summary>
111 /// <param name="path">Path where to load</param>
112 internal static void LoadConfig(string path = lastRenderingDataSavePath)
113 {
114 var last = LoadConfigInternal(path);
115 if (last != null)
116 currentContext = last;
117 }
118
119 /// <summary>
120 /// Save the current set of datas
121 /// </summary>
122 /// <param name="path">[optional] Path to save. By default, saved in Library folder</param>
123 internal static void SaveConfig(string path = lastRenderingDataSavePath)
124 {
125 if (currentContext != null && !currentContext.Equals(null))
126 InternalEditorUtility.SaveToSerializedFileAndForget(new[] { currentContext }, path, true);
127 }
128
129 /// <summary>Open the LookDev window</summary>
130 public static void Open()
131 {
132 EditorWindow.GetWindow<DisplayWindow>();
133 }
134
135 /// <summary>Close the LookDev window</summary>
136 public static void Close()
137 {
138 (s_ViewDisplayer as EditorWindow)?.Close();
139 s_ViewDisplayer = null;
140 (s_EnvironmentDisplayer as EditorWindow)?.Close();
141 s_EnvironmentDisplayer = null;
142 }
143
144 internal static void Initialize(DisplayWindow window)
145 {
146 s_ViewDisplayer = window;
147 s_EnvironmentDisplayer = window;
148 open = true;
149
150 // Lookdev Initialize can be called when the window is re-created by the editor layout system.
151 // In that case, the current context won't be null and there might be objects to reload from the temp ID
152 ConfigureLookDev(reloadWithTemporaryID: TryFindCurrentContext() != null);
153 }
154
155 [Callbacks.DidReloadScripts]
156 static void OnEditorReload()
157 {
158 var windows = Resources.FindObjectsOfTypeAll<DisplayWindow>();
159 s_ViewDisplayer = windows.Length > 0 ? windows[0] : null;
160 s_EnvironmentDisplayer = windows.Length > 0 ? windows[0] : null;
161 open = s_ViewDisplayer != null;
162 if (open)
163 ConfigureLookDev(reloadWithTemporaryID: true);
164 }
165
166 static void ConfigureLookDev(bool reloadWithTemporaryID)
167 {
168 open = true;
169 waitingConfigure = true;
170 if (s_CurrentContext == null || s_CurrentContext.Equals(null))
171 LoadConfig();
172 WaitingSRPReloadForConfiguringRenderer(5, reloadWithTemporaryID: reloadWithTemporaryID);
173 }
174
175 static void WaitingSRPReloadForConfiguringRenderer(int maxAttempt, bool reloadWithTemporaryID, int attemptNumber = 0)
176 {
177 if (supported)
178 {
179 waitingConfigure = false;
180 ConfigureRenderer(reloadWithTemporaryID);
181 LinkViewDisplayer();
182 LinkEnvironmentDisplayer();
183 ReloadStage(reloadWithTemporaryID);
184 }
185 else if (attemptNumber < maxAttempt)
186 EditorApplication.delayCall +=
187 () => WaitingSRPReloadForConfiguringRenderer(maxAttempt, reloadWithTemporaryID, ++attemptNumber);
188 else
189 waitingConfigure = false;
190 }
191
192 static void ConfigureRenderer(bool reloadWithTemporaryID)
193 {
194 s_Stages?.Dispose(); //clean previous occurrence on reloading
195 s_Stages = new StageCache(dataProvider);
196 s_Compositor?.Dispose(); //clean previous occurrence on reloading
197 s_Compositor = new Compositer(s_ViewDisplayer, dataProvider, s_Stages);
198 }
199
200 static void LinkViewDisplayer()
201 {
202 s_ViewDisplayer.OnClosed += () =>
203 {
204 s_Compositor?.Dispose();
205 s_Compositor = null;
206 s_Stages?.Dispose();
207 s_Stages = null;
208 s_ViewDisplayer = null;
209 //currentContext = null;
210
211 SaveConfig();
212
213 open = false;
214 };
215 s_ViewDisplayer.OnLayoutChanged += (layout, envPanelOpen) =>
216 {
217 currentContext.layout.viewLayout = layout;
218 currentContext.layout.showedSidePanel = envPanelOpen;
219 SaveConfig();
220 };
221 s_ViewDisplayer.OnChangingObjectInView += (go, index, localPos) =>
222 {
223 switch (index)
224 {
225 case ViewCompositionIndex.First:
226 case ViewCompositionIndex.Second:
227 currentContext.GetViewContent((ViewIndex)index).UpdateViewedObject(go);
228 SaveContextChangeAndApply((ViewIndex)index);
229 break;
230 case ViewCompositionIndex.Composite:
231 ViewIndex viewIndex = s_Compositor.GetViewFromComposition(localPos);
232 currentContext.GetViewContent(viewIndex).UpdateViewedObject(go);
233 SaveContextChangeAndApply(viewIndex);
234 break;
235 }
236 };
237 s_ViewDisplayer.OnChangingEnvironmentInView += (obj, index, localPos) =>
238 {
239 switch (index)
240 {
241 case ViewCompositionIndex.First:
242 case ViewCompositionIndex.Second:
243 currentContext.GetViewContent((ViewIndex)index).UpdateEnvironment(obj);
244 SaveContextChangeAndApply((ViewIndex)index);
245 break;
246 case ViewCompositionIndex.Composite:
247 ViewIndex viewIndex = s_Compositor.GetViewFromComposition(localPos);
248 currentContext.GetViewContent(viewIndex).UpdateEnvironment(obj);
249 SaveContextChangeAndApply(viewIndex);
250 break;
251 }
252 };
253 }
254
255 static void LinkEnvironmentDisplayer()
256 {
257 s_EnvironmentDisplayer.OnChangingEnvironmentLibrary += UpdateEnvironmentLibrary;
258 }
259
260 static void UpdateEnvironmentLibrary(EnvironmentLibrary library)
261 {
262 LookDev.currentContext.UpdateEnvironmentLibrary(library);
263 }
264
265 static void ReloadStage(bool reloadWithTemporaryID)
266 {
267 currentContext.GetViewContent(ViewIndex.First).LoadAll(reloadWithTemporaryID);
268 ApplyContextChange(ViewIndex.First);
269 currentContext.GetViewContent(ViewIndex.Second).LoadAll(reloadWithTemporaryID);
270 ApplyContextChange(ViewIndex.Second);
271 }
272
273 static void ApplyContextChange(ViewIndex index)
274 {
275 s_Stages.UpdateSceneObjects(index);
276 s_Stages.UpdateSceneLighting(index, dataProvider);
277 s_ViewDisplayer.Repaint();
278 }
279
280 /// <summary>Update the rendered element with element in the context</summary>
281 /// <param name="index">The index of the stage to update</param>
282 internal static void SaveContextChangeAndApply(ViewIndex index)
283 {
284 SaveConfig();
285 ApplyContextChange(index);
286 }
287 }
288}