def _createInitialGrid(self):
        """ Creates the initial cloud grid """
        shader = Shader.loadCompute(Shader.SLGLSL, "Shader/Clouds/InitialGrid.compute")
        dummy = NodePath("dummy")
        dummy.setShader(shader)
        dummy.setShaderInput("cloudGrid", self.voxelGrid)
        sattr = dummy.getAttrib(ShaderAttrib)
        Globals.base.graphicsEngine.dispatch_compute(
            (self.cloudResolution / 8, self.cloudResolution / 8, self.cloudResolutionH / 8), sattr, Globals.base.win.getGsg())

        shader = Shader.loadCompute(Shader.SLGLSL, "Shader/Clouds/CloudNoise.compute")
        dummy = NodePath("dummy")
        dummy.setShader(shader)
        dummy.setShaderInput("noiseGrid", self.cloudNoise)
        sattr = dummy.getAttrib(ShaderAttrib)
        Globals.base.graphicsEngine.dispatch_compute(
            (64 / 8, 64 / 8, 64 / 8), sattr, Globals.base.win.getGsg())
Exemple #2
0
    def _createInitialGrid(self):
        """ Creates the initial cloud grid """
        shader = Shader.loadCompute(Shader.SLGLSL,
                                    "Shader/Clouds/InitialGrid.compute")
        dummy = NodePath("dummy")
        dummy.setShader(shader)
        dummy.setShaderInput("cloudGrid", self.voxelGrid)
        sattr = dummy.getAttrib(ShaderAttrib)
        Globals.base.graphicsEngine.dispatch_compute(
            (self.cloudResolution / 8, self.cloudResolution / 8,
             self.cloudResolutionH / 8), sattr, Globals.base.win.getGsg())

        shader = Shader.loadCompute(Shader.SLGLSL,
                                    "Shader/Clouds/CloudNoise.compute")
        dummy = NodePath("dummy")
        dummy.setShader(shader)
        dummy.setShaderInput("noiseGrid", self.cloudNoise)
        sattr = dummy.getAttrib(ShaderAttrib)
        Globals.base.graphicsEngine.dispatch_compute((64 / 8, 64 / 8, 64 / 8),
                                                     sattr,
                                                     Globals.base.win.getGsg())
