Bimap is a custom bi-directional map.

It is extracted from HwRenderPrimitiveFactory and cleaned-up a bit.
This commit is contained in:
Mathias Agopian
2024-02-21 15:50:44 -08:00
committed by Mathias Agopian
parent 486b9eef1e
commit 6e5930c2a0
5 changed files with 262 additions and 103 deletions
+1
View File
@@ -137,6 +137,7 @@ set(SRCS
set(PRIVATE_HDRS
src/Allocators.h
src/Bimap.h
src/BufferPoolAllocator.h
src/ColorSpaceUtils.h
src/Culler.h
+128
View File
@@ -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 <utils/debug.h>
#include <tsl/robin_map.h>
#include <functional>
#include <memory>
#include <utility>
#include <stddef.h>
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 Key, typename Value,
typename KeyHash = std::hash<Key>,
typename ValueHash = std::hash<Value>,
typename Allocator = std::allocator<Key>>
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<KeyDelegate, Value, KeyHasherDelegate>;
using BackwardMap = tsl::robin_map<Value, KeyDelegate, ValueHash>;
Allocator mAllocator;
ForwardMap mForwardMap;
BackwardMap mBackwardMap;
public:
Bimap() = default;
explicit Bimap(Allocator&& allocator)
: mAllocator(std::forward<Allocator>(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<Key *>(it->second.pKey), 1);
// remove the entries from both maps
mForwardMap.erase(pos);
mBackwardMap.erase(it);
}
};
} // namespace filament
#endif // TNT_FILAMENT_BIMAP_H
+40 -56
View File
@@ -16,6 +16,16 @@
#include "HwRenderPrimitiveFactory.h"
#include <backend/DriverApiForward.h>
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
#include <private/backend/DriverApi.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/Hash.h>
#include <stdlib.h>
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);
}
}
+43 -44
View File
@@ -17,17 +17,15 @@
#ifndef TNT_FILAMENT_HWRENDERPRIMITIVEFACTORY_H
#define TNT_FILAMENT_HWRENDERPRIMITIVEFACTORY_H
#include "Bimap.h"
#include <backend/DriverApiForward.h>
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
#include <private/backend/DriverApi.h>
#include <utils/Allocator.h>
#include <tsl/robin_map.h>
#include <functional>
#include <set>
#include <stddef.h>
#include <stdint.h>
@@ -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<Handle::HandleId> 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<sizeof(Key)>,
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<Entry, Arena>>;
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<Key, Value, KeyHash, ValueHash,
utils::STLAllocator<Key, PoolAllocatorArena>> mBimap;
};
} // namespace filament
+50 -3
View File
@@ -395,10 +395,10 @@ public:
}
PoolAllocator(void* begin, size_t size) noexcept
: mFreeList(begin, static_cast<char *>(begin) + size, ELEMENT_SIZE, ALIGNMENT, OFFSET) {
: PoolAllocator(begin, static_cast<char *>(begin) + size) {
}
template <typename AREA>
template<typename AREA>
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<ELEMENT_SIZE, ALIGNMENT, 0, FREELIST>,
private HeapAllocator {
using PoolAllocator = PoolAllocator<ELEMENT_SIZE, ALIGNMENT, 0, FREELIST>;
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<char*>(begin) + size) {
}
template<typename AREA>
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 <typename T, size_t OFFSET = 0>
@@ -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<typename U>