d3d12_descriptor_heap_manager.h (7622B)
1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com> 2 // SPDX-License-Identifier: (GPL-3.0 OR PolyForm-Strict-1.0.0) 3 4 #pragma once 5 6 #include "common/hash_combine.h" 7 #include "common/types.h" 8 #include "common/windows_headers.h" 9 10 #include <bitset> 11 #include <cstring> 12 #include <d3d12.h> 13 #include <unordered_map> 14 #include <vector> 15 #include <wrl/client.h> 16 17 class Error; 18 19 // This class provides an abstraction for D3D12 descriptor heaps. 20 struct D3D12DescriptorHandle final 21 { 22 enum : u32 23 { 24 INVALID_INDEX = 0xFFFFFFFF 25 }; 26 27 D3D12_CPU_DESCRIPTOR_HANDLE cpu_handle{}; 28 D3D12_GPU_DESCRIPTOR_HANDLE gpu_handle{}; 29 u32 index = INVALID_INDEX; 30 31 ALWAYS_INLINE operator bool() const { return index != INVALID_INDEX; } 32 33 ALWAYS_INLINE operator D3D12_CPU_DESCRIPTOR_HANDLE() const { return cpu_handle; } 34 ALWAYS_INLINE operator D3D12_GPU_DESCRIPTOR_HANDLE() const { return gpu_handle; } 35 36 ALWAYS_INLINE bool operator==(const D3D12DescriptorHandle& rhs) const { return (index == rhs.index); } 37 ALWAYS_INLINE bool operator!=(const D3D12DescriptorHandle& rhs) const { return (index != rhs.index); } 38 ALWAYS_INLINE bool operator<(const D3D12DescriptorHandle& rhs) const { return (index < rhs.index); } 39 ALWAYS_INLINE bool operator<=(const D3D12DescriptorHandle& rhs) const { return (index <= rhs.index); } 40 ALWAYS_INLINE bool operator>(const D3D12DescriptorHandle& rhs) const { return (index > rhs.index); } 41 ALWAYS_INLINE bool operator>=(const D3D12DescriptorHandle& rhs) const { return (index >= rhs.index); } 42 43 ALWAYS_INLINE void Clear() 44 { 45 cpu_handle = {}; 46 gpu_handle = {}; 47 index = INVALID_INDEX; 48 } 49 }; 50 51 class D3D12DescriptorHeapManager final 52 { 53 public: 54 D3D12DescriptorHeapManager(); 55 ~D3D12DescriptorHeapManager(); 56 57 ID3D12DescriptorHeap* GetDescriptorHeap() const { return m_descriptor_heap.Get(); } 58 u32 GetDescriptorIncrementSize() const { return m_descriptor_increment_size; } 59 60 bool Create(ID3D12Device* device, D3D12_DESCRIPTOR_HEAP_TYPE type, u32 num_descriptors, bool shader_visible, 61 Error* error); 62 void Destroy(); 63 64 bool Allocate(D3D12DescriptorHandle* handle); 65 void Free(D3D12DescriptorHandle* handle); 66 void Free(u32 index); 67 68 private: 69 Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> m_descriptor_heap; 70 u32 m_num_descriptors = 0; 71 u32 m_descriptor_increment_size = 0; 72 bool m_shader_visible = false; 73 74 D3D12_CPU_DESCRIPTOR_HANDLE m_heap_base_cpu = {}; 75 D3D12_GPU_DESCRIPTOR_HANDLE m_heap_base_gpu = {}; 76 77 static constexpr u32 BITSET_SIZE = 1024; 78 using BitSetType = std::bitset<BITSET_SIZE>; 79 std::vector<BitSetType> m_free_slots = {}; 80 }; 81 82 class D3D12DescriptorAllocator 83 { 84 public: 85 D3D12DescriptorAllocator(); 86 ~D3D12DescriptorAllocator(); 87 88 ALWAYS_INLINE ID3D12DescriptorHeap* GetDescriptorHeap() const { return m_descriptor_heap.Get(); } 89 ALWAYS_INLINE u32 GetDescriptorIncrementSize() const { return m_descriptor_increment_size; } 90 91 bool Create(ID3D12Device* device, D3D12_DESCRIPTOR_HEAP_TYPE type, u32 num_descriptors, Error* error); 92 void Destroy(); 93 94 bool Allocate(u32 num_handles, D3D12DescriptorHandle* out_base_handle); 95 void Reset(); 96 97 private: 98 Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> m_descriptor_heap; 99 u32 m_descriptor_increment_size = 0; 100 u32 m_num_descriptors = 0; 101 u32 m_current_offset = 0; 102 103 D3D12_CPU_DESCRIPTOR_HANDLE m_heap_base_cpu = {}; 104 D3D12_GPU_DESCRIPTOR_HANDLE m_heap_base_gpu = {}; 105 }; 106 107 template<u32 NumSamplers> 108 class D3D12GroupedSamplerAllocator : private D3D12DescriptorAllocator 109 { 110 struct Key 111 { 112 u32 idx[NumSamplers]; 113 114 ALWAYS_INLINE bool operator==(const Key& rhs) const { return (std::memcmp(idx, rhs.idx, sizeof(idx)) == 0); } 115 ALWAYS_INLINE bool operator!=(const Key& rhs) const { return (std::memcmp(idx, rhs.idx, sizeof(idx)) != 0); } 116 }; 117 118 struct KeyHash 119 { 120 ALWAYS_INLINE std::size_t operator()(const Key& key) const 121 { 122 size_t seed = 0; 123 for (u32 i : key.idx) 124 hash_combine(seed, i); 125 return seed; 126 } 127 }; 128 129 public: 130 D3D12GroupedSamplerAllocator(); 131 ~D3D12GroupedSamplerAllocator(); 132 133 using D3D12DescriptorAllocator::GetDescriptorHeap; 134 using D3D12DescriptorAllocator::GetDescriptorIncrementSize; 135 136 bool Create(ID3D12Device* device, u32 num_descriptors, Error* error); 137 void Destroy(); 138 139 bool LookupSingle(ID3D12Device* device, D3D12DescriptorHandle* gpu_handle, const D3D12DescriptorHandle& cpu_handle); 140 bool LookupGroup(ID3D12Device* device, D3D12DescriptorHandle* gpu_handle, const D3D12DescriptorHandle* cpu_handles); 141 142 // Clears cache but doesn't reset allocator. 143 void InvalidateCache(); 144 145 void Reset(); 146 bool ShouldReset() const; 147 148 private: 149 std::unordered_map<Key, D3D12DescriptorHandle, KeyHash> m_groups; 150 }; 151 152 template<u32 NumSamplers> 153 D3D12GroupedSamplerAllocator<NumSamplers>::D3D12GroupedSamplerAllocator() = default; 154 155 template<u32 NumSamplers> 156 D3D12GroupedSamplerAllocator<NumSamplers>::~D3D12GroupedSamplerAllocator() = default; 157 158 template<u32 NumSamplers> 159 bool D3D12GroupedSamplerAllocator<NumSamplers>::Create(ID3D12Device* device, u32 num_descriptors, Error* error) 160 { 161 return D3D12DescriptorAllocator::Create(device, D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, num_descriptors, error); 162 } 163 164 template<u32 NumSamplers> 165 void D3D12GroupedSamplerAllocator<NumSamplers>::Destroy() 166 { 167 D3D12DescriptorAllocator::Destroy(); 168 } 169 170 template<u32 NumSamplers> 171 void D3D12GroupedSamplerAllocator<NumSamplers>::Reset() 172 { 173 m_groups.clear(); 174 D3D12DescriptorAllocator::Reset(); 175 } 176 177 template<u32 NumSamplers> 178 void D3D12GroupedSamplerAllocator<NumSamplers>::InvalidateCache() 179 { 180 m_groups.clear(); 181 } 182 183 template<u32 NumSamplers> 184 bool D3D12GroupedSamplerAllocator<NumSamplers>::LookupSingle(ID3D12Device* device, D3D12DescriptorHandle* gpu_handle, 185 const D3D12DescriptorHandle& cpu_handle) 186 { 187 Key key; 188 key.idx[0] = cpu_handle.index; 189 for (u32 i = 1; i < NumSamplers; i++) 190 key.idx[i] = 0; 191 192 auto it = m_groups.find(key); 193 if (it != m_groups.end()) 194 { 195 *gpu_handle = it->second; 196 return true; 197 } 198 199 if (!Allocate(1, gpu_handle)) 200 return false; 201 202 device->CopyDescriptorsSimple(1, *gpu_handle, cpu_handle, D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER); 203 m_groups.emplace(key, *gpu_handle); 204 return true; 205 } 206 207 template<u32 NumSamplers> 208 bool D3D12GroupedSamplerAllocator<NumSamplers>::LookupGroup(ID3D12Device* device, D3D12DescriptorHandle* gpu_handle, 209 const D3D12DescriptorHandle* cpu_handles) 210 { 211 Key key; 212 for (u32 i = 0; i < NumSamplers; i++) 213 key.idx[i] = cpu_handles[i].index; 214 215 auto it = m_groups.find(key); 216 if (it != m_groups.end()) 217 { 218 *gpu_handle = it->second; 219 return true; 220 } 221 222 if (!Allocate(NumSamplers, gpu_handle)) 223 return false; 224 225 D3D12_CPU_DESCRIPTOR_HANDLE dst_handle = *gpu_handle; 226 UINT dst_size = NumSamplers; 227 D3D12_CPU_DESCRIPTOR_HANDLE src_handles[NumSamplers]; 228 UINT src_sizes[NumSamplers]; 229 for (u32 i = 0; i < NumSamplers; i++) 230 { 231 src_handles[i] = cpu_handles[i]; 232 src_sizes[i] = 1; 233 } 234 device->CopyDescriptors(1, &dst_handle, &dst_size, NumSamplers, src_handles, src_sizes, 235 D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER); 236 237 m_groups.emplace(key, *gpu_handle); 238 return true; 239 } 240 241 template<u32 NumSamplers> 242 bool D3D12GroupedSamplerAllocator<NumSamplers>::ShouldReset() const 243 { 244 // We only reset the sampler heap if more than half of the descriptors are used. 245 // This saves descriptor copying when there isn't a large number of sampler configs per frame. 246 return m_groups.size() >= (D3D12_MAX_SHADER_VISIBLE_SAMPLER_HEAP_SIZE / 2); 247 }