blob: 7c1599b7715f7192fd664558bfb0f8f7c8ab4d69 [file]
// Copyright 2017 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef SRC_DAWN_NATIVE_COMMANDALLOCATOR_H_
#define SRC_DAWN_NATIVE_COMMANDALLOCATOR_H_
#include <array>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <memory>
#include <string_view>
#include <vector>
#include "partition_alloc/pointers/raw_ptr_exclusion.h"
#include "src/dawn/common/Math.h"
#include "src/utils/assert.h"
#include "src/utils/compiler.h"
#include "src/utils/heap_array.h"
#include "src/utils/non_copyable.h"
#include "src/utils/span.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.
// TODO(crbug.com/491867541): Move Ref<T>s out of the commands and onto the CommandEncoder instead,
// so that we don't need to walk all the commands to free the Refs.
// These are the lists of blocks, should not be used directly, only through CommandAllocator
// and CommandIterator
using BlockDef = HeapArray<std::byte>;
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
// The alignment used for all allocations in the command allocator. It is constant to help avoid
// complexity to handle variable alignment.
static inline constexpr size_t kMaxAllocatedCommandAlignment = sizeof(uint64_t);
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>
requires(std::is_same_v<UnderlyingType<E>, uint32_t>)
bool NextCommandId(E* commandId) {
std::optional<uint32_t> id = NextCommandId();
*reinterpret_cast<uint32_t*>(commandId) = id.value_or(detail::kEndOfBlock);
return id.has_value();
}
template <typename T>
requires(alignof(T) <= kMaxAllocatedCommandAlignment)
T* NextCommand() {
return reinterpret_cast<T*>(NextCommand(sizeof(T)).data());
}
template <typename T, typename Index>
requires(alignof(T) <= kMaxAllocatedCommandAlignment &&
(std::is_same_v<Index, size_t> || !std::is_integral_v<Index>))
ityp::span<Index, const T> NextData(Index count) {
return ReinterpretSpan<const T, Index>(NextData(sizeof(T) * count));
}
// 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 std::optional<uint32_t> NextCommandId() {
DAWN_ASSERT(IsPtrAligned(mCurrentBlock.data(), kMaxAllocatedCommandAlignment));
DAWN_ASSERT(mCurrentBlock.size() >= sizeof(uint32_t));
uint32_t id = *reinterpret_cast<uint32_t*>(mCurrentBlock.data());
if (id != detail::kEndOfBlock) {
mCurrentBlock = mCurrentBlock.subspan(kMaxAllocatedCommandAlignment);
return {id};
}
return NextCommandIdInNewBlock();
}
std::optional<uint32_t> NextCommandIdInNewBlock();
DAWN_FORCE_INLINE Span<std::byte> NextCommand(size_t commandSize) {
DAWN_ASSERT(IsPtrAligned(mCurrentBlock.data(), kMaxAllocatedCommandAlignment));
size_t alignedSize = Align(commandSize, kMaxAllocatedCommandAlignment);
return mCurrentBlock.TakeFirst(alignedSize).first(commandSize);
}
DAWN_FORCE_INLINE Span<std::byte> NextData(size_t dataSize) {
std::optional<uint32_t> id = NextCommandId();
DAWN_ASSERT(id.has_value());
DAWN_ASSERT(id.value() == detail::kAdditionalData);
return NextCommand(dataSize);
}
CommandBlocks mBlocks;
size_t mCurrentBlockIndex = 0;
// RAW_PTR_EXCLUSION: This is an extremely hot span during command iteration, but always points
// to at least a valid uint32_t, either inside a block, or at mEndOfBlock.
RAW_PTR_EXCLUSION Span<std::byte> mCurrentBlock;
// Used to avoid a special case for empty iterators.
alignas(kMaxAllocatedCommandAlignment) 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>
requires(std::is_same_v<UnderlyingType<E>, uint32_t> &&
alignof(T) <= kMaxAllocatedCommandAlignment)
T* Allocate(E commandId) {
Span<std::byte> allocation = Allocate(static_cast<uint32_t>(commandId), sizeof(T));
if (allocation.empty()) {
return nullptr;
}
T* result = reinterpret_cast<T*>(allocation.data());
new (result) T;
return result;
}
template <typename T>
requires(alignof(T) <= kMaxAllocatedCommandAlignment)
T* AllocateData(size_t count) {
Span<std::byte> allocation = AllocateData(sizeof(T) * count);
if (allocation.empty()) {
return nullptr;
}
Span<T> results = ReinterpretSpan<T>(allocation);
for (auto& value : results) {
new (&value) T;
}
return results.data();
}
size_t GetCommandBlocksCount() const;
private:
// 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 =
kMaxAllocatedCommandAlignment + // The CommandID + padding before the command.
kMaxAllocatedCommandAlignment + // The padding after the command data.
sizeof(uint32_t); // The EndOfBlock tag.
// The default value of mLastAllocationSize.
static constexpr size_t kDefaultBaseAllocationSize = 2048;
friend CommandIterator;
CommandBlocks&& AcquireBlocks();
DAWN_FORCE_INLINE Span<std::byte> Allocate(uint32_t commandId, size_t commandSize) {
DAWN_ASSERT(!mCurrentBlock.empty());
DAWN_ASSERT(commandId != detail::kEndOfBlock);
// It should always be possible to allocate one id, for kEndOfBlock tagging,
DAWN_ASSERT(IsPtrAligned(mCurrentBlock.data(), kMaxAllocatedCommandAlignment));
DAWN_ASSERT(mCurrentBlock.size() >= sizeof(uint32_t));
// The memory after the ID will contain the following:
// - the current ID
// - padding to align the command to kMaxAllocatedCommandAlignment
// - the command of size commandSize
// - padding to align the next ID to kMaxAllocatedCommandAlignment
// - the next ID of size sizeof(uint32_t)
// The good case were we have enough space for the command data and upper bound of the
// extra required space.
if ((mCurrentBlock.size() >= kWorstCaseAdditionalSize) &&
(mCurrentBlock.size() - kWorstCaseAdditionalSize >= commandSize)) {
Span<std::byte> idAlloc = mCurrentBlock.TakeFirst(kMaxAllocatedCommandAlignment);
*reinterpret_cast<uint32_t*>(idAlloc.data()) = commandId;
size_t alignedSize = Align(commandSize, kMaxAllocatedCommandAlignment);
return mCurrentBlock.TakeFirst(alignedSize).first(commandSize);
}
return AllocateInNewBlock(commandId, commandSize);
}
Span<std::byte> AllocateInNewBlock(uint32_t commandId, size_t commandSize);
DAWN_FORCE_INLINE Span<std::byte> AllocateData(size_t commandSize) {
return Allocate(detail::kAdditionalData, commandSize);
}
void AppendNewBlock(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 AppendNewBlock. This avoids having to special case the
// initialization in Allocate.
alignas(kMaxAllocatedCommandAlignment) std::array<std::byte, 4> mPlaceholderSpace = {};
// Span to the current range of allocation in the block. Guaranteed to allow for at least one
// uint32_t if not empty, so that the special kEndOfBlock command id can always be written.
// Empty iff the blocks were moved out.
// RAW_PTR_EXCLUSION: This is an extremely hot span during command allocation, but always set
// to a valid allocation (either the placeholder space, or a real allocated block).
RAW_PTR_EXCLUSION Span<std::byte> mCurrentBlock;
};
} // namespace dawn::native
#endif // SRC_DAWN_NATIVE_COMMANDALLOCATOR_H_