1010// *********************************************************
1111
1212#pragma once
13+ #if (D3D12_SDK_VERSION >= 619)
14+ #define HAVE_D3D12_TRIM_CALLBACKS 1
15+ #endif // (D3D12_SDK_VERSION >= 619)
16+
1317namespace D3DX12Residency
1418{
1519#if 0
@@ -118,6 +122,9 @@ namespace D3DX12Residency
118122 ResidencyStatus (RESIDENCY_STATUS::RESIDENT),
119123 LastGPUSyncPoint (0 ),
120124 LastUsedTimestamp (0 )
125+ #ifdef HAVE_D3D12_TRIM_CALLBACKS
126+ , LastUsedPeriodicTrimNotificationIndex(0 )
127+ #endif // HAVE_D3D12_TRIM_CALLBACKS
121128 {
122129 memset (CommandListsUsedOn, 0 , sizeof (CommandListsUsedOn));
123130 }
@@ -142,6 +149,9 @@ namespace D3DX12Residency
142149
143150 UINT64 LastGPUSyncPoint;
144151 UINT64 LastUsedTimestamp;
152+ #ifdef HAVE_D3D12_TRIM_CALLBACKS
153+ UINT64 LastUsedPeriodicTrimNotificationIndex;
154+ #endif // HAVE_D3D12_TRIM_CALLBACKS
145155
146156 // This is used to track which open command lists this resource is currently used on.
147157 bool CommandListsUsedOn[MAX_NUM_CONCURRENT_CMD_LISTS];
@@ -630,6 +640,33 @@ namespace D3DX12Residency
630640 pResourceEntry = ResidentObjectListHead.Flink ;
631641 }
632642 }
643+ #ifdef HAVE_D3D12_TRIM_CALLBACKS
644+ void TrimUnusedAllocationsSinceLastNotificationPeriod (UINT64 CurrentPeriodicTrimNotificationIndex, DeviceWideSyncPoint* MaxSyncPoint, ID3D12Pageable** EvictionList, UINT32& NumObjectsToEvict, UINT64& BytesToEvict)
645+ {
646+ LIST_ENTRY* pResourceEntry = ResidentObjectListHead.Flink ;
647+ while (pResourceEntry != &ResidentObjectListHead)
648+ {
649+ ManagedObject* pObject = CONTAINING_RECORD (pResourceEntry, ManagedObject, ListEntry);
650+ pResourceEntry = pResourceEntry->Flink ;
651+
652+ // Only trim allocations done on the GPU
653+ if (MaxSyncPoint && pObject->LastGPUSyncPoint >= MaxSyncPoint->GenerationID )
654+ {
655+ break ;
656+ }
657+
658+ // Only evict things which haven't been used since the last trim notification
659+ if (pObject->LastUsedPeriodicTrimNotificationIndex < (CurrentPeriodicTrimNotificationIndex - 1 ))
660+ {
661+ RESIDENCY_CHECK (pObject->ResidencyStatus == ManagedObject::RESIDENCY_STATUS::RESIDENT);
662+ EvictionList[NumObjectsToEvict++] = pObject->pUnderlying ;
663+ BytesToEvict += pObject->Size ;
664+ Evict (pObject);
665+ pResourceEntry = ResidentObjectListHead.Flink ;
666+ }
667+ }
668+ }
669+ #endif // HAVE_D3D12_TRIM_CALLBACKS
633670
634671 ManagedObject* GetResidentListHead ()
635672 {
@@ -655,6 +692,9 @@ namespace D3DX12Residency
655692 ResidencyManagerInternal (SyncManager* pSyncManagerIn) :
656693 Device (nullptr ),
657694 Device3 (nullptr ),
695+ #ifdef HAVE_D3D12_TRIM_CALLBACKS
696+ Device15 (nullptr ),
697+ #endif // HAVE_D3D12_TRIM_CALLBACKS
658698#ifdef __ID3D12DeviceDownlevel_INTERFACE_DEFINED__
659699 DeviceDownlevel (nullptr ),
660700#endif
@@ -667,6 +707,15 @@ namespace D3DX12Residency
667707 FinishAsyncWork (false ),
668708 cStartEvicted (false ),
669709 CurrentSyncPointGeneration (0 ),
710+ #ifdef HAVE_D3D12_TRIM_CALLBACKS
711+ // Incremented each trim notification callback invocation
712+ // Start at 1 so that resources used in the first period are not immediately evicted
713+ c_CurrentPeriodicTrimNotificationIndexInitialValue (1u ),
714+ CurrentPeriodicTrimNotificationIndex(c_CurrentPeriodicTrimNotificationIndexInitialValue),
715+ // Cookie returned at trim notification callback registration. UINT32_MAX indicates no callback registered.
716+ c_PeriodicTrimCallbackCookie_Unregistered(UINT32_MAX),
717+ PeriodicTrimCallbackCookie(UINT32_MAX),
718+ #endif // HAVE_D3D12_TRIM_CALLBACKS
670719 NumQueuesSeen (0 ),
671720 NodeIndex (0 ),
672721 CurrentAsyncWorkloadHead (0 ),
@@ -767,11 +816,37 @@ namespace D3DX12Residency
767816 }
768817#endif
769818
819+ #ifdef HAVE_D3D12_TRIM_CALLBACKS
820+ // Register for Trim Notification Callback if supported by the OS
821+ // or ignore the failure and just not do periodic trims on OS that don't support it.
822+ if (SUCCEEDED (Device->QueryInterface (&Device15)))
823+ {
824+ D3D12_REGISTER_TRIM_NOTIFICATION registerArgs = { &PeriodicTrimNotificationCallback, this , 0 };
825+ if (SUCCEEDED (Device15->RegisterTrimNotificationCallback (®isterArgs)))
826+ {
827+ PeriodicTrimCallbackCookie = registerArgs.CallbackCookie ;
828+ }
829+ }
830+ #endif // HAVE_D3D12_TRIM_CALLBACKS
831+
770832 return hr;
771833 }
772834
773835 void Destroy ()
774836 {
837+ #ifdef HAVE_D3D12_TRIM_CALLBACKS
838+ if ((PeriodicTrimCallbackCookie != c_PeriodicTrimCallbackCookie_Unregistered) &&
839+ Device15 &&
840+ SUCCEEDED (Device15->UnregisterTrimNotificationCallback (PeriodicTrimCallbackCookie)))
841+ {
842+ // Exclusive access with PeriodicTrimNotificationCallback to
843+ // modify CurrentPeriodicTrimNotificationIndex
844+ Internal::ScopedLock Lock (&Mutex);
845+ PeriodicTrimCallbackCookie = c_PeriodicTrimCallbackCookie_Unregistered;
846+ CurrentPeriodicTrimNotificationIndex = c_CurrentPeriodicTrimNotificationIndexInitialValue;
847+ }
848+ #endif // HAVE_D3D12_TRIM_CALLBACKS
849+
775850 AsyncThreadFence.Destroy ();
776851
777852 if (CompletionEvent != INVALID_HANDLE_VALUE)
@@ -842,6 +917,13 @@ namespace D3DX12Residency
842917 Device3->Release ();
843918 Device3 = nullptr ;
844919 }
920+ #ifdef HAVE_D3D12_TRIM_CALLBACKS
921+ if (Device15)
922+ {
923+ Device15->Release ();
924+ Device15 = nullptr ;
925+ }
926+ #endif // HAVE_D3D12_TRIM_CALLBACKS
845927
846928#ifdef __ID3D12DeviceDownlevel_INTERFACE_DEFINED__
847929 if (DeviceDownlevel)
@@ -1147,6 +1229,78 @@ namespace D3DX12Residency
11471229
11481230 return 0 ;
11491231 }
1232+ #ifdef HAVE_D3D12_TRIM_CALLBACKS
1233+ void static APIENTRY PeriodicTrimNotificationCallback (const D3D12_TRIM_NOTIFICATION* pData)
1234+ {
1235+ ResidencyManagerInternal* pResidencyManager = reinterpret_cast <ResidencyManagerInternal*>(pData->pContext );
1236+ assert (pResidencyManager != nullptr );
1237+
1238+ // A lock must be taken here as the state of the objects will be altered
1239+ // and this also gives us exclusive access with ProcessPagingWork and other
1240+ // functions that modify the residency manager state.
1241+ Internal::ScopedLock Lock (&pResidencyManager->Mutex );
1242+
1243+ // Always increase the index even if we don't do a trim this time
1244+ pResidencyManager->CurrentPeriodicTrimNotificationIndex ++;
1245+
1246+ // Get the last completed GPU queue sync point
1247+ Internal::DeviceWideSyncPoint* FirstUncompletedSyncPoint = pResidencyManager->DequeueCompletedSyncPoints ();
1248+
1249+ // Let's collect a list of objects to evict and then evict them all at once
1250+ ID3D12Pageable** pEvictionList = new ID3D12Pageable*[pResidencyManager->LRU .NumResidentObjects ];
1251+ UINT32 NumObjectsToEvict = 0 ;
1252+ UINT64 BytesToEvict = 0 ;
1253+
1254+ if (pData->Flags & D3D12_TRIM_NOTIFICATION_FLAG_PERIODIC_TRIM)
1255+ {
1256+ pResidencyManager->LRU .TrimUnusedAllocationsSinceLastNotificationPeriod (
1257+ pResidencyManager->CurrentPeriodicTrimNotificationIndex ,
1258+ FirstUncompletedSyncPoint,
1259+ pEvictionList,
1260+ NumObjectsToEvict,
1261+ BytesToEvict
1262+ );
1263+ }
1264+
1265+ if (pData->Flags & D3D12_TRIM_NOTIFICATION_FLAG_TRIM_TO_BUDGET)
1266+ {
1267+ // Get current memory usage
1268+ DXGI_QUERY_VIDEO_MEMORY_INFO LocalMemory = {};
1269+ pResidencyManager->GetCurrentBudget (&LocalMemory, DXGI_MEMORY_SEGMENT_GROUP_LOCAL);
1270+ DXGI_QUERY_VIDEO_MEMORY_INFO NonLocalMemory = {};
1271+ pResidencyManager->GetCurrentBudget (&NonLocalMemory, DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL);
1272+ UINT64 TotalUsage = LocalMemory.CurrentUsage + NonLocalMemory.CurrentUsage ;
1273+
1274+ // Adjust TotalUsage for any bytes already slated for eviction above if D3D12_TRIM_NOTIFICATION_FLAG_PERIODIC_TRIM was set
1275+ TotalUsage = (TotalUsage >= BytesToEvict) ? (TotalUsage - BytesToEvict) : 0 ;
1276+
1277+ UINT64 BytesToTrim = RESIDENCY_MIN (pData->NumBytesToTrim , TotalUsage);
1278+ UINT64 TargetBudget = TotalUsage - BytesToTrim;
1279+
1280+ // We want to evict all the objects in the LRU that are older than the last completed sync point
1281+ // or until we reach the target budget.
1282+ // But if there are no in flight sync points, we can evict everything if needed using CurrentSyncPointGeneration
1283+ // as the upper bound which is the next sync point to be used.
1284+ UINT64 CompletionSyncToWaitFor = FirstUncompletedSyncPoint ? FirstUncompletedSyncPoint->GenerationID : pResidencyManager->CurrentSyncPointGeneration ;
1285+
1286+ pResidencyManager->LRU .TrimToSyncPointInclusive (
1287+ TotalUsage,
1288+ TargetBudget,
1289+ pEvictionList,
1290+ NumObjectsToEvict,
1291+ CompletionSyncToWaitFor);
1292+ }
1293+
1294+ // If there are any objects to evict, do so now
1295+ if (NumObjectsToEvict)
1296+ {
1297+ [[maybe_unused]] HRESULT hrEvict = pResidencyManager->Device ->Evict (NumObjectsToEvict, pEvictionList);
1298+ assert (SUCCEEDED (hrEvict));
1299+ }
1300+
1301+ delete[] (pEvictionList);
1302+ }
1303+ #endif // HAVE_D3D12_TRIM_CALLBACKS
11501304
11511305 // This will be run from a worker thread and will emulate a software queue for making gpu resources resident or evicted.
11521306 // The GPU will be synchronized by this queue to ensure that it never executes using an evicted resource.
@@ -1197,6 +1351,9 @@ namespace D3DX12Residency
11971351 pObject->LastGPUSyncPoint = pWork->SyncPointGeneration ;
11981352
11991353 pObject->LastUsedTimestamp = CurrentTime.QuadPart ;
1354+ #ifdef HAVE_D3D12_TRIM_CALLBACKS
1355+ pObject->LastUsedPeriodicTrimNotificationIndex = CurrentPeriodicTrimNotificationIndex;
1356+ #endif // HAVE_D3D12_TRIM_CALLBACKS
12001357 LRU.ObjectReferenced (pObject);
12011358 }
12021359
@@ -1530,12 +1687,21 @@ namespace D3DX12Residency
15301687
15311688 LIST_ENTRY InFlightSyncPointsHead;
15321689 UINT64 CurrentSyncPointGeneration;
1690+ #ifdef HAVE_D3D12_TRIM_CALLBACKS
1691+ const UINT64 c_CurrentPeriodicTrimNotificationIndexInitialValue;
1692+ UINT64 CurrentPeriodicTrimNotificationIndex;
1693+ const DWORD c_PeriodicTrimCallbackCookie_Unregistered;
1694+ DWORD PeriodicTrimCallbackCookie;
1695+ #endif // HAVE_D3D12_TRIM_CALLBACKS
15331696
15341697 HANDLE CompletionEvent;
15351698 HANDLE AsyncThreadWorkCompletionEvent;
15361699
15371700 ID3D12Device* Device;
15381701 ID3D12Device3* Device3;
1702+ #ifdef HAVE_D3D12_TRIM_CALLBACKS
1703+ ID3D12Device15* Device15;
1704+ #endif // HAVE_D3D12_TRIM_CALLBACKS
15391705#ifdef __ID3D12DeviceDownlevel_INTERFACE_DEFINED__
15401706 ID3D12DeviceDownlevel* DeviceDownlevel;
15411707#endif
0 commit comments