summaryrefslogtreecommitdiff
path: root/src/common/tiny_mt.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/common/tiny_mt.h')
-rw-r--r--src/common/tiny_mt.h250
1 files changed, 250 insertions, 0 deletions
diff --git a/src/common/tiny_mt.h b/src/common/tiny_mt.h
new file mode 100644
index 000000000..19ae5b7d6
--- /dev/null
+++ b/src/common/tiny_mt.h
@@ -0,0 +1,250 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8
9#include "common/alignment.h"
10#include "common/common_types.h"
11
12namespace Common {
13
14// Implementation of TinyMT (mersenne twister RNG).
15// Like Nintendo, we will use the sample parameters.
16class TinyMT {
17public:
18 static constexpr std::size_t NumStateWords = 4;
19
20 struct State {
21 std::array<u32, NumStateWords> data{};
22 };
23
24private:
25 static constexpr u32 ParamMat1 = 0x8F7011EE;
26 static constexpr u32 ParamMat2 = 0xFC78FF1F;
27 static constexpr u32 ParamTmat = 0x3793FDFF;
28
29 static constexpr u32 ParamMult = 0x6C078965;
30 static constexpr u32 ParamPlus = 0x0019660D;
31 static constexpr u32 ParamXor = 0x5D588B65;
32
33 static constexpr u32 TopBitmask = 0x7FFFFFFF;
34
35 static constexpr int MinimumInitIterations = 8;
36 static constexpr int NumDiscardedInitOutputs = 8;
37
38 static constexpr u32 XorByShifted27(u32 value) {
39 return value ^ (value >> 27);
40 }
41
42 static constexpr u32 XorByShifted30(u32 value) {
43 return value ^ (value >> 30);
44 }
45
46private:
47 State state{};
48
49private:
50 // Internal API.
51 void FinalizeInitialization() {
52 const u32 state0 = this->state.data[0] & TopBitmask;
53 const u32 state1 = this->state.data[1];
54 const u32 state2 = this->state.data[2];
55 const u32 state3 = this->state.data[3];
56
57 if (state0 == 0 && state1 == 0 && state2 == 0 && state3 == 0) {
58 this->state.data[0] = 'T';
59 this->state.data[1] = 'I';
60 this->state.data[2] = 'N';
61 this->state.data[3] = 'Y';
62 }
63
64 for (int i = 0; i < NumDiscardedInitOutputs; i++) {
65 this->GenerateRandomU32();
66 }
67 }
68
69 u32 GenerateRandomU24() {
70 return (this->GenerateRandomU32() >> 8);
71 }
72
73 static void GenerateInitialValuePlus(TinyMT::State* state, int index, u32 value) {
74 u32& state0 = state->data[(index + 0) % NumStateWords];
75 u32& state1 = state->data[(index + 1) % NumStateWords];
76 u32& state2 = state->data[(index + 2) % NumStateWords];
77 u32& state3 = state->data[(index + 3) % NumStateWords];
78
79 const u32 x = XorByShifted27(state0 ^ state1 ^ state3) * ParamPlus;
80 const u32 y = x + index + value;
81
82 state0 = y;
83 state1 += x;
84 state2 += y;
85 }
86
87 static void GenerateInitialValueXor(TinyMT::State* state, int index) {
88 u32& state0 = state->data[(index + 0) % NumStateWords];
89 u32& state1 = state->data[(index + 1) % NumStateWords];
90 u32& state2 = state->data[(index + 2) % NumStateWords];
91 u32& state3 = state->data[(index + 3) % NumStateWords];
92
93 const u32 x = XorByShifted27(state0 + state1 + state3) * ParamXor;
94 const u32 y = x - index;
95
96 state0 = y;
97 state1 ^= x;
98 state2 ^= y;
99 }
100
101public:
102 constexpr TinyMT() = default;
103
104 // Public API.
105
106 // Initialization.
107 void Initialize(u32 seed) {
108 this->state.data[0] = seed;
109 this->state.data[1] = ParamMat1;
110 this->state.data[2] = ParamMat2;
111 this->state.data[3] = ParamTmat;
112
113 for (int i = 1; i < MinimumInitIterations; i++) {
114 const u32 mixed = XorByShifted30(this->state.data[(i - 1) % NumStateWords]);
115 this->state.data[i % NumStateWords] ^= mixed * ParamMult + i;
116 }
117
118 this->FinalizeInitialization();
119 }
120
121 void Initialize(const u32* seed, int seed_count) {
122 this->state.data[0] = 0;
123 this->state.data[1] = ParamMat1;
124 this->state.data[2] = ParamMat2;
125 this->state.data[3] = ParamTmat;
126
127 {
128 const int num_init_iterations = std::max(seed_count + 1, MinimumInitIterations) - 1;
129
130 GenerateInitialValuePlus(&this->state, 0, seed_count);
131
132 for (int i = 0; i < num_init_iterations; i++) {
133 GenerateInitialValuePlus(&this->state, (i + 1) % NumStateWords,
134 (i < seed_count) ? seed[i] : 0);
135 }
136
137 for (int i = 0; i < static_cast<int>(NumStateWords); i++) {
138 GenerateInitialValueXor(&this->state,
139 (i + 1 + num_init_iterations) % NumStateWords);
140 }
141 }
142
143 this->FinalizeInitialization();
144 }
145
146 // State management.
147 void GetState(TinyMT::State& out) const {
148 out.data = this->state.data;
149 }
150
151 void SetState(const TinyMT::State& state_) {
152 this->state.data = state_.data;
153 }
154
155 // Random generation.
156 void GenerateRandomBytes(void* dst, std::size_t size) {
157 const uintptr_t start = reinterpret_cast<uintptr_t>(dst);
158 const uintptr_t end = start + size;
159 const uintptr_t aligned_start = Common::AlignUp(start, 4);
160 const uintptr_t aligned_end = Common::AlignDown(end, 4);
161
162 // Make sure we're aligned.
163 if (start < aligned_start) {
164 const u32 rnd = this->GenerateRandomU32();
165 std::memcpy(dst, &rnd, aligned_start - start);
166 }
167
168 // Write as many aligned u32s as we can.
169 {
170 u32* cur_dst = reinterpret_cast<u32*>(aligned_start);
171 u32* const end_dst = reinterpret_cast<u32*>(aligned_end);
172
173 while (cur_dst < end_dst) {
174 *(cur_dst++) = this->GenerateRandomU32();
175 }
176 }
177
178 // Handle any leftover unaligned data.
179 if (aligned_end < end) {
180 const u32 rnd = this->GenerateRandomU32();
181 std::memcpy(reinterpret_cast<void*>(aligned_end), &rnd, end - aligned_end);
182 }
183 }
184
185 u32 GenerateRandomU32() {
186 // Advance state.
187 const u32 x0 =
188 (this->state.data[0] & TopBitmask) ^ this->state.data[1] ^ this->state.data[2];
189 const u32 y0 = this->state.data[3];
190 const u32 x1 = x0 ^ (x0 << 1);
191 const u32 y1 = y0 ^ (y0 >> 1) ^ x1;
192
193 const u32 state0 = this->state.data[1];
194 u32 state1 = this->state.data[2];
195 u32 state2 = x1 ^ (y1 << 10);
196 const u32 state3 = y1;
197
198 if ((y1 & 1) != 0) {
199 state1 ^= ParamMat1;
200 state2 ^= ParamMat2;
201 }
202
203 this->state.data[0] = state0;
204 this->state.data[1] = state1;
205 this->state.data[2] = state2;
206 this->state.data[3] = state3;
207
208 // Temper.
209 const u32 t1 = state0 + (state2 >> 8);
210 u32 t0 = state3 ^ t1;
211
212 if ((t1 & 1) != 0) {
213 t0 ^= ParamTmat;
214 }
215
216 return t0;
217 }
218
219 u64 GenerateRandomU64() {
220 const u32 lo = this->GenerateRandomU32();
221 const u32 hi = this->GenerateRandomU32();
222 return (u64{hi} << 32) | u64{lo};
223 }
224
225 float GenerateRandomF32() {
226 // Floats have 24 bits of mantissa.
227 constexpr u32 MantissaBits = 24;
228 return static_cast<float>(GenerateRandomU24()) * (1.0f / (1U << MantissaBits));
229 }
230
231 double GenerateRandomF64() {
232 // Doubles have 53 bits of mantissa.
233 // The smart way to generate 53 bits of random would be to use 32 bits
234 // from the first rnd32() call, and then 21 from the second.
235 // Nintendo does not. They use (32 - 5) = 27 bits from the first rnd32()
236 // call, and (32 - 6) bits from the second. We'll do what they do, but
237 // There's not a clear reason why.
238 constexpr u32 MantissaBits = 53;
239 constexpr u32 Shift1st = (64 - MantissaBits) / 2;
240 constexpr u32 Shift2nd = (64 - MantissaBits) - Shift1st;
241
242 const u32 first = (this->GenerateRandomU32() >> Shift1st);
243 const u32 second = (this->GenerateRandomU32() >> Shift2nd);
244
245 return (1.0 * first * (u64{1} << (32 - Shift2nd)) + second) *
246 (1.0 / (u64{1} << MantissaBits));
247 }
248};
249
250} // namespace Common