diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index f1146aa21..2007adf3f 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -67,6 +67,7 @@ add_library(common timer.cpp timer.h types.h + xorshift_prng.h ) target_include_directories(common PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..") diff --git a/src/common/common.vcxproj b/src/common/common.vcxproj index c4c36981c..e302aab2b 100644 --- a/src/common/common.vcxproj +++ b/src/common/common.vcxproj @@ -50,6 +50,7 @@ + diff --git a/src/common/common.vcxproj.filters b/src/common/common.vcxproj.filters index 09070d4e1..c67d63fad 100644 --- a/src/common/common.vcxproj.filters +++ b/src/common/common.vcxproj.filters @@ -53,6 +53,7 @@ + diff --git a/src/common/xorshift_prng.h b/src/common/xorshift_prng.h new file mode 100644 index 000000000..246687e2c --- /dev/null +++ b/src/common/xorshift_prng.h @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: 2025 Connor McLaughlin +// SPDX-License-Identifier: CC0-1.0 + +#pragma once + +#include "types.h" + +class XorShift128PlusPlus +{ +public: + struct State + { + u64 s0; + u64 s1; + }; + + static constexpr State GetInitialState(u64 seed) + { + u64 x = seed; + return State{SplitMix64(x), SplitMix64(x)}; + } + + constexpr XorShift128PlusPlus() : m_state(GetInitialState(0)) {} + constexpr XorShift128PlusPlus(u64 seed) : m_state(GetInitialState(seed)) {} + + ALWAYS_INLINE const State& GetState() const { return m_state; } + State* GetMutableStatePtr() { return &m_state; } + void SetState(State& state) { m_state = state; } + + void Reset(u64 seed) { m_state = GetInitialState(seed); } + + u64 Next() + { + // https://xoroshiro.di.unimi.it/xoroshiro128plusplus.c + u64 s0 = m_state.s0; + u64 s1 = m_state.s1; + u64 result = RotateLeft64(s0 + s1, 17) + s0; + + s1 ^= s0; + m_state.s0 = RotateLeft64(s0, 49) ^ s1 ^ (s1 << 21); // a, b + m_state.s1 = RotateLeft64(s1, 28); // c + + return result; + } + + ALWAYS_INLINE u64 NextRange(u64 n) + { + // This constant should be folded. + const u64 max_allowed_value = (UINT64_C(0xFFFFFFFFFFFFFFFF) / n) * n; + for (;;) + { + const u64 x = Next(); + if (x > max_allowed_value) + continue; + + return x % n; + } + } + + template + ALWAYS_INLINE T NextRange(T low, T high) + { + return low + static_cast(NextRange(static_cast(high - low))); + } + +private: + static constexpr u64 SplitMix64(u64& x) + { + // https://xoroshiro.di.unimi.it/splitmix64.c + u64 z = (x += 0x9e3779b97f4a7c15); + z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; + z = (z ^ (z >> 27)) * 0x94d049bb133111eb; + return z ^ (z >> 31); + } + + static ALWAYS_INLINE u64 RotateLeft64(const u64 x, int k) { return (x << k) | (x >> (64 - k)); } + + State m_state; +};