def update_ndarray(self, new_array=None): """to allow numpy arrays to be patched in to textures without regenerating new glTextureBuffers i.e. for movie textures""" if new_array is not None: self.image = new_array opengles.glBindTexture(GL_TEXTURE_2D, self._tex) # set filters according to mipmap and filter request for t in [GL_TEXTURE_MIN_FILTER, GL_TEXTURE_MAG_FILTER]: opengles.glTexParameteri(GL_TEXTURE_2D, t, self._get_filter(t)) opengles.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, self.m_repeat) opengles.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, self.m_repeat) iformat = self._get_format_from_array(self.image, self.i_format) opengles.glTexImage2D( GL_TEXTURE_2D, 0, iformat, self.ix, self.iy, 0, iformat, GL_UNSIGNED_BYTE, self.image.ctypes.data_as(ctypes.POINTER(ctypes.c_ubyte))) if opengles.glGetError() == GL_OUT_OF_MEMORY: LOGGER.critical('Out of GPU memory') #opengles.glEnable(GL_TEXTURE_2D) # invalid in OpenGLES 2 if self.mipmap: opengles.glGenerateMipmap(GL_TEXTURE_2D) if self.free_after_load: self.image = None self._loaded = False
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 update_ndarray(self, new_array=None, texture_num=None): """to allow numpy arrays to be patched in to textures without regenerating new glTextureBuffers i.e. for movie textures *new_array* ndarray, if supplied this will be the pixel data for the new Texture2D *texture_num* int, if supplied this will make the update effective for a specific sampler number i.e. as held in the Buffer.textures array. This will be required where multiple textures are used on some of the Buffers being drawn in the scene""" if new_array is not None: self.image = new_array if texture_num is not None: opengles.glActiveTexture(GL_TEXTURE0 + texture_num) opengles.glBindTexture(GL_TEXTURE_2D, self._tex) # set filters according to mipmap and filter request for t in [GL_TEXTURE_MIN_FILTER, GL_TEXTURE_MAG_FILTER]: opengles.glTexParameteri(GL_TEXTURE_2D, t, self._get_filter(t)) opengles.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, self.m_repeat) opengles.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, self.m_repeat) iformat = self._get_format_from_array(self.image, self.i_format) opengles.glTexImage2D( GL_TEXTURE_2D, 0, iformat, self.ix, self.iy, 0, iformat, GL_UNSIGNED_BYTE, self.image.ctypes.data_as(ctypes.POINTER(ctypes.c_ubyte))) if opengles.glGetError() == GL_OUT_OF_MEMORY: LOGGER.critical('Out of GPU memory in Texture.update_ndarray') #opengles.glEnable(GL_TEXTURE_2D) # invalid in OpenGLES 2 if self.mipmap: opengles.glGenerateMipmap(GL_TEXTURE_2D) if self.free_after_load: self.image = None self.file_string = None self._loaded = False
def _prepare(self): 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.constants import opengles, GL_CLAMP_TO_EDGE, GL_ALWAYS, GL_NEVER, GL_DEPTH_TEST, GL_VERSION # used for reimplementing the draw call with instancing from pi3d.constants import GLsizei, GLint, GLboolean, GL_FLOAT, GL_UNSIGNED_SHORT, GLuint, GL_ARRAY_BUFFER, GL_STATIC_DRAW, GLsizeiptr, 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 and an array containing the position and speed for all of them 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) 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 = pi3d.util.OffScreenTexture.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)