Skip to content

Commit b550a78

Browse files
authored
Samples/Desktop/D3D12Residency: Support for trim notifications (#948)
* Samples/Desktop/D3D12Residency: Bump SDK and toolset * Samples/Desktop/D3D12Residency: Add AgilitySDK v1.619.0 dependency * Samples/Desktop/D3D12Residency: Use dependency Libraries/D3DX12Residency/d3dx12Residency.h * Libraries/D3DX12Residency/d3dx12Residency.h: Add periodic trim notifications support
1 parent 7890a7e commit b550a78

6 files changed

Lines changed: 181 additions & 1639 deletions

File tree

Libraries/D3DX12Residency/d3dx12Residency.h

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
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+
1317
namespace 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(&registerArgs)))
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

Libraries/D3DX12Residency/readme.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ Residency correlates to whether a heap is accessible by the GPU or not. If it i
6262

6363
Note that when you call ```Evict```, the heap is only marked for eviction. VidMM will try really hard not to evict it if it doesn't need to thereby making the subsequent ```MakeResident``` call (when you call it) a no-op (ie. nothing needs to happen and your heap is ready for use almost immediately).
6464

65+
#### Automatic Trim Notifications
66+
The library now automatically registers for OS trim notifications when supported. This mechanism is particularly useful for applications that do not submit command lists frequently such as image/video editors, since these apps may hold GPU resources idle for extended periods without the library's normal LRU eviction being triggered by command list execution.
67+
68+
The implementation uses D3D12's `RegisterTrimNotificationCallback` which internally registers with the video kernel memory manager through [`D3DKMTRegisterTrimNotification`](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/d3dkmthk/nf-d3dkmthk-d3dkmtregistertrimnotification). The video memory manager will periodically notify the application to evict anything that hasn't been used in a given timeframe.
69+
6570
#### What does calling ```MakeResident``` do?
6671
```MakeResident``` is a blocking call which will bring your heap data and page table mappings back as they were before you called Evict. You don't need to copy the data back in.
6772

Samples/Desktop/D3D12Residency/src/D3D12Residency.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
#include "stdafx.h"
1313
#include "D3D12Residency.h"
1414

15-
extern "C" { __declspec(dllexport) extern const UINT D3D12SDKVersion = 618; }
15+
extern "C" { __declspec(dllexport) extern const UINT D3D12SDKVersion = 619; }
1616
extern "C" { __declspec(dllexport) extern const char* D3D12SDKPath = u8".\\D3D12\\"; }
1717

1818
D3D12Residency::D3D12Residency(UINT width, UINT height, std::wstring name) :

