def _load_opengl(self): self.vbuf = GLuint() opengles.glGenBuffers(GLsizei(1), ctypes.byref(self.vbuf)) self.ebuf = GLuint() opengles.glGenBuffers(GLsizei(1), ctypes.byref(self.ebuf)) self.disp.vbufs_dict[str(self.vbuf)] = [self.vbuf, 0] self.disp.ebufs_dict[str(self.ebuf)] = [self.ebuf, 0] self._select() opengles.glBufferData(GL_ARRAY_BUFFER, self.array_buffer.nbytes, self.array_buffer.ctypes.data_as(ctypes.POINTER(GLfloat)), GL_STATIC_DRAW) opengles.glBufferData(GL_ELEMENT_ARRAY_BUFFER, self.element_array_buffer.nbytes, self.element_array_buffer.ctypes.data_as(ctypes.POINTER(GLfloat)), GL_STATIC_DRAW) if opengles.glGetError() == GL_OUT_OF_MEMORY: LOGGER.critical('Out of GPU memory')
def _load_opengl(self): self.vbuf = c_uint() opengles.glGenBuffers(1, ctypes.byref(self.vbuf)) self.ebuf = c_uint() opengles.glGenBuffers(1, ctypes.byref(self.ebuf)) self.disp.vbufs_dict[str(self.vbuf)] = [self.vbuf, 0] self.disp.ebufs_dict[str(self.ebuf)] = [self.ebuf, 0] self._select() opengles.glBufferData(GL_ARRAY_BUFFER, self.array_buffer.nbytes, self.array_buffer.ctypes.data_as(ctypes.POINTER(ctypes.c_float)), GL_STATIC_DRAW) opengles.glBufferData(GL_ELEMENT_ARRAY_BUFFER, self.element_array_buffer.nbytes, self.element_array_buffer.ctypes.data_as(ctypes.POINTER(ctypes.c_float)), GL_STATIC_DRAW) if opengles.glGetError() == GL_OUT_OF_MEMORY: LOGGER.critical('Out of GPU memory')
def _prepare(self) -> None: self.should_prepare = False import numpy as np # pi3d has to be imported in the same thread that it will draw in # Thus, import it here instead of at the top of the file import pi3d from pi3d.util.OffScreenTexture import OffScreenTexture from pi3d.constants import ( opengles, GL_CLAMP_TO_EDGE, GL_ALWAYS, ) # used for reimplementing the draw call with instancing from pi3d.constants import ( GLsizei, GLint, GLboolean, GL_FLOAT, GLuint, GL_ARRAY_BUFFER, GL_STATIC_DRAW, GLfloat, ) from PIL import Image # Setup display and initialise pi3d self.display = pi3d.Display.create( w=self.width, h=self.height, window_title="Raveberry" ) # error 0x500 after Display create # error = opengles.glGetError() # Set a pink background color so mistakes are clearly visible # self.display.set_background(1, 0, 1, 1) self.display.set_background(0, 0, 0, 1) # print OpenGL Version, useful for debugging # import ctypes # def print_char_p(addr): # g = (ctypes.c_char*32).from_address(addr) # i = 0 # while True: # c = g[i] # if c == b'\x00': # break # sys.stdout.write(c.decode()) # i += 1 # sys.stdout.write('\n') # print_char_p(opengles.glGetString(GL_VERSION)) # Visualization is split into five parts: # The background, the particles, the spectrum, the logo and after effects. # * The background is a vertical gradient that cycles through HSV color space, # speeding up with strong bass. # * Particles are multiple sprites that are created at a specified x,y-coordinate # and fly towards the camera. # Due to the projection into screenspace they seem to move away from the center. # * The spectrum is a white circle that represents the fft-transformation of the # currently played audio. It is smoothed to avoid strong spikes. # * The logo is a black circle on top of the spectrum containing the logo. # * After effects add a vignette. # Each of these parts is represented with pi3d Shapes. # They have their own shader and are ordered on the z-axis to ensure correct overlapping. background_shader = pi3d.Shader( os.path.join(settings.BASE_DIR, "core/lights/circle/background") ) self.background = pi3d.Sprite(w=2, h=2) self.background.set_shader(background_shader) self.background.positionZ(0) self.particle_shader = pi3d.Shader( os.path.join(settings.BASE_DIR, "core/lights/circle/particle") ) # create one sprite for all particles self.particle_sprite = pi3d.Sprite(w=self.PARTICLE_SIZE, h=self.PARTICLE_SIZE) self.particle_sprite.set_shader(self.particle_shader) self.particle_sprite.positionZ(0) # this array containes the position and speed for all particles particles = self._initial_particles() # This part was modified from https://learnopengl.com/Advanced-OpenGL/Instancing self.instance_vbo = GLuint() opengles.glGenBuffers(GLsizei(1), ctypes.byref(self.instance_vbo)) opengles.glBindBuffer(GL_ARRAY_BUFFER, self.instance_vbo) particles_raw = particles.ctypes.data_as(ctypes.POINTER(GLfloat)) opengles.glBufferData( GL_ARRAY_BUFFER, particles.nbytes, particles_raw, GL_STATIC_DRAW ) opengles.glBindBuffer(GL_ARRAY_BUFFER, GLuint(0)) attr_particle = opengles.glGetAttribLocation( self.particle_shader.program, b"particle" ) opengles.glEnableVertexAttribArray(attr_particle) opengles.glBindBuffer(GL_ARRAY_BUFFER, self.instance_vbo) opengles.glVertexAttribPointer( attr_particle, GLint(4), GL_FLOAT, GLboolean(0), 0, 0 ) opengles.glBindBuffer(GL_ARRAY_BUFFER, GLuint(0)) opengles.glVertexAttribDivisor(attr_particle, GLuint(1)) spectrum_shader = pi3d.Shader( os.path.join(settings.BASE_DIR, "core/lights/circle/spectrum") ) # use the ratio to compute small sizes for the sprites ratio = self.width / self.height self.spectrum = pi3d.Sprite(w=2 / ratio, h=2) self.spectrum.set_shader(spectrum_shader) self.spectrum.positionZ(0) # initialize the spectogram history with zeroes self.fft = np.zeros( (self.FFT_HIST, self.cava.bars - 2 * self.SPECTRUM_CUT), dtype=np.uint8 ) logo_shader = pi3d.Shader( os.path.join(settings.BASE_DIR, "core/lights/circle/logo") ) self.logo = pi3d.Sprite(w=1.375 / ratio, h=1.375) self.logo.set_shader(logo_shader) self.logo.positionZ(0) logo_image = Image.open( os.path.join(settings.STATIC_ROOT, "graphics/raveberry_square.png") ) self.logo_array = np.frombuffer(logo_image.tobytes(), dtype=np.uint8) self.logo_array = self.logo_array.reshape( (logo_image.size[1], logo_image.size[0], 3) ) # add space for the spectrum self.logo_array = np.concatenate( ( self.logo_array, np.zeros((self.FFT_HIST, logo_image.size[0], 3), dtype=np.uint8), ), axis=0, ) # add alpha channel self.logo_array = np.concatenate( ( self.logo_array, np.ones( (self.logo_array.shape[0], self.logo_array.shape[1], 1), dtype=np.uint8, ), ), axis=2, ) # In order to save memory, the logo and the spectrum share one texture. # The upper 256x256 pixels are the raveberry logo. # Below are 256xFFT_HIST pixels for the spectrum. # The lower part is periodically updated every frame while the logo stays static. self.dynamic_texture = pi3d.Texture(self.logo_array) # Prevent interpolation from opposite edge self.dynamic_texture.m_repeat = GL_CLAMP_TO_EDGE self.spectrum.set_textures([self.dynamic_texture]) self.logo.set_textures([self.dynamic_texture]) after_shader = pi3d.Shader( os.path.join(settings.BASE_DIR, "core/lights/circle/after") ) self.after = pi3d.Sprite(w=2, h=2) self.after.set_shader(after_shader) self.after.positionZ(0) # create an OffscreenTexture to allow scaling. # By first rendering into a smaller Texture a lot of computation is saved. # This OffscreenTexture is then drawn at the end of the draw loop. self.post = OffScreenTexture("scale") self.post_sprite = pi3d.Sprite(w=2, h=2) post_shader = pi3d.Shader( os.path.join(settings.BASE_DIR, "core/lights/circle/scale") ) self.post_sprite.set_shader(post_shader) self.post_sprite.set_textures([self.post]) self.total_bass = 0 self.last_loop = time.time() self.time_elapsed = 0 opengles.glDepthFunc(GL_ALWAYS)