Exemple #3
0
class GPU_Image:
    
    lib_path = "/e/dev/solex/gpu/gpu_image_lib.glsl"
        
    def __init__(self, ref_img,
                       workgroup_size = LVector3i(32,32,1),
                       img_format = Texture.FRgba8,
                       print_times = False):
        
        self.workgroup_size = workgroup_size
        self.img_format = img_format
        self.x_size = ref_img.getXSize()
        self.y_size = ref_img.getYSize()
        self.z_size = 1
        self.prepare_time = 0
        self.process_time = 0
        self.extract_time = 0
        
        self.__NP = NodePath("gpu")
        self.__gsg = base.win.getGsg()
        self.__ref_tex = self.__get_Texture(ref_img)
        self.__LINES = self.__Setup()
        self.__print_times = print_times
    
    def __enter__(self):
        self.process_time = 0
        self.extract_time = 0
        return self
            
    def __exit__(self, *e_info):
        if self.__print_times:
            total_time = round(self.prepare_time+self.process_time+self.extract_time, 3)
            prep_time = round(self.prepare_time, 3)
            proc_time = round(self.process_time, 3)
            extr_time = round(self.extract_time, 3)
            print()
            print("GPU_Image total time: {}".format(total_time))
            print("  prepare: {}  ({}%)".format(prep_time, round(prep_time/total_time*100),2))
            print("  process: {}  ({}%)".format(proc_time, round(proc_time/total_time*100),2))
            print("  extract: {}  ({}%)".format(extr_time, round(extr_time/total_time*100),2))
            
    
    def __get_Texture(self, ref_img):
        # Convert ref_img into texture.
        with TimeIt() as prep_timer:
            ref_tex = Texture()
            # Ensure ref image has an alpha channel.
            if not ref_img.hasAlpha():
                ref_img.addAlpha()
                ref_img.alphaFill(1.0)
            # Load tex and set format
            ref_tex.load(ref_img)
            ref_tex.setFormat(self.img_format)
        self.prepare_time += round(prep_timer.total_time, 3)
        return ref_tex

    def __Setup(self):
        """Prepares GPU_Image obj to receive python calls to
        the shader library."""
        
        # Open the shader as a file and get lines so we
        # can extract some setup info from it.
        shader_os_path = Filename(self.lib_path).toOsLongName()
        with open(shader_os_path, "r") as shader_file:
            lines = list(shader_file.readlines())
        
        # Extract lib function names.
        for line in lines:
            # Each function within the image_lib is defined within the confines
            # of an "#ifdef/#endif" block; the name we need immediately follows
            # the "#ifdef" keyword. This name gets mapped directly to "self"
            # as an alias for "self.__Call" so that the user can simply call
            # the shader function as though it were a regular method of "self".
            if line.startswith("#ifdef"):
                func_name = line.split(" ")[1].strip()
                
                # Setup callback that redirects to self.__Call each time
                # "self.<func_name> is called, passing along arguments;
                # return the modified image (as a Texture object).
                def call(func_name=func_name, **kwargs):
                    mod_tex = self.__Call(str(func_name), **kwargs)
                    return mod_tex
                
                # Map "func_name" directly to "self".
                self.__dict__[func_name] = call
                
        # Add workgroup size layout declaration.
        wg = self.workgroup_size
        wg_str = "layout (local_size_x={}, local_size_y={}) in;\n"
        wg_line = wg_str.format(wg.x, wg.y)
        lines.insert(8, wg_line)
        return lines

    def __Call(self, func_name, **kwargs):
        """Receive python call and redirect request to relevant
        function in image shader library; return modified image."""
        
        # Copy self.__Lines (need to keep orig for further calls) and
        # add "#define" statement to top to trigger compilation of
        # relevant "#ifdef/def" function block in shader.
        
        lines = list(self.__LINES)
        lines.insert(2, "#define {}".format(func_name))
        
        # Assemble lines into shader str and compile.
        shader_str = "".join(lines)
        self.__NP.setShader(Shader.makeCompute(Shader.SL_GLSL, shader_str))
        
        # Set block size from workgroup size.
        block_x = int(self.x_size/self.workgroup_size.x)
        block_y = int(self.y_size/self.workgroup_size.y)
        block_z = int(self.z_size/self.workgroup_size.z)
        block_size = LVector3i(block_x,block_y,block_z)
        
        # Create mod_tex for GPU.
        with TimeIt() as prep_timer:
            mod_img = PNMImage(self.x_size, self.y_size, 4)
            mod_tex = Texture()
            mod_tex.load(mod_img)
            mod_tex.setMinfilter(Texture.FTLinear)
            mod_tex.setFormat(self.img_format)
        self.prepare_time += prep_timer.total_time
        
        # Pass textures to shader.
        self.__NP.setShaderInput("ref_tex", self.__ref_tex)
        self.__NP.setShaderInput("mod_tex", mod_tex)
        
        # Set any additional required inputs for this function.
        for input_name, input_val in list(kwargs.items()):
            if type(input_val) == PNMImage:
                input_val = self.__get_Texture(input_val)
            self.__NP.setShaderInput(input_name, input_val)
        
        # Call function in shader library.
        shader_attrib = self.__NP.getAttrib(ShaderAttrib)
        with TimeIt() as proc_timer:
            base.graphicsEngine.dispatch_compute(block_size, shader_attrib, self.__gsg)
        self.process_time += proc_timer.total_time
        
        # Extract modified texture from GPU.
        with TimeIt() as extract_timer: 
            base.graphicsEngine.extractTextureData(mod_tex, self.__gsg)
        self.extract_time += extract_timer.total_time
        return mod_tex
