(self)
Renders one frame of the visualization.
        Raises a RenderingStoppedException if pi3d does no rendering anymore.
        (most probably because the window was closed)
Renders one frame of the visualization.
        Raises a RenderingStoppedException if pi3d does no rendering anymore.
        (most probably because the window was closed)
Renders one frame of the visualization . Raises a RenderingStoppedException if pi3d does no rendering anymore . ( most probably because the window was closed )
def draw(self) -> None:
        """Renders one frame of the visualization.
        Raises a RenderingStoppedException if pi3d does no rendering anymore.
        (most probably because the window was closed)"""
        import numpy as np
        from scipy.ndimage.filters import gaussian_filter
        from pi3d.Camera import Camera
        from pi3d.constants import (
            opengles,
            GL_SRC_ALPHA,
            GL_ONE_MINUS_SRC_ALPHA,
            GLsizei,
            GLboolean,
            GLint,
            GL_FLOAT,
            GL_ARRAY_BUFFER,
            GL_UNSIGNED_SHORT,
            GL_TEXTURE_2D,
            GL_UNSIGNED_BYTE,
        )

        time_logging = False

        if self.should_prepare:
            self._prepare()

        if self.manager.alarm_program.factor != -1:
            self.alarm_factor = max(0.001, self.manager.alarm_program.factor)
        else:
            self.alarm_factor = 0

        then = time.time()

        # loop_running does all of pi3d's logic and needs to be called every frame
        if not self.display.loop_running():
            raise RenderingStoppedException

        now = self.display.time
        self.time_delta = now - self.last_loop
        self.last_loop = now
        self.time_elapsed += self.time_delta

        if time_logging:
            print(f"{time.time() - then} main loop")
            then = time.time()

        # use a sliding window to smooth the spectrum with a gauss function
        # truncating does not save significant time (~3% for this step)

        # new_frame = np.array(self.cava.current_frame, dtype="float32")
        new_frame = gaussian_filter(self.cava.current_frame, sigma=1.5, mode="nearest")
        # only needed when not all bars are used
        # new_frame = new_frame[self.SPECTRUM_CUT : -self.SPECTRUM_CUT]
        new_frame = -0.5 * new_frame ** 3 + 1.5 * new_frame
        new_frame *= 255
        current_frame = new_frame

        if time_logging:
            print(f"{time.time() - then} spectrum smoothing")
            then = time.time()

        # Value used for circle shake and background color cycle
        # select the first few values and compute their average
        bass_elements = math.ceil(self.BASS_MAX * self.cava.bars)
        self.bass_value = sum(current_frame[0:bass_elements]) / bass_elements / 255
        self.bass_value = max(self.bass_value, self.alarm_factor)
        self.total_bass = self.total_bass + self.bass_value
        # the fraction of time that there was bass
        self.bass_fraction = self.total_bass / self.time_elapsed / self.manager.ups

        self.uniform_values = {
            48: self.width / self.scale,
            49: self.height / self.scale,
            50: self.scale,
            51: self.FFT_HIST,
            52: self.NUM_PARTICLES,
            53: self.PARTICLE_SPAWN_Z,
            54: self.time_elapsed,
            55: self.time_delta,
            56: self.alarm_factor,
            57: self.bass_value,
            58: self.total_bass,
            59: self.bass_fraction,
        }

        # start rendering to the smaller OffscreenTexture
        # we decrease the size of the texture so it only allocates that much memory
        # otherwise it would use as much as the displays size, negating its positive effect
        self.post.ix = int(self.post.ix / self.scale)
        self.post.iy = int(self.post.iy / self.scale)
        opengles.glViewport(
            GLint(0),
            GLint(0),
            GLsizei(int(self.width / self.scale)),
            GLsizei(int(self.height / self.scale)),
        )
        self.post._start()
        self.post.ix = self.width
        self.post.iy = self.height

        self._set_unif(self.background, [48, 49, 54, 56, 58])
        self.background.draw()

        if time_logging:
            print(f"{time.time() - then} background draw")
            then = time.time()

        # enable additive blending so the draw order of overlapping particles does not matter
        opengles.glBlendFunc(1, 1)

        self._set_unif(self.particle_sprite, [53, 54, 59])

        # copied code from pi3d.Shape.draw()
        # we don't need modelmatrices, normals ord textures and always blend
        self.particle_sprite.load_opengl()
        camera = Camera.instance()
        if not camera.mtrx_made:
            camera.make_mtrx()
        self.particle_sprite.MRaw = self.particle_sprite.tr1
        self.particle_sprite.M[0, :, :] = self.particle_sprite.MRaw[:, :]
        self.particle_sprite.M[1, :, :] = np.dot(
            self.particle_sprite.MRaw, camera.mtrx
        )[:, :]

        # Buffer.draw()
        buf = self.particle_sprite.buf[0]
        buf.load_opengl()
        shader = buf.shader
        shader.use()
        opengles.glUniformMatrix4fv(
            shader.unif_modelviewmatrix,
            GLsizei(2),
            GLboolean(0),
            self.particle_sprite.M.ctypes.data,
        )
        opengles.glUniform3fv(shader.unif_unif, GLsizei(20), self.particle_sprite.unif)
        buf._select()
        opengles.glVertexAttribPointer(
            shader.attr_vertex, GLint(3), GL_FLOAT, GLboolean(0), buf.N_BYTES, 0
        )
        opengles.glEnableVertexAttribArray(shader.attr_vertex)
        opengles.glVertexAttribPointer(
            shader.attr_texcoord, GLint(2), GL_FLOAT, GLboolean(0), buf.N_BYTES, 24
        )
        opengles.glEnableVertexAttribArray(shader.attr_texcoord)
        buf.disp.last_shader = shader
        opengles.glUniform3fv(shader.unif_unib, GLsizei(5), buf.unib)

        opengles.glBindBuffer(GL_ARRAY_BUFFER, self.instance_vbo)
        opengles.glDrawElementsInstanced(
            buf.draw_method,
            GLsizei(buf.ntris * 3),
            GL_UNSIGNED_SHORT,
            0,
            self.NUM_PARTICLES,
        )

        # restore normal blending
        opengles.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)

        if time_logging:
            print(f"{time.time() - then} particle draw")
            then = time.time()

        # roll the history one further, insert the current one.
        # we use a texture with four channels eventhough we only need one, refer to this post:
        # https://community.khronos.org/t/updating-textures-per-frame/75020/3
        # basically the gpu converts it anyway, so other formats would be slower
        history = np.zeros(
            (self.FFT_HIST, self.cava.bars - 2 * self.SPECTRUM_CUT, 4), dtype="uint8"
        )
        self.fft = np.roll(self.fft, 1, 0)
        self.fft[0] = current_frame
        history[:, :, 0] = self.fft

        if time_logging:
            print(f"{time.time() - then} spectrum roll")
            then = time.time()

        # change the spectrum part of the texture (the lower 256xFFT_HIST pixels)
        opengles.glBindTexture(GL_TEXTURE_2D, self.dynamic_texture._tex)
        iformat = self.dynamic_texture._get_format_from_array(
            history, self.dynamic_texture.i_format
        )
        # If everything goes fine, this takes around 0.0003s.
        # Usually, performance degrades and the duration increases to ~0.0200s
        # My guess is that the fast speeds result from the texture staying in cache,
        # but with different stuff going on in the system the cache is used for different data
        # Unfortunately I have no idea how to improve this.
        opengles.glTexSubImage2D(
            GL_TEXTURE_2D,
            0,
            0,
            self.dynamic_texture.ix,
            history.shape[1],
            history.shape[0],
            iformat,
            GL_UNSIGNED_BYTE,
            history.ctypes.data_as(ctypes.POINTER(ctypes.c_ubyte)),
        )

        if time_logging:
            print(f"{time.time() - then} glTexImage2D")
            then = time.time()

        self._set_unif(self.spectrum, [48, 49, 51, 52, 53, 54, 55, 57, 58])
        self.spectrum.draw()

        if time_logging:
            print(f"{time.time() - then} spectrum draw")
            then = time.time()

        self._set_unif(self.logo, [48, 49, 51, 54, 57, 58])
        self.logo.draw()

        if time_logging:
            print(f"{time.time() - then} logo draw")
            then = time.time()

        self._set_unif(self.after, [48, 49, 54, 57])
        self.after.draw()

        if time_logging:
            print(f"{time.time() - then} after draw")
            then = time.time()

        self.post._end()

        opengles.glViewport(
            GLint(0), GLint(0), GLsizei(self.width), GLsizei(self.height)
        )
        self._set_unif(self.post_sprite, [50])
        self.post_sprite.draw()

        if time_logging:
            print(f"{time.time() - then} post draw")
            print(f"scale: {self.scale}")
            print("=====")
