diff --git a/include/gpgmm_d3d12.h b/include/gpgmm_d3d12.h index 7faa06c7..4e1d43b4 100644 --- a/include/gpgmm_d3d12.h +++ b/include/gpgmm_d3d12.h @@ -137,6 +137,14 @@ namespace gpgmm::d3d12 { cannot be determined. */ RESIDENCY_HEAP_FLAG_CREATE_RESIDENT = 0x2, + + /** \brief Creates a residency heap that is locked. + + A locked heap cannot be evicted once made resident. + + This flag is equivalent to calling LockHeap immediately after creation. + */ + RESIDENCY_HEAP_FLAG_CREATE_LOCKED = 0x4, }; DEFINE_ENUM_FLAG_OPERATORS(RESIDENCY_HEAP_FLAGS) @@ -572,6 +580,8 @@ namespace gpgmm::d3d12 { Unlocking a heap allows the residency manager will evict it when over budget. @param pHeap A pointer to the heap being unlocked. + + \return S_OK if unlocking was successful or S_FALSE if a lock remains. */ virtual HRESULT UnlockHeap(IResidencyHeap * pHeap) = 0; diff --git a/src/gpgmm/d3d12/ErrorD3D12.h b/src/gpgmm/d3d12/ErrorD3D12.h index 2a09f4be..1bdc5ad5 100644 --- a/src/gpgmm/d3d12/ErrorD3D12.h +++ b/src/gpgmm/d3d12/ErrorD3D12.h @@ -51,6 +51,11 @@ #define GPGMM_ASSERT_FAILED(hr) ASSERT(SUCCEEDED(hr)); #define GPGMM_ASSERT_SUCCEEDED(hr) ASSERT(FAILED(hr)); +// Same as FAILED but also returns true if S_FALSE. +// S_FALSE is used to denote a result where the operation didn't do anything. +// For example, passing NULL to create an object without returning it will destroy it. +#define GPGMM_UNSUCCESSFUL(hr) (FAILED(hr) || (hr == S_FALSE)) + namespace gpgmm::d3d12 { HRESULT GetErrorResult(ErrorCode error); diff --git a/src/gpgmm/d3d12/ResidencyHeapD3D12.cpp b/src/gpgmm/d3d12/ResidencyHeapD3D12.cpp index 989edb77..6550e81f 100644 --- a/src/gpgmm/d3d12/ResidencyHeapD3D12.cpp +++ b/src/gpgmm/d3d12/ResidencyHeapD3D12.cpp @@ -158,7 +158,7 @@ namespace gpgmm::d3d12 { } std::unique_ptr heap( - new ResidencyHeap(pPageable, newDescriptor, isResidencyDisabled)); + new ResidencyHeap(pResidencyManager, pPageable, newDescriptor, isResidencyDisabled)); if (!isResidencyDisabled) { // Check if the underlying memory was implicitly made resident. @@ -191,19 +191,30 @@ namespace gpgmm::d3d12 { GetDevice(pPageable)); } else { if (newDescriptor.Flags & RESIDENCY_HEAP_FLAG_CREATE_RESIDENT) { - GPGMM_RETURN_IF_FAILED(residencyManager->LockHeap(heap.get()), - GetDevice(pPageable)); - GPGMM_RETURN_IF_FAILED(residencyManager->UnlockHeap(heap.get()), - GetDevice(pPageable)); + GPGMM_RETURN_IF_FAILED(heap->Lock(), GetDevice(pPageable)); + GPGMM_RETURN_IF_FAILED(heap->Unlock(), GetDevice(pPageable)); ASSERT(heap->GetInfo().Status == RESIDENCY_HEAP_STATUS_RESIDENT); } } + + if (descriptor.Flags & RESIDENCY_HEAP_FLAG_CREATE_LOCKED) { + GPGMM_RETURN_IF_FAILED(heap->Lock(), GetDevice(pPageable)); + } + } else { if (descriptor.Flags & RESIDENCY_HEAP_FLAG_CREATE_RESIDENT) { WarnLog(MessageId::kPerformanceWarning, heap.get()) << "RESIDENCY_HEAP_FLAG_CREATE_RESIDENT was specified but had no effect " "becauase residency management is not being used."; } + + // Heap created not resident requires no budget to be created. + if (descriptor.Flags & RESIDENCY_HEAP_FLAG_CREATE_LOCKED) { + ErrorLog(ErrorCode::kInvalidArgument, heap.get()) + << "RESIDENCY_HEAP_FLAG_CREATE_LOCKED cannot be specified without a residency " + "manager."; + return E_INVALIDARG; + } } GPGMM_RETURN_IF_FAILED(heap->SetDebugName(newDescriptor.DebugName), GetDevice(pPageable)); @@ -278,26 +289,32 @@ namespace gpgmm::d3d12 { ppResidencyHeapOut); } - ResidencyHeap::ResidencyHeap(ComPtr pageable, + ResidencyHeap::ResidencyHeap(ComPtr residencyManager, + ComPtr pageable, const RESIDENCY_HEAP_DESC& descriptor, bool isResidencyDisabled) : MemoryBase(descriptor.SizeInBytes, descriptor.Alignment), + mResidencyManager(std::move(residencyManager)), mPageable(std::move(pageable)), mHeapSegment(descriptor.HeapSegment), mResidencyLock(0), - mIsResidencyDisabled(isResidencyDisabled), mState(RESIDENCY_HEAP_STATUS_UNKNOWN) { ASSERT(mPageable != nullptr); - if (!mIsResidencyDisabled) { + if (residencyManager != nullptr) { GPGMM_TRACE_EVENT_OBJECT_NEW(this); } } ResidencyHeap::~ResidencyHeap() { - if (mIsResidencyDisabled) { + if (mResidencyManager == nullptr) { return; } + if (IsResidencyLocked() && GPGMM_UNSUCCESSFUL(Unlock())) { + DebugLog(MessageId::kUnknown, this) + << "Heap was locked for residency while being destroyed."; + } + // When a heap is destroyed, it no longer resides in resident memory, so we must evict // it from the residency cache. If this heap is not manually removed from the residency // cache, the ResidencyManager will attempt to use it after it has been deallocated. @@ -364,4 +381,18 @@ namespace gpgmm::d3d12 { return DebugObject::SetDebugName(Name); } + HRESULT ResidencyHeap::Lock() { + ASSERT(mResidencyManager != nullptr); + return mResidencyManager->LockHeap(this); + } + + HRESULT ResidencyHeap::Unlock() { + ASSERT(mResidencyManager != nullptr); + return mResidencyManager->UnlockHeap(this); + } + + IResidencyManager* ResidencyHeap::GetResidencyManager() const { + return mResidencyManager.Get(); + } + } // namespace gpgmm::d3d12 diff --git a/src/gpgmm/d3d12/ResidencyHeapD3D12.h b/src/gpgmm/d3d12/ResidencyHeapD3D12.h index 0512139e..2b96ece8 100644 --- a/src/gpgmm/d3d12/ResidencyHeapD3D12.h +++ b/src/gpgmm/d3d12/ResidencyHeapD3D12.h @@ -59,10 +59,16 @@ namespace gpgmm::d3d12 { LPCWSTR GetDebugName() const override; HRESULT SetDebugName(LPCWSTR Name) override; + IResidencyManager* GetResidencyManager() const; + + HRESULT Lock(); + HRESULT Unlock(); + private: friend ResidencyManager; - ResidencyHeap(ComPtr pageable, + ResidencyHeap(ComPtr residencyManager, + ComPtr pageable, const RESIDENCY_HEAP_DESC& descriptor, bool isResidencyDisabled); @@ -87,13 +93,13 @@ namespace gpgmm::d3d12 { void AddResidencyLockRef(); void ReleaseResidencyLock(); + ComPtr mResidencyManager; ComPtr mPageable; // mLastUsedFenceValue denotes the last time this pageable was submitted to the GPU. uint64_t mLastUsedFenceValue = 0; DXGI_MEMORY_SEGMENT_GROUP mHeapSegment; RefCounted mResidencyLock; - bool mIsResidencyDisabled; RESIDENCY_HEAP_STATUS mState; }; } // namespace gpgmm::d3d12 diff --git a/src/gpgmm/d3d12/ResidencyManagerD3D12.cpp b/src/gpgmm/d3d12/ResidencyManagerD3D12.cpp index 20c3816c..616041cc 100644 --- a/src/gpgmm/d3d12/ResidencyManagerD3D12.cpp +++ b/src/gpgmm/d3d12/ResidencyManagerD3D12.cpp @@ -241,7 +241,7 @@ namespace gpgmm::d3d12 { // If the heap was never locked, nothing further should be done. if (!heap->IsResidencyLocked()) { - return S_OK; + return S_FALSE; } if (heap->IsInList()) { @@ -256,7 +256,7 @@ namespace gpgmm::d3d12 { // If another lock still exists on the heap, nothing further should be done. if (heap->IsResidencyLocked()) { - return S_OK; + return S_FALSE; } // When all locks have been removed, the resource remains resident and becomes tracked in diff --git a/src/gpgmm/d3d12/ResourceAllocationD3D12.cpp b/src/gpgmm/d3d12/ResourceAllocationD3D12.cpp index 270039c3..43b2ce02 100644 --- a/src/gpgmm/d3d12/ResourceAllocationD3D12.cpp +++ b/src/gpgmm/d3d12/ResourceAllocationD3D12.cpp @@ -43,7 +43,6 @@ namespace gpgmm::d3d12 { } // namespace ResourceAllocation::ResourceAllocation(const RESOURCE_RESOURCE_ALLOCATION_DESC& desc, - ResidencyManager* residencyManager, MemoryAllocatorBase* allocator, ResidencyHeap* resourceHeap, MemoryBlock* block, @@ -54,7 +53,6 @@ namespace gpgmm::d3d12 { static_cast(desc.Type), block, desc.SizeInBytes), - mResidencyManager(residencyManager), mResource(std::move(resource)), mOffsetFromResource(desc.OffsetFromResource) { ASSERT(resourceHeap != nullptr); @@ -63,6 +61,9 @@ namespace gpgmm::d3d12 { ResourceAllocation::~ResourceAllocation() { GPGMM_TRACE_EVENT_OBJECT_DESTROY(this); + } + + void ResourceAllocation::DeleteThis() { if (mMappedCount.GetRefCount() > 0) { WarnLog(MessageId::kPerformanceWarning, this) << "Destroying a mapped resource allocation is allowed but discouraged. Please " @@ -72,17 +73,15 @@ namespace gpgmm::d3d12 { // If the developer forgots to unlock the heap, do so now so the heap can be made eligable // for eviction. - if (mResidencyManager != nullptr) { - mResidencyManager->UnlockHeap(GetMemory()); + ResidencyHeap* residencyHeap = static_cast(GetMemory()); + if (residencyHeap->GetResidencyManager() != nullptr) { + residencyHeap->Unlock(); WarnLog(MessageId::kPerformanceWarning, this) << "Destroying a locked resource allocation is allowed but discouraged. Please " "call UnlockHeap the same number of times as LockHeap before releasing the " - "resource " - "allocation."; + "resource allocation."; } - } - void ResourceAllocation::DeleteThis() { GetAllocator()->DeallocateMemory(std::unique_ptr(this)); } @@ -102,9 +101,9 @@ namespace gpgmm::d3d12 { return GetErrorResult(ErrorCode::kBadOperation); } - if (mResidencyManager != nullptr) { - GPGMM_RETURN_IF_FAILED(mResidencyManager->LockHeap(GetMemory()), - GetDevice(mResource.Get())); + ResidencyHeap* residencyHeap = static_cast(GetMemory()); + if (residencyHeap->GetResidencyManager() != nullptr) { + GPGMM_RETURN_IF_FAILED(residencyHeap->Lock(), GetDevice(mResource.Get())); } // Range coordinates are always subresource-relative so the range should only be @@ -142,8 +141,9 @@ namespace gpgmm::d3d12 { } // Underlying heap cannot be evicted until the last Unmap. - if (mResidencyManager != nullptr && mMappedCount.Unref()) { - mResidencyManager->UnlockHeap(GetMemory()); + ResidencyHeap* residencyHeap = static_cast(GetMemory()); + if (residencyHeap->GetResidencyManager() != nullptr && mMappedCount.Unref()) { + residencyHeap->Unlock(); } D3D12_RANGE newWrittenRange{}; diff --git a/src/gpgmm/d3d12/ResourceAllocationD3D12.h b/src/gpgmm/d3d12/ResourceAllocationD3D12.h index d68d5ca3..07fe934e 100644 --- a/src/gpgmm/d3d12/ResourceAllocationD3D12.h +++ b/src/gpgmm/d3d12/ResourceAllocationD3D12.h @@ -61,7 +61,6 @@ namespace gpgmm::d3d12 { friend ResourceAllocator; ResourceAllocation(const RESOURCE_RESOURCE_ALLOCATION_DESC& desc, - ResidencyManager* residencyManager, MemoryAllocatorBase* allocator, ResidencyHeap* resourceHeap, MemoryBlock* block, @@ -78,7 +77,6 @@ namespace gpgmm::d3d12 { // ObjectBase interface DEFINE_OBJECT_BASE_OVERRIDES(IResourceAllocation) - ResidencyManager* const mResidencyManager; ComPtr mResource; const uint64_t mOffsetFromResource; diff --git a/src/gpgmm/d3d12/ResourceAllocatorD3D12.cpp b/src/gpgmm/d3d12/ResourceAllocatorD3D12.cpp index 8f097eae..7b273c6b 100644 --- a/src/gpgmm/d3d12/ResourceAllocatorD3D12.cpp +++ b/src/gpgmm/d3d12/ResourceAllocatorD3D12.cpp @@ -1332,8 +1332,8 @@ namespace gpgmm::d3d12 { allocationDesc.DebugName = allocationDescriptor.DebugName; *ppResourceAllocationOut = new ResourceAllocation( - allocationDesc, mResidencyManager.Get(), subAllocation.GetAllocator(), - resourceHeap, subAllocation.GetBlock(), std::move(committedResource)); + allocationDesc, subAllocation.GetAllocator(), resourceHeap, + subAllocation.GetBlock(), std::move(committedResource)); return S_OK; })); @@ -1373,8 +1373,8 @@ namespace gpgmm::d3d12 { allocationDesc.DebugName = allocationDescriptor.DebugName; *ppResourceAllocationOut = new ResourceAllocation( - allocationDesc, mResidencyManager.Get(), subAllocation.GetAllocator(), - resourceHeap, subAllocation.GetBlock(), std::move(placedResource)); + allocationDesc, subAllocation.GetAllocator(), resourceHeap, + subAllocation.GetBlock(), std::move(placedResource)); return S_OK; })); @@ -1417,8 +1417,8 @@ namespace gpgmm::d3d12 { allocationDesc.DebugName = allocationDescriptor.DebugName; *ppResourceAllocationOut = new ResourceAllocation( - allocationDesc, mResidencyManager.Get(), allocation.GetAllocator(), - resourceHeap, allocation.GetBlock(), std::move(placedResource)); + allocationDesc, allocation.GetAllocator(), resourceHeap, + allocation.GetBlock(), std::move(placedResource)); return S_OK; })); @@ -1478,8 +1478,7 @@ namespace gpgmm::d3d12 { if (ppResourceAllocationOut != nullptr) { *ppResourceAllocationOut = new ResourceAllocation( - allocationDesc, mResidencyManager.Get(), this, resourceHeap.Detach(), nullptr, - std::move(committedResource)); + allocationDesc, this, resourceHeap.Detach(), nullptr, std::move(committedResource)); } return ErrorCode::kNone; @@ -1567,8 +1566,8 @@ namespace gpgmm::d3d12 { allocationDesc.Type = RESOURCE_ALLOCATION_TYPE_STANDALONE; *ppResourceAllocationOut = new ResourceAllocation( - allocationDesc, nullptr, this, static_cast(resourceHeap.Detach()), - nullptr, pCommittedResource); + allocationDesc, this, static_cast(resourceHeap.Detach()), nullptr, + pCommittedResource); return S_OK; } diff --git a/src/tests/end2end/D3D12ResidencyManagerTests.cpp b/src/tests/end2end/D3D12ResidencyManagerTests.cpp index a4ba17af..ed33fdb0 100644 --- a/src/tests/end2end/D3D12ResidencyManagerTests.cpp +++ b/src/tests/end2end/D3D12ResidencyManagerTests.cpp @@ -64,6 +64,21 @@ class D3D12ResidencyManagerTests : public D3D12TestBase, public ::testing::Test return residencyDesc; } + // Configures a residency heap for testing residency on any adapter. + D3D12_HEAP_DESC GetBasicHeapDesc(uint64_t sizeInBytes, D3D12_HEAP_TYPE heapType) const { + D3D12_HEAP_PROPERTIES heapProperties = {}; + heapProperties.Type = heapType; + + D3D12_HEAP_DESC heapDesc = {}; + heapDesc.Properties = heapProperties; + heapDesc.SizeInBytes = sizeInBytes; + + // Assume tier 1, which all adapters support. + heapDesc.Flags |= D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS; + + return heapDesc; + } + uint64_t GetBudgetLeft(IResidencyManager* residencyManager, const DXGI_MEMORY_SEGMENT_GROUP& heapSegment) { DXGI_QUERY_VIDEO_MEMORY_INFO segment = {}; @@ -154,6 +169,41 @@ TEST_F(D3D12ResidencyManagerTests, CreateResourceHeapNotResident) { &createHeapContext, nullptr)); } +TEST_F(D3D12ResidencyManagerTests, CreateResourceHeapLocked) { + ComPtr residencyManager; + ASSERT_SUCCEEDED(CreateResidencyManager(CreateBasicResidencyDesc(kDefaultBudget), mDevice.Get(), + mAdapter.Get(), &residencyManager)); + ASSERT_NE(residencyManager.Get(), nullptr); + + D3D12_HEAP_DESC heapDesc = GetBasicHeapDesc(GPGMM_MB_TO_BYTES(10), D3D12_HEAP_TYPE_DEFAULT); + + RESIDENCY_HEAP_DESC residencyHeapDesc = {}; + residencyHeapDesc.HeapSegment = DXGI_MEMORY_SEGMENT_GROUP_LOCAL; + + CreateResourceHeapCallbackContext createHeapContext(mDevice.Get(), &heapDesc); + + RESIDENCY_HEAP_DESC unlockedResidencyHeapDesc = residencyHeapDesc; + + ComPtr resourceHeap; + ASSERT_SUCCEEDED(CreateResidencyHeap(unlockedResidencyHeapDesc, residencyManager.Get(), + CreateResourceHeapCallbackContext::CreateHeap, + &createHeapContext, &resourceHeap)); + EXPECT_FALSE(resourceHeap->GetInfo().IsLocked); + + RESIDENCY_HEAP_DESC lockedResidencyHeapDesc = residencyHeapDesc; + lockedResidencyHeapDesc.Flags |= RESIDENCY_HEAP_FLAG_CREATE_LOCKED; + + ASSERT_SUCCEEDED(CreateResidencyHeap(lockedResidencyHeapDesc, residencyManager.Get(), + CreateResourceHeapCallbackContext::CreateHeap, + &createHeapContext, &resourceHeap)); + EXPECT_TRUE(resourceHeap->GetInfo().IsLocked); + + // Residency manager must exist to create the heap locked. + ASSERT_FAILED(CreateResidencyHeap(lockedResidencyHeapDesc, nullptr, + CreateResourceHeapCallbackContext::CreateHeap, + &createHeapContext, nullptr)); +} + TEST_F(D3D12ResidencyManagerTests, CreateResourceHeap) { ComPtr residencyManager; ASSERT_SUCCEEDED(CreateResidencyManager(CreateBasicResidencyDesc(kDefaultBudget), mDevice.Get(), @@ -212,6 +262,29 @@ TEST_F(D3D12ResidencyManagerTests, CreateResourceHeap) { &createHeapContext, nullptr)); } + { + RESIDENCY_HEAP_DESC unlockedResidencyHeapDesc = residencyHeapDesc; + + ComPtr resourceHeap; + ASSERT_SUCCEEDED(CreateResidencyHeap(unlockedResidencyHeapDesc, residencyManager.Get(), + CreateResourceHeapCallbackContext::CreateHeap, + &createHeapContext, &resourceHeap)); + EXPECT_FALSE(resourceHeap->GetInfo().IsLocked); + + RESIDENCY_HEAP_DESC lockedResidencyHeapDesc = residencyHeapDesc; + lockedResidencyHeapDesc.Flags |= RESIDENCY_HEAP_FLAG_CREATE_LOCKED; + + ASSERT_SUCCEEDED(CreateResidencyHeap(lockedResidencyHeapDesc, residencyManager.Get(), + CreateResourceHeapCallbackContext::CreateHeap, + &createHeapContext, &resourceHeap)); + EXPECT_TRUE(resourceHeap->GetInfo().IsLocked); + + // Residency manager must exist to create the heap locked. + ASSERT_FAILED(CreateResidencyHeap(lockedResidencyHeapDesc, nullptr, + CreateResourceHeapCallbackContext::CreateHeap, + &createHeapContext, nullptr)); + } + // Create a resource heap without residency. ComPtr resourceHeap; ASSERT_SUCCEEDED(CreateResidencyHeap(residencyHeapDesc, nullptr,