blob: 641a9370815e278f77d1c6caf6ce07bf447e6be9 [file] [log] [blame] [edit]
import { assert } from '../../common/util/util.js';
import { kValue } from './constants.js';
/**
* Seed-able deterministic pseudo random generator for the WebGPU CTS
*
* This generator requires setting a seed value and the sequence of values
* generated is deterministic based on the seed.
*
* This generator is intended to be a replacement for Math.random().
*
* This generator is not cryptographically secure, though nothing in the CTS
* should be needing cryptographic security.
*
* The current implementation is based on TinyMT
* (https://github.com/MersenneTwister-Lab/TinyMT), which is a version of
* Mersenne Twister that has reduced the internal state size at the cost of
* shortening the period length of the generated sequence. The period is still
* 2^127 - 1 entries long, so should be sufficient for use in the CTS, but it is
* less costly to create multiple instances of the class.
*/
export class PRNG {
// Storing variables for temper() as members, so they don't need to be
// reallocated per call to temper()
private readonly t_vars: Uint32Array;
// Storing variables for next() as members, so they don't need to be
// reallocated per call to next()
private readonly n_vars: Uint32Array;
// Generator internal state
private readonly state: Uint32Array;
// Default tuning parameters for TinyMT.
// These are tested to not generate an all zero initial state.
private static readonly kMat1: number = 0x8f7011ee;
private static readonly kMat2: number = 0xfc78ff1f;
private static readonly kTMat: number = 0x3793fdff;
// TinyMT algorithm internal magic numbers
private static readonly kMask = 0x7fffffff;
private static readonly kMinLoop = 8;
private static readonly kPreLoop = 8;
private static readonly kSH0 = 1;
private static readonly kSH1 = 10;
private static readonly kSH8 = 8;
// u32.max + 1, used to scale the u32 value from temper() to [0, 1).
private static readonly kRandomDivisor = 4294967296.0;
/**
* constructor
*
* @param seed value used to initialize random number sequence. Results are
* guaranteed to be deterministic based on this.
* This value must be in the range of unsigned 32-bit integers.
* Non-integers will be rounded.
*/
constructor(seed: number) {
assert(seed >= 0 && seed <= kValue.u32.max, 'seed to PRNG needs to a u32');
this.t_vars = new Uint32Array(2);
this.n_vars = new Uint32Array(2);
this.state = new Uint32Array([Math.round(seed), PRNG.kMat1, PRNG.kMat2, PRNG.kTMat]);
for (let i = 1; i < PRNG.kMinLoop; i++) {
this.state[i & 3] ^=
i + Math.imul(1812433253, this.state[(i - 1) & 3] ^ (this.state[(i - 1) & 3] >>> 30));
}
// Check that the initial state isn't all 0s, since the algorithm assumes
// that this never occurs
assert(
(this.state[0] & PRNG.kMask) !== 0 ||
this.state[1] !== 0 ||
this.state[2] !== 0 ||
this.state[2] !== 0,
'Initialization of PRNG unexpectedly generated all 0s initial state, this means the tuning parameters are bad'
);
for (let i = 0; i < PRNG.kPreLoop; i++) {
this.next();
}
}
/** Advances the internal state to the next values */
private next() {
this.n_vars[0] = (this.state[0] & PRNG.kMask) ^ this.state[1] ^ this.state[2];
this.n_vars[1] = this.state[3];
this.n_vars[0] ^= this.n_vars[0] << PRNG.kSH0;
this.n_vars[1] ^= (this.n_vars[1] >>> PRNG.kSH0) ^ this.n_vars[0];
this.state[0] = this.state[1];
this.state[1] = this.state[2];
this.state[2] = this.n_vars[0] ^ (this.n_vars[1] << PRNG.kSH1);
this.state[3] = this.n_vars[1];
if ((this.n_vars[1] & 1) !== 0) {
this.state[1] ^= PRNG.kMat1;
this.state[2] ^= PRNG.kMat2;
}
}
/** @returns a 32-bit unsigned integer based on the current state */
private temper(): number {
this.t_vars[0] = this.state[3];
this.t_vars[1] = this.state[0] + (this.state[2] >>> PRNG.kSH8);
this.t_vars[0] ^= this.t_vars[1];
if ((this.t_vars[1] & 1) !== 0) {
this.t_vars[0] ^= PRNG.kTMat;
}
return this.t_vars[0];
}
/** @returns a value on the range of [0, 1) and advances the state */
public random(): number {
this.next();
return this.temper() / PRNG.kRandomDivisor;
}
/** @returns a 32-bit unsigned integer value and advances the state */
public randomU32(): number {
this.next();
return this.temper();
}
}