class WaterManager(DebugObject):

    """ Simple wrapper arround WaterDisplacement which combines 3 displacement
    maps into one, and also generates a normal map """

    def __init__(self):
        DebugObject.__init__(self, "WaterManager")
        self.options = OceanOptions()
        self.options.size = 512
        self.options.windDir.normalize()
        self.options.waveAmplitude *= 1e-7

        self.displacementTex = Texture("Displacement")
        self.displacementTex.setup2dTexture(
            self.options.size, self.options.size,
            Texture.TFloat, Texture.FRgba16)

        self.normalTex = Texture("Normal")
        self.normalTex.setup2dTexture(
            self.options.size, self.options.size,
            Texture.TFloat, Texture.FRgba16)

        self.combineShader = Shader.loadCompute(Shader.SLGLSL,
            "Shader/WaterFFT/Combine.compute")

        self.ptaTime = PTAFloat.emptyArray(1)

        # Create a gaussian random texture, as shaders aren't well suited
        # for that
        setRandomSeed(523)
        self.randomStorage = PNMImage(self.options.size, self.options.size, 4)
        self.randomStorage.setMaxval((2 ** 16) - 1)

        for x in xrange(self.options.size):
            for y in xrange(self.options.size):
                rand1 = self._getGaussianRandom() / 10.0 + 0.5
                rand2 = self._getGaussianRandom() / 10.0 + 0.5
                self.randomStorage.setXel(x, y, float(rand1), float(rand2), 0)
                self.randomStorage.setAlpha(x, y, 1.0)

        self.randomStorageTex = Texture("RandomStorage")
        self.randomStorageTex.load(self.randomStorage)
        self.randomStorageTex.setFormat(Texture.FRgba16)
        self.randomStorageTex.setMinfilter(Texture.FTNearest)
        self.randomStorageTex.setMagfilter(Texture.FTNearest)

        # Create the texture wwhere the intial height (H0 + Omega0) is stored.
        self.texInitialHeight = Texture("InitialHeight")
        self.texInitialHeight.setup2dTexture(
            self.options.size, self.options.size,
            Texture.TFloat, Texture.FRgba16)
        self.texInitialHeight.setMinfilter(Texture.FTNearest)
        self.texInitialHeight.setMagfilter(Texture.FTNearest)

        # Create the shader which populates the initial height texture
        self.shaderInitialHeight = Shader.loadCompute(Shader.SLGLSL,
            "Shader/WaterFFT/InitialHeight.compute")
        self.nodeInitialHeight = NodePath("initialHeight")
        self.nodeInitialHeight.setShader(self.shaderInitialHeight)
        self.nodeInitialHeight.setShaderInput("dest", self.texInitialHeight)
        self.nodeInitialHeight.setShaderInput(
            "N", LVecBase2i(self.options.size))
        self.nodeInitialHeight.setShaderInput(
            "patchLength", self.options.patchLength)
        self.nodeInitialHeight.setShaderInput("windDir", self.options.windDir)
        self.nodeInitialHeight.setShaderInput(
            "windSpeed", self.options.windSpeed)
        self.nodeInitialHeight.setShaderInput(
            "waveAmplitude", self.options.waveAmplitude)
        self.nodeInitialHeight.setShaderInput(
            "windDependency", self.options.windDependency)
        self.nodeInitialHeight.setShaderInput(
            "randomTex", self.randomStorageTex)

        self.attrInitialHeight = self.nodeInitialHeight.getAttrib(ShaderAttrib)

        self.heightTextures = []
        for i in xrange(3):

            tex = Texture("Height")
            tex.setup2dTexture(self.options.size, self.options.size,
                               Texture.TFloat, Texture.FRgba16)
            tex.setMinfilter(Texture.FTNearest)
            tex.setMagfilter(Texture.FTNearest)
            tex.setWrapU(Texture.WMClamp)
            tex.setWrapV(Texture.WMClamp)
            self.heightTextures.append(tex)

        # Also create the shader which updates the spectrum
        self.shaderUpdate = Shader.loadCompute(Shader.SLGLSL,
            "Shader/WaterFFT/Update.compute")
        self.nodeUpdate = NodePath("update")
        self.nodeUpdate.setShader(self.shaderUpdate)
        self.nodeUpdate.setShaderInput("outH0x", self.heightTextures[0])
        self.nodeUpdate.setShaderInput("outH0y", self.heightTextures[1])
        self.nodeUpdate.setShaderInput("outH0z", self.heightTextures[2])
        self.nodeUpdate.setShaderInput("initialHeight", self.texInitialHeight)
        self.nodeUpdate.setShaderInput("N", LVecBase2i(self.options.size))
        self.nodeUpdate.setShaderInput("time", self.ptaTime)
        self.attrUpdate = self.nodeUpdate.getAttrib(ShaderAttrib)

        # Create 3 FFTs
        self.fftX = GPUFFT(self.options.size, self.heightTextures[0],
                           self.options.normalizationFactor)
        self.fftY = GPUFFT(self.options.size, self.heightTextures[1],
                           self.options.normalizationFactor)
        self.fftZ = GPUFFT(self.options.size, self.heightTextures[2],
                           self.options.normalizationFactor)

        self.combineNode = NodePath("Combine")
        self.combineNode.setShader(self.combineShader)
        self.combineNode.setShaderInput(
            "displacementX", self.fftX.getResultTexture())
        self.combineNode.setShaderInput(
            "displacementY", self.fftY.getResultTexture())
        self.combineNode.setShaderInput(
            "displacementZ", self.fftZ.getResultTexture())
        self.combineNode.setShaderInput("normalDest", self.normalTex)
        self.combineNode.setShaderInput(
            "displacementDest", self.displacementTex)
        self.combineNode.setShaderInput(
            "N", LVecBase2i(self.options.size))
        self.combineNode.setShaderInput(
            "choppyScale", self.options.choppyScale)
        self.combineNode.setShaderInput(
            "gridLength", self.options.patchLength)
        # Store only the shader attrib as this is way faster
        self.attrCombine = self.combineNode.getAttrib(ShaderAttrib)

    def _getGaussianRandom(self):
        """ Returns a gaussian random number """
        u1 = generateRandom()
        u2 = generateRandom()
        if u1 < 1e-6:
            u1 = 1e-6
        return sqrt(-2 * log(u1)) * cos(2 * pi * u2)

    def setup(self):
        """ Setups the manager """

        Globals.base.graphicsEngine.dispatch_compute(
            (self.options.size / 16,
             self.options.size / 16, 1), self.attrInitialHeight,
            Globals.base.win.get_gsg())

    def getDisplacementTexture(self):
        """ Returns the displacement texture, storing the 3D Displacement in
        the RGB channels """
        return self.displacementTex

    def getNormalTexture(self):
        """ Returns the normal texture, storing the normal in world space in
        the RGB channels """
        return self.normalTex

    def update(self):
        """ Updates the displacement / normal map """

        self.ptaTime[0] = 1 + \
            Globals.clock.getFrameTime() * self.options.timeScale

        Globals.base.graphicsEngine.dispatch_compute(
            (self.options.size / 16,
             self.options.size / 16, 1), self.attrUpdate,
            Globals.base.win.get_gsg())

        self.fftX.execute()
        self.fftY.execute()
        self.fftZ.execute()

        # Execute the shader which combines the 3 displacement maps into
        # 1 displacement texture and 1 normal texture. We could use dFdx in
        # the fragment shader, however that gives no accurate results as
        # dFdx returns the same value for a 2x2 pixel block
        Globals.base.graphicsEngine.dispatch_compute(
            (self.options.size / 16,
             self.options.size / 16, 1), self.attrCombine,
            Globals.base.win.get_gsg())
