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