Residency 2: Create a d3d12::Heap During Direct Allocations

When creating a directly allocated resource in D3D12, also create a
dawn_native::d3d12::Heap to represent that allocation alongside the
ResourceHeapAllocation. This matches D3D12's allocation model when using
CreateCommittedResource and makes residency management much easier.

Bug: dawn:193
Change-Id: I2280863dcfca57bad72962a2b097f8f2d4cc7dad
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/16381
Commit-Queue: Brandon Jones <brandon1.jones@intel.com>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/src/dawn_native/ResourceMemoryAllocation.h b/src/dawn_native/ResourceMemoryAllocation.h
index a66129a..1eb68e7 100644
--- a/src/dawn_native/ResourceMemoryAllocation.h
+++ b/src/dawn_native/ResourceMemoryAllocation.h
@@ -31,6 +31,9 @@
         // Memory sub-divided using one or more blocks of various sizes.
         kSubAllocated,
 
+        // Memory was allocated outside of Dawn.
+        kExternal,
+
         // Memory not allocated or freed.
         kInvalid
     };
diff --git a/src/dawn_native/d3d12/Forward.h b/src/dawn_native/d3d12/Forward.h
index ade12e3..1143a4a 100644
--- a/src/dawn_native/d3d12/Forward.h
+++ b/src/dawn_native/d3d12/Forward.h
@@ -26,6 +26,7 @@
     class CommandBuffer;
     class ComputePipeline;
     class Device;
+    class Heap;
     class PipelineLayout;
     class Queue;
     class RenderPipeline;
@@ -47,6 +48,7 @@
         using PipelineLayoutType = PipelineLayout;
         using QueueType = Queue;
         using RenderPipelineType = RenderPipeline;
+        using ResourceHeapType = Heap;
         using SamplerType = Sampler;
         using ShaderModuleType = ShaderModule;
         using StagingBufferType = StagingBuffer;
diff --git a/src/dawn_native/d3d12/HeapAllocatorD3D12.cpp b/src/dawn_native/d3d12/HeapAllocatorD3D12.cpp
index e16f380..dd6c641 100644
--- a/src/dawn_native/d3d12/HeapAllocatorD3D12.cpp
+++ b/src/dawn_native/d3d12/HeapAllocatorD3D12.cpp
@@ -46,7 +46,7 @@
             mDevice->GetD3D12Device()->CreateHeap(&heapDesc, IID_PPV_ARGS(&heap)),
             "ID3D12Device::CreateHeap"));
 
-        return {std::make_unique<Heap>(std::move(heap))};
+        return {std::make_unique<Heap>(std::move(heap), size)};
     }
 
     void HeapAllocator::DeallocateResourceHeap(std::unique_ptr<ResourceHeapBase> heap) {
diff --git a/src/dawn_native/d3d12/HeapD3D12.cpp b/src/dawn_native/d3d12/HeapD3D12.cpp
index 2e35bdf..2548ae0 100644
--- a/src/dawn_native/d3d12/HeapD3D12.cpp
+++ b/src/dawn_native/d3d12/HeapD3D12.cpp
@@ -15,11 +15,25 @@
 #include "dawn_native/d3d12/HeapD3D12.h"
 
 namespace dawn_native { namespace d3d12 {
-
-    Heap::Heap(ComPtr<ID3D12Heap> heap) : mHeap(std::move(heap)) {
+    Heap::Heap(ComPtr<ID3D12Pageable> d3d12Pageable, uint64_t size)
+        : mD3d12Pageable(std::move(d3d12Pageable)), mSize(size) {
     }
 
+    // This function should only be used when mD3D12Pageable was initialized from a ID3D12Pageable
+    // that was initially created as an ID3D12Heap (i.e. SubAllocation). If the ID3D12Pageable was
+    // initially created as an ID3D12Resource (i.e. DirectAllocation), then use GetD3D12Pageable().
     ComPtr<ID3D12Heap> Heap::GetD3D12Heap() const {
-        return mHeap;
+        ComPtr<ID3D12Heap> heap;
+        HRESULT result = mD3d12Pageable.As(&heap);
+        ASSERT(SUCCEEDED(result));
+        return heap;
+    }
+
+    ComPtr<ID3D12Pageable> Heap::GetD3D12Pageable() const {
+        return mD3d12Pageable;
+    }
+
+    uint64_t Heap::GetSize() const {
+        return mSize;
     }
 }}  // namespace dawn_native::d3d12
\ No newline at end of file
diff --git a/src/dawn_native/d3d12/HeapD3D12.h b/src/dawn_native/d3d12/HeapD3D12.h
index 834e42a..c3e648e 100644
--- a/src/dawn_native/d3d12/HeapD3D12.h
+++ b/src/dawn_native/d3d12/HeapD3D12.h
@@ -22,13 +22,17 @@
 
     class Heap : public ResourceHeapBase {
       public:
-        Heap(ComPtr<ID3D12Heap> heap);
+        Heap(ComPtr<ID3D12Pageable> d3d12Pageable, uint64_t size);
         ~Heap() = default;
 
         ComPtr<ID3D12Heap> GetD3D12Heap() const;
+        ComPtr<ID3D12Pageable> GetD3D12Pageable() const;
+
+        uint64_t GetSize() const;
 
       private:
-        ComPtr<ID3D12Heap> mHeap;
+        ComPtr<ID3D12Pageable> mD3d12Pageable;
+        uint64_t mSize = 0;
     };
 }}  // namespace dawn_native::d3d12
 
