1 // Copyright 2017 The Dawn Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #include "dawn_native/d3d12/BufferD3D12.h" 16 17 #include "common/Assert.h" 18 #include "common/Constants.h" 19 #include "common/Math.h" 20 #include "dawn_native/d3d12/CommandRecordingContext.h" 21 #include "dawn_native/d3d12/D3D12Error.h" 22 #include "dawn_native/d3d12/DeviceD3D12.h" 23 #include "dawn_native/d3d12/HeapD3D12.h" 24 #include "dawn_native/d3d12/ResidencyManagerD3D12.h" 25 26 namespace dawn_native { namespace d3d12 { 27 28 namespace { D3D12ResourceFlags(wgpu::BufferUsage usage)29 D3D12_RESOURCE_FLAGS D3D12ResourceFlags(wgpu::BufferUsage usage) { 30 D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FLAG_NONE; 31 32 if (usage & wgpu::BufferUsage::Storage) { 33 flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; 34 } 35 36 return flags; 37 } 38 D3D12BufferUsage(wgpu::BufferUsage usage)39 D3D12_RESOURCE_STATES D3D12BufferUsage(wgpu::BufferUsage usage) { 40 D3D12_RESOURCE_STATES resourceState = D3D12_RESOURCE_STATE_COMMON; 41 42 if (usage & wgpu::BufferUsage::CopySrc) { 43 resourceState |= D3D12_RESOURCE_STATE_COPY_SOURCE; 44 } 45 if (usage & wgpu::BufferUsage::CopyDst) { 46 resourceState |= D3D12_RESOURCE_STATE_COPY_DEST; 47 } 48 if (usage & (wgpu::BufferUsage::Vertex | wgpu::BufferUsage::Uniform)) { 49 resourceState |= D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER; 50 } 51 if (usage & wgpu::BufferUsage::Index) { 52 resourceState |= D3D12_RESOURCE_STATE_INDEX_BUFFER; 53 } 54 if (usage & wgpu::BufferUsage::Storage) { 55 resourceState |= D3D12_RESOURCE_STATE_UNORDERED_ACCESS; 56 } 57 if (usage & kReadOnlyStorage) { 58 resourceState |= (D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE | 59 D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); 60 } 61 if (usage & wgpu::BufferUsage::Indirect) { 62 resourceState |= D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT; 63 } 64 65 return resourceState; 66 } 67 D3D12HeapType(wgpu::BufferUsage allowedUsage)68 D3D12_HEAP_TYPE D3D12HeapType(wgpu::BufferUsage allowedUsage) { 69 if (allowedUsage & wgpu::BufferUsage::MapRead) { 70 return D3D12_HEAP_TYPE_READBACK; 71 } else if (allowedUsage & wgpu::BufferUsage::MapWrite) { 72 return D3D12_HEAP_TYPE_UPLOAD; 73 } else { 74 return D3D12_HEAP_TYPE_DEFAULT; 75 } 76 } 77 } // namespace 78 Buffer(Device * device,const BufferDescriptor * descriptor)79 Buffer::Buffer(Device* device, const BufferDescriptor* descriptor) 80 : BufferBase(device, descriptor) { 81 } 82 Initialize()83 MaybeError Buffer::Initialize() { 84 D3D12_RESOURCE_DESC resourceDescriptor; 85 resourceDescriptor.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; 86 resourceDescriptor.Alignment = 0; 87 resourceDescriptor.Width = GetSize(); 88 resourceDescriptor.Height = 1; 89 resourceDescriptor.DepthOrArraySize = 1; 90 resourceDescriptor.MipLevels = 1; 91 resourceDescriptor.Format = DXGI_FORMAT_UNKNOWN; 92 resourceDescriptor.SampleDesc.Count = 1; 93 resourceDescriptor.SampleDesc.Quality = 0; 94 resourceDescriptor.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; 95 // Add CopyDst for non-mappable buffer initialization in CreateBufferMapped 96 // and robust resource initialization. 97 resourceDescriptor.Flags = D3D12ResourceFlags(GetUsage() | wgpu::BufferUsage::CopyDst); 98 99 auto heapType = D3D12HeapType(GetUsage()); 100 auto bufferUsage = D3D12_RESOURCE_STATE_COMMON; 101 102 // D3D12 requires buffers on the READBACK heap to have the D3D12_RESOURCE_STATE_COPY_DEST 103 // state 104 if (heapType == D3D12_HEAP_TYPE_READBACK) { 105 bufferUsage |= D3D12_RESOURCE_STATE_COPY_DEST; 106 mFixedResourceState = true; 107 mLastUsage = wgpu::BufferUsage::CopyDst; 108 } 109 110 // D3D12 requires buffers on the UPLOAD heap to have the D3D12_RESOURCE_STATE_GENERIC_READ 111 // state 112 if (heapType == D3D12_HEAP_TYPE_UPLOAD) { 113 bufferUsage |= D3D12_RESOURCE_STATE_GENERIC_READ; 114 mFixedResourceState = true; 115 mLastUsage = wgpu::BufferUsage::CopySrc; 116 } 117 118 DAWN_TRY_ASSIGN( 119 mResourceAllocation, 120 ToBackend(GetDevice())->AllocateMemory(heapType, resourceDescriptor, bufferUsage)); 121 return {}; 122 } 123 ~Buffer()124 Buffer::~Buffer() { 125 DestroyInternal(); 126 } 127 GetD3D12Resource() const128 ComPtr<ID3D12Resource> Buffer::GetD3D12Resource() const { 129 return mResourceAllocation.GetD3D12Resource(); 130 } 131 132 // When true is returned, a D3D12_RESOURCE_BARRIER has been created and must be used in a 133 // ResourceBarrier call. Failing to do so will cause the tracked state to become invalid and can 134 // cause subsequent errors. TrackUsageAndGetResourceBarrier(CommandRecordingContext * commandContext,D3D12_RESOURCE_BARRIER * barrier,wgpu::BufferUsage newUsage)135 bool Buffer::TrackUsageAndGetResourceBarrier(CommandRecordingContext* commandContext, 136 D3D12_RESOURCE_BARRIER* barrier, 137 wgpu::BufferUsage newUsage) { 138 // Track the underlying heap to ensure residency. 139 Heap* heap = ToBackend(mResourceAllocation.GetResourceHeap()); 140 commandContext->TrackHeapUsage(heap, GetDevice()->GetPendingCommandSerial()); 141 142 // Return the resource barrier. 143 return TransitionUsageAndGetResourceBarrier(commandContext, barrier, newUsage); 144 } 145 TrackUsageAndTransitionNow(CommandRecordingContext * commandContext,wgpu::BufferUsage newUsage)146 void Buffer::TrackUsageAndTransitionNow(CommandRecordingContext* commandContext, 147 wgpu::BufferUsage newUsage) { 148 D3D12_RESOURCE_BARRIER barrier; 149 150 if (TrackUsageAndGetResourceBarrier(commandContext, &barrier, newUsage)) { 151 commandContext->GetCommandList()->ResourceBarrier(1, &barrier); 152 } 153 } 154 155 // When true is returned, a D3D12_RESOURCE_BARRIER has been created and must be used in a 156 // ResourceBarrier call. Failing to do so will cause the tracked state to become invalid and can 157 // cause subsequent errors. TransitionUsageAndGetResourceBarrier(CommandRecordingContext * commandContext,D3D12_RESOURCE_BARRIER * barrier,wgpu::BufferUsage newUsage)158 bool Buffer::TransitionUsageAndGetResourceBarrier(CommandRecordingContext* commandContext, 159 D3D12_RESOURCE_BARRIER* barrier, 160 wgpu::BufferUsage newUsage) { 161 // Resources in upload and readback heaps must be kept in the COPY_SOURCE/DEST state 162 if (mFixedResourceState) { 163 ASSERT(mLastUsage == newUsage); 164 return false; 165 } 166 167 D3D12_RESOURCE_STATES lastState = D3D12BufferUsage(mLastUsage); 168 D3D12_RESOURCE_STATES newState = D3D12BufferUsage(newUsage); 169 170 // If the transition is from-UAV-to-UAV, then a UAV barrier is needed. 171 // If one of the usages isn't UAV, then other barriers are used. 172 bool needsUAVBarrier = lastState == D3D12_RESOURCE_STATE_UNORDERED_ACCESS && 173 newState == D3D12_RESOURCE_STATE_UNORDERED_ACCESS; 174 175 if (needsUAVBarrier) { 176 barrier->Type = D3D12_RESOURCE_BARRIER_TYPE_UAV; 177 barrier->Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; 178 barrier->UAV.pResource = GetD3D12Resource().Get(); 179 180 mLastUsage = newUsage; 181 return true; 182 } 183 184 // We can skip transitions to already current usages. 185 if ((mLastUsage & newUsage) == newUsage) { 186 return false; 187 } 188 189 mLastUsage = newUsage; 190 191 // The COMMON state represents a state where no write operations can be pending, which makes 192 // it possible to transition to and from some states without synchronizaton (i.e. without an 193 // explicit ResourceBarrier call). A buffer can be implicitly promoted to 1) a single write 194 // state, or 2) multiple read states. A buffer that is accessed within a command list will 195 // always implicitly decay to the COMMON state after the call to ExecuteCommandLists 196 // completes - this is because all buffer writes are guaranteed to be completed before the 197 // next ExecuteCommandLists call executes. 198 // https://docs.microsoft.com/en-us/windows/desktop/direct3d12/using-resource-barriers-to-synchronize-resource-states-in-direct3d-12#implicit-state-transitions 199 200 // To track implicit decays, we must record the pending serial on which a transition will 201 // occur. When that buffer is used again, the previously recorded serial must be compared to 202 // the last completed serial to determine if the buffer has implicity decayed to the common 203 // state. 204 const Serial pendingCommandSerial = ToBackend(GetDevice())->GetPendingCommandSerial(); 205 if (pendingCommandSerial > mLastUsedSerial) { 206 lastState = D3D12_RESOURCE_STATE_COMMON; 207 mLastUsedSerial = pendingCommandSerial; 208 } 209 210 // All possible buffer states used by Dawn are eligible for implicit promotion from COMMON. 211 // These are: COPY_SOURCE, VERTEX_AND_COPY_BUFFER, INDEX_BUFFER, COPY_DEST, 212 // UNORDERED_ACCESS, and INDIRECT_ARGUMENT. Note that for implicit promotion, the 213 // destination state cannot be 1) more than one write state, or 2) both a read and write 214 // state. This goes unchecked here because it should not be allowed through render/compute 215 // pass validation. 216 if (lastState == D3D12_RESOURCE_STATE_COMMON) { 217 return false; 218 } 219 220 barrier->Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; 221 barrier->Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; 222 barrier->Transition.pResource = GetD3D12Resource().Get(); 223 barrier->Transition.StateBefore = lastState; 224 barrier->Transition.StateAfter = newState; 225 barrier->Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; 226 227 return true; 228 } 229 GetVA() const230 D3D12_GPU_VIRTUAL_ADDRESS Buffer::GetVA() const { 231 return mResourceAllocation.GetGPUPointer(); 232 } 233 OnMapCommandSerialFinished(uint32_t mapSerial,void * data,bool isWrite)234 void Buffer::OnMapCommandSerialFinished(uint32_t mapSerial, void* data, bool isWrite) { 235 if (isWrite) { 236 CallMapWriteCallback(mapSerial, WGPUBufferMapAsyncStatus_Success, data, GetSize()); 237 } else { 238 CallMapReadCallback(mapSerial, WGPUBufferMapAsyncStatus_Success, data, GetSize()); 239 } 240 } 241 IsMapWritable() const242 bool Buffer::IsMapWritable() const { 243 // TODO(enga): Handle CPU-visible memory on UMA 244 return (GetUsage() & (wgpu::BufferUsage::MapRead | wgpu::BufferUsage::MapWrite)) != 0; 245 } 246 MapAtCreationImpl(uint8_t ** mappedPointer)247 MaybeError Buffer::MapAtCreationImpl(uint8_t** mappedPointer) { 248 // The mapped buffer can be accessed at any time, so it must be locked to ensure it is never 249 // evicted. This buffer should already have been made resident when it was created. 250 Heap* heap = ToBackend(mResourceAllocation.GetResourceHeap()); 251 DAWN_TRY(ToBackend(GetDevice())->GetResidencyManager()->LockMappableHeap(heap)); 252 253 mWrittenMappedRange = {0, static_cast<size_t>(GetSize())}; 254 DAWN_TRY(CheckHRESULT(GetD3D12Resource()->Map(0, &mWrittenMappedRange, 255 reinterpret_cast<void**>(mappedPointer)), 256 "D3D12 map at creation")); 257 return {}; 258 } 259 MapReadAsyncImpl(uint32_t serial)260 MaybeError Buffer::MapReadAsyncImpl(uint32_t serial) { 261 // The mapped buffer can be accessed at any time, so we must make the buffer resident and 262 // lock it to ensure it is never evicted. 263 Heap* heap = ToBackend(mResourceAllocation.GetResourceHeap()); 264 DAWN_TRY(ToBackend(GetDevice())->GetResidencyManager()->LockMappableHeap(heap)); 265 266 mWrittenMappedRange = {}; 267 D3D12_RANGE readRange = {0, static_cast<size_t>(GetSize())}; 268 char* data = nullptr; 269 DAWN_TRY( 270 CheckHRESULT(GetD3D12Resource()->Map(0, &readRange, reinterpret_cast<void**>(&data)), 271 "D3D12 map read async")); 272 // There is no need to transition the resource to a new state: D3D12 seems to make the GPU 273 // writes available when the fence is passed. 274 MapRequestTracker* tracker = ToBackend(GetDevice())->GetMapRequestTracker(); 275 tracker->Track(this, serial, data, false); 276 return {}; 277 } 278 MapWriteAsyncImpl(uint32_t serial)279 MaybeError Buffer::MapWriteAsyncImpl(uint32_t serial) { 280 // The mapped buffer can be accessed at any time, so we must make the buffer resident and 281 // lock it to ensure it is never evicted. 282 Heap* heap = ToBackend(mResourceAllocation.GetResourceHeap()); 283 DAWN_TRY(ToBackend(GetDevice())->GetResidencyManager()->LockMappableHeap(heap)); 284 285 mWrittenMappedRange = {0, static_cast<size_t>(GetSize())}; 286 char* data = nullptr; 287 DAWN_TRY(CheckHRESULT( 288 GetD3D12Resource()->Map(0, &mWrittenMappedRange, reinterpret_cast<void**>(&data)), 289 "D3D12 map write async")); 290 // There is no need to transition the resource to a new state: D3D12 seems to make the CPU 291 // writes available on queue submission. 292 MapRequestTracker* tracker = ToBackend(GetDevice())->GetMapRequestTracker(); 293 tracker->Track(this, serial, data, true); 294 return {}; 295 } 296 UnmapImpl()297 void Buffer::UnmapImpl() { 298 GetD3D12Resource()->Unmap(0, &mWrittenMappedRange); 299 // When buffers are mapped, they are locked to keep them in resident memory. We must unlock 300 // them when they are unmapped. 301 Heap* heap = ToBackend(mResourceAllocation.GetResourceHeap()); 302 ToBackend(GetDevice())->GetResidencyManager()->UnlockMappableHeap(heap); 303 mWrittenMappedRange = {}; 304 } 305 DestroyImpl()306 void Buffer::DestroyImpl() { 307 // We must ensure that if a mapped buffer is destroyed, it does not leave a dangling lock 308 // reference on its heap. 309 if (IsMapped()) { 310 Heap* heap = ToBackend(mResourceAllocation.GetResourceHeap()); 311 ToBackend(GetDevice())->GetResidencyManager()->UnlockMappableHeap(heap); 312 } 313 314 ToBackend(GetDevice())->DeallocateMemory(mResourceAllocation); 315 } 316 MapRequestTracker(Device * device)317 MapRequestTracker::MapRequestTracker(Device* device) : mDevice(device) { 318 } 319 ~MapRequestTracker()320 MapRequestTracker::~MapRequestTracker() { 321 ASSERT(mInflightRequests.Empty()); 322 } 323 Track(Buffer * buffer,uint32_t mapSerial,void * data,bool isWrite)324 void MapRequestTracker::Track(Buffer* buffer, uint32_t mapSerial, void* data, bool isWrite) { 325 Request request; 326 request.buffer = buffer; 327 request.mapSerial = mapSerial; 328 request.data = data; 329 request.isWrite = isWrite; 330 331 mInflightRequests.Enqueue(std::move(request), mDevice->GetPendingCommandSerial()); 332 } 333 Tick(Serial finishedSerial)334 void MapRequestTracker::Tick(Serial finishedSerial) { 335 for (auto& request : mInflightRequests.IterateUpTo(finishedSerial)) { 336 request.buffer->OnMapCommandSerialFinished(request.mapSerial, request.data, 337 request.isWrite); 338 } 339 mInflightRequests.ClearUpTo(finishedSerial); 340 } 341 342 }} // namespace dawn_native::d3d12 343