| // Copyright 2017 The Dawn Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #ifndef SRC_DAWN_NATIVE_COMMANDALLOCATOR_H_ |
| #define SRC_DAWN_NATIVE_COMMANDALLOCATOR_H_ |
| |
| #include <cstddef> |
| #include <cstdint> |
| #include <limits> |
| #include <vector> |
| |
| #include "dawn/common/Assert.h" |
| #include "dawn/common/Math.h" |
| #include "dawn/common/NonCopyable.h" |
| |
| namespace dawn::native { |
| |
| // Allocation for command buffers should be fast. To avoid doing an allocation per command |
| // or to avoid copying commands when reallocing, we use a linear allocator in a growing set |
| // of large memory blocks. We also use this to have the format to be (u32 commandId, command), |
| // so that iteration over the commands is easy. |
| |
| // Usage of the allocator and iterator: |
| // CommandAllocator allocator; |
| // DrawCommand* cmd = allocator.Allocate<DrawCommand>(CommandType::Draw); |
| // // Fill command |
| // // Repeat allocation and filling commands |
| // |
| // CommandIterator commands(allocator); |
| // CommandType type; |
| // while(commands.NextCommandId(&type)) { |
| // switch(type) { |
| // case CommandType::Draw: |
| // DrawCommand* draw = commands.NextCommand<DrawCommand>(); |
| // // Do the draw |
| // break; |
| // // other cases |
| // } |
| // } |
| |
| // Note that you need to extract the commands from the CommandAllocator before destroying it |
| // and must tell the CommandIterator when the allocated commands have been processed for |
| // deletion. |
| |
| // These are the lists of blocks, should not be used directly, only through CommandAllocator |
| // and CommandIterator |
| struct BlockDef { |
| size_t size; |
| uint8_t* block; |
| }; |
| using CommandBlocks = std::vector<BlockDef>; |
| |
| namespace detail { |
| constexpr uint32_t kEndOfBlock = std::numeric_limits<uint32_t>::max(); |
| constexpr uint32_t kAdditionalData = std::numeric_limits<uint32_t>::max() - 1; |
| } // namespace detail |
| |
| class CommandAllocator; |
| |
| class CommandIterator : public NonCopyable { |
| public: |
| CommandIterator(); |
| ~CommandIterator(); |
| |
| CommandIterator(CommandIterator&& other); |
| CommandIterator& operator=(CommandIterator&& other); |
| |
| // Shorthand constructor for acquiring CommandBlocks from a single CommandAllocator. |
| explicit CommandIterator(CommandAllocator allocator); |
| |
| void AcquireCommandBlocks(std::vector<CommandAllocator> allocators); |
| |
| template <typename E> |
| bool NextCommandId(E* commandId) { |
| return NextCommandId(reinterpret_cast<uint32_t*>(commandId)); |
| } |
| template <typename T> |
| T* NextCommand() { |
| return static_cast<T*>(NextCommand(sizeof(T), alignof(T))); |
| } |
| template <typename T> |
| T* NextData(size_t count) { |
| return static_cast<T*>(NextData(sizeof(T) * count, alignof(T))); |
| } |
| |
| // Sets iterator to the beginning of the commands without emptying the list. This method can |
| // be used if iteration was stopped early and the iterator needs to be restarted. |
| void Reset(); |
| |
| // This method must to be called after commands have been deleted. This indicates that the |
| // commands have been submitted and they are no longer valid. |
| void MakeEmptyAsDataWasDestroyed(); |
| |
| private: |
| bool IsEmpty() const; |
| |
| DAWN_FORCE_INLINE bool NextCommandId(uint32_t* commandId) { |
| uint8_t* idPtr = AlignPtr(mCurrentPtr, alignof(uint32_t)); |
| ASSERT(idPtr + sizeof(uint32_t) <= |
| mBlocks[mCurrentBlock].block + mBlocks[mCurrentBlock].size); |
| |
| uint32_t id = *reinterpret_cast<uint32_t*>(idPtr); |
| |
| if (id != detail::kEndOfBlock) { |
| mCurrentPtr = idPtr + sizeof(uint32_t); |
| *commandId = id; |
| return true; |
| } |
| return NextCommandIdInNewBlock(commandId); |
| } |
| |
| bool NextCommandIdInNewBlock(uint32_t* commandId); |
| |
| DAWN_FORCE_INLINE void* NextCommand(size_t commandSize, size_t commandAlignment) { |
| uint8_t* commandPtr = AlignPtr(mCurrentPtr, commandAlignment); |
| ASSERT(commandPtr + sizeof(commandSize) <= |
| mBlocks[mCurrentBlock].block + mBlocks[mCurrentBlock].size); |
| |
| mCurrentPtr = commandPtr + commandSize; |
| return commandPtr; |
| } |
| |
| DAWN_FORCE_INLINE void* NextData(size_t dataSize, size_t dataAlignment) { |
| uint32_t id; |
| bool hasId = NextCommandId(&id); |
| ASSERT(hasId); |
| ASSERT(id == detail::kAdditionalData); |
| |
| return NextCommand(dataSize, dataAlignment); |
| } |
| |
| CommandBlocks mBlocks; |
| uint8_t* mCurrentPtr = nullptr; |
| size_t mCurrentBlock = 0; |
| // Used to avoid a special case for empty iterators. |
| uint32_t mEndOfBlock = detail::kEndOfBlock; |
| }; |
| |
| class CommandAllocator : public NonCopyable { |
| public: |
| CommandAllocator(); |
| ~CommandAllocator(); |
| |
| // NOTE: A moved-from CommandAllocator is reset to its initial empty state. |
| CommandAllocator(CommandAllocator&&); |
| CommandAllocator& operator=(CommandAllocator&&); |
| |
| // Frees all blocks held by the allocator and restores it to its initial empty state. |
| void Reset(); |
| |
| bool IsEmpty() const; |
| |
| template <typename T, typename E> |
| T* Allocate(E commandId) { |
| static_assert(sizeof(E) == sizeof(uint32_t)); |
| static_assert(alignof(E) == alignof(uint32_t)); |
| static_assert(alignof(T) <= kMaxSupportedAlignment); |
| T* result = |
| reinterpret_cast<T*>(Allocate(static_cast<uint32_t>(commandId), sizeof(T), alignof(T))); |
| if (!result) { |
| return nullptr; |
| } |
| new (result) T; |
| return result; |
| } |
| |
| template <typename T> |
| T* AllocateData(size_t count) { |
| static_assert(alignof(T) <= kMaxSupportedAlignment); |
| T* result = reinterpret_cast<T*>(AllocateData(sizeof(T) * count, alignof(T))); |
| if (!result) { |
| return nullptr; |
| } |
| for (size_t i = 0; i < count; i++) { |
| new (result + i) T; |
| } |
| return result; |
| } |
| |
| private: |
| // This is used for some internal computations and can be any power of two as long as code |
| // using the CommandAllocator passes the static_asserts. |
| static constexpr size_t kMaxSupportedAlignment = 8; |
| |
| // To avoid checking for overflows at every step of the computations we compute an upper |
| // bound of the space that will be needed in addition to the command data. |
| static constexpr size_t kWorstCaseAdditionalSize = |
| sizeof(uint32_t) + kMaxSupportedAlignment + alignof(uint32_t) + sizeof(uint32_t); |
| |
| // The default value of mLastAllocationSize. |
| static constexpr size_t kDefaultBaseAllocationSize = 2048; |
| |
| friend CommandIterator; |
| CommandBlocks&& AcquireBlocks(); |
| |
| DAWN_FORCE_INLINE uint8_t* Allocate(uint32_t commandId, |
| size_t commandSize, |
| size_t commandAlignment) { |
| ASSERT(mCurrentPtr != nullptr); |
| ASSERT(mEndPtr != nullptr); |
| ASSERT(commandId != detail::kEndOfBlock); |
| |
| // It should always be possible to allocate one id, for kEndOfBlock tagging, |
| ASSERT(IsPtrAligned(mCurrentPtr, alignof(uint32_t))); |
| ASSERT(mEndPtr >= mCurrentPtr); |
| ASSERT(static_cast<size_t>(mEndPtr - mCurrentPtr) >= sizeof(uint32_t)); |
| |
| // The memory after the ID will contain the following: |
| // - the current ID |
| // - padding to align the command, maximum kMaxSupportedAlignment |
| // - the command of size commandSize |
| // - padding to align the next ID, maximum alignof(uint32_t) |
| // - the next ID of size sizeof(uint32_t) |
| |
| // This can't overflow because by construction mCurrentPtr always has space for the next |
| // ID. |
| size_t remainingSize = static_cast<size_t>(mEndPtr - mCurrentPtr); |
| |
| // The good case were we have enough space for the command data and upper bound of the |
| // extra required space. |
| if ((remainingSize >= kWorstCaseAdditionalSize) && |
| (remainingSize - kWorstCaseAdditionalSize >= commandSize)) { |
| uint32_t* idAlloc = reinterpret_cast<uint32_t*>(mCurrentPtr); |
| *idAlloc = commandId; |
| |
| uint8_t* commandAlloc = AlignPtr(mCurrentPtr + sizeof(uint32_t), commandAlignment); |
| mCurrentPtr = AlignPtr(commandAlloc + commandSize, alignof(uint32_t)); |
| |
| return commandAlloc; |
| } |
| return AllocateInNewBlock(commandId, commandSize, commandAlignment); |
| } |
| |
| uint8_t* AllocateInNewBlock(uint32_t commandId, size_t commandSize, size_t commandAlignment); |
| |
| DAWN_FORCE_INLINE uint8_t* AllocateData(size_t commandSize, size_t commandAlignment) { |
| return Allocate(detail::kAdditionalData, commandSize, commandAlignment); |
| } |
| |
| bool GetNewBlock(size_t minimumSize); |
| |
| void ResetPointers(); |
| |
| CommandBlocks mBlocks; |
| size_t mLastAllocationSize = kDefaultBaseAllocationSize; |
| |
| // Data used for the block range at initialization so that the first call to Allocate sees |
| // there is not enough space and calls GetNewBlock. This avoids having to special case the |
| // initialization in Allocate. |
| uint32_t mPlaceholderEnum[1] = {0}; |
| |
| // Pointers to the current range of allocation in the block. Guaranteed to allow for at |
| // least one uint32_t if not nullptr, so that the special kEndOfBlock command id can always |
| // be written. Nullptr iff the blocks were moved out. |
| uint8_t* mCurrentPtr = nullptr; |
| uint8_t* mEndPtr = nullptr; |
| }; |
| |
| } // namespace dawn::native |
| |
| #endif // SRC_DAWN_NATIVE_COMMANDALLOCATOR_H_ |