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