Skip to content

Commit 4c80b80

Browse files
authored
Improve FastRNG implementation (#2056)
* Update FastRNG and improve particle system and tests * Update FastRNG.h * remove unnecessary includes * Adapt unit_tests * unit test rotL too
1 parent 49e19bb commit 4c80b80

File tree

4 files changed

+286
-131
lines changed

4 files changed

+286
-131
lines changed

core/2d/ParticleSystem.cpp

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2308,19 +2308,14 @@ void ParticleEmissionMaskCache::bakeEmissionMask(std::string_view maskId,
23082308
if (inbetweenSamples > 1)
23092309
{
23102310
float a = data[(y * w + x) * 4 + 3] / 255.0F;
2311-
if (a >= alphaThreshold && !inverted)
2312-
for (float i = 0; i < 1.0F; i += 1.0F / inbetweenSamples)
2313-
points.emplace_back(Vec2{float(x + i), float(h - y + i)});
2314-
if (a < alphaThreshold && inverted)
2311+
if (a >= alphaThreshold && !inverted || a < alphaThreshold && inverted)
23152312
for (float i = 0; i < 1.0F; i += 1.0F / inbetweenSamples)
23162313
points.emplace_back(Vec2{float(x + i), float(h - y + i)});
23172314
}
23182315
else
23192316
{
23202317
float a = data[(y * w + x) * 4 + 3] / 255.0F;
2321-
if (a >= alphaThreshold && !inverted)
2322-
points.emplace_back(Vec2{float(x), float(h - y)});
2323-
if (a < alphaThreshold && inverted)
2318+
if (a >= alphaThreshold && !inverted || a < alphaThreshold && inverted)
23242319
points.emplace_back(Vec2{float(x), float(h - y)});
23252320
}
23262321
}

core/math/FastRNG.h

Lines changed: 129 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,158 @@
1-
#ifndef _FAST_RNG_H__
2-
#define _FAST_RNG_H__
3-
4-
#include <cmath>
5-
#include <limits.h>
6-
7-
/** A fast more effective seeded random number generator struct, made by kiss rng.
1+
/****************************************************************************
2+
Copyright (c) 2012 cocos2d-x.org
3+
Copyright (c) 2013-2016 Chukong Technologies Inc.
4+
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
5+
Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md).
6+
7+
https://axmol.dev/
8+
9+
Permission is hereby granted, free of charge, to any person obtaining a copy
10+
of this software and associated documentation files (the "Software"), to deal
11+
in the Software without restriction, including without limitation the rights
12+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
copies of the Software, and to permit persons to whom the Software is
14+
furnished to do so, subject to the following conditions:
15+
16+
The above copyright notice and this permission notice shall be included in
17+
all copies or substantial portions of the Software.
18+
19+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
THE SOFTWARE.
26+
****************************************************************************/
27+
28+
#ifndef __FAST_RNG_H__
29+
#define __FAST_RNG_H__
30+
31+
/** A fast more effective seeded random number generator struct, uses xoshiro128**.
832
* It uses a simple algorithm to improve the speed of generating random numbers with a decent quality,
933
* Use this if you're planning to generate large amounts of random numbers in a single frame.
1034
*
1135
* @since axmol-1.0.0b8
1236
*/
1337
struct FastRNG
1438
{
15-
const uint32_t RNG_RAND_MAX = std::numeric_limits<uint32_t>::max();
16-
uint32_t _x = 1;
17-
uint32_t _y = 2;
18-
uint32_t _z = 4;
19-
uint32_t _w = 8;
20-
uint32_t _carry = 0;
21-
uint32_t _k = 0;
22-
uint32_t _m = 0;
23-
uint64_t _seed = 0;
24-
25-
FastRNG() { seed_rng((uint32_t)time(NULL)); }
26-
FastRNG(uint64_t seed) { seed_rng_64(seed); }
27-
28-
// initialize this object with seed
29-
void seed_rng(uint64_t seed)
39+
uint32_t s[4];
40+
41+
// SplitMix64 implementation, doesn't modify any state for this instance
42+
// but it is used to seed xoshiro128** state
43+
uint64_t nextSeed(uint64_t& state)
3044
{
31-
auto seed1 = static_cast<uint32_t>(seed);
32-
auto seed2 = static_cast<uint32_t>(seed >> 32);
33-
34-
_seed = seed;
35-
_x = static_cast<int32_t>(seed1) | 1;
36-
_y = static_cast<int32_t>(seed2) | 2;
37-
_z = static_cast<int32_t>(seed1) | 4;
38-
_w = static_cast<int32_t>(seed2) | 8;
39-
_carry = 0;
45+
uint64_t z = (state += 0x9e3779b97f4a7c15);
46+
z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9;
47+
z = (z ^ (z >> 27)) * 0x94d049bb133111eb;
48+
return z ^ (z >> 31);
4049
}
4150

42-
// initialize this object with a uint64_t seed
43-
// DEPRECATED: use seed_rng instead
44-
void seed_rng_64(uint64_t seed) { seed_rng(seed); }
51+
FastRNG() { seed(static_cast<uint64_t>(rand()) << 32 | rand()); }
4552

46-
// returns a random uint32_t value
47-
uint32_t rng()
53+
// there is no need to seed this instance of FastRNG
54+
// because it has already been seeded with rand() by constructor
55+
// you can override the seed by giving your own 64-bit seed
56+
void seed(uint64_t seed)
4857
{
49-
_x = _x * 69069 + 12345;
50-
_y ^= _y << 13;
51-
_y ^= _y >> 17;
52-
_y ^= _y << 5;
53-
_k = (_z >> 2) + (_w >> 3) + (_carry >> 2);
54-
_m = _w + _w + _z + _carry;
55-
_z = _w;
56-
_w = _m;
57-
_carry = _k >> 30;
58-
return _x + _y + _w;
58+
uint64_t state = seed;
59+
uint64_t states[2];
60+
memset(states, 0, 16);
61+
states[0] = nextSeed(state);
62+
states[1] = nextSeed(state);
63+
memcpy(s, states, 16);
5964
}
6065

61-
// returns a random integer from min to max
62-
int32_t range(int32_t min, int32_t max)
66+
// returns a copy of x rotated k bits to the left
67+
static inline uint32_t rotL(const uint32_t x, int k) { return (x << k) | (x >> (32 - k)); }
68+
69+
// steps once into the state, returns a random from 0 to UINT32_MAX
70+
uint32_t next()
6371
{
64-
return static_cast<int32_t>(min + rng() / (RNG_RAND_MAX / (max - min)));
72+
const uint32_t result = rotL(s[1] * 5, 7) * 9;
73+
74+
const uint32_t t = s[1] << 9;
75+
76+
s[2] ^= s[0];
77+
s[3] ^= s[1];
78+
s[1] ^= s[2];
79+
s[0] ^= s[3];
80+
81+
s[2] ^= t;
82+
83+
s[3] = rotL(s[3], 11);
84+
85+
return result;
6586
}
6687

67-
// returns a random unsigned integer from min to max
68-
uint32_t rangeu(uint32_t min, uint32_t max)
88+
// generates a random real that ranges from 0.0 to 1.0
89+
template <typename T>
90+
T nextReal()
6991
{
70-
return static_cast<uint32_t>(min + rng() / (RNG_RAND_MAX / (max - min)));
92+
if (std::is_same<T, float>::value)
93+
return static_cast<T>(next() >> 8) * 0x1.0p-24f;
94+
else if (std::is_same<T, double>::value)
95+
return static_cast<T>((static_cast<uint64_t>(next()) << 32 | next()) >> 11) * 0x1.0p-53;
96+
return 0; // possibly assert?
7197
}
7298

73-
// returns a random integer from 0 to max
74-
int32_t max(int32_t max = INT_MAX)
99+
// generates a random real that ranges from min to max
100+
template <typename T>
101+
T nextReal(T min, T max)
75102
{
76-
return static_cast<int32_t>(static_cast<float>(rng()) / static_cast<float>(RNG_RAND_MAX / (max - 0)));
103+
return static_cast<T>(min + nextReal<T>() * (max - min));
77104
}
78105

79-
// returns a random unsigned integer from 0 to max
80-
uint32_t maxu(uint32_t max = UINT_MAX) { return static_cast<uint32_t>(0 + rng() / (RNG_RAND_MAX / (max - 0))); }
81-
82-
// returns a random float from min to max
83-
float rangef(float min = -1.0F, float max = 1.0F)
106+
// generates a random integer that ranges from min inclusive to max exclusive [min, max) and is uniformly distributed using fastrange algorithm
107+
template <typename T>
108+
T nextInt(T min, T max)
84109
{
85-
return min + static_cast<float>(rng()) / (static_cast<float>(RNG_RAND_MAX) / (max - min));
110+
return min + static_cast<T>(nextMax(static_cast<uint32_t>(max - min)));
86111
}
87112

88-
// returns a random float from 0.0 to max
89-
float maxf(float max) { return static_cast<float>(rng()) / (static_cast<float>(RNG_RAND_MAX) / (max - 0.0f)); }
90-
91-
// returns a random float from 0.0 to 1.0
92-
float float01() { return static_cast<float>(rng()) / (static_cast<float>(RNG_RAND_MAX) / (1.0f - 0.0f)); }
113+
// generates a random integer from 0 to max exclusive that is uniformly distributed using fastrange algorithm
114+
uint32_t nextMax(uint32_t max)
115+
{
116+
uint64_t multiresult = static_cast<uint64_t>(next()) * max;
117+
uint32_t leftover = static_cast<uint32_t>(multiresult);
118+
if (leftover < max)
119+
{
120+
uint32_t threshold = (0 - max) % max;
121+
while (leftover < threshold)
122+
{
123+
multiresult = static_cast<uint64_t>(next()) * max;
124+
leftover = static_cast<uint32_t>(multiresult);
125+
}
126+
}
127+
return multiresult >> 32;
128+
}
93129

94-
// returns either false or true randomly
95-
bool bool01() { return static_cast<bool>(rng() & 1); }
130+
// wrapper for nextInt<int32_t>(min, max)
131+
int32_t range(int32_t min, int32_t max) { return nextInt<int32_t>(min, max); }
132+
// wrapper for nextInt<int32_t>(0, max)
133+
int32_t max(int32_t max = INT32_MAX) { return nextInt<int32_t>(0, max); }
134+
135+
// wrapper for nextInt<uint32_t>(min, max)
136+
uint32_t rangeu(uint32_t min, uint32_t max) { return nextInt<uint32_t>(min, max); }
137+
// wrapper for nextInt<uint32_t>(0, max)
138+
uint32_t maxu(uint32_t max = UINT_MAX) { return nextInt<uint32_t>(0, max); }
139+
140+
// wrapper for nextReal<float>(min, max)
141+
float rangef(float min = -1.0F, float max = 1.0F) { return nextReal<float>(min, max); }
142+
// wrapper for nextReal<float>(0, max)
143+
float maxf(float max) { return nextReal<float>(0, max); }
144+
145+
// wrapper for nextReal<double>(min, max)
146+
double ranged(double min = -1.0F, double max = 1.0F) { return nextReal<double>(min, max); }
147+
// wrapper for nextReal<double>(0, max)
148+
double maxd(double max) { return nextReal<double>(0, max); }
149+
150+
// wrapper for nextReal<float>()
151+
float float01() { return nextReal<float>(); }
152+
// wrapper for nextReal<double>()
153+
float double01() { return nextReal<double>(); }
154+
// wrapper for next() & 1, true or false based on LSB
155+
float bool01() { return next() & 1; }
96156
};
97157

98-
#endif // _FAST_RNG_H__
158+
#endif // __FAST_RNG_H__

tests/cpp-tests/Source/ParticleTest/ParticleTest.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1764,21 +1764,25 @@ void DemoEmissionShapeAlphaMask::onEnter()
17641764
removeChild(_background, true);
17651765
_background = nullptr;
17661766

1767-
_emitter = ParticleFireworks::createWithTotalParticles(8000);
1767+
_emitter = ParticleFireworks::createWithTotalParticles(6000);
17681768
_emitter->retain();
17691769
addChild(_emitter, 10);
17701770
_emitter->setTexture(Director::getInstance()->getTextureCache()->addImage(s_stars1));
17711771

17721772
_emitter->setBlendAdditive(true);
17731773

1774+
_emitter->setLife(1);
17741775
_emitter->setGravity({0, 0});
17751776
_emitter->setSpeed(0);
17761777
_emitter->setSpeedVar(0);
17771778

1778-
_emitter->setStartSize(3);
1779+
_emitter->setStartColor(Color4F::WHITE);
1780+
_emitter->setEndColor(Color4F::WHITE);
1781+
1782+
_emitter->setStartSize(6);
17791783
_emitter->setEndSize(3);
17801784

1781-
_emitter->setEmissionRate(1000);
1785+
_emitter->setEmissionRate(6000);
17821786

17831787
_emitter->setEmissionShapes(true);
17841788

0 commit comments

Comments
 (0)