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 video_load_frame(self, frame): """ Load frame (numpy array) in texture """ # return self.video_texture.update_ndarray(frame, 0) tex = self.video_texture opengles.glActiveTexture(GL_TEXTURE0) opengles.glBindTexture(GL_TEXTURE_2D, tex._tex) opengles.glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, tex.ix, tex.iy, self.frame_format, GL_UNSIGNED_BYTE, frame.ctypes.data_as(ctypes.POINTER(ctypes.c_ubyte))) opengles.glGenerateMipmap(GL_TEXTURE_2D)
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 draw(self, shape=None, M=None, unif=None, shader=None, textures=None, ntl=None, shny=None, fullset=True): """Draw this Buffer, called by the parent Shape.draw() Keyword arguments: *shape* Shape object this Buffer belongs to, has to be passed at draw to avoid circular reference *shader* Shader object *textures* array of Texture objects *ntl* multiple for tiling normal map which can be less than or greater than 1.0. 0.0 disables the normal mapping, float *shiny* how strong to make the reflection 0.0 to 1.0, float """ self.load_opengl() shader = shader or self.shader or shape.shader or Shader.instance() shader.use() opengles.glUniformMatrix4fv(shader.unif_modelviewmatrix, GLsizei(3), GLboolean(0), M.ctypes.data) opengles.glUniform3fv(shader.unif_unif, GLsizei(20), unif) textures = textures or self.textures if ntl is not None: self.unib[0] = ntl if shny is not None: self.unib[1] = shny self._select() opengles.glVertexAttribPointer(shader.attr_vertex, GLint(3), GL_FLOAT, GLboolean(0), self.N_BYTES, 0) opengles.glEnableVertexAttribArray(shader.attr_vertex) if self.N_BYTES > 12: opengles.glVertexAttribPointer(shader.attr_normal, GLint(3), GL_FLOAT, GLboolean(0), self.N_BYTES, 12) opengles.glEnableVertexAttribArray(shader.attr_normal) if self.N_BYTES > 24: opengles.glVertexAttribPointer(shader.attr_texcoord, GLint(2), GL_FLOAT, GLboolean(0), self.N_BYTES, 24) opengles.glEnableVertexAttribArray(shader.attr_texcoord) opengles.glDisable(GL_BLEND) self.unib[2] = 0.6 for t, texture in enumerate(textures): if (self.disp.last_textures[t] != texture or self.disp.last_shader != shader or self.disp.offscreen_tex): # very slight speed increase for sprites opengles.glActiveTexture(GL_TEXTURE0 + t) assert texture.tex(), 'There was an empty texture in your Buffer.' opengles.glBindTexture(GL_TEXTURE_2D, texture.tex()) opengles.glUniform1i(shader.unif_tex[t], GLint(t)) self.disp.last_textures[t] = texture if texture.blend: # i.e. if any of the textures set to blend then all will for this shader. self.unib[2] = 0.05 if self.unib[2] != 0.6 or shape.unif[13] < 1.0 or shape.unif[14] < 1.0: #use unib[2] as flag to indicate if any Textures to be blended #needs to be done outside for..textures so materials can be transparent opengles.glEnable(GL_BLEND) self.unib[2] = 0.05 self.disp.last_shader = shader opengles.glUniform3fv(shader.unif_unib, GLsizei(5), self.unib) opengles.glEnable(GL_DEPTH_TEST) # TODO find somewhere more efficient to do this opengles.glDrawElements(self.draw_method, GLsizei(self.ntris * 3), GL_UNSIGNED_SHORT, 0)
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.lights.alarm_program.factor != -1: self.alarm_factor = max(0.001, self.lights.alarm_program.factor) else: self.alarm_factor = 0 then = time.time() self.display.loop_running() 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") 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.lights.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 ) 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, shape=None, M=None, unif=None, shader=None, textures=None, ntl=None, shny=None, fullset=True): """Draw this Buffer, called by the parent Shape.draw() Keyword arguments: *shape* Shape object this Buffer belongs to, has to be passed at draw to avoid circular reference *shader* Shader object *textures* array of Texture objects *ntl* multiple for tiling normal map which can be less than or greater than 1.0. 0.0 disables the normal mapping, float *shiny* how strong to make the reflection 0.0 to 1.0, float """ self.load_opengl() shader = shader or self.shader or shape.shader or Shader.instance() shader.use() opengles.glUniformMatrix4fv(shader.unif_modelviewmatrix, 3, ctypes.c_ubyte(0), M.ctypes.data) opengles.glUniform3fv(shader.unif_unif, 20, unif) textures = textures or self.textures if ntl is not None: self.unib[0] = ntl if shny is not None: self.unib[1] = shny self._select() opengles.glVertexAttribPointer(shader.attr_vertex, 3, GL_FLOAT, 0, self.N_BYTES, 0) opengles.glEnableVertexAttribArray(shader.attr_vertex) if self.N_BYTES > 12: opengles.glVertexAttribPointer(shader.attr_normal, 3, GL_FLOAT, 0, self.N_BYTES, 12) opengles.glEnableVertexAttribArray(shader.attr_normal) if self.N_BYTES > 24: opengles.glVertexAttribPointer(shader.attr_texcoord, 2, GL_FLOAT, 0, self.N_BYTES, 24) opengles.glEnableVertexAttribArray(shader.attr_texcoord) opengles.glDisable(GL_BLEND) self.unib[2] = 0.6 for t, texture in enumerate(textures): if (self.disp.last_textures[t] != texture or self.disp.last_shader != shader or self.disp.offscreen_tex): # very slight speed increase for sprites opengles.glActiveTexture(GL_TEXTURE0 + t) assert texture.tex(), 'There was an empty texture in your Buffer.' opengles.glBindTexture(GL_TEXTURE_2D, texture.tex()) opengles.glUniform1i(shader.unif_tex[t], t) self.disp.last_textures[t] = texture if texture.blend: # i.e. if any of the textures set to blend then all will for this shader. self.unib[2] = 0.05 if self.unib[2] != 0.6 or shape.unif[13] < 1.0 or shape.unif[14] < 1.0: #use unib[2] as flag to indicate if any Textures to be blended #needs to be done outside for..textures so materials can be transparent opengles.glEnable(GL_BLEND) self.unib[2] = 0.05 self.disp.last_shader = shader opengles.glUniform3fv(shader.unif_unib, 5, self.unib) opengles.glEnable(GL_DEPTH_TEST) # TODO find somewhere more efficient to do this opengles.glDrawElements(self.draw_method, self.ntris * 3, GL_UNSIGNED_SHORT, 0)