Skip to content

Commit e7bc79e

Browse files
Add new API to CachedShadowManager and fix WouldFitInAtlas (#3387)
* 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>
1 parent ead6703 commit e7bc79e

File tree

4 files changed

+213
-14
lines changed

4 files changed

+213
-14
lines changed

com.unity.render-pipelines.high-definition/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1919
- Added browsing of the documentation of Compositor Window
2020
- Added a complete solution for volumetric clouds for HDRP including a cloud map generation tool.
2121
- 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.
22+
- Added new API in CachedShadowManager
2223

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

51+
- 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).
52+
5053
### Changed
5154
- Changed Window/Render Pipeline/HD Render Pipeline Wizard to Window/Rendering/HDRP Wizard
5255
- Removed the material pass probe volumes evaluation mode.

com.unity.render-pipelines.high-definition/Documentation~/Shadows-in-HDRP.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ If the shadow atlas is full when a Light requests a spot, the cached shadow mana
132132
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`
133133
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.
134134

135+
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`.
136+
135137
### Preserving shadow atlas placement
136138

137139
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.

com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDCachedShadowAtlas.cs

Lines changed: 79 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,24 @@ struct CachedTransform
3030
internal Vector3 angles; // Only for area and spot
3131
}
3232

33+
enum SlotValue : byte
34+
{
35+
Free,
36+
Occupied,
37+
TempOccupied // Used when checking if it will fit.
38+
}
39+
3340
private int m_AtlasResolutionInSlots; // Atlas Resolution / m_MinSlotSize
3441

3542
private bool m_NeedOptimalPacking = true; // Whenever this is set to true, the pending lights are sorted before insertion.
3643

37-
private List<bool> m_AtlasSlots; // One entry per slot (of size m_MinSlotSize) true if occupied, false if free.
44+
private List<SlotValue> m_AtlasSlots; // One entry per slot (of size m_MinSlotSize) true if occupied, false if free.
3845

