From 6e5930c2a00ab32cd6bfa8e1065d1aba22e09eba Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Wed, 14 Feb 2024 23:32:07 -0800 Subject: [PATCH] Bimap is a custom bi-directional map. It is extracted from HwRenderPrimitiveFactory and cleaned-up a bit. --- filament/CMakeLists.txt | 1 + filament/src/Bimap.h | 128 ++++++++++++++++++++++ filament/src/HwRenderPrimitiveFactory.cpp | 96 +++++++--------- filament/src/HwRenderPrimitiveFactory.h | 87 ++++++++------- libs/utils/include/utils/Allocator.h | 53 ++++++++- 5 files changed, 262 insertions(+), 103 deletions(-) create mode 100644 filament/src/Bimap.h diff --git a/filament/CMakeLists.txt b/filament/CMakeLists.txt index 3b3babcd29..38d2f42292 100644 --- a/filament/CMakeLists.txt +++ b/filament/CMakeLists.txt @@ -137,6 +137,7 @@ set(SRCS set(PRIVATE_HDRS src/Allocators.h + src/Bimap.h src/BufferPoolAllocator.h src/ColorSpaceUtils.h src/Culler.h diff --git a/filament/src/Bimap.h b/filament/src/Bimap.h new file mode 100644 index 0000000000..c765b542a0 --- /dev/null +++ b/filament/src/Bimap.h @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * 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 TNT_FILAMENT_BIMAP_H +#define TNT_FILAMENT_BIMAP_H + +#include + +#include + +#include +#include +#include + +#include + +namespace filament { + +/* + * A semi-generic custom bimap. This bimap stores a key/value pair and can retrieve + * the value from the key and the key from the value. + * It is optimized for large keys and small values. The keys are stored out-of-line and are + * never moved. + */ +template, + typename ValueHash = std::hash, + typename Allocator = std::allocator> +class Bimap { + + struct KeyDelegate { + Key const* pKey = nullptr; + bool operator==(KeyDelegate const& rhs) const noexcept { + return *pKey == *rhs.pKey; + } + }; + + // KeyWrapperHash delegates the hash computation to KeyHash + struct KeyHasherDelegate { + size_t operator()(KeyDelegate const& p) const noexcept { + KeyHash const h; + return h(*p.pKey); + } + }; + + using ForwardMap = tsl::robin_map; + using BackwardMap = tsl::robin_map; + + Allocator mAllocator; + ForwardMap mForwardMap; + BackwardMap mBackwardMap; + +public: + Bimap() = default; + explicit Bimap(Allocator&& allocator) + : mAllocator(std::forward(allocator)) { + } + + void reserve(size_t capacity) { + mForwardMap.reserve(capacity); + mBackwardMap.reserve(capacity); + } + + bool empty() const noexcept { + return mForwardMap.empty() && mBackwardMap.empty(); + } + + // insert a new key/value pair + void insert(Key const& key, Value const& value) noexcept { + Key* pKey = mAllocator.allocate(1); // allocate storage for the key + new((void*)pKey) Key{ key }; // copy-construct the key + mForwardMap.insert({{ pKey }, value }); + mBackwardMap.insert({ value, { pKey }}); + } + + typename ForwardMap::iterator end() { return mForwardMap.end(); } + + // Find the value iterator from the key in O(1) + typename ForwardMap::const_iterator find(Key const& key) const { + return mForwardMap.find(KeyDelegate{ .pKey = &key }); + } + typename ForwardMap::iterator find(Key const& key) { + return mForwardMap.find(KeyDelegate{ .pKey = &key }); + } + + // Find the key iterator from the value in O(1). precondition, the value must exist. + typename BackwardMap::const_iterator find(Value const& value) const { + auto pos = mBackwardMap.find(value); + assert_invariant( pos != mBackwardMap.end() ); + return pos; + } + typename BackwardMap::iterator find(Value& value) const { + return mBackwardMap.find(value); + } + + // erase a key/value pair using an iterator to the value + void erase(typename BackwardMap::const_iterator it) { + // find the key + Key const& key = *(it->second.pKey); + // and its iterator + auto pos = find(key); + // destroy the key + it->second.pKey->~Key(); + // free its memory + mAllocator.deallocate(const_cast(it->second.pKey), 1); + // remove the entries from both maps + mForwardMap.erase(pos); + mBackwardMap.erase(it); + } +}; + +} // namespace filament + + +#endif // TNT_FILAMENT_BIMAP_H diff --git a/filament/src/HwRenderPrimitiveFactory.cpp b/filament/src/HwRenderPrimitiveFactory.cpp index 635407d31b..1845e9eb78 100644 --- a/filament/src/HwRenderPrimitiveFactory.cpp +++ b/filament/src/HwRenderPrimitiveFactory.cpp @@ -16,6 +16,16 @@ #include "HwRenderPrimitiveFactory.h" +#include +#include +#include + +#include + +#include +#include +#include + #include namespace filament { @@ -23,86 +33,60 @@ namespace filament { using namespace utils; using namespace backend; -bool operator<(HwRenderPrimitiveFactory::Key const& lhs, +size_t HwRenderPrimitiveFactory::KeyHash::operator()( + HwRenderPrimitiveFactory::Key const& p) const noexcept { + return utils::hash::combine( + p.params.vbh.getId(), + utils::hash::combine(p.params.ibh.getId(), (size_t)p.params.type)); +} + +bool operator==(HwRenderPrimitiveFactory::Key const& lhs, HwRenderPrimitiveFactory::Key const& rhs) noexcept { - if (lhs.vbh == rhs.vbh) { - if (lhs.ibh == rhs.ibh) { - return lhs.type < rhs.type; - } else { - return lhs.ibh < rhs.ibh; - } - } else { - return lhs.vbh < rhs.vbh; - } -} - -inline bool operator<(HwRenderPrimitiveFactory::Entry const& lhs, - HwRenderPrimitiveFactory::Entry const& rhs) noexcept { - return lhs.key < rhs.key; -} - -inline bool operator<(HwRenderPrimitiveFactory::Key const& lhs, - HwRenderPrimitiveFactory::Entry const& rhs) noexcept { - return lhs < rhs.key; -} - -inline bool operator<(HwRenderPrimitiveFactory::Entry const& lhs, - HwRenderPrimitiveFactory::Key const& rhs) noexcept { - return lhs.key < rhs; + return lhs.params.vbh == rhs.params.vbh && + lhs.params.ibh == rhs.params.ibh && + lhs.params.type == rhs.params.type; } // ------------------------------------------------------------------------------------------------ HwRenderPrimitiveFactory::HwRenderPrimitiveFactory() : mArena("HwRenderPrimitiveFactory::mArena", SET_ARENA_SIZE), - mSet(mArena) { - mMap.reserve(256); + mBimap(mArena) { + mBimap.reserve(256); } HwRenderPrimitiveFactory::~HwRenderPrimitiveFactory() noexcept = default; void HwRenderPrimitiveFactory::terminate(DriverApi&) noexcept { - assert_invariant(mMap.empty()); - assert_invariant(mSet.empty()); + assert_invariant(mBimap.empty()); } -RenderPrimitiveHandle HwRenderPrimitiveFactory::create(DriverApi& driver, - VertexBufferHandle vbh, IndexBufferHandle ibh, - PrimitiveType type) noexcept { - - const Key key = { vbh, ibh, type }; +auto HwRenderPrimitiveFactory::create(DriverApi& driver, + backend::VertexBufferHandle vbh, + backend::IndexBufferHandle ibh, + backend::PrimitiveType type) noexcept -> Handle { // see if we already have seen this RenderPrimitive - auto pos = mSet.find(key); + Key const key{{ vbh, ibh, type }, 1 }; + auto pos = mBimap.find(key); // the common case is that we've never seen it (i.e.: no reuse) - if (UTILS_LIKELY(pos == mSet.end())) { - // create the backend object + if (UTILS_LIKELY(pos == mBimap.end())) { auto handle = driver.createRenderPrimitive(vbh, ibh, type); - // insert key/handle in our set with a refcount of 1 - // IMPORTANT: std::set<> doesn't invalidate iterators in insert/erase - auto [ipos, _] = mSet.insert({ key, handle, 1 }); - // map the handle back to the key/payload - mMap.insert({ handle.getId(), ipos }); + mBimap.insert(key, { handle }); return handle; } - pos->refs++; - return pos->handle; + + pos->first.pKey->refs++; + return pos->second.handle; } -void HwRenderPrimitiveFactory::destroy(DriverApi& driver, RenderPrimitiveHandle rph) noexcept { +void HwRenderPrimitiveFactory::destroy(DriverApi& driver, Handle handle) noexcept { // look for this handle in our map - auto pos = mMap.find(rph.getId()); - - // it must be there - assert_invariant(pos != mMap.end()); - - // check the refcount and destroy if needed - auto ipos = pos->second; - if (--ipos->refs == 0) { - mSet.erase(ipos); - mMap.erase(pos); - driver.destroyRenderPrimitive(rph); + auto pos = mBimap.find(Value{ handle }); + if (--pos->second.pKey->refs == 0) { + mBimap.erase(pos); + driver.destroyRenderPrimitive(handle); } } diff --git a/filament/src/HwRenderPrimitiveFactory.h b/filament/src/HwRenderPrimitiveFactory.h index 1ccb1e9aa0..94ce8f4555 100644 --- a/filament/src/HwRenderPrimitiveFactory.h +++ b/filament/src/HwRenderPrimitiveFactory.h @@ -17,17 +17,15 @@ #ifndef TNT_FILAMENT_HWRENDERPRIMITIVEFACTORY_H #define TNT_FILAMENT_HWRENDERPRIMITIVEFACTORY_H +#include "Bimap.h" + +#include #include #include -#include - #include -#include - #include -#include #include #include @@ -38,6 +36,7 @@ class FEngine; class HwRenderPrimitiveFactory { public: + using Handle = backend::RenderPrimitiveHandle; HwRenderPrimitiveFactory(); ~HwRenderPrimitiveFactory() noexcept; @@ -49,64 +48,64 @@ public: void terminate(backend::DriverApi& driver) noexcept; - backend::RenderPrimitiveHandle create(backend::DriverApi& driver, - backend::VertexBufferHandle vbh, - backend::IndexBufferHandle ibh, - backend::PrimitiveType type) noexcept; - - void destroy(backend::DriverApi& driver, - backend::RenderPrimitiveHandle rph) noexcept; - -private: - struct Key { // 20 bytes + struct Parameters { // 20 bytes backend::VertexBufferHandle vbh; // 4 backend::IndexBufferHandle ibh; // 4 backend::PrimitiveType type; // 4 }; - struct Entry { // 28 bytes - Key key; // 20 - backend::RenderPrimitiveHandle handle; // 4 - mutable uint32_t refs; // 4 + Handle create(backend::DriverApi& driver, + backend::VertexBufferHandle vbh, + backend::IndexBufferHandle ibh, + backend::PrimitiveType type) noexcept; + + void destroy(backend::DriverApi& driver, Handle handle) noexcept; + +private: + struct Key { + Parameters params; + mutable uint32_t refs; // 4 bytes }; + struct KeyHash { + size_t operator()(Key const& p) const noexcept; + }; + + friend bool operator==(Key const& lhs, Key const& rhs) noexcept; + + + struct Value { // 4 bytes + Handle handle; + }; + + struct ValueHash { + size_t operator()(Value const& p) const noexcept { + std::hash const h; + return h(p.handle.getId()); + } + }; + + friend bool operator==(Value const& lhs, Value const& rhs) noexcept { + return lhs.handle == rhs.handle; + } // Size of the arena used for the "set" part of the bimap static constexpr size_t SET_ARENA_SIZE = 4 * 1024 * 1024; // Arena for the set<>, using a pool allocator inside a heap area. - using Arena = utils::Arena< - utils::PoolAllocator<64>, // this seems to work with clang and msvc + using PoolAllocatorArena = utils::Arena< + utils::PoolAllocatorWithFallback, utils::LockingPolicy::NoLock, -#ifndef NDEBUG - utils::TrackingPolicy::HighWatermark, -#else utils::TrackingPolicy::Untracked, -#endif utils::AreaPolicy::HeapArea>; - using Set = std::set< - Entry, - std::less<>, - utils::STLAllocator>; - - using Map = tsl::robin_map< - backend::RenderPrimitiveHandle::HandleId, - Set::const_iterator>; // Arena where the set memory is allocated - Arena mArena; + PoolAllocatorArena mArena; - // set of HwRenderPrimitive data - Set mSet; - - // map of RenderPrimitiveHandle to Set Entry - Map mMap; - - friend bool operator<(Key const& lhs, Key const& rhs) noexcept; - friend bool operator<(Key const& lhs, Entry const& rhs) noexcept; - friend bool operator<(Entry const& lhs, Key const& rhs) noexcept; - friend bool operator<(Entry const& lhs, Entry const& rhs) noexcept; + // The special Bimap + Bimap> mBimap; }; } // namespace filament diff --git a/libs/utils/include/utils/Allocator.h b/libs/utils/include/utils/Allocator.h index 073206f48b..7b9978f5b9 100644 --- a/libs/utils/include/utils/Allocator.h +++ b/libs/utils/include/utils/Allocator.h @@ -395,10 +395,10 @@ public: } PoolAllocator(void* begin, size_t size) noexcept - : mFreeList(begin, static_cast(begin) + size, ELEMENT_SIZE, ALIGNMENT, OFFSET) { + : PoolAllocator(begin, static_cast(begin) + size) { } - template + template explicit PoolAllocator(const AREA& area) noexcept : PoolAllocator(area.begin(), area.end()) { } @@ -424,6 +424,53 @@ private: FREELIST mFreeList; }; +template < + size_t ELEMENT_SIZE, + size_t ALIGNMENT = alignof(std::max_align_t), + typename FREELIST = FreeList> +class PoolAllocatorWithFallback : + private PoolAllocator, + private HeapAllocator { + using PoolAllocator = PoolAllocator; + void* mBegin; + void* mEnd; +public: + PoolAllocatorWithFallback(void* begin, void* end) noexcept + : PoolAllocator(begin, end), mBegin(begin), mEnd(end) { + } + + PoolAllocatorWithFallback(void* begin, size_t size) noexcept + : PoolAllocatorWithFallback(begin, static_cast(begin) + size) { + } + + template + explicit PoolAllocatorWithFallback(const AREA& area) noexcept + : PoolAllocatorWithFallback(area.begin(), area.end()) { + } + + bool isHeapAllocation(void* p) const noexcept { + return p < mBegin || p >= mEnd; + } + + // our allocator concept + void* alloc(size_t size = ELEMENT_SIZE, size_t alignment = ALIGNMENT) noexcept { + void* p = PoolAllocator::alloc(size, alignment); + if (UTILS_UNLIKELY(!p)) { + p = HeapAllocator::alloc(size, alignment); + } + assert_invariant(p); + return p; + } + + void free(void* p, size_t size) noexcept { + if (UTILS_LIKELY(!isHeapAllocation(p))) { + PoolAllocator::free(p, size); + } else { + HeapAllocator::free(p); + } + } +}; + #define UTILS_MAX(a,b) ((a) > (b) ? (a) : (b)) template @@ -884,7 +931,7 @@ public: public: // we don't make this explicit, so that we can initialize a vector using a STLAllocator - // from an Arena, avoiding to have to repeat the vector type. + // from an Arena, avoiding having to repeat the vector type. STLAllocator(ARENA& arena) : mArena(arena) { } // NOLINT(google-explicit-constructor) template