diff --git a/src/dawn_native/d3d12/ResourceAllocatorManagerD3D12.cpp b/src/dawn_native/d3d12/ResourceAllocatorManagerD3D12.cpp
index 1fed89f..257b003 100644
--- a/src/dawn_native/d3d12/ResourceAllocatorManagerD3D12.cpp
+++ b/src/dawn_native/d3d12/ResourceAllocatorManagerD3D12.cpp
@@ -187,6 +187,13 @@
 
         mAllocationsToDelete.Enqueue(allocation, mDevice->GetPendingCommandSerial());
 
+        // Directly allocated ResourceHeapAllocations are created with a heap object that must be
+        // manually deleted upon deallocation. See ResourceAllocatorManager::CreateCommittedResource
+        // for more information.
+        if (allocation.GetInfo().mMethod == AllocationMethod::kDirect) {
+            delete allocation.GetResourceHeap();
+        }
+
         // Invalidate the allocation immediately in case one accidentally
         // calls DeallocateMemory again using the same allocation.
         allocation.Invalidate();
@@ -246,7 +253,7 @@
             return ResourceHeapAllocation{};  // invalid
         }
 
-        ID3D12Heap* heap = static_cast<Heap*>(allocation.GetResourceHeap())->GetD3D12Heap().Get();
+        Heap* heap = ToBackend(allocation.GetResourceHeap());
 
         // With placed resources, a single heap can be reused.
         // The resource placed at an offset is only reclaimed
@@ -256,13 +263,14 @@
         // barrier).
         // https://docs.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12device-createplacedresource
         ComPtr<ID3D12Resource> placedResource;
-        DAWN_TRY(CheckOutOfMemoryHRESULT(mDevice->GetD3D12Device()->CreatePlacedResource(
-                                             heap, allocation.GetOffset(), &resourceDescriptor,
-                                             initialUsage, nullptr, IID_PPV_ARGS(&placedResource)),
-                                         "ID3D12Device::CreatePlacedResource"));
+        DAWN_TRY(CheckOutOfMemoryHRESULT(
+            mDevice->GetD3D12Device()->CreatePlacedResource(
+                heap->GetD3D12Heap().Get(), allocation.GetOffset(), &resourceDescriptor,
+                initialUsage, nullptr, IID_PPV_ARGS(&placedResource)),
+            "ID3D12Device::CreatePlacedResource"));
 
         return ResourceHeapAllocation{allocation.GetInfo(), allocation.GetOffset(),
-                                      std::move(placedResource)};
+                                      std::move(placedResource), heap};
     }
 
     ResultOrError<ResourceHeapAllocation> ResourceAllocatorManager::CreateCommittedResource(
@@ -297,11 +305,18 @@
                                         initialUsage, nullptr, IID_PPV_ARGS(&committedResource)),
                                     "ID3D12Device::CreateCommittedResource"));
 
+        // When using CreateCommittedResource, D3D12 creates an implicit heap that contains the
+        // resource allocation. Because Dawn's memory residency management occurs at the resource
+        // heap granularity, every directly allocated ResourceHeapAllocation also stores a Heap
+        // object. This object is created manually, and must be deleted manually upon deallocation
+        // of the committed resource.
+        Heap* heap = new Heap(committedResource, resourceInfo.SizeInBytes);
+
         AllocationInfo info;
         info.mMethod = AllocationMethod::kDirect;
 
         return ResourceHeapAllocation{info,
-                                      /*offset*/ 0, std::move(committedResource)};
+                                      /*offset*/ 0, std::move(committedResource), heap};
     }
 
 }}  // namespace dawn_native::d3d12
