-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from RyeMutt/blender
Update mikktspace code to use the blender forks improvements
- Loading branch information
Showing
8 changed files
with
1,508 additions
and
2,061 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
/* SPDX-FileCopyrightText: 2012-2021 Meta Platforms, Inc. and affiliates. | ||
* SPDX-FileCopyrightText: 2022 Blender Authors | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 */ | ||
|
||
/* Simplified version of Folly's AtomicHashArray | ||
* (https://github.com/facebook/folly/blob/main/folly/AtomicHashArray.h). | ||
* | ||
* Notable changes: | ||
* - Standalone and header-only. | ||
* - Behaves like a set, not like a map: There's no value type anymore, only keys. | ||
* - Capacity check logic have been removed, the code assumes you know the required size in | ||
* advance. | ||
* - Custom allocator support has been removed. | ||
* - Erase has been removed. | ||
* - Find has been removed. | ||
*/ | ||
|
||
/** \file | ||
* \ingroup mikktspace | ||
*/ | ||
|
||
#pragma once | ||
|
||
#ifdef _MSC_VER | ||
# include <intrin.h> | ||
#endif | ||
|
||
#include <atomic> | ||
#include <type_traits> | ||
|
||
namespace mikk { | ||
|
||
struct AtomicHashSetLinearProbeFcn { | ||
inline size_t operator()(size_t idx, size_t /* numProbes */, size_t capacity) const | ||
{ | ||
idx += 1; // linear probing | ||
|
||
// Avoid modulus because it's slow | ||
return (idx < capacity) ? idx : (idx - capacity); | ||
} | ||
}; | ||
|
||
struct AtomicHashSetQuadraticProbeFcn { | ||
inline size_t operator()(size_t idx, size_t numProbes, size_t capacity) const | ||
{ | ||
idx += numProbes; // quadratic probing | ||
|
||
// Avoid modulus because it's slow | ||
return (idx < capacity) ? idx : (idx - capacity); | ||
} | ||
}; | ||
|
||
template<class KeyT, | ||
bool isAtomic, | ||
class KeyHash = std::hash<KeyT>, | ||
class KeyEqual = std::equal_to<KeyT>, | ||
class ProbeFcn = AtomicHashSetLinearProbeFcn> | ||
class AtomicHashSet { | ||
static_assert((std::is_convertible<KeyT, int32_t>::value || | ||
std::is_convertible<KeyT, int64_t>::value || | ||
std::is_convertible<KeyT, const void *>::value), | ||
"You are trying to use AtomicHashSet with disallowed key " | ||
"types. You must use atomically compare-and-swappable integer " | ||
"keys, or a different container class."); | ||
|
||
public: | ||
const size_t capacity_; | ||
const KeyT kEmptyKey_; | ||
|
||
KeyHash hasher_; | ||
KeyEqual equalityChecker_; | ||
|
||
private: | ||
size_t kAnchorMask_; | ||
/* When using a single thread, we can avoid overhead by not bothering with atomic cells. */ | ||
typedef typename std::conditional<isAtomic, std::atomic<KeyT>, KeyT>::type cell_type; | ||
std::vector<cell_type> cells_; | ||
|
||
public: | ||
struct Config { | ||
KeyT emptyKey; | ||
double maxLoadFactor; | ||
double growthFactor; | ||
size_t capacity; // if positive, overrides maxLoadFactor | ||
|
||
// Cannot have constexpr ctor because some compilers rightly complain. | ||
Config() : emptyKey((KeyT)-1), maxLoadFactor(0.8), growthFactor(-1), capacity(0) {} | ||
}; | ||
|
||
/* Instead of a mess of arguments, we take a max size and a Config struct to | ||
* simulate named ctor parameters. The Config struct has sensible defaults | ||
* for everything, but is overloaded - if you specify a positive capacity, | ||
* that will be used directly instead of computing it based on maxLoadFactor. | ||
*/ | ||
AtomicHashSet(size_t maxSize, | ||
KeyHash hasher = KeyHash(), | ||
KeyEqual equalityChecker = KeyEqual(), | ||
const Config &c = Config()) | ||
: capacity_(size_t(double(maxSize) / c.maxLoadFactor) + 1), | ||
kEmptyKey_(c.emptyKey), | ||
hasher_(hasher), | ||
equalityChecker_(equalityChecker), | ||
cells_(capacity_) | ||
{ | ||
/* Get next power of two. Could be done more effiently with builtin_clz, but this is not | ||
* performance-critical. */ | ||
kAnchorMask_ = 1; | ||
while (kAnchorMask_ < capacity_) { | ||
kAnchorMask_ *= 2; | ||
} | ||
/* Get mask for lower bits. */ | ||
kAnchorMask_ -= 1; | ||
|
||
/* Not great, but the best we can do to support both atomic and non-atomic cells | ||
* since std::atomic doesn't have a copy constructor so cells_(capacity_, kEmptyKey_) | ||
* in the initializer list won't work. */ | ||
std::fill((KeyT *)cells_.data(), (KeyT *)cells_.data() + capacity_, kEmptyKey_); | ||
} | ||
|
||
AtomicHashSet(const AtomicHashSet &) = delete; | ||
AtomicHashSet &operator=(const AtomicHashSet &) = delete; | ||
|
||
~AtomicHashSet() = default; | ||
|
||
/* Sequential specialization. */ | ||
bool tryUpdateCell(KeyT *cell, KeyT &existingKey, KeyT newKey) | ||
{ | ||
if (*cell == existingKey) { | ||
*cell = newKey; | ||
return true; | ||
} | ||
existingKey = *cell; | ||
return false; | ||
} | ||
|
||
/* Atomic specialization. */ | ||
bool tryUpdateCell(std::atomic<KeyT> *cell, KeyT &existingKey, KeyT newKey) | ||
{ | ||
return cell->compare_exchange_strong(existingKey, newKey, std::memory_order_acq_rel); | ||
} | ||
|
||
std::pair<KeyT, bool> emplace(KeyT key) | ||
{ | ||
size_t idx = keyToAnchorIdx(key); | ||
size_t numProbes = 0; | ||
for (;;) { | ||
cell_type *cell = &cells_[idx]; | ||
KeyT existingKey = kEmptyKey_; | ||
/* Try to replace empty cell with our key. */ | ||
if (tryUpdateCell(cell, existingKey, key)) { | ||
/* Cell was empty, we're done. */ | ||
return std::make_pair(key, true); | ||
} | ||
|
||
/* Cell was not empty, check if the existing key is equal. */ | ||
if (equalityChecker_(existingKey, key)) { | ||
/* Found equal element, we're done. */ | ||
return std::make_pair(existingKey, false); | ||
} | ||
|
||
/* Continue to next cell according to probe strategy. */ | ||
++numProbes; | ||
if ((numProbes >= capacity_)) { | ||
// probed every cell...fail | ||
assert(false); | ||
return std::make_pair(kEmptyKey_, false); | ||
} | ||
|
||
idx = ProbeFcn()(idx, numProbes, capacity_); | ||
} | ||
} | ||
|
||
private: | ||
inline size_t keyToAnchorIdx(const KeyT k) const | ||
{ | ||
const size_t hashVal = hasher_(k); | ||
const size_t probe = hashVal & kAnchorMask_; | ||
return (probe < capacity_) ? probe : hashVal % capacity_; | ||
} | ||
|
||
}; // AtomicHashSet | ||
|
||
} // namespace mikk |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
/* SPDX-FileCopyrightText: 2020-2023 Blender Authors | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 */ | ||
|
||
/** \file | ||
* \ingroup mikktspace | ||
*/ | ||
|
||
#pragma once | ||
|
||
#include <cmath> | ||
|
||
namespace mikk { | ||
|
||
struct float3 { | ||
float x, y, z; | ||
|
||
float3() = default; | ||
|
||
float3(const float *ptr) : x{ptr[0]}, y{ptr[1]}, z{ptr[2]} {} | ||
|
||
float3(const float (*ptr)[3]) : float3((const float *)ptr) {} | ||
|
||
explicit float3(float value) : x(value), y(value), z(value) {} | ||
|
||
explicit float3(int value) : x((float)value), y((float)value), z((float)value) {} | ||
|
||
float3(float x_, float y_, float z_) : x{x_}, y{y_}, z{z_} {} | ||
|
||
static float3 zero() | ||
{ | ||
return {0.0f, 0.0f, 0.0f}; | ||
} | ||
|
||
friend float3 operator*(const float3 &a, float b) | ||
{ | ||
return {a.x * b, a.y * b, a.z * b}; | ||
} | ||
|
||
friend float3 operator*(float b, const float3 &a) | ||
{ | ||
return {a.x * b, a.y * b, a.z * b}; | ||
} | ||
|
||
friend float3 operator-(const float3 &a, const float3 &b) | ||
{ | ||
return {a.x - b.x, a.y - b.y, a.z - b.z}; | ||
} | ||
|
||
friend float3 operator-(const float3 &a) | ||
{ | ||
return {-a.x, -a.y, -a.z}; | ||
} | ||
|
||
friend bool operator==(const float3 &a, const float3 &b) | ||
{ | ||
return a.x == b.x && a.y == b.y && a.z == b.z; | ||
} | ||
|
||
float length_squared() const | ||
{ | ||
return x * x + y * y + z * z; | ||
} | ||
|
||
float length() const | ||
{ | ||
return sqrt(length_squared()); | ||
} | ||
|
||
static float distance(const float3 &a, const float3 &b) | ||
{ | ||
return (a - b).length(); | ||
} | ||
|
||
friend float3 operator+(const float3 &a, const float3 &b) | ||
{ | ||
return {a.x + b.x, a.y + b.y, a.z + b.z}; | ||
} | ||
|
||
void operator+=(const float3 &b) | ||
{ | ||
this->x += b.x; | ||
this->y += b.y; | ||
this->z += b.z; | ||
} | ||
|
||
friend float3 operator*(const float3 &a, const float3 &b) | ||
{ | ||
return {a.x * b.x, a.y * b.y, a.z * b.z}; | ||
} | ||
|
||
float3 normalize() const | ||
{ | ||
const float len = length(); | ||
return (len != 0.0f) ? *this * (1.0f / len) : *this; | ||
} | ||
|
||
float reduce_add() const | ||
{ | ||
return x + y + z; | ||
} | ||
}; | ||
|
||
inline float dot(const float3 &a, const float3 &b) | ||
{ | ||
return a.x * b.x + a.y * b.y + a.z * b.z; | ||
} | ||
|
||
inline float distance(const float3 &a, const float3 &b) | ||
{ | ||
return float3::distance(a, b); | ||
} | ||
|
||
/* Projects v onto the surface with normal n. */ | ||
inline float3 project(const float3 &n, const float3 &v) | ||
{ | ||
return (v - n * dot(n, v)).normalize(); | ||
} | ||
|
||
} // namespace mikk |
Oops, something went wrong.