• 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 #ifndef DAWNNATIVE_COMMAND_ALLOCATOR_H_
16 #define DAWNNATIVE_COMMAND_ALLOCATOR_H_
17 
18 #include "common/Assert.h"
19 #include "common/Math.h"
20 #include "common/NonCopyable.h"
21 
22 #include <cstddef>
23 #include <cstdint>
24 #include <vector>
25 
26 namespace dawn_native {
27 
28     // Allocation for command buffers should be fast. To avoid doing an allocation per command
29     // or to avoid copying commands when reallocing, we use a linear allocator in a growing set
30     // of large memory blocks. We also use this to have the format to be (u32 commandId, command),
31     // so that iteration over the commands is easy.
32 
33     // Usage of the allocator and iterator:
34     //     CommandAllocator allocator;
35     //     DrawCommand* cmd = allocator.Allocate<DrawCommand>(CommandType::Draw);
36     //     // Fill command
37     //     // Repeat allocation and filling commands
38     //
39     //     CommandIterator commands(allocator);
40     //     CommandType type;
41     //     while(commands.NextCommandId(&type)) {
42     //         switch(type) {
43     //              case CommandType::Draw:
44     //                  DrawCommand* draw = commands.NextCommand<DrawCommand>();
45     //                  // Do the draw
46     //                  break;
47     //              // other cases
48     //         }
49     //     }
50 
51     // Note that you need to extract the commands from the CommandAllocator before destroying it
52     // and must tell the CommandIterator when the allocated commands have been processed for
53     // deletion.
54 
55     // These are the lists of blocks, should not be used directly, only through CommandAllocator
56     // and CommandIterator
57     struct BlockDef {
58         size_t size;
59         uint8_t* block;
60     };
61     using CommandBlocks = std::vector<BlockDef>;
62 
63     namespace detail {
64         constexpr uint32_t kEndOfBlock = std::numeric_limits<uint32_t>::max();
65         constexpr uint32_t kAdditionalData = std::numeric_limits<uint32_t>::max() - 1;
66     }  // namespace detail
67 
68     class CommandAllocator;
69 
70     class CommandIterator : public NonCopyable {
71       public:
72         CommandIterator();
73         ~CommandIterator();
74 
75         CommandIterator(CommandIterator&& other);
76         CommandIterator& operator=(CommandIterator&& other);
77 
78         // Shorthand constructor for acquiring CommandBlocks from a single CommandAllocator.
79         explicit CommandIterator(CommandAllocator allocator);
80 
81         void AcquireCommandBlocks(std::vector<CommandAllocator> allocators);
82 
83         template <typename E>
NextCommandId(E * commandId)84         bool NextCommandId(E* commandId) {
85             return NextCommandId(reinterpret_cast<uint32_t*>(commandId));
86         }
87         template <typename T>
NextCommand()88         T* NextCommand() {
89             return static_cast<T*>(NextCommand(sizeof(T), alignof(T)));
90         }
91         template <typename T>
NextData(size_t count)92         T* NextData(size_t count) {
93             return static_cast<T*>(NextData(sizeof(T) * count, alignof(T)));
94         }
95 
96         // Sets iterator to the beginning of the commands without emptying the list. This method can
97         // be used if iteration was stopped early and the iterator needs to be restarted.
98         void Reset();
99 
100         // This method must to be called after commands have been deleted. This indicates that the
101         // commands have been submitted and they are no longer valid.
102         void MakeEmptyAsDataWasDestroyed();
103 
104       private:
105         bool IsEmpty() const;
106 
NextCommandId(uint32_t * commandId)107         DAWN_FORCE_INLINE bool NextCommandId(uint32_t* commandId) {
108             uint8_t* idPtr = AlignPtr(mCurrentPtr, alignof(uint32_t));
109             ASSERT(idPtr + sizeof(uint32_t) <=
110                    mBlocks[mCurrentBlock].block + mBlocks[mCurrentBlock].size);
111 
112             uint32_t id = *reinterpret_cast<uint32_t*>(idPtr);
113 
114             if (id != detail::kEndOfBlock) {
115                 mCurrentPtr = idPtr + sizeof(uint32_t);
116                 *commandId = id;
117                 return true;
118             }
119             return NextCommandIdInNewBlock(commandId);
120         }
121 
122         bool NextCommandIdInNewBlock(uint32_t* commandId);
123 
NextCommand(size_t commandSize,size_t commandAlignment)124         DAWN_FORCE_INLINE void* NextCommand(size_t commandSize, size_t commandAlignment) {
125             uint8_t* commandPtr = AlignPtr(mCurrentPtr, commandAlignment);
126             ASSERT(commandPtr + sizeof(commandSize) <=
127                    mBlocks[mCurrentBlock].block + mBlocks[mCurrentBlock].size);
128 
129             mCurrentPtr = commandPtr + commandSize;
130             return commandPtr;
131         }
132 
NextData(size_t dataSize,size_t dataAlignment)133         DAWN_FORCE_INLINE void* NextData(size_t dataSize, size_t dataAlignment) {
134             uint32_t id;
135             bool hasId = NextCommandId(&id);
136             ASSERT(hasId);
137             ASSERT(id == detail::kAdditionalData);
138 
139             return NextCommand(dataSize, dataAlignment);
140         }
141 
142         CommandBlocks mBlocks;
143         uint8_t* mCurrentPtr = nullptr;
144         size_t mCurrentBlock = 0;
145         // Used to avoid a special case for empty iterators.
146         uint32_t mEndOfBlock = detail::kEndOfBlock;
147     };
148 
149     class CommandAllocator : public NonCopyable {
150       public:
151         CommandAllocator();
152         ~CommandAllocator();
153 
154         // NOTE: A moved-from CommandAllocator is reset to its initial empty state.
155         CommandAllocator(CommandAllocator&&);
156         CommandAllocator& operator=(CommandAllocator&&);
157 
158         // Frees all blocks held by the allocator and restores it to its initial empty state.
159         void Reset();
160 
161         bool IsEmpty() const;
162 
163         template <typename T, typename E>
Allocate(E commandId)164         T* Allocate(E commandId) {
165             static_assert(sizeof(E) == sizeof(uint32_t), "");
166             static_assert(alignof(E) == alignof(uint32_t), "");
167             static_assert(alignof(T) <= kMaxSupportedAlignment, "");
168             T* result = reinterpret_cast<T*>(
169                 Allocate(static_cast<uint32_t>(commandId), sizeof(T), alignof(T)));
170             if (!result) {
171                 return nullptr;
172             }
173             new (result) T;
174             return result;
175         }
176 
177         template <typename T>
AllocateData(size_t count)178         T* AllocateData(size_t count) {
179             static_assert(alignof(T) <= kMaxSupportedAlignment, "");
180             T* result = reinterpret_cast<T*>(AllocateData(sizeof(T) * count, alignof(T)));
181             if (!result) {
182                 return nullptr;
183             }
184             for (size_t i = 0; i < count; i++) {
185                 new (result + i) T;
186             }
187             return result;
188         }
189 
190       private:
191         // This is used for some internal computations and can be any power of two as long as code
192         // using the CommandAllocator passes the static_asserts.
193         static constexpr size_t kMaxSupportedAlignment = 8;
194 
195         // To avoid checking for overflows at every step of the computations we compute an upper
196         // bound of the space that will be needed in addition to the command data.
197         static constexpr size_t kWorstCaseAdditionalSize =
198             sizeof(uint32_t) + kMaxSupportedAlignment + alignof(uint32_t) + sizeof(uint32_t);
199 
200         // The default value of mLastAllocationSize.
201         static constexpr size_t kDefaultBaseAllocationSize = 2048;
202 
203         friend CommandIterator;
204         CommandBlocks&& AcquireBlocks();
205 
Allocate(uint32_t commandId,size_t commandSize,size_t commandAlignment)206         DAWN_FORCE_INLINE uint8_t* Allocate(uint32_t commandId,
207                                             size_t commandSize,
208                                             size_t commandAlignment) {
209             ASSERT(mCurrentPtr != nullptr);
210             ASSERT(mEndPtr != nullptr);
211             ASSERT(commandId != detail::kEndOfBlock);
212 
213             // It should always be possible to allocate one id, for kEndOfBlock tagging,
214             ASSERT(IsPtrAligned(mCurrentPtr, alignof(uint32_t)));
215             ASSERT(mEndPtr >= mCurrentPtr);
216             ASSERT(static_cast<size_t>(mEndPtr - mCurrentPtr) >= sizeof(uint32_t));
217 
218             // The memory after the ID will contain the following:
219             //   - the current ID
220             //   - padding to align the command, maximum kMaxSupportedAlignment
221             //   - the command of size commandSize
222             //   - padding to align the next ID, maximum alignof(uint32_t)
223             //   - the next ID of size sizeof(uint32_t)
224 
225             // This can't overflow because by construction mCurrentPtr always has space for the next
226             // ID.
227             size_t remainingSize = static_cast<size_t>(mEndPtr - mCurrentPtr);
228 
229             // The good case were we have enough space for the command data and upper bound of the
230             // extra required space.
231             if ((remainingSize >= kWorstCaseAdditionalSize) &&
232                 (remainingSize - kWorstCaseAdditionalSize >= commandSize)) {
233                 uint32_t* idAlloc = reinterpret_cast<uint32_t*>(mCurrentPtr);
234                 *idAlloc = commandId;
235 
236                 uint8_t* commandAlloc = AlignPtr(mCurrentPtr + sizeof(uint32_t), commandAlignment);
237                 mCurrentPtr = AlignPtr(commandAlloc + commandSize, alignof(uint32_t));
238 
239                 return commandAlloc;
240             }
241             return AllocateInNewBlock(commandId, commandSize, commandAlignment);
242         }
243 
244         uint8_t* AllocateInNewBlock(uint32_t commandId,
245                                     size_t commandSize,
246                                     size_t commandAlignment);
247 
AllocateData(size_t commandSize,size_t commandAlignment)248         DAWN_FORCE_INLINE uint8_t* AllocateData(size_t commandSize, size_t commandAlignment) {
249             return Allocate(detail::kAdditionalData, commandSize, commandAlignment);
250         }
251 
252         bool GetNewBlock(size_t minimumSize);
253 
254         void ResetPointers();
255 
256         CommandBlocks mBlocks;
257         size_t mLastAllocationSize = kDefaultBaseAllocationSize;
258 
259         // Data used for the block range at initialization so that the first call to Allocate sees
260         // there is not enough space and calls GetNewBlock. This avoids having to special case the
261         // initialization in Allocate.
262         uint32_t mDummyEnum[1] = {0};
263 
264         // Pointers to the current range of allocation in the block. Guaranteed to allow for at
265         // least one uint32_t if not nullptr, so that the special kEndOfBlock command id can always
266         // be written. Nullptr iff the blocks were moved out.
267         uint8_t* mCurrentPtr = nullptr;
268         uint8_t* mEndPtr = nullptr;
269     };
270 
271 }  // namespace dawn_native
272 
273 #endif  // DAWNNATIVE_COMMAND_ALLOCATOR_H_
274