using System;
using System.Collections.Generic;
#if UNITY_EDITOR
using System.IO;
using UnityEditor;
#endif
namespace UnityEngine.Rendering
{
///
/// A component that stores baked probe volume state and data references. Normally hidden from the user.
///
[ExecuteAlways]
[AddComponentMenu("")] // Hide.
public class ProbeVolumePerSceneData : MonoBehaviour, ISerializationCallbackReceiver
{
[Serializable]
internal struct PerScenarioData
{
public int sceneHash;
public TextAsset cellDataAsset; // Contains L0 L1 SH data
public TextAsset cellOptionalDataAsset; // Contains L2 SH data
}
[Serializable]
struct SerializablePerScenarioDataItem
{
public string scenario;
public PerScenarioData data;
}
[SerializeField] internal ProbeVolumeAsset asset;
[SerializeField] internal TextAsset cellSharedDataAsset; // Contains bricks and validity data
[SerializeField] internal TextAsset cellSupportDataAsset; // Contains debug data
[SerializeField] List serializedScenarios = new();
internal Dictionary scenarios = new();
bool assetLoaded = false;
string activeScenario = null, otherScenario = null;
///
/// OnAfterDeserialize implementation.
///
void ISerializationCallbackReceiver.OnAfterDeserialize()
{
scenarios.Clear();
foreach (var scenarioData in serializedScenarios)
scenarios.Add(scenarioData.scenario, scenarioData.data);
}
///
/// OnBeforeSerialize implementation.
///
void ISerializationCallbackReceiver.OnBeforeSerialize()
{
serializedScenarios.Clear();
foreach (var kvp in scenarios)
{
serializedScenarios.Add(new SerializablePerScenarioDataItem()
{
scenario = kvp.Key,
data = kvp.Value,
});
}
}
#if UNITY_EDITOR
void DeleteAsset(Object asset)
{
if (asset != null && AssetDatabase.TryGetGUIDAndLocalFileIdentifier(asset, out string guid, out long instanceID))
{
var assetPath = AssetDatabase.GUIDToAssetPath(guid);
AssetDatabase.DeleteAsset(assetPath);
}
}
#endif
internal void Clear()
{
QueueAssetRemoval();
#if UNITY_EDITOR
try
{
AssetDatabase.StartAssetEditing();
DeleteAsset(asset);
DeleteAsset(cellSharedDataAsset);
DeleteAsset(cellSupportDataAsset);
foreach (var scenarioData in scenarios.Values)
{
DeleteAsset(scenarioData.cellDataAsset);
DeleteAsset(scenarioData.cellOptionalDataAsset);
}
}
finally
{
AssetDatabase.StopAssetEditing();
AssetDatabase.Refresh();
EditorUtility.SetDirty(this);
}
#endif
scenarios.Clear();
}
internal void RemoveScenario(string scenario)
{
#if UNITY_EDITOR
if (scenarios.TryGetValue(scenario, out var scenarioData))
{
AssetDatabase.DeleteAsset(AssetDatabase.GetAssetPath(scenarioData.cellDataAsset));
AssetDatabase.DeleteAsset(AssetDatabase.GetAssetPath(scenarioData.cellOptionalDataAsset));
EditorUtility.SetDirty(this);
}
#endif
scenarios.Remove(scenario);
}
internal void RenameScenario(string scenario, string newName)
{
if (!scenarios.TryGetValue(scenario, out var data))
return;
scenarios.Remove(scenario);
scenarios.Add(newName, data);
#if UNITY_EDITOR
EditorUtility.SetDirty(this);
var baseName = ProbeVolumeAsset.assetName + "-" + newName;
void RenameAsset(Object asset, string extension)
{
var oldPath = AssetDatabase.GetAssetPath(asset);
AssetDatabase.RenameAsset(oldPath, baseName + extension);
}
RenameAsset(data.cellDataAsset, ".CellData.bytes");
RenameAsset(data.cellOptionalDataAsset, ".CellOptionalData.bytes");
#endif
}
internal bool ResolveCells() => ResolveSharedCellData() && ResolvePerScenarioCellData();
internal bool ResolveSharedCellData() => asset != null && asset.ResolveSharedCellData(cellSharedDataAsset, cellSupportDataAsset);
bool ResolvePerScenarioCellData()
{
int loadedCount = 0, targetLoaded = otherScenario == null ? 1 : 2;
if (activeScenario != null && scenarios.TryGetValue(activeScenario, out var data0))
{
if (asset.ResolvePerScenarioCellData(data0.cellDataAsset, data0.cellOptionalDataAsset, 0))
loadedCount++;
}
if (otherScenario != null && scenarios.TryGetValue(otherScenario, out var data1))
{
if (asset.ResolvePerScenarioCellData(data1.cellDataAsset, data1.cellOptionalDataAsset, loadedCount))
loadedCount++;
}
for (var i = 0; i < asset.cells.Length; ++i)
asset.cells[i].hasTwoScenarios = loadedCount == 2;
return loadedCount == targetLoaded;
}
internal void QueueAssetLoading()
{
if (asset == null || asset.IsInvalid() || !ResolvePerScenarioCellData())
return;
var refVol = ProbeReferenceVolume.instance;
refVol.AddPendingAssetLoading(asset);
assetLoaded = true;
#if UNITY_EDITOR
if (refVol.sceneData != null)
refVol.bakingProcessSettings = refVol.sceneData.GetBakeSettingsForScene(gameObject.scene);
#endif
}
internal void QueueAssetRemoval()
{
if (asset != null)
ProbeReferenceVolume.instance.AddPendingAssetRemoval(asset);
assetLoaded = false;
}
void OnEnable()
{
ProbeReferenceVolume.instance.RegisterPerSceneData(this);
if (ProbeReferenceVolume.instance.sceneData != null)
Initialize();
// otherwise baking state will be initialized in ProbeReferenceVolume.Initialize when sceneData is loaded
}
void OnDisable()
{
QueueAssetRemoval();
activeScenario = otherScenario = null;
ProbeReferenceVolume.instance.UnregisterPerSceneData(this);
}
internal void Initialize()
{
ResolveSharedCellData();
QueueAssetRemoval();
activeScenario = ProbeReferenceVolume.instance.sceneData.lightingScenario;
otherScenario = ProbeReferenceVolume.instance.sceneData.otherScenario;
QueueAssetLoading();
}
internal void UpdateActiveScenario(string activeScenario, string otherScenario)
{
if (asset == null)
return;
// if we just change scenario, don't need to queue anything
// Just load cells from disk and wait for blending to stream updates to gpu
this.activeScenario = activeScenario;
this.otherScenario = otherScenario;
if (!assetLoaded)
QueueAssetLoading();
else if (!ResolvePerScenarioCellData())
QueueAssetRemoval();
}
#if UNITY_EDITOR
internal string GetAssetPathSafe(Object asset)
{
if (asset != null && AssetDatabase.TryGetGUIDAndLocalFileIdentifier(asset, out string guid, out long instanceID))
return AssetDatabase.GUIDToAssetPath(guid);
return "";
}
internal void GetBlobFileNames(out string cellDataFilename, out string cellOptionalDataFilename, out string cellSharedDataFilename, out string cellSupportDataFilename)
{
var scenario = ProbeReferenceVolume.instance.lightingScenario;
string basePath = Path.Combine(ProbeVolumeAsset.GetDirectory(gameObject.scene.path, gameObject.scene.name), ProbeVolumeAsset.assetName);
string GetOrCreateFileName(Object o, string extension)
{
var res = AssetDatabase.GetAssetPath(o);
if (string.IsNullOrEmpty(res)) res = basePath + extension;
return res;
}
cellDataFilename = GetOrCreateFileName(scenarios[scenario].cellDataAsset, "-" + scenario + ".CellData.bytes");
cellOptionalDataFilename = GetOrCreateFileName(scenarios[scenario].cellOptionalDataAsset, "-" + scenario + ".CellOptionalData.bytes");
cellSharedDataFilename = GetOrCreateFileName(cellSharedDataAsset, ".CellSharedData.bytes");
cellSupportDataFilename = GetOrCreateFileName(cellSupportDataAsset, ".CellSupportData.bytes");
}
// Returns the file size in bytes
long GetFileSize(string path) => File.Exists(path) ? new FileInfo(path).Length : 0;
internal long GetDiskSizeOfSharedData()
{
return GetFileSize(GetAssetPathSafe(cellSharedDataAsset)) + GetFileSize(GetAssetPathSafe(cellSupportDataAsset));
}
internal long GetDiskSizeOfScenarioData(string scenario)
{
if (scenario == null || !scenarios.TryGetValue(scenario, out var data))
return 0;
return GetFileSize(GetAssetPathSafe(data.cellDataAsset)) + GetFileSize(GetAssetPathSafe(data.cellOptionalDataAsset));
}
///
/// Call this function during OnProcessScene to strip debug from project builds.
///
public void StripSupportData()
{
cellSupportDataAsset = null;
}
#endif
}
}