def draw ( self ) -> None : import numpy as np from scipy . ndimage . filters import gaussian_filter from pi3d . Camera import Camera from pi3d . constants import ( opengles , GL_SRC_ALPHA , GL_ONE_MINUS_SRC_ALPHA , GLsizei , GLboolean , GLint , GL_FLOAT , GL_ARRAY_BUFFER , GL_UNSIGNED_SHORT , GL_TEXTURE_2D , GL_UNSIGNED_BYTE , ) time_logging = False if self . should_prepare : self . _prepare ( ) if self . manager . alarm_program . factor != - 1 : self . alarm_factor = max ( 0.001 , self . manager . alarm_program . factor ) else : self . alarm_factor = 0 then = time . time ( ) # loop_running does all of pi3d's logic and needs to be called every frame if not self . display . loop_running ( ) : raise RenderingStoppedException now = self . display . time self . time_delta = now - self . last_loop self . last_loop = now self . time_elapsed += self . time_delta if time_logging : print ( f"{time.time() - then} main loop" ) then = time . time ( ) # use a sliding window to smooth the spectrum with a gauss function # truncating does not save significant time (~3% for this step) # new_frame = np.array(self.cava.current_frame, dtype="float32") new_frame = gaussian_filter ( self . cava . current_frame , sigma = 1.5 , mode = "nearest" ) # only needed when not all bars are used # new_frame = new_frame[self.SPECTRUM_CUT : -self.SPECTRUM_CUT] new_frame = - 0.5 * new_frame ** 3 + 1.5 * new_frame new_frame *= 255 current_frame = new_frame if time_logging : print ( f"{time.time() - then} spectrum smoothing" ) then = time . time ( ) # Value used for circle shake and background color cycle # select the first few values and compute their average bass_elements = math . ceil ( self . BASS_MAX * self . cava . bars ) self . bass_value = sum ( current_frame [ 0 : bass_elements ] ) / bass_elements / 255 self . bass_value = max ( self . bass_value , self . alarm_factor ) self . total_bass = self . total_bass + self . bass_value # the fraction of time that there was bass self . bass_fraction = self . total_bass / self . time_elapsed / self . manager . ups self . uniform_values = { 48 : self . width / self . scale , 49 : self . height / self . scale , 50 : self . scale , 51 : self . FFT_HIST , 52 : self . NUM_PARTICLES , 53 : self . PARTICLE_SPAWN_Z , 54 : self . time_elapsed , 55 : self . time_delta , 56 : self . alarm_factor , 57 : self . bass_value , 58 : self . total_bass , 59 : self . bass_fraction , } # start rendering to the smaller OffscreenTexture # we decrease the size of the texture so it only allocates that much memory # otherwise it would use as much as the displays size, negating its positive effect self . post . ix = int ( self . post . ix / self . scale ) self . post . iy = int ( self . post . iy / self . scale ) opengles . glViewport ( GLint ( 0 ) , GLint ( 0 ) , GLsizei ( int ( self . width / self . scale ) ) , GLsizei ( int ( self . height / self . scale ) ) , ) self . post . _start ( ) self . post . ix = self . width self . post . iy = self . height self . _set_unif ( self . background , [ 48 , 49 , 54 , 56 , 58 ] ) self . background . draw ( ) if time_logging : print ( f"{time.time() - then} background draw" ) then = time . time ( ) # enable additive blending so the draw order of overlapping particles does not matter opengles . glBlendFunc ( 1 , 1 ) self . _set_unif ( self . particle_sprite , [ 53 , 54 , 59 ] ) # copied code from pi3d.Shape.draw() # we don't need modelmatrices, normals ord textures and always blend self . particle_sprite . load_opengl ( ) camera = Camera . instance ( ) if not camera . mtrx_made : camera . make_mtrx ( ) self . particle_sprite . MRaw = self . particle_sprite . tr1 self . particle_sprite . M [ 0 , : , : ] = self . particle_sprite . MRaw [ : , : ] self . particle_sprite . M [ 1 , : , : ] = np . dot ( self . particle_sprite . MRaw , camera . mtrx ) [ : , : ] # Buffer.draw() buf = self . particle_sprite . buf [ 0 ] buf . load_opengl ( ) shader = buf . shader shader . use ( ) opengles . glUniformMatrix4fv ( shader . unif_modelviewmatrix , GLsizei ( 2 ) , GLboolean ( 0 ) , self . particle_sprite . M . ctypes . data , ) opengles . glUniform3fv ( shader . unif_unif , GLsizei ( 20 ) , self . particle_sprite . unif ) buf . _select ( ) opengles . glVertexAttribPointer ( shader . attr_vertex , GLint ( 3 ) , GL_FLOAT , GLboolean ( 0 ) , buf . N_BYTES , 0 ) opengles . glEnableVertexAttribArray ( shader . attr_vertex ) opengles . glVertexAttribPointer ( shader . attr_texcoord , GLint ( 2 ) , GL_FLOAT , GLboolean ( 0 ) , buf . N_BYTES , 24 ) opengles . glEnableVertexAttribArray ( shader . attr_texcoord ) buf . disp . last_shader = shader opengles . glUniform3fv ( shader . unif_unib , GLsizei ( 5 ) , buf . unib ) opengles . glBindBuffer ( GL_ARRAY_BUFFER , self . instance_vbo ) opengles . glDrawElementsInstanced ( buf . draw_method , GLsizei ( buf . ntris * 3 ) , GL_UNSIGNED_SHORT , 0 , self . NUM_PARTICLES , ) # restore normal blending opengles . glBlendFunc ( GL_SRC_ALPHA , GL_ONE_MINUS_SRC_ALPHA ) if time_logging : print ( f"{time.time() - then} particle draw" ) then = time . time ( ) # roll the history one further, insert the current one. # we use a texture with four channels eventhough we only need one, refer to this post: # https://community.khronos.org/t/updating-textures-per-frame/75020/3 # basically the gpu converts it anyway, so other formats would be slower history = np . zeros ( ( self . FFT_HIST , self . cava . bars - 2 * self . SPECTRUM_CUT , 4 ) , dtype = "uint8" ) self . fft = np . roll ( self . fft , 1 , 0 ) self . fft [ 0 ] = current_frame history [ : , : , 0 ] = self . fft if time_logging : print ( f"{time.time() - then} spectrum roll" ) then = time . time ( ) # change the spectrum part of the texture (the lower 256xFFT_HIST pixels) opengles . glBindTexture ( GL_TEXTURE_2D , self . dynamic_texture . _tex ) iformat = self . dynamic_texture . _get_format_from_array ( history , self . dynamic_texture . i_format ) # If everything goes fine, this takes around 0.0003s. # Usually, performance degrades and the duration increases to ~0.0200s # My guess is that the fast speeds result from the texture staying in cache, # but with different stuff going on in the system the cache is used for different data # Unfortunately I have no idea how to improve this. opengles . glTexSubImage2D ( GL_TEXTURE_2D , 0 , 0 , self . dynamic_texture . ix , history . shape [ 1 ] , history . shape [ 0 ] , iformat , GL_UNSIGNED_BYTE , history . ctypes . data_as ( ctypes . POINTER ( ctypes . c_ubyte ) ) , ) if time_logging : print ( f"{time.time() - then} glTexImage2D" ) then = time . time ( ) self . _set_unif ( self . spectrum , [ 48 , 49 , 51 , 52 , 53 , 54 , 55 , 57 , 58 ] ) self . spectrum . draw ( ) if time_logging : print ( f"{time.time() - then} spectrum draw" ) then = time . time ( ) self . _set_unif ( self . logo , [ 48 , 49 , 51 , 54 , 57 , 58 ] ) self . logo . draw ( ) if time_logging : print ( f"{time.time() - then} logo draw" ) then = time . time ( ) self . _set_unif ( self . after , [ 48 , 49 , 54 , 57 ] ) self . after . draw ( ) if time_logging : print ( f"{time.time() - then} after draw" ) then = time . time ( ) self . post . _end ( ) opengles . glViewport ( GLint ( 0 ) , GLint ( 0 ) , GLsizei ( self . width ) , GLsizei ( self . height ) ) self . _set_unif ( self . post_sprite , [ 50 ] ) self . post_sprite . draw ( ) if time_logging : print ( f"{time.time() - then} post draw" ) print ( f"scale: {self.scale}" ) print ( "=====" )