MightyBOBcnc / nixis

A python program for procedurally generating planet-scale maps for Earth-like, spherical worlds.
MIT License
8 stars 0 forks source link

Investigate Updated OpenSimplex #10

Open MightyBOBcnc opened 1 year ago

MightyBOBcnc commented 1 year ago

Nixis is using a stripped down OpenSimplex modified to support numba for its noise generation. However, newer updates to OpenSimplex have built-in numba support. Investigate whether the updated OpenSimplex will work with Nixis and whether its performance is on par with the custom fork that Nixis is currently using. If so we could replace the custom fork.

NorthernScott commented 1 year ago

I haven't done any speed tests yet but I have gotten the code base working with the main OpenSimplex module. One thing I haven't successfully done is gotten the numba integration working on the sample_octaves() func.

MightyBOBcnc commented 1 year ago

The reason the OpenSimplex module doesn't play nice with a jit decorated sample_octaves() func in lathe.py is that the OpenSimplex class isn't jit decorated, and neither are the other api functions in api.py: https://github.com/lmas/opensimplex/blob/master/opensimplex/api.py

Jit compiled code in numba can't call undecorated code, so if sample_octaves is jit decorated and it calls sample_noise and then sample_noise calls osi.noise3 and then the noise3 uses the class, and the class calls the functions in internals.py they all need to be jit decorated. (The funcs in internals.py are jit decorated, but the undecorated "gap" in api.py breaks the "chain" of decoration.)

It's possible to jit decorate a class: https://numba.readthedocs.io/en/stable/user/jitclass.html

Here's a simple class that numba can successfully compile:

from numba.experimental import jitclass
from numba import int32, float32

@jitclass
class WaterDroplet(object):
    prev_loc: int32  # Drop's previous index
    cur_loc: int32  # Drop's current index
    next_loc: int32  # Drop's destination index

    def __init__(self, prev_loc, cur_loc, next_loc):
        self.prev_loc = prev_loc
        self.cur_loc = cur_loc
        self.next_loc= next_loc

    def debug(self):
        print("prev_loc:", self.prev_loc)
        print("cur_loc:", self.cur_loc)
        print("next_loc:", self.next_loc)

I'm not sure what the type spec for the OpenSimplex class would be, however, because x, y, z (and w) for some of the internal functions are floats, but for others they are ndarrays, but they share the same variable names in the class. Maybe for the array versions the var names could be changed to something such as xa, ya, za, wa like:

def noise4array(self, xa: np.ndarray, ya: np.ndarray, za: np.ndarray, wa: np.ndarray) -> np.ndarray:
    return _noise4a(xa, ya, za, wa, self._perm)

Of course this means we'd be back to using a custom OpenSimplex fork again, unless we can get a pull request upstream to lmas (Alex) for decorating everything in api.py and he accepts it.

Or maybe calling directly to the functions within internals.py similar to the way Nixis currently does with perm, pgi = osi.init(world_seed) might work to keep the full chain jit decorated without needing a custom fork or upstream commit. Not the best solution, I admit. https://github.com/MightyBOBcnc/nixis/blob/main/nixis.py#L308 https://github.com/MightyBOBcnc/nixis/blob/main/nixis.py#L330