3946
// 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.
4047
// This also mean slightly worse performance, however hopefully the number of cached shadow lights is not huge at any tie.
4148
private Dictionary<int, CachedShadowRecord> m_PlacedShadows;
4249
private Dictionary<int, CachedShadowRecord> m_ShadowsPendingRendering;
50+
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.
4351
private Dictionary<int, HDAdditionalLightData> m_RegisteredLightDataPendingPlacement;
4452
private Dictionary<int, CachedShadowRecord> m_RecordsPendingPlacement; // Note: this is different from m_RegisteredLightDataPendingPlacement because it contains records that were allocated in the system
4553
// 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.
@@ -57,6 +65,7 @@ public HDCachedShadowAtlas(ShadowMapType type)
5765
{
5866
m_PlacedShadows = new Dictionary<int, CachedShadowRecord>(s_InitialCapacity);
5967
m_ShadowsPendingRendering = new Dictionary<int, CachedShadowRecord>(s_InitialCapacity);
68+
m_ShadowsWithValidData = new Dictionary<int, int>(s_InitialCapacity);
6069
m_TempListForPlacement = new List<CachedShadowRecord>(s_InitialCapacity);
6170

6271
m_RegisteredLightDataPendingPlacement = new Dictionary<int, HDAdditionalLightData>(s_InitialCapacity);
@@ -73,10 +82,10 @@ public override void InitAtlas(RenderPipelineResources renderPipelineResources,
7382
m_IsACacheForShadows = true;
7483

7584
m_AtlasResolutionInSlots = HDUtils.DivRoundUp(width, m_MinSlotSize);
76-
m_AtlasSlots = new List<bool>(m_AtlasResolutionInSlots * m_AtlasResolutionInSlots);
85+
m_AtlasSlots = new List<SlotValue>(m_AtlasResolutionInSlots * m_AtlasResolutionInSlots);
7786
for (int i = 0; i < m_AtlasResolutionInSlots * m_AtlasResolutionInSlots; ++i)
7887
{
79-
m_AtlasSlots.Add(false);
88+
m_AtlasSlots.Add(SlotValue.Free);
8089
}
8190

8291
// 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
@@ -108,21 +117,26 @@ public void AddBlitRequestsForUpdatedShadows(HDDynamicShadowAtlas dynamicAtlas)
108117
// ------------------------------------------------------------------------------------------
109118
private bool IsEntryEmpty(int x, int y)
110119
{
111-
return (m_AtlasSlots[y * m_AtlasResolutionInSlots + x] == false);
120+
return (m_AtlasSlots[y * m_AtlasResolutionInSlots + x] == SlotValue.Free);
112121
}
113122

114123
private bool IsEntryFull(int x, int y)
115124
{
116-
return (m_AtlasSlots[y * m_AtlasResolutionInSlots + x]);
125+
return (m_AtlasSlots[y * m_AtlasResolutionInSlots + x]) != SlotValue.Free;
126+
}
127+
128+
private bool IsEntryTempOccupied(int x, int y)
129+
{
130+
return (m_AtlasSlots[y * m_AtlasResolutionInSlots + x]) == SlotValue.TempOccupied;
117131
}
118132

119133
// 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)}
120134
private void FillEntries(int x, int y, int numEntries)
121135
{
122-
MarkEntries(x, y, numEntries, true);
136+
MarkEntries(x, y, numEntries, SlotValue.Occupied);
123137
}
124138

125-
private void MarkEntries(int x, int y, int numEntries, bool value)
139+
private void MarkEntries(int x, int y, int numEntries, SlotValue value)
126140
{
127141
for (int j = y; j < y + numEntries; ++j)
128142
{
@@ -149,10 +163,9 @@ private bool CheckSlotAvailability(int x, int y, int numEntries)
149163
return true;
150164
}
151165

152-
internal bool FindSlotInAtlas(int resolution, out int x, out int y)
166+
internal bool FindSlotInAtlas(int resolution, bool tempFill, out int x, out int y)
153167
{
154168
int numEntries = HDUtils.DivRoundUp(resolution, m_MinSlotSize);
155-
156169
for (int j = 0; j < m_AtlasResolutionInSlots; ++j)
157170
{
158171
for (int i = 0; i < m_AtlasResolutionInSlots; ++i)
@@ -161,6 +174,10 @@ internal bool FindSlotInAtlas(int resolution, out int x, out int y)
161174
{
162175
x = i;
163176
y = j;
177+
178+
if (tempFill)
179+
MarkEntries(x, y, numEntries, SlotValue.TempOccupied);
180+
164181
return true;
165182
}
166183
}
@@ -171,6 +188,26 @@ internal bool FindSlotInAtlas(int resolution, out int x, out int y)
171188
return false;
172189
}
173190

191+
internal void FreeTempFilled(int x, int y, int resolution)
192+
{
193+
int numEntries = HDUtils.DivRoundUp(resolution, m_MinSlotSize);
194+
for (int j = y; j < y + numEntries; ++j)
195+
{
196+
for (int i = x; i < x + numEntries; ++i)
197+
{
198+
if (m_AtlasSlots[j * m_AtlasResolutionInSlots + i] == SlotValue.TempOccupied)
199+
{
200+
m_AtlasSlots[j * m_AtlasResolutionInSlots + i] = SlotValue.Free;
201+
}
202+
}
203+
}
204+
}
205+
206+
internal bool FindSlotInAtlas(int resolution, out int x, out int y)
207+
{
208+
return FindSlotInAtlas(resolution, false, out x, out y);
209+
}
210+
174211
internal bool GetSlotInAtlas(int resolution, out int x, out int y)
175212
{
176213
if (FindSlotInAtlas(resolution, out x, out y))
@@ -241,8 +278,9 @@ internal void EvictLight(HDAdditionalLightData lightData)
241278
#endif
242279
m_PlacedShadows.Remove(shadowIdx);
243280
m_ShadowsPendingRendering.Remove(shadowIdx);
281+
m_ShadowsWithValidData.Remove(shadowIdx);
244282

245-
MarkEntries((int)recordToRemove.offsetInAtlas.z, (int)recordToRemove.offsetInAtlas.w, HDUtils.DivRoundUp(recordToRemove.viewportSize, m_MinSlotSize), false);
283+
MarkEntries((int)recordToRemove.offsetInAtlas.z, (int)recordToRemove.offsetInAtlas.w, HDUtils.DivRoundUp(recordToRemove.viewportSize, m_MinSlotSize), SlotValue.Free);
246284
m_CanTryPlacement = true;
247285
}
248286
}
@@ -362,7 +400,7 @@ private bool PlaceMultipleShadows(int startIdx, int numberOfShadows)
362400
int numEntries = HDUtils.DivRoundUp(m_TempListForPlacement[startIdx].viewportSize, m_MinSlotSize);
363401
for (int j = 0; j < successfullyPlaced; ++j)
364402
{
365-
MarkEntries(placements[j].x, placements[j].y, numEntries, false);
403+
MarkEntries(placements[j].x, placements[j].y, numEntries, SlotValue.Free);
366404
}
367405
}
368406

@@ -446,12 +484,13 @@ internal void DefragmentAtlasAndReRender(HDShadowInitParameters initParams)
446484

447485
for (int i = 0; i < m_AtlasResolutionInSlots * m_AtlasResolutionInSlots; ++i)
448486
{
449-
m_AtlasSlots[i] = false;
487+
m_AtlasSlots[i] = SlotValue.Free;
450488
}
451489

452490
// Clear the other state lists.
453491
m_PlacedShadows.Clear();
454492
m_ShadowsPendingRendering.Clear();
493+
m_ShadowsWithValidData.Clear();
455494
m_RecordsPendingPlacement.Clear(); // We'll reset what records are pending.
456495

457496
// Sort in order to obtain a more optimal packing.
@@ -495,6 +534,33 @@ internal bool ShadowIsPendingRendering(int shadowIdx)
495534
return m_ShadowsPendingRendering.ContainsKey(shadowIdx);
496535
}
497536

