681 lines
30 KiB
C#
681 lines
30 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine.Experimental.Rendering.RenderGraphModule;
|
|
|
|
namespace UnityEngine.Rendering.HighDefinition
|
|
{
|
|
class HDCachedShadowAtlas : HDShadowAtlas
|
|
{
|
|
static private int s_InitialCapacity = 256;
|
|
|
|
// Constants.
|
|
private const int m_MinSlotSize = 64;
|
|
private const int m_MaxShadowsPerLight = 6;
|
|
|
|
|
|
private int m_NextLightID = 0;
|
|
private bool m_CanTryPlacement = false;
|
|
|
|
struct CachedShadowRecord
|
|
{
|
|
internal int shadowIndex;
|
|
internal int viewportSize; // We assume only square shadows maps.
|
|
internal Vector4 offsetInAtlas; // When is registered xy is the offset in the texture atlas, in UVs, the zw is the entry offset in the C# representation.
|
|
internal bool rendersOnPlacement;
|
|
}
|
|
|
|
// We need an extra struct to track differences in the light transform
|
|
// since we don't have such a callback (a-la invalidate) for those.
|
|
struct CachedTransform
|
|
{
|
|
internal Vector3 position;
|
|
internal Vector3 angles; // Only for area and spot
|
|
}
|
|
|
|
enum SlotValue : byte
|
|
{
|
|
Free,
|
|
Occupied,
|
|
TempOccupied // Used when checking if it will fit.
|
|
}
|
|
|
|
private int m_AtlasResolutionInSlots; // Atlas Resolution / m_MinSlotSize
|
|
|
|
private bool m_NeedOptimalPacking = true; // Whenever this is set to true, the pending lights are sorted before insertion.
|
|
|
|
private List<SlotValue> m_AtlasSlots; // One entry per slot (of size m_MinSlotSize) true if occupied, false if free.
|
|
|
|
// Note: Some of these could be simple lists, but since we might need to search by index some of them and we want to avoid GC alloc, a dictionary is easier.
|
|
// This also mean slightly worse performance, however hopefully the number of cached shadow lights is not huge at any tie.
|
|
private Dictionary<int, CachedShadowRecord> m_PlacedShadows;
|
|
private Dictionary<int, CachedShadowRecord> m_ShadowsPendingRendering;
|
|
private Dictionary<int, int> m_ShadowsWithValidData; // Shadows that have been placed and rendered at least once (OnDemand shadows are not rendered unless requested explicitly). It is a dictionary for fast access by shadow index.
|
|
private Dictionary<int, HDAdditionalLightData> m_RegisteredLightDataPendingPlacement;
|
|
private Dictionary<int, CachedShadowRecord> m_RecordsPendingPlacement; // Note: this is different from m_RegisteredLightDataPendingPlacement because it contains records that were allocated in the system
|
|
// but they lost their spot (e.g. post defrag). They don't have a light associated anymore if not by index, so we keep a separate collection.
|
|
|
|
private Dictionary<int, CachedTransform> m_TransformCaches;
|
|
private List<CachedShadowRecord> m_TempListForPlacement;
|
|
|
|
|
|
private ShadowMapType m_ShadowType;
|
|
|
|
// ------------------------------------------------------------------------------------------
|
|
// Init Functions
|
|
// ------------------------------------------------------------------------------------------
|
|
public HDCachedShadowAtlas(ShadowMapType type)
|
|
{
|
|
m_PlacedShadows = new Dictionary<int, CachedShadowRecord>(s_InitialCapacity);
|
|
m_ShadowsPendingRendering = new Dictionary<int, CachedShadowRecord>(s_InitialCapacity);
|
|
m_ShadowsWithValidData = new Dictionary<int, int>(s_InitialCapacity);
|
|
m_TempListForPlacement = new List<CachedShadowRecord>(s_InitialCapacity);
|
|
|
|
m_RegisteredLightDataPendingPlacement = new Dictionary<int, HDAdditionalLightData>(s_InitialCapacity);
|
|
m_RecordsPendingPlacement = new Dictionary<int, CachedShadowRecord>(s_InitialCapacity);
|
|
|
|
m_TransformCaches = new Dictionary<int, CachedTransform>(s_InitialCapacity / 2);
|
|
|
|
m_ShadowType = type;
|
|
}
|
|
|
|
public override void InitAtlas(HDShadowAtlasInitParameters atlasInitParams)
|
|
{
|
|
base.InitAtlas(atlasInitParams);
|
|
m_IsACacheForShadows = true;
|
|
|
|
m_AtlasResolutionInSlots = HDUtils.DivRoundUp(width, m_MinSlotSize);
|
|
m_AtlasSlots = new List<SlotValue>(m_AtlasResolutionInSlots * m_AtlasResolutionInSlots);
|
|
for (int i = 0; i < m_AtlasResolutionInSlots * m_AtlasResolutionInSlots; ++i)
|
|
{
|
|
m_AtlasSlots.Add(SlotValue.Free);
|
|
}
|
|
|
|
// Note: If changing the characteristics of the atlas via HDRP asset, the lights OnEnable will not be called again so we are missing them, however we can explicitly
|
|
// put them back up for placement. If this is the first Init of the atlas, the lines below do nothing.
|
|
DefragmentAtlasAndReRender(atlasInitParams.initParams);
|
|
m_CanTryPlacement = true;
|
|
m_NeedOptimalPacking = true;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------
|
|
|
|
// ------------------------------------------------------------------------------------------
|
|
// Functions for mixed cached shadows that need to live in cached atlas
|
|
// ------------------------------------------------------------------------------------------
|
|
|
|
public void AddBlitRequestsForUpdatedShadows(HDDynamicShadowAtlas dynamicAtlas)
|
|
{
|
|
foreach (var request in m_ShadowRequests)
|
|
{
|
|
if (request.shouldRenderCachedComponent) // meaning it has been updated this time frame
|
|
{
|
|
dynamicAtlas.AddRequestToPendingBlitFromCache(request);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------
|
|
// Functions to access and deal with the C# representation of the atlas
|
|
// ------------------------------------------------------------------------------------------
|
|
private bool IsEntryEmpty(int x, int y)
|
|
{
|
|
return (m_AtlasSlots[y * m_AtlasResolutionInSlots + x] == SlotValue.Free);
|
|
}
|
|
|
|
private bool IsEntryFull(int x, int y)
|
|
{
|
|
return (m_AtlasSlots[y * m_AtlasResolutionInSlots + x]) != SlotValue.Free;
|
|
}
|
|
|
|
private bool IsEntryTempOccupied(int x, int y)
|
|
{
|
|
return (m_AtlasSlots[y * m_AtlasResolutionInSlots + x]) == SlotValue.TempOccupied;
|
|
}
|
|
|
|
// Always fill slots in a square shape, for example : if x = 1 and y = 2, if numEntries = 2 it will fill {(1,2),(2,2),(1,3),(2,3)}
|
|
private void FillEntries(int x, int y, int numEntries)
|
|
{
|
|
MarkEntries(x, y, numEntries, SlotValue.Occupied);
|
|
}
|
|
|
|
private void MarkEntries(int x, int y, int numEntries, SlotValue value)
|
|
{
|
|
for (int j = y; j < y + numEntries; ++j)
|
|
{
|
|
for (int i = x; i < x + numEntries; ++i)
|
|
{
|
|
m_AtlasSlots[j * m_AtlasResolutionInSlots + i] = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Checks if we have a square slot available starting from (x,y) and of size numEntries.
|
|
private bool CheckSlotAvailability(int x, int y, int numEntries)
|
|
{
|
|
for (int j = y; j < y + numEntries; ++j)
|
|
{
|
|
for (int i = x; i < x + numEntries; ++i)
|
|
{
|
|
if (i >= m_AtlasResolutionInSlots || j >= m_AtlasResolutionInSlots || IsEntryFull(i, j))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
internal bool FindSlotInAtlas(int resolution, bool tempFill, out int x, out int y)
|
|
{
|
|
int numEntries = HDUtils.DivRoundUp(resolution, m_MinSlotSize);
|
|
for (int j = 0; j < m_AtlasResolutionInSlots; ++j)
|
|
{
|
|
for (int i = 0; i < m_AtlasResolutionInSlots; ++i)
|
|
{
|
|
if (CheckSlotAvailability(i, j, numEntries))
|
|
{
|
|
x = i;
|
|
y = j;
|
|
|
|
if (tempFill)
|
|
MarkEntries(x, y, numEntries, SlotValue.TempOccupied);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
x = 0;
|
|
y = 0;
|
|
return false;
|
|
}
|
|
|
|
internal void FreeTempFilled(int x, int y, int resolution)
|
|
{
|
|
int numEntries = HDUtils.DivRoundUp(resolution, m_MinSlotSize);
|
|
for (int j = y; j < y + numEntries; ++j)
|
|
{
|
|
for (int i = x; i < x + numEntries; ++i)
|
|
{
|
|
if (m_AtlasSlots[j * m_AtlasResolutionInSlots + i] == SlotValue.TempOccupied)
|
|
{
|
|
m_AtlasSlots[j * m_AtlasResolutionInSlots + i] = SlotValue.Free;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
internal bool FindSlotInAtlas(int resolution, out int x, out int y)
|
|
{
|
|
return FindSlotInAtlas(resolution, false, out x, out y);
|
|
}
|
|
|
|
internal bool GetSlotInAtlas(int resolution, out int x, out int y)
|
|
{
|
|
if (FindSlotInAtlas(resolution, out x, out y))
|
|
{
|
|
int numEntries = HDUtils.DivRoundUp(resolution, m_MinSlotSize);
|
|
FillEntries(x, y, numEntries);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
|
|
// ------------------------------------------------------------------------------------------
|
|
// Entry and exit points to the atlas
|
|
// ------------------------------------------------------------------------------------------
|
|
|
|
internal int GetNextLightIdentifier()
|
|
{
|
|
int outputId = m_NextLightID;
|
|
m_NextLightID += m_MaxShadowsPerLight; // We give unique identifiers to each
|
|
return outputId;
|
|
}
|
|
|
|
internal void RegisterLight(HDAdditionalLightData lightData)
|
|
{
|
|
// If we are trying to register something that we have already placed, we do nothing
|
|
if (lightData.lightIdxForCachedShadows >= 0 && m_PlacedShadows.ContainsKey(lightData.lightIdxForCachedShadows))
|
|
return;
|
|
|
|
// We register only if not already pending placement and if enabled.
|
|
if (!m_RegisteredLightDataPendingPlacement.ContainsKey(lightData.lightIdxForCachedShadows) && lightData.isActiveAndEnabled)
|
|
{
|
|
#if UNITY_2020_2_OR_NEWER
|
|
lightData.legacyLight.useViewFrustumForShadowCasterCull = false;
|
|
#endif
|
|
lightData.lightIdxForCachedShadows = GetNextLightIdentifier();
|
|
RegisterTransformCacheSlot(lightData);
|
|
m_RegisteredLightDataPendingPlacement.Add(lightData.lightIdxForCachedShadows, lightData);
|
|
m_CanTryPlacement = true;
|
|
}
|
|
}
|
|
|
|
internal void EvictLight(HDAdditionalLightData lightData)
|
|
{
|
|
m_RegisteredLightDataPendingPlacement.Remove(lightData.lightIdxForCachedShadows);
|
|
RemoveTransformFromCache(lightData);
|
|
|
|
int numberOfShadows = (lightData.type == HDLightType.Point) ? 6 : 1;
|
|
|
|
int lightIdx = lightData.lightIdxForCachedShadows;
|
|
lightData.lightIdxForCachedShadows = -1;
|
|
|
|
for (int i = 0; i < numberOfShadows; ++i)
|
|
{
|
|
bool valueFound = false;
|
|
int shadowIdx = lightIdx + i;
|
|
|
|
m_RecordsPendingPlacement.Remove(shadowIdx);
|
|
|
|
valueFound = m_PlacedShadows.TryGetValue(shadowIdx, out CachedShadowRecord recordToRemove);
|
|
|
|
if (valueFound)
|
|
{
|
|
#if UNITY_2020_2_OR_NEWER
|
|
lightData.legacyLight.useViewFrustumForShadowCasterCull = true;
|
|
#endif
|
|
m_PlacedShadows.Remove(shadowIdx);
|
|
m_ShadowsPendingRendering.Remove(shadowIdx);
|
|
m_ShadowsWithValidData.Remove(shadowIdx);
|
|
|
|
MarkEntries((int)recordToRemove.offsetInAtlas.z, (int)recordToRemove.offsetInAtlas.w, HDUtils.DivRoundUp(recordToRemove.viewportSize, m_MinSlotSize), SlotValue.Free);
|
|
m_CanTryPlacement = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void RegisterTransformCacheSlot(HDAdditionalLightData lightData)
|
|
{
|
|
if (lightData.lightIdxForCachedShadows >= 0 && lightData.updateUponLightMovement && !m_TransformCaches.ContainsKey(lightData.lightIdxForCachedShadows))
|
|
{
|
|
CachedTransform transform;
|
|
transform.position = lightData.transform.position;
|
|
transform.angles = lightData.transform.eulerAngles;
|
|
m_TransformCaches.Add(lightData.lightIdxForCachedShadows, transform);
|
|
}
|
|
}
|
|
|
|
internal void RemoveTransformFromCache(HDAdditionalLightData lightData)
|
|
{
|
|
m_TransformCaches.Remove(lightData.lightIdxForCachedShadows);
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------
|
|
|
|
|
|
// ------------------------------------------------------------------------------------------
|
|
// Atlassing on the actual textures
|
|
// ------------------------------------------------------------------------------------------
|
|
|
|
|
|
void InsertionSort(ref List<CachedShadowRecord> list, int startIndex, int lastIndex)
|
|
{
|
|
int i = startIndex;
|
|
|
|
while (i < lastIndex)
|
|
{
|
|
var curr = list[i];
|
|
|
|
int j = i - 1;
|
|
|
|
// Sort in descending order.
|
|
while ((j >= 0) && ((curr.viewportSize > list[j].viewportSize)))
|
|
{
|
|
list[j + 1] = list[j];
|
|
j--;
|
|
}
|
|
|
|
list[j + 1] = curr;
|
|
i++;
|
|
}
|
|
}
|
|
|
|
private void AddLightListToRecordList(Dictionary<int, HDAdditionalLightData> lightList, HDShadowInitParameters initParams, ref List<CachedShadowRecord> recordList)
|
|
{
|
|
foreach (var currentLightData in lightList.Values)
|
|
{
|
|
int resolution = 0;
|
|
|
|
resolution = currentLightData.GetResolutionFromSettings(m_ShadowType, initParams);
|
|
|
|
HDLightType lightType = currentLightData.type;
|
|
int numberOfShadows = (lightType == HDLightType.Point) ? 6 : 1;
|
|
|
|
for (int i = 0; i < numberOfShadows; ++i)
|
|
{
|
|
CachedShadowRecord record;
|
|
record.shadowIndex = currentLightData.lightIdxForCachedShadows + i;
|
|
record.viewportSize = resolution;
|
|
record.offsetInAtlas = new Vector4(-1, -1, -1, -1); // Will be set later.
|
|
// Only situation in which we allow not to render on placement if it is OnDemand and onDomandShadowRenderOnPlacement is false
|
|
record.rendersOnPlacement = (currentLightData.shadowUpdateMode == ShadowUpdateMode.OnDemand) ? (currentLightData.forceRenderOnPlacement || currentLightData.onDomandShadowRenderOnPlacement) : true;
|
|
currentLightData.forceRenderOnPlacement = false; // reset the force flag as we scheduled the rendering forcefully already.
|
|
recordList.Add(record);
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool PlaceMultipleShadows(int startIdx, int numberOfShadows)
|
|
{
|
|
int firstShadowIdx = m_TempListForPlacement[startIdx].shadowIndex;
|
|
|
|
Vector2Int[] placements = new Vector2Int[m_MaxShadowsPerLight];
|
|
int successfullyPlaced = 0;
|
|
|
|
for (int j = 0; j < numberOfShadows; ++j)
|
|
{
|
|
var record = m_TempListForPlacement[startIdx + j];
|
|
|
|
Debug.Assert(firstShadowIdx + j == record.shadowIndex);
|
|
|
|
int x, y;
|
|
if (GetSlotInAtlas(record.viewportSize, out x, out y))
|
|
{
|
|
successfullyPlaced++;
|
|
placements[j] = new Vector2Int(x, y);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If they all fit, we actually placed them, otherwise we mark the slot that we temp filled as free and go on.
|
|
if (successfullyPlaced == numberOfShadows) // Success.
|
|
{
|
|
for (int j = 0; j < numberOfShadows; ++j)
|
|
{
|
|
var record = m_TempListForPlacement[startIdx + j];
|
|
|
|
record.offsetInAtlas = new Vector4(placements[j].x * m_MinSlotSize, placements[j].y * m_MinSlotSize, placements[j].x, placements[j].y);
|
|
|
|
if (record.rendersOnPlacement)
|
|
{
|
|
m_ShadowsPendingRendering.Add(record.shadowIndex, record);
|
|
}
|
|
m_PlacedShadows.Add(record.shadowIndex, record);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else if (successfullyPlaced > 0) // Couldn't place them all, but we placed something, so we revert those placements.
|
|
{
|
|
int numEntries = HDUtils.DivRoundUp(m_TempListForPlacement[startIdx].viewportSize, m_MinSlotSize);
|
|
for (int j = 0; j < successfullyPlaced; ++j)
|
|
{
|
|
MarkEntries(placements[j].x, placements[j].y, numEntries, SlotValue.Free);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private void PerformPlacement()
|
|
{
|
|
for (int i = 0; i < m_TempListForPlacement.Count;)
|
|
{
|
|
int x, y;
|
|
var record = m_TempListForPlacement[i];
|
|
|
|
// Since each light gets its index += m_MaxShadowsPerLight, if we have a non %6 == 0, it means it is a shadow from a light with mulitple shadows
|
|
bool isFirstOfASeries = (record.shadowIndex % m_MaxShadowsPerLight == 0) && ((i + 1) < m_TempListForPlacement.Count) && (m_TempListForPlacement[i + 1].shadowIndex % m_MaxShadowsPerLight != 0);
|
|
|
|
// NOTE: We assume that if we have a series of shadows, we have six of them! If it is not the case anymore this code should be updated
|
|
// (likely the record should contain how many shadows are associated).
|
|
if (isFirstOfASeries)
|
|
{
|
|
if (PlaceMultipleShadows(i, m_MaxShadowsPerLight))
|
|
{
|
|
m_RegisteredLightDataPendingPlacement.Remove(record.shadowIndex); // We placed all the shadows of the light, hence we can remove the light from pending placement.
|
|
for (int subIdx = 0; subIdx < m_MaxShadowsPerLight; ++subIdx)
|
|
{
|
|
m_RecordsPendingPlacement.Remove(record.shadowIndex + subIdx);
|
|
}
|
|
}
|
|
|
|
i += m_MaxShadowsPerLight; // We will not need to process depending shadows.
|
|
}
|
|
else // We have only one shadow to place.
|
|
{
|
|
bool fit = GetSlotInAtlas(record.viewportSize, out x, out y);
|
|
if (fit)
|
|
{
|
|
// Convert offset to atlas offset.
|
|
record.offsetInAtlas = new Vector4(x * m_MinSlotSize, y * m_MinSlotSize, x, y);
|
|
|
|
if (record.rendersOnPlacement)
|
|
{
|
|
m_ShadowsPendingRendering.Add(record.shadowIndex, record);
|
|
}
|
|
m_PlacedShadows.Add(record.shadowIndex, record);
|
|
m_RegisteredLightDataPendingPlacement.Remove(record.shadowIndex);
|
|
m_RecordsPendingPlacement.Remove(record.shadowIndex);
|
|
}
|
|
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// This is the external api to say: do the placement if needed.
|
|
// Also, we assign the resolutions here since we didn't know about HDShadowInitParameters during OnEnable of the light.
|
|
internal void AssignOffsetsInAtlas(HDShadowInitParameters initParameters)
|
|
{
|
|
if (m_RegisteredLightDataPendingPlacement.Count > 0 && m_CanTryPlacement)
|
|
{
|
|
m_TempListForPlacement.Clear();
|
|
|
|
m_TempListForPlacement.AddRange(m_RecordsPendingPlacement.Values);
|
|
AddLightListToRecordList(m_RegisteredLightDataPendingPlacement, initParameters, ref m_TempListForPlacement);
|
|
|
|
if (m_NeedOptimalPacking)
|
|
{
|
|
InsertionSort(ref m_TempListForPlacement, 0, m_TempListForPlacement.Count);
|
|
m_NeedOptimalPacking = false;
|
|
}
|
|
|
|
PerformPlacement();
|
|
|
|
m_CanTryPlacement = false; // It is pointless we try the placement every frame if no modifications to the amount of light registered happened.
|
|
}
|
|
}
|
|
|
|
internal void DefragmentAtlasAndReRender(HDShadowInitParameters initParams)
|
|
{
|
|
m_TempListForPlacement.Clear();
|
|
|
|
m_TempListForPlacement.AddRange(m_PlacedShadows.Values);
|
|
m_TempListForPlacement.AddRange(m_RecordsPendingPlacement.Values);
|
|
AddLightListToRecordList(m_RegisteredLightDataPendingPlacement, initParams, ref m_TempListForPlacement);
|
|
|
|
for (int i = 0; i < m_AtlasResolutionInSlots * m_AtlasResolutionInSlots; ++i)
|
|
{
|
|
m_AtlasSlots[i] = SlotValue.Free;
|
|
}
|
|
|
|
// Clear the other state lists.
|
|
m_PlacedShadows.Clear();
|
|
m_ShadowsPendingRendering.Clear();
|
|
m_ShadowsWithValidData.Clear();
|
|
m_RecordsPendingPlacement.Clear(); // We'll reset what records are pending.
|
|
|
|
// Sort in order to obtain a more optimal packing.
|
|
InsertionSort(ref m_TempListForPlacement, 0, m_TempListForPlacement.Count);
|
|
|
|
PerformPlacement();
|
|
|
|
// This is fairly inefficient, but simple and this function should be called very rarely.
|
|
// We need to add to pending the records that were placed but were not in m_RegisteredLightDataPendingPlacement
|
|
// but they don't have a place yet.
|
|
foreach (var record in m_TempListForPlacement)
|
|
{
|
|
if (!m_PlacedShadows.ContainsKey(record.shadowIndex)) // If we couldn't place it
|
|
{
|
|
int parentLightIdx = record.shadowIndex - (record.shadowIndex % m_MaxShadowsPerLight);
|
|
if (!m_RegisteredLightDataPendingPlacement.ContainsKey(parentLightIdx)) // Did not come originally from m_RegisteredLightDataPendingPlacement
|
|
{
|
|
if (!m_RecordsPendingPlacement.ContainsKey(record.shadowIndex))
|
|
m_RecordsPendingPlacement.Add(record.shadowIndex, record);
|
|
}
|
|
}
|
|
}
|
|
|
|
m_CanTryPlacement = false;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------
|
|
|
|
|
|
// ------------------------------------------------------------------------------------------
|
|
// Functions to query and change state of a shadow
|
|
// ------------------------------------------------------------------------------------------
|
|
internal bool LightIsPendingPlacement(HDAdditionalLightData lightData)
|
|
{
|
|
return (m_RegisteredLightDataPendingPlacement.ContainsKey(lightData.lightIdxForCachedShadows) ||
|
|
m_RecordsPendingPlacement.ContainsKey(lightData.lightIdxForCachedShadows));
|
|
}
|
|
|
|
internal bool ShadowIsPendingRendering(int shadowIdx)
|
|
{
|
|
return m_ShadowsPendingRendering.ContainsKey(shadowIdx);
|
|
}
|
|
|
|
internal bool ShadowHasRenderedAtLeastOnce(int shadowIdx)
|
|
{
|
|
return m_ShadowsWithValidData.ContainsKey(shadowIdx);
|
|
}
|
|
|
|
internal bool FullLightShadowHasRenderedAtLeastOnce(HDAdditionalLightData lightData)
|
|
{
|
|
int cachedShadowIdx = lightData.lightIdxForCachedShadows;
|
|
if (lightData.type == HDLightType.Point)
|
|
{
|
|
bool allRendered = true;
|
|
for (int i = 0; i < 6; ++i)
|
|
{
|
|
allRendered = allRendered && m_ShadowsWithValidData.ContainsKey(cachedShadowIdx + i);
|
|
}
|
|
|
|
return allRendered;
|
|
}
|
|
return m_ShadowsWithValidData.ContainsKey(cachedShadowIdx);
|
|
}
|
|
|
|
internal bool LightIsPlaced(HDAdditionalLightData lightData)
|
|
{
|
|
int cachedShadowIdx = lightData.lightIdxForCachedShadows;
|
|
return cachedShadowIdx >= 0 && m_PlacedShadows.ContainsKey(cachedShadowIdx);
|
|
}
|
|
|
|
internal void ScheduleShadowUpdate(HDAdditionalLightData lightData)
|
|
{
|
|
if (!lightData.isActiveAndEnabled) return;
|
|
|
|
int lightIdx = lightData.lightIdxForCachedShadows;
|
|
Debug.Assert(lightIdx >= 0);
|
|
|
|
if (!m_PlacedShadows.ContainsKey(lightIdx))
|
|
{
|
|
if (m_RegisteredLightDataPendingPlacement.ContainsKey(lightIdx))
|
|
return;
|
|
|
|
lightData.forceRenderOnPlacement = true;
|
|
RegisterLight(lightData);
|
|
}
|
|
else
|
|
{
|
|
int numberOfShadows = (lightData.type == HDLightType.Point) ? 6 : 1;
|
|
for (int i = 0; i < numberOfShadows; ++i)
|
|
{
|
|
int shadowIdx = lightIdx + i;
|
|
ScheduleShadowUpdate(shadowIdx);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void ScheduleShadowUpdate(int shadowIdx)
|
|
{
|
|
// It needs to be placed already, otherwise needs to go through the registering cycle first.
|
|
CachedShadowRecord shadowRecord;
|
|
bool recordFound = m_PlacedShadows.TryGetValue(shadowIdx, out shadowRecord);
|
|
Debug.Assert(recordFound);
|
|
// Return to avoid error when assert is skipped.
|
|
if (!recordFound) return;
|
|
|
|
// It already schedule for update we do nothing;
|
|
if (m_ShadowsPendingRendering.ContainsKey(shadowIdx))
|
|
return;
|
|
|
|
// Put the record up for rendering
|
|
m_ShadowsPendingRendering.Add(shadowIdx, shadowRecord);
|
|
}
|
|
|
|
internal void MarkAsRendered(int shadowIdx)
|
|
{
|
|
if (m_ShadowsPendingRendering.ContainsKey(shadowIdx))
|
|
{
|
|
m_ShadowsPendingRendering.Remove(shadowIdx);
|
|
if (!m_ShadowsWithValidData.ContainsKey(shadowIdx))
|
|
m_ShadowsWithValidData.Add(shadowIdx, shadowIdx);
|
|
}
|
|
}
|
|
|
|
// Used to update the resolution request processed by the light loop
|
|
internal void UpdateResolutionRequest(ref HDShadowResolutionRequest request, int shadowIdx)
|
|
{
|
|
CachedShadowRecord record;
|
|
bool valueFound = m_PlacedShadows.TryGetValue(shadowIdx, out record);
|
|
|
|
if (!valueFound)
|
|
{
|
|
Debug.LogWarning("Trying to render a cached shadow map that doesn't have a slot in the atlas yet.");
|
|
}
|
|
|
|
request.cachedAtlasViewport = new Rect(record.offsetInAtlas.x, record.offsetInAtlas.y, record.viewportSize, record.viewportSize);
|
|
request.resolution = new Vector2(record.viewportSize, record.viewportSize);
|
|
}
|
|
|
|
internal bool NeedRenderingDueToTransformChange(HDAdditionalLightData lightData, HDLightType lightType)
|
|
{
|
|
bool needUpdate = false;
|
|
|
|
if (m_TransformCaches.TryGetValue(lightData.lightIdxForCachedShadows, out CachedTransform cachedTransform))
|
|
{
|
|
float positionThreshold = lightData.cachedShadowTranslationUpdateThreshold;
|
|
Vector3 positionDiffVec = cachedTransform.position - lightData.transform.position;
|
|
float positionDiff = Vector3.Dot(positionDiffVec, positionDiffVec);
|
|
if (positionDiff > positionThreshold * positionThreshold)
|
|
{
|
|
needUpdate = true;
|
|
}
|
|
if (lightType != HDLightType.Point)
|
|
{
|
|
float angleDiffThreshold = lightData.cachedShadowAngleUpdateThreshold;
|
|
Vector3 angleDiff = cachedTransform.angles - lightData.transform.eulerAngles;
|
|
// Any angle difference
|
|
if (Mathf.Abs(angleDiff.x) > angleDiffThreshold || Mathf.Abs(angleDiff.y) > angleDiffThreshold || Mathf.Abs(angleDiff.z) > angleDiffThreshold)
|
|
{
|
|
needUpdate = true;
|
|
}
|
|
}
|
|
|
|
if (needUpdate)
|
|
{
|
|
// Update the record (CachedTransform is a struct, so we remove old one and replace with a new one)
|
|
m_TransformCaches.Remove(lightData.lightIdxForCachedShadows);
|
|
cachedTransform.position = lightData.transform.position;
|
|
cachedTransform.angles = lightData.transform.eulerAngles;
|
|
m_TransformCaches.Add(lightData.lightIdxForCachedShadows, cachedTransform);
|
|
}
|
|
}
|
|
|
|
return needUpdate;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------
|
|
}
|
|
}
|