Bimap is a custom bi-directional map.
It is extracted from HwRenderPrimitiveFactory and cleaned-up a bit.
This commit is contained in:
committed by
Mathias Agopian
parent
486b9eef1e
commit
6e5930c2a0
@@ -137,6 +137,7 @@ set(SRCS
|
||||
|
||||
set(PRIVATE_HDRS
|
||||
src/Allocators.h
|
||||
src/Bimap.h
|
||||
src/BufferPoolAllocator.h
|
||||
src/ColorSpaceUtils.h
|
||||
src/Culler.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 <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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user