def __init__(self, size, source_tex, normalization_factor):
        """ Creates a new fft instance. The source texture has to specified
        from the begining, as the shaderAttributes are pregenerated for
        performance reasons """

        self.size = size
        self.log2_size = int(math.log(size, 2))
        self.normalization_factor = normalization_factor

        # Create a ping and a pong texture, because we can't write to the
        # same texture while reading to it (that would lead to unexpected
        # behaviour, we could solve that by using an appropriate thread size,
        # but it works fine so far)
        self.ping_texture = Texture("FFTPing")
        self.ping_texture.setup_2d_texture(self.size, self.size, Texture.TFloat, Texture.FRgba32)
        self.pong_texture = Texture("FFTPong")
        self.pong_texture.setup_2d_texture(self.size, self.size, Texture.TFloat, Texture.FRgba32)
        self.source_tex = source_tex

        for tex in [self.ping_texture, self.pong_texture, source_tex]:
            tex.set_minfilter(Texture.FTNearest)
            tex.set_magfilter(Texture.FTNearest)
            tex.set_wrap_u(Texture.WMClamp)
            tex.set_wrap_v(Texture.WMClamp)

        # Pregenerate weights & indices for the shaders
        self._compute_weighting()

        # Pre generate the shaders, we have 2 passes: Horizontal and Vertical
        # which both execute log2(N) times with varying radii
        self.horizontal_fft_shader = Shader.load_compute(
            Shader.SLGLSL, "/$$rp/rpcore/water/shader/horizontal_fft.compute"
        )
        self.horizontal_fft = NodePath("HorizontalFFT")
        self.horizontal_fft.set_shader(self.horizontal_fft_shader)
        self.horizontal_fft.set_shader_input("precomputedWeights", self.weights_lookup_tex)
        self.horizontal_fft.set_shader_input("N", LVecBase2i(self.size))

        self.vertical_fft_shader = Shader.load_compute(Shader.SLGLSL, "/$$rp/rpcore/water/shader/vertical_fft.compute")
        self.vertical_fft = NodePath("VerticalFFT")
        self.vertical_fft.set_shader(self.vertical_fft_shader)
        self.vertical_fft.set_shader_input("precomputedWeights", self.weights_lookup_tex)
        self.vertical_fft.set_shader_input("N", LVecBase2i(self.size))

        # Create a texture where the result is stored
        self.result_texture = Texture("Result")
        self.result_texture.setup2dTexture(self.size, self.size, Texture.TFloat, Texture.FRgba16)
        self.result_texture.set_minfilter(Texture.FTLinear)
        self.result_texture.set_magfilter(Texture.FTLinear)

        # Prepare the shader attributes, so we don't have to regenerate them
        # every frame -> That is VERY slow (3ms per fft instance)
        self._prepare_attributes()
 Creates a new fft instance. The source texture has to specified
        from the begining, as the shaderAttributes are pregenerated for
        performance reasons 
metadrive/render_pipeline/rpcore/water/gpu_fft.py