Samples/Desktop/D3D12Residency/src/D3D12Residency.vcxproj

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<Import Project="packages\Microsoft.Direct3D.D3D12.1.619.0\build\native\Microsoft.Direct3D.D3D12.props" Condition="Exists('packages\Microsoft.Direct3D.D3D12.1.619.0\build\native\Microsoft.Direct3D.D3D12.props')" />
34
<Import Project="packages\Microsoft.Direct3D.DXC.1.8.2505.32\build\native\Microsoft.Direct3D.DXC.props" Condition="Exists('packages\Microsoft.Direct3D.DXC.1.8.2505.32\build\native\Microsoft.Direct3D.DXC.props')" />
4-
<Import Project="packages\Microsoft.Direct3D.D3D12.1.618.3\build\native\Microsoft.Direct3D.D3D12.props" Condition="Exists('packages\Microsoft.Direct3D.D3D12.1.618.3\build\native\Microsoft.Direct3D.D3D12.props')" />
55
<ItemGroup Label="ProjectConfigurations">
66
<ProjectConfiguration Include="Debug|x64">
77
<Configuration>Debug</Configuration>
@@ -17,19 +17,19 @@
1717
<Keyword>Win32Proj</Keyword>
1818
<RootNamespace>D3D12Residency</RootNamespace>
1919
<ProjectName>D3D12Residency</ProjectName>
20-
<WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
20+
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
2121
</PropertyGroup>
2222
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
2323
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
2424
<ConfigurationType>Application</ConfigurationType>
2525
<UseDebugLibraries>true</UseDebugLibraries>
26-
<PlatformToolset>v142</PlatformToolset>
26+
<PlatformToolset>v143</PlatformToolset>
2727
<CharacterSet>Unicode</CharacterSet>
2828
</PropertyGroup>
2929
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
3030
<ConfigurationType>Application</ConfigurationType>
3131
<UseDebugLibraries>false</UseDebugLibraries>
32-
<PlatformToolset>v142</PlatformToolset>
32+
<PlatformToolset>v143</PlatformToolset>
3333
<WholeProgramOptimization>true</WholeProgramOptimization>
3434
<CharacterSet>Unicode</CharacterSet>
3535
</PropertyGroup>
@@ -64,7 +64,7 @@
6464
<MinimalRebuild>false</MinimalRebuild>
6565
<AdditionalUsingDirectories>
6666
</AdditionalUsingDirectories>
67-
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
67+
<AdditionalIncludeDirectories>..\..\..\..\Libraries\D3DX12Residency\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
6868
</ClCompile>
6969
<Link>
7070
<GenerateDebugInformation>true</GenerateDebugInformation>
@@ -108,7 +108,6 @@ dxc.exe -nologo -Tps_6_0 -E"PSMain" -Zi -Qembed_debug -Fo"$(OutDir)%(Filename)_P
108108
<ItemGroup>
109109
<ClInclude Include="Win32Application.h" />
110110
<ClInclude Include="D3D12Residency.h" />
111-
<ClInclude Include="d3dx12Residency.h" />
112111
<ClInclude Include="DXSample.h" />
113112
<ClInclude Include="DXSampleHelper.h" />
114113
<ClInclude Include="stdafx.h" />
@@ -129,17 +128,17 @@ dxc.exe -nologo -Tps_6_0 -E"PSMain" -Zi -Qembed_debug -Fo"$(OutDir)%(Filename)_P
129128
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
130129
<ImportGroup Label="ExtensionTargets">
131130
<Import Project="packages\WinPixEventRuntime.1.0.240308001\build\WinPixEventRuntime.targets" Condition="Exists('packages\WinPixEventRuntime.1.0.240308001\build\WinPixEventRuntime.targets')" />
132-
<Import Project="packages\Microsoft.Direct3D.D3D12.1.618.3\build\native\Microsoft.Direct3D.D3D12.targets" Condition="Exists('packages\Microsoft.Direct3D.D3D12.1.618.3\build\native\Microsoft.Direct3D.D3D12.targets')" />
133131
<Import Project="packages\Microsoft.Direct3D.DXC.1.8.2505.32\build\native\Microsoft.Direct3D.DXC.targets" Condition="Exists('packages\Microsoft.Direct3D.DXC.1.8.2505.32\build\native\Microsoft.Direct3D.DXC.targets')" />
132+
<Import Project="packages\Microsoft.Direct3D.D3D12.1.619.0\build\native\Microsoft.Direct3D.D3D12.targets" Condition="Exists('packages\Microsoft.Direct3D.D3D12.1.619.0\build\native\Microsoft.Direct3D.D3D12.targets')" />
134133
</ImportGroup>
135134
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
136135
<PropertyGroup>
137136
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
138137
</PropertyGroup>
139138
<Error Condition="!Exists('packages\WinPixEventRuntime.1.0.240308001\build\WinPixEventRuntime.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\WinPixEventRuntime.1.0.240308001\build\WinPixEventRuntime.targets'))" />
140-
<Error Condition="!Exists('packages\Microsoft.Direct3D.D3D12.1.618.3\build\native\Microsoft.Direct3D.D3D12.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Microsoft.Direct3D.D3D12.1.618.3\build\native\Microsoft.Direct3D.D3D12.props'))" />
141-
<Error Condition="!Exists('packages\Microsoft.Direct3D.D3D12.1.618.3\build\native\Microsoft.Direct3D.D3D12.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Microsoft.Direct3D.D3D12.1.618.3\build\native\Microsoft.Direct3D.D3D12.targets'))" />
142139
<Error Condition="!Exists('packages\Microsoft.Direct3D.DXC.1.8.2505.32\build\native\Microsoft.Direct3D.DXC.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Microsoft.Direct3D.DXC.1.8.2505.32\build\native\Microsoft.Direct3D.DXC.props'))" />
143140
<Error Condition="!Exists('packages\Microsoft.Direct3D.DXC.1.8.2505.32\build\native\Microsoft.Direct3D.DXC.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Microsoft.Direct3D.DXC.1.8.2505.32\build\native\Microsoft.Direct3D.DXC.targets'))" />
141+
<Error Condition="!Exists('packages\Microsoft.Direct3D.D3D12.1.619.0\build\native\Microsoft.Direct3D.D3D12.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Microsoft.Direct3D.D3D12.1.619.0\build\native\Microsoft.Direct3D.D3D12.props'))" />
142+
<Error Condition="!Exists('packages\Microsoft.Direct3D.D3D12.1.619.0\build\native\Microsoft.Direct3D.D3D12.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Microsoft.Direct3D.D3D12.1.619.0\build\native\Microsoft.Direct3D.D3D12.targets'))" />
144143
</Target>
145144
</Project>

0 commit comments

Comments
 (0)