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