// 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 DAWNNATIVE_COMMAND_ALLOCATOR_H_ #define DAWNNATIVE_COMMAND_ALLOCATOR_H_ #include #include #include 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(CommandType::Draw); // // Fill command // // Repeat allocation and filling commands // // CommandIterator commands(allocator); // CommandType type; // void* command; // while(commands.NextCommandId(&e)) { // switch(e) { // case CommandType::Draw: // DrawCommand* draw = commands.NextCommand(); // // 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; class CommandAllocator; // TODO(cwallez@chromium.org): prevent copy for both iterator and allocator class CommandIterator { public: CommandIterator(); ~CommandIterator(); CommandIterator(CommandIterator&& other); CommandIterator& operator=(CommandIterator&& other); CommandIterator(CommandAllocator&& allocator); CommandIterator& operator=(CommandAllocator&& allocator); template bool NextCommandId(E* commandId) { return NextCommandId(reinterpret_cast(commandId)); } template T* NextCommand() { return reinterpret_cast(NextCommand(sizeof(T), alignof(T))); } template T* NextData(size_t count) { return reinterpret_cast(NextData(sizeof(T) * count, alignof(T))); } // Needs to be called if iteration was stopped early. void Reset(); void DataWasDestroyed(); private: bool IsEmpty() const; bool NextCommandId(uint32_t* commandId); void* NextCommand(size_t commandSize, size_t commandAlignment); void* NextData(size_t dataSize, size_t dataAlignment); CommandBlocks mBlocks; uint8_t* mCurrentPtr = nullptr; size_t mCurrentBlock = 0; // Used to avoid a special case for empty iterators. uint32_t mEndOfBlock; bool mDataWasDestroyed = false; }; class CommandAllocator { public: CommandAllocator(); ~CommandAllocator(); template T* Allocate(E commandId) { static_assert(sizeof(E) == sizeof(uint32_t), ""); static_assert(alignof(E) == alignof(uint32_t), ""); return reinterpret_cast( Allocate(static_cast(commandId), sizeof(T), alignof(T))); } template T* AllocateData(size_t count) { return reinterpret_cast(AllocateData(sizeof(T) * count, alignof(T))); } private: friend CommandIterator; CommandBlocks&& AcquireBlocks(); uint8_t* Allocate(uint32_t commandId, size_t commandSize, size_t commandAlignment); uint8_t* AllocateData(size_t dataSize, size_t dataAlignment); bool GetNewBlock(size_t minimumSize); CommandBlocks mBlocks; size_t mLastAllocationSize = 2048; // Pointers to the current range of allocation in the block. Guaranteed to allow for at // least one uint32_t is not nullptr, so that the special EndOfBlock command id can always // be written. Nullptr iff the blocks were moved out. uint8_t* mCurrentPtr = nullptr; uint8_t* mEndPtr = nullptr; // 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 mDummyEnum[1] = {0}; }; } // namespace dawn_native #endif // DAWNNATIVE_COMMAND_ALLOCATOR_H_