diff --git a/src/dawn_native/d3d12/ResourceHeapAllocationD3D12.cpp b/src/dawn_native/d3d12/ResourceHeapAllocationD3D12.cpp
index bf805cb..c3a89f0 100644
--- a/src/dawn_native/d3d12/ResourceHeapAllocationD3D12.cpp
+++ b/src/dawn_native/d3d12/ResourceHeapAllocationD3D12.cpp
@@ -14,13 +14,17 @@
 
 #include "dawn_native/d3d12/ResourceHeapAllocationD3D12.h"
 
+#include "dawn_native/d3d12/HeapD3D12.h"
+
 #include <utility>
 
 namespace dawn_native { namespace d3d12 {
     ResourceHeapAllocation::ResourceHeapAllocation(const AllocationInfo& info,
                                                    uint64_t offset,
-                                                   ComPtr<ID3D12Resource> resource)
-        : ResourceMemoryAllocation(info, offset, nullptr), mResource(std::move(resource)) {
+                                                   ComPtr<ID3D12Resource> resource,
+                                                   Heap* heap)
+        : ResourceMemoryAllocation(info, offset, heap), mResource(std::move(resource)) {
+        ASSERT((info.mMethod == AllocationMethod::kExternal) == (heap == nullptr));
     }
 
     void ResourceHeapAllocation::Invalidate() {
diff --git a/src/dawn_native/d3d12/ResourceHeapAllocationD3D12.h b/src/dawn_native/d3d12/ResourceHeapAllocationD3D12.h
index d764a96..71b00fd 100644
--- a/src/dawn_native/d3d12/ResourceHeapAllocationD3D12.h
+++ b/src/dawn_native/d3d12/ResourceHeapAllocationD3D12.h
@@ -20,12 +20,15 @@
 
 namespace dawn_native { namespace d3d12 {
 
+    class Heap;
+
     class ResourceHeapAllocation : public ResourceMemoryAllocation {
       public:
         ResourceHeapAllocation() = default;
         ResourceHeapAllocation(const AllocationInfo& info,
                                uint64_t offset,
-                               ComPtr<ID3D12Resource> resource);
+                               ComPtr<ID3D12Resource> resource,
+                               Heap* heap);
         ~ResourceHeapAllocation() override = default;
 
         void Invalidate() override;
diff --git a/src/dawn_native/d3d12/TextureD3D12.cpp b/src/dawn_native/d3d12/TextureD3D12.cpp
index b07c3ed..84c0cb4 100644
--- a/src/dawn_native/d3d12/TextureD3D12.cpp
+++ b/src/dawn_native/d3d12/TextureD3D12.cpp
@@ -322,8 +322,11 @@
         mDxgiKeyedMutex = std::move(dxgiKeyedMutex);
 
         AllocationInfo info;
-        info.mMethod = AllocationMethod::kDirect;
-        mResourceAllocation = {info, 0, std::move(d3d12Resource)};
+        info.mMethod = AllocationMethod::kExternal;
+        // When creating the ResourceHeapAllocation, the resource heap is set to nullptr because the
+        // texture is owned externally. The texture's owning entity must remain responsible for
+        // memory management.
+        mResourceAllocation = {info, 0, std::move(d3d12Resource), nullptr};
 
         return {};
     }
@@ -370,8 +373,11 @@
                      ComPtr<ID3D12Resource> nativeTexture)
         : TextureBase(device, descriptor, TextureState::OwnedExternal) {
         AllocationInfo info;
-        info.mMethod = AllocationMethod::kDirect;
-        mResourceAllocation = {info, 0, std::move(nativeTexture)};
+        info.mMethod = AllocationMethod::kExternal;
+        // When creating the ResourceHeapAllocation, the resource heap is set to nullptr because the
+        // texture is owned externally. The texture's owning entity must remain responsible for
+        // memory management.
+        mResourceAllocation = {info, 0, std::move(nativeTexture), nullptr};
 
         SetIsSubresourceContentInitialized(true, 0, descriptor->mipLevelCount, 0,
                                            descriptor->arrayLayerCount);