Skip to content

Commit

Permalink
Merge pull request #2 from RyeMutt/blender
Browse files Browse the repository at this point in the history
Update mikktspace code to use the blender forks improvements
  • Loading branch information
RunitaiLinden authored Apr 19, 2024
2 parents bfdc566 + 47372de commit e967e1b
Show file tree
Hide file tree
Showing 8 changed files with 1,508 additions and 2,061 deletions.
4 changes: 2 additions & 2 deletions autobuild.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
<key>canonical_repo</key>
<string>https://github.com/secondlife/3p-mikktspace</string>
<key>copyright</key>
<string>Copyright (C) 2011 by Morten S. Mikkelsen</string>
<string>Copyright (C) 2011 by Morten S. Mikkelsen, Copyright (C) 2022 Blender Authors</string>
<key>description</key>
<string>Mikktspace Tangent Generator</string>
<key>license</key>
<string>Copyright (C) 2011 by Morten S. Mikkelsen</string>
<string>Apache 2.0</string>
<key>license_file</key>
<string>mikktspace.txt</string>
<key>name</key>
Expand Down
184 changes: 184 additions & 0 deletions mikktspace/include/mikktspace/mikk_atomic_hash_set.hh
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
120 changes: 120 additions & 0 deletions mikktspace/include/mikktspace/mikk_float3.hh
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
Loading

0 comments on commit e967e1b

Please sign in to comment.