537+
internal bool ShadowHasRenderedAtLeastOnce(int shadowIdx)
538+
{
539+
return m_ShadowsWithValidData.ContainsKey(shadowIdx);
540+
}
541+
542+
internal bool FullLightShadowHasRenderedAtLeastOnce(HDAdditionalLightData lightData)
543+
{
544+
int cachedShadowIdx = lightData.lightIdxForCachedShadows;
545+
if (lightData.type == HDLightType.Point)
546+
{
547+
bool allRendered = true;
548+
for (int i = 0; i < 6; ++i)
549+
{
550+
allRendered = allRendered && m_ShadowsWithValidData.ContainsKey(cachedShadowIdx + i);
551+
}
552+
553+
return allRendered;
554+
}
555+
return m_ShadowsWithValidData.ContainsKey(cachedShadowIdx);
556+
}
557+
558+
internal bool LightIsPlaced(HDAdditionalLightData lightData)
559+
{
560+
int cachedShadowIdx = lightData.lightIdxForCachedShadows;
561+
return cachedShadowIdx >= 0 && m_PlacedShadows.ContainsKey(cachedShadowIdx);
562+
}
563+
498564
internal void ScheduleShadowUpdate(HDAdditionalLightData lightData)
499565
{
500566
if (!lightData.isActiveAndEnabled) return;
@@ -542,6 +608,7 @@ internal void MarkAsRendered(int shadowIdx)
542608
if (m_ShadowsPendingRendering.ContainsKey(shadowIdx))
543609
{
544610
m_ShadowsPendingRendering.Remove(shadowIdx);
611+
m_ShadowsWithValidData.Add(shadowIdx, shadowIdx);
545612
}
546613
}
547614

0 commit comments

Comments
 (0)