Exemple #5
0
class WaterManager(DebugObject):
    """ Simple wrapper arround WaterDisplacement which combines 3 displacement
    maps into one, and also generates a normal map """
    def __init__(self):
        DebugObject.__init__(self, "WaterManager")
        self.options = OceanOptions()
        self.options.size = 512
        self.options.windDir.normalize()
        self.options.waveAmplitude *= 1e-7

        self.displacementTex = Texture("Displacement")
        self.displacementTex.setup2dTexture(self.options.size,
                                            self.options.size, Texture.TFloat,
                                            Texture.FRgba16)

        self.normalTex = Texture("Normal")
        self.normalTex.setup2dTexture(self.options.size, self.options.size,
                                      Texture.TFloat, Texture.FRgba16)

        self.combineShader = BetterShader.loadCompute(
            "Shader/Water/Combine.compute")

        self.ptaTime = PTAFloat.emptyArray(1)

        # Create a gaussian random texture, as shaders aren't well suited
        # for that
        setRandomSeed(523)
        self.randomStorage = PNMImage(self.options.size, self.options.size, 4)
        self.randomStorage.setMaxval((2**16) - 1)

        for x in xrange(self.options.size):
            for y in xrange(self.options.size):
                rand1 = self._getGaussianRandom() / 10.0 + 0.5
                rand2 = self._getGaussianRandom() / 10.0 + 0.5
                self.randomStorage.setXel(x, y, LVecBase3d(rand1, rand2, 0))
                self.randomStorage.setAlpha(x, y, 1.0)

        self.randomStorageTex = Texture("RandomStorage")
        self.randomStorageTex.load(self.randomStorage)
        self.randomStorageTex.setFormat(Texture.FRgba16)
        self.randomStorageTex.setMinfilter(Texture.FTNearest)
        self.randomStorageTex.setMagfilter(Texture.FTNearest)

        # Create the texture wwhere the intial height (H0 + Omega0) is stored.
        self.texInitialHeight = Texture("InitialHeight")
        self.texInitialHeight.setup2dTexture(self.options.size,
                                             self.options.size, Texture.TFloat,
                                             Texture.FRgba16)
        self.texInitialHeight.setMinfilter(Texture.FTNearest)
        self.texInitialHeight.setMagfilter(Texture.FTNearest)

        # Create the shader which populates the initial height texture
        self.shaderInitialHeight = BetterShader.loadCompute(
            "Shader/Water/InitialHeight.compute")
        self.nodeInitialHeight = NodePath("initialHeight")
        self.nodeInitialHeight.setShader(self.shaderInitialHeight)
        self.nodeInitialHeight.setShaderInput("dest", self.texInitialHeight)
        self.nodeInitialHeight.setShaderInput("N",
                                              LVecBase2i(self.options.size))
        self.nodeInitialHeight.setShaderInput("patchLength",
                                              self.options.patchLength)
        self.nodeInitialHeight.setShaderInput("windDir", self.options.windDir)
        self.nodeInitialHeight.setShaderInput("windSpeed",
                                              self.options.windSpeed)
        self.nodeInitialHeight.setShaderInput("waveAmplitude",
                                              self.options.waveAmplitude)
        self.nodeInitialHeight.setShaderInput("windDependency",
                                              self.options.windDependency)
        self.nodeInitialHeight.setShaderInput("randomTex",
                                              self.randomStorageTex)

        self.attrInitialHeight = self.nodeInitialHeight.getAttrib(ShaderAttrib)

        self.heightTextures = []
        for i in xrange(3):

            tex = Texture("Height")
            tex.setup2dTexture(self.options.size, self.options.size,
                               Texture.TFloat, Texture.FRgba16)
            tex.setMinfilter(Texture.FTNearest)
            tex.setMagfilter(Texture.FTNearest)
            tex.setWrapU(Texture.WMClamp)
            tex.setWrapV(Texture.WMClamp)
            self.heightTextures.append(tex)

        # Also create the shader which updates the spectrum
        self.shaderUpdate = BetterShader.loadCompute(
            "Shader/Water/Update.compute")
        self.nodeUpdate = NodePath("update")
        self.nodeUpdate.setShader(self.shaderUpdate)
        self.nodeUpdate.setShaderInput("outH0x", self.heightTextures[0])
        self.nodeUpdate.setShaderInput("outH0y", self.heightTextures[1])
        self.nodeUpdate.setShaderInput("outH0z", self.heightTextures[2])
        self.nodeUpdate.setShaderInput("initialHeight", self.texInitialHeight)
        self.nodeUpdate.setShaderInput("N", LVecBase2i(self.options.size))
        self.nodeUpdate.setShaderInput("time", self.ptaTime)
        self.attrUpdate = self.nodeUpdate.getAttrib(ShaderAttrib)

        # Create 3 FFTs
        self.fftX = GPUFFT(self.options.size, self.heightTextures[0],
                           self.options.normalizationFactor)
        self.fftY = GPUFFT(self.options.size, self.heightTextures[1],
                           self.options.normalizationFactor)
        self.fftZ = GPUFFT(self.options.size, self.heightTextures[2],
                           self.options.normalizationFactor)

        self.combineNode = NodePath("Combine")
        self.combineNode.setShader(self.combineShader)
        self.combineNode.setShaderInput("displacementX",
                                        self.fftX.getResultTexture())
        self.combineNode.setShaderInput("displacementY",
                                        self.fftY.getResultTexture())
        self.combineNode.setShaderInput("displacementZ",
                                        self.fftZ.getResultTexture())
        self.combineNode.setShaderInput("normalDest", self.normalTex)
        self.combineNode.setShaderInput("displacementDest",
                                        self.displacementTex)
        self.combineNode.setShaderInput("N", LVecBase2i(self.options.size))
        self.combineNode.setShaderInput("choppyScale",
                                        self.options.choppyScale)
        self.combineNode.setShaderInput("gridLength", self.options.patchLength)
        # Store only the shader attrib as this is way faster
        self.attrCombine = self.combineNode.getAttrib(ShaderAttrib)

    def _getGaussianRandom(self):
        """ Returns a gaussian random number """
        u1 = generateRandom()
        u2 = generateRandom()
        if u1 < 1e-6:
            u1 = 1e-6
        return sqrt(-2 * log(u1)) * cos(2 * pi * u2)

    def setup(self):
        """ Setups the manager """

        Globals.base.graphicsEngine.dispatch_compute(
            (self.options.size / 16, self.options.size / 16, 1),
            self.attrInitialHeight, Globals.base.win.get_gsg())

    def getDisplacementTexture(self):
        """ Returns the displacement texture, storing the 3D Displacement in
        the RGB channels """
        return self.displacementTex

    def getNormalTexture(self):
        """ Returns the normal texture, storing the normal in world space in
        the RGB channels """
        return self.normalTex

    def update(self):
        """ Updates the displacement / normal map """

        self.ptaTime[0] = 1 + \
            Globals.clock.getFrameTime() * self.options.timeScale

        Globals.base.graphicsEngine.dispatch_compute(
            (self.options.size / 16, self.options.size / 16, 1),
            self.attrUpdate, Globals.base.win.get_gsg())

        self.fftX.execute()
        self.fftY.execute()
        self.fftZ.execute()

        # Execute the shader which combines the 3 displacement maps into
        # 1 displacement texture and 1 normal texture. We could use dFdx in
        # the fragment shader, however that gives no accurate results as
        # dFdx returns the same value for a 2x2 pixel block
        Globals.base.graphicsEngine.dispatch_compute(
            (self.options.size / 16, self.options.size / 16, 1),
            self.attrCombine, Globals.base.win.get_gsg())