Skip to content

Commit

Permalink
Add new API to CachedShadowManager and fix WouldFitInAtlas (#3387)
Browse files Browse the repository at this point in the history
* Fix WillFitAtlas + add API entry with light data itself.

* Light is placed API

* Changelog

* Placed and rendered API

* Fix assert

* Doc update

Co-authored-by: sebastienlagarde <sebastien@unity3d.com>
  • Loading branch information
FrancescoC-unity and sebastienlagarde authored Feb 22, 2021
1 parent ead6703 commit e7bc79e
Show file tree
Hide file tree
Showing 4 changed files with 213 additions and 14 deletions.
3 changes: 3 additions & 0 deletions com.unity.render-pipelines.high-definition/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Added browsing of the documentation of Compositor Window
- Added a complete solution for volumetric clouds for HDRP including a cloud map generation tool.
- Added a Force Forward Emissive option for Lit Material that forces the Emissive contribution to render in a separate forward pass when the Lit Material is in Deferred Lit shader Mode.
- Added new API in CachedShadowManager

### Fixed
- Fixed an exception when opening the color picker in the material UI (case 1307143).
Expand Down Expand Up @@ -47,6 +48,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Fixed sub-shadow rendering for cached shadow maps.
- Fixed PCSS filtering issues with cached shadow maps.

- Fixed WouldFitInAtlas that would previously return wrong results if any one face of a point light would fit (it used to return true even though the light in entirety wouldn't fit).

### Changed
- Changed Window/Render Pipeline/HD Render Pipeline Wizard to Window/Rendering/HDRP Wizard
- Removed the material pass probe volumes evaluation mode.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ If the shadow atlas is full when a Light requests a spot, the cached shadow mana
After a Scene loads with all the already placed Lights, if you add a new Light with cached shadows to the Scene, HDRP tries to place it in order to fill the holes in the atlas. However, depending on the order of insertion, the atlas may be fragmented and the holes available are not enough to place the Light's shadow map in. In this case, you can defragment the atlas to allow for additional Lights. To do this, pass the target atlas into the following function: `HDCachedShadowManager.instance.DefragAtlas`
Note that this causes HDRP to mark all the shadow maps in the atlas as dirty which means HDRP renders them the moment their parent Light becomes visible.

It is possible to check if a light has its shadow maps has a placement in the cached shadow atlas `HDCachedShadowManager.instance.LightHasBeenPlacedInAtlas` and if it has been placed and rendered at least once with `HDCachedShadowManager.instance.LightHasBeenPlaceAndRenderedAtLeastOnce`.

### Preserving shadow atlas placement

If you disable the Light or change its **Update Mode** to **Every Frame**, the cached shadow manager unreserves the Light's shadow map's space in the cached shadow atlas and HDRP begins to render the Light's shadow map to the normal shadow atlases every frame. If the cached shadow manager needs to allocate space on the atlas for another Light, it can overwrite the space currently taken up by the original Light's shadow map.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,24 @@ struct CachedTransform
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<bool> m_AtlasSlots; // One entry per slot (of size m_MinSlotSize) true if occupied, false if free.
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.
Expand All @@ -57,6 +65,7 @@ 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);
Expand All @@ -73,10 +82,10 @@ public override void InitAtlas(RenderPipelineResources renderPipelineResources,
m_IsACacheForShadows = true;

m_AtlasResolutionInSlots = HDUtils.DivRoundUp(width, m_MinSlotSize);
m_AtlasSlots = new List<bool>(m_AtlasResolutionInSlots * m_AtlasResolutionInSlots);
m_AtlasSlots = new List<SlotValue>(m_AtlasResolutionInSlots * m_AtlasResolutionInSlots);
for (int i = 0; i < m_AtlasResolutionInSlots * m_AtlasResolutionInSlots; ++i)
{
m_AtlasSlots.Add(false);
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
Expand Down Expand Up @@ -108,21 +117,26 @@ public void AddBlitRequestsForUpdatedShadows(HDDynamicShadowAtlas dynamicAtlas)
// ------------------------------------------------------------------------------------------
private bool IsEntryEmpty(int x, int y)
{
return (m_AtlasSlots[y * m_AtlasResolutionInSlots + x] == false);
return (m_AtlasSlots[y * m_AtlasResolutionInSlots + x] == SlotValue.Free);
}

private bool IsEntryFull(int x, int y)
{
return (m_AtlasSlots[y * m_AtlasResolutionInSlots + x]);
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, true);
MarkEntries(x, y, numEntries, SlotValue.Occupied);
}

private void MarkEntries(int x, int y, int numEntries, bool value)
private void MarkEntries(int x, int y, int numEntries, SlotValue value)
{
for (int j = y; j < y + numEntries; ++j)
{
Expand All @@ -149,10 +163,9 @@ private bool CheckSlotAvailability(int x, int y, int numEntries)
return true;
}

internal bool FindSlotInAtlas(int resolution, out int x, out int y)
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)
Expand All @@ -161,6 +174,10 @@ internal bool FindSlotInAtlas(int resolution, out int x, out int y)
{
x = i;
y = j;

if (tempFill)
MarkEntries(x, y, numEntries, SlotValue.TempOccupied);

return true;
}
}
Expand All @@ -171,6 +188,26 @@ internal bool FindSlotInAtlas(int resolution, out int x, out int y)
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))
Expand Down Expand Up @@ -241,8 +278,9 @@ internal void EvictLight(HDAdditionalLightData lightData)
#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), false);
MarkEntries((int)recordToRemove.offsetInAtlas.z, (int)recordToRemove.offsetInAtlas.w, HDUtils.DivRoundUp(recordToRemove.viewportSize, m_MinSlotSize), SlotValue.Free);
m_CanTryPlacement = true;
}
}
Expand Down Expand Up @@ -362,7 +400,7 @@ private bool PlaceMultipleShadows(int startIdx, int numberOfShadows)
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, false);
MarkEntries(placements[j].x, placements[j].y, numEntries, SlotValue.Free);
}
}

Expand Down Expand Up @@ -446,12 +484,13 @@ internal void DefragmentAtlasAndReRender(HDShadowInitParameters initParams)

for (int i = 0; i < m_AtlasResolutionInSlots * m_AtlasResolutionInSlots; ++i)
{
m_AtlasSlots[i] = false;
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.
Expand Down Expand Up @@ -495,6 +534,33 @@ 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;
Expand Down Expand Up @@ -542,6 +608,7 @@ internal void MarkAsRendered(int shadowIdx)
if (m_ShadowsPendingRendering.ContainsKey(shadowIdx))
{
m_ShadowsPendingRendering.Remove(shadowIdx);
m_ShadowsWithValidData.Add(shadowIdx, shadowIdx);
}
}

Expand Down
Loading

0 comments on commit e7bc79e

Please sign in to comment.