• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/CommandAllocator.h"
16 
17 #include "common/Assert.h"
18 #include "common/Math.h"
19 
20 #include <algorithm>
21 #include <climits>
22 #include <cstdlib>
23 
24 namespace dawn_native {
25 
26     constexpr uint32_t EndOfBlock = UINT_MAX;          // std::numeric_limits<uint32_t>::max();
27     constexpr uint32_t AdditionalData = UINT_MAX - 1;  // std::numeric_limits<uint32_t>::max() - 1;
28 
29     // TODO(cwallez@chromium.org): figure out a way to have more type safety for the iterator
30 
CommandIterator()31     CommandIterator::CommandIterator() : mEndOfBlock(EndOfBlock) {
32         Reset();
33     }
34 
~CommandIterator()35     CommandIterator::~CommandIterator() {
36         ASSERT(mDataWasDestroyed);
37 
38         if (!IsEmpty()) {
39             for (auto& block : mBlocks) {
40                 free(block.block);
41             }
42         }
43     }
44 
CommandIterator(CommandIterator && other)45     CommandIterator::CommandIterator(CommandIterator&& other) : mEndOfBlock(EndOfBlock) {
46         if (!other.IsEmpty()) {
47             mBlocks = std::move(other.mBlocks);
48             other.Reset();
49         }
50         other.DataWasDestroyed();
51         Reset();
52     }
53 
operator =(CommandIterator && other)54     CommandIterator& CommandIterator::operator=(CommandIterator&& other) {
55         if (!other.IsEmpty()) {
56             mBlocks = std::move(other.mBlocks);
57             other.Reset();
58         } else {
59             mBlocks.clear();
60         }
61         other.DataWasDestroyed();
62         Reset();
63         return *this;
64     }
65 
CommandIterator(CommandAllocator && allocator)66     CommandIterator::CommandIterator(CommandAllocator&& allocator)
67         : mBlocks(allocator.AcquireBlocks()), mEndOfBlock(EndOfBlock) {
68         Reset();
69     }
70 
operator =(CommandAllocator && allocator)71     CommandIterator& CommandIterator::operator=(CommandAllocator&& allocator) {
72         mBlocks = allocator.AcquireBlocks();
73         Reset();
74         return *this;
75     }
76 
Reset()77     void CommandIterator::Reset() {
78         mCurrentBlock = 0;
79 
80         if (mBlocks.empty()) {
81             // This will case the first NextCommandId call to try to move to the next block and stop
82             // the iteration immediately, without special casing the initialization.
83             mCurrentPtr = reinterpret_cast<uint8_t*>(&mEndOfBlock);
84             mBlocks.emplace_back();
85             mBlocks[0].size = sizeof(mEndOfBlock);
86             mBlocks[0].block = mCurrentPtr;
87         } else {
88             mCurrentPtr = AlignPtr(mBlocks[0].block, alignof(uint32_t));
89         }
90     }
91 
DataWasDestroyed()92     void CommandIterator::DataWasDestroyed() {
93         mDataWasDestroyed = true;
94     }
95 
IsEmpty() const96     bool CommandIterator::IsEmpty() const {
97         return mBlocks[0].block == reinterpret_cast<const uint8_t*>(&mEndOfBlock);
98     }
99 
NextCommandId(uint32_t * commandId)100     bool CommandIterator::NextCommandId(uint32_t* commandId) {
101         uint8_t* idPtr = AlignPtr(mCurrentPtr, alignof(uint32_t));
102         ASSERT(idPtr + sizeof(uint32_t) <=
103                mBlocks[mCurrentBlock].block + mBlocks[mCurrentBlock].size);
104 
105         uint32_t id = *reinterpret_cast<uint32_t*>(idPtr);
106 
107         if (id == EndOfBlock) {
108             mCurrentBlock++;
109             if (mCurrentBlock >= mBlocks.size()) {
110                 Reset();
111                 *commandId = EndOfBlock;
112                 return false;
113             }
114             mCurrentPtr = AlignPtr(mBlocks[mCurrentBlock].block, alignof(uint32_t));
115             return NextCommandId(commandId);
116         }
117 
118         mCurrentPtr = idPtr + sizeof(uint32_t);
119         *commandId = id;
120         return true;
121     }
122 
NextCommand(size_t commandSize,size_t commandAlignment)123     void* CommandIterator::NextCommand(size_t commandSize, size_t commandAlignment) {
124         uint8_t* commandPtr = AlignPtr(mCurrentPtr, commandAlignment);
125         ASSERT(commandPtr + sizeof(commandSize) <=
126                mBlocks[mCurrentBlock].block + mBlocks[mCurrentBlock].size);
127 
128         mCurrentPtr = commandPtr + commandSize;
129         return commandPtr;
130     }
131 
NextData(size_t dataSize,size_t dataAlignment)132     void* CommandIterator::NextData(size_t dataSize, size_t dataAlignment) {
133         uint32_t id;
134         bool hasId = NextCommandId(&id);
135         ASSERT(hasId);
136         ASSERT(id == AdditionalData);
137 
138         return NextCommand(dataSize, dataAlignment);
139     }
140 
141     // Potential TODO(cwallez@chromium.org):
142     //  - Host the size and pointer to next block in the block itself to avoid having an allocation
143     //    in the vector
144     //  - Assume T's alignof is, say 64bits, static assert it, and make commandAlignment a constant
145     //    in Allocate
146     //  - Be able to optimize allocation to one block, for command buffers expected to live long to
147     //    avoid cache misses
148     //  - Better block allocation, maybe have Dawn API to say command buffer is going to have size
149     //    close to another
150 
CommandAllocator()151     CommandAllocator::CommandAllocator()
152         : mCurrentPtr(reinterpret_cast<uint8_t*>(&mDummyEnum[0])),
153           mEndPtr(reinterpret_cast<uint8_t*>(&mDummyEnum[1])) {
154     }
155 
~CommandAllocator()156     CommandAllocator::~CommandAllocator() {
157         ASSERT(mBlocks.empty());
158     }
159 
AcquireBlocks()160     CommandBlocks&& CommandAllocator::AcquireBlocks() {
161         ASSERT(mCurrentPtr != nullptr && mEndPtr != nullptr);
162         ASSERT(IsPtrAligned(mCurrentPtr, alignof(uint32_t)));
163         ASSERT(mCurrentPtr + sizeof(uint32_t) <= mEndPtr);
164         *reinterpret_cast<uint32_t*>(mCurrentPtr) = EndOfBlock;
165 
166         mCurrentPtr = nullptr;
167         mEndPtr = nullptr;
168         return std::move(mBlocks);
169     }
170 
Allocate(uint32_t commandId,size_t commandSize,size_t commandAlignment)171     uint8_t* CommandAllocator::Allocate(uint32_t commandId,
172                                         size_t commandSize,
173                                         size_t commandAlignment) {
174         ASSERT(mCurrentPtr != nullptr);
175         ASSERT(mEndPtr != nullptr);
176         ASSERT(commandId != EndOfBlock);
177 
178         // It should always be possible to allocate one id, for EndOfBlock tagging,
179         ASSERT(IsPtrAligned(mCurrentPtr, alignof(uint32_t)));
180         ASSERT(mEndPtr >= mCurrentPtr);
181         ASSERT(static_cast<size_t>(mEndPtr - mCurrentPtr) >= sizeof(uint32_t));
182 
183         // The memory after the ID will contain the following:
184         //   - the current ID
185         //   - padding to align the command, maximum kMaxSupportedAlignment
186         //   - the command of size commandSize
187         //   - padding to align the next ID, maximum alignof(uint32_t)
188         //   - the next ID of size sizeof(uint32_t)
189         //
190         // To avoid checking for overflows at every step of the computations we compute an upper
191         // bound of the space that will be needed in addition to the command data.
192         static constexpr size_t kWorstCaseAdditionalSize =
193             sizeof(uint32_t) + kMaxSupportedAlignment + alignof(uint32_t) + sizeof(uint32_t);
194 
195         // This can't overflow because by construction mCurrentPtr always has space for the next ID.
196         size_t remainingSize = static_cast<size_t>(mEndPtr - mCurrentPtr);
197 
198         // The good case were we have enough space for the command data and upper bound of the
199         // extra required space.
200         if ((remainingSize >= kWorstCaseAdditionalSize) &&
201             (remainingSize - kWorstCaseAdditionalSize >= commandSize)) {
202             uint32_t* idAlloc = reinterpret_cast<uint32_t*>(mCurrentPtr);
203             *idAlloc = commandId;
204 
205             uint8_t* commandAlloc = AlignPtr(mCurrentPtr + sizeof(uint32_t), commandAlignment);
206             mCurrentPtr = AlignPtr(commandAlloc + commandSize, alignof(uint32_t));
207 
208             return commandAlloc;
209         }
210 
211         // When there is not enough space, we signal the EndOfBlock, so that the iterator knows to
212         // move to the next one. EndOfBlock on the last block means the end of the commands.
213         uint32_t* idAlloc = reinterpret_cast<uint32_t*>(mCurrentPtr);
214         *idAlloc = EndOfBlock;
215 
216         // We'll request a block that can contain at least the command ID, the command and an
217         // additional ID to contain the EndOfBlock tag.
218         size_t requestedBlockSize = commandSize + kWorstCaseAdditionalSize;
219 
220         // The computation of the request could overflow.
221         if (DAWN_UNLIKELY(requestedBlockSize <= commandSize)) {
222             return nullptr;
223         }
224 
225         if (DAWN_UNLIKELY(!GetNewBlock(requestedBlockSize))) {
226             return nullptr;
227         }
228         return Allocate(commandId, commandSize, commandAlignment);
229     }
230 
AllocateData(size_t commandSize,size_t commandAlignment)231     uint8_t* CommandAllocator::AllocateData(size_t commandSize, size_t commandAlignment) {
232         return Allocate(AdditionalData, commandSize, commandAlignment);
233     }
234 
GetNewBlock(size_t minimumSize)235     bool CommandAllocator::GetNewBlock(size_t minimumSize) {
236         // Allocate blocks doubling sizes each time, to a maximum of 16k (or at least minimumSize).
237         mLastAllocationSize =
238             std::max(minimumSize, std::min(mLastAllocationSize * 2, size_t(16384)));
239 
240         uint8_t* block = static_cast<uint8_t*>(malloc(mLastAllocationSize));
241         if (DAWN_UNLIKELY(block == nullptr)) {
242             return false;
243         }
244 
245         mBlocks.push_back({mLastAllocationSize, block});
246         mCurrentPtr = AlignPtr(block, alignof(uint32_t));
247         mEndPtr = block + mLastAllocationSize;
248         return true;
249     }
250 
251 }  // namespace dawn_native
252