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())
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())
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())
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())