예제 #1
0
파일: Display.py 프로젝트: xuwumin/pi3d
 def _tidy(self):
   to_del = []
   for i in self.textures_dict:
     tex = self.textures_dict[i]
     if tex[1] == 1:
       opengles.glDeleteTextures(GLsizei(1), byref(tex[0]))
       to_del.append(i)
   for i in to_del:
     del self.textures_dict[i]
   to_del = []
   for i in self.vbufs_dict:
     vbuf = self.vbufs_dict[i]
     if vbuf[1] == 1:
       opengles.glDeleteBuffers(GLsizei(1), byref(vbuf[0]))
       to_del.append(i)
   for i in to_del:
     del self.vbufs_dict[i]
   to_del = []
   for i in self.ebufs_dict:
     ebuf = self.ebufs_dict[i]
     if ebuf[1] == 1:
       opengles.glDeleteBuffers(GLsizei(1), byref(ebuf[0]))
       to_del.append(i)
   for i in to_del:
     del self.ebufs_dict[i]
   self.tidy_needed = False
예제 #2
0
def masked_screenshot(x, y, w, h):
    """returns numpy array from part of screen so it can be used by applications
  drawing low resolution offscreen textures using scaling.
  """
    img = np.zeros((h, w, 4), dtype=np.uint8)
    opengles.glReadPixels(GLint(x), GLint(y), GLsizei(w), GLsizei(h), GL_RGBA,
                          GL_UNSIGNED_BYTE,
                          img.ctypes.data_as(ctypes.POINTER(GLubyte)))
    return img[::-1, :, :3].copy()
예제 #3
0
    def draw(self, shape):
        """ draw the shape using the clashtest Shader

    Arguments:
      *shape*
        Shape object that will be drawn
    """
        if not self.s_flg:
            opengles.glEnable(GL_SCISSOR_TEST)
            opengles.glScissor(GLint(0), GLint(self.y0), GLsizei(self.ix),
                               GLsizei(1))
            self.s_flg = True
        shape.draw(shader=self.shader)
예제 #4
0
 def start_capture(self, clear=True):
     """ after calling this method all object.draw()s will rendered
 to this texture and not appear on the display. Large objects
 will obviously take a while to draw and re-draw
 """
     super(PostProcess, self)._start(clear=clear)
     from pi3d.Display import Display
     xx = int(Display.INSTANCE.width / 2.0 * (1.0 - self.scale))
     yy = int(Display.INSTANCE.height / 2.0 * (1.0 - self.scale))
     ww = int(Display.INSTANCE.width * self.scale)
     hh = int(Display.INSTANCE.height * self.scale)
     opengles.glEnable(GL_SCISSOR_TEST)
     opengles.glScissor(GLint(xx), GLint(yy), GLsizei(ww), GLsizei(hh))
예제 #5
0
 def repaint(self, t):
     self.move()
     self.bounce_wall(Display.INSTANCE.width, Display.INSTANCE.height)
     if t == 0:  #TODO this is not good but there needs to be a way to say last ball!
         opengles.glScissor(GLint(0), GLint(0),
                            GLsizei(int(Display.INSTANCE.width)),
                            GLsizei(int(Display.INSTANCE.height)))
         #NB the screen coordinates for glScissor have origin in BOTTOM left
     else:
         opengles.glScissor(
             GLint(int(self.or_x + self.unif[0] - self.radius - 5)),
             GLint(int(self.or_y + self.unif[1] - self.radius - 5)),
             GLsizei(self.w + 10), GLsizei(self.h + 10))
     self.draw()
예제 #6
0
    def __init__(self, name=""):
        """ new system doesn't use Texture
    """
        from pi3d.Display import Display
        self.disp = Display.INSTANCE
        self.ix, self.iy = self.disp.width, self.disp.height

        color = GLuint()
        depth = GLuint()
        self.framebuffer = (GLuint * 1)()
        pi3d.opengles.glGenFramebuffers(GLsizei(1), self.framebuffer)
        self.depthbuffer = (GLuint * 1)()
        pi3d.opengles.glGenRenderbuffers(GLsizei(1), self.depthbuffer)

        pi3d.opengles.glGenTextures(1, ctypes.byref(color))
        pi3d.opengles.glBindTexture(GL_TEXTURE_2D, color)
        pi3d.opengles.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
                                      GL_NEAREST)
        pi3d.opengles.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
                                      GL_LINEAR)
        pi3d.opengles.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
                                      GL_CLAMP_TO_EDGE)
        pi3d.opengles.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,
                                      GL_CLAMP_TO_EDGE)
        pi3d.opengles.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, self.ix, self.iy,
                                   0, GL_RGBA, GL_UNSIGNED_BYTE, None)
        pi3d.opengles.glGenerateMipmap(GL_TEXTURE_2D)
        pi3d.opengles.glBindTexture(GL_TEXTURE_2D, 0)

        pi3d.opengles.glGenTextures(1, ctypes.byref(depth))
        pi3d.opengles.glBindTexture(GL_TEXTURE_2D, depth)
        pi3d.opengles.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
                                      GL_NEAREST)
        pi3d.opengles.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
                                      GL_LINEAR)
        pi3d.opengles.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
                                      GL_CLAMP_TO_EDGE)
        pi3d.opengles.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,
                                      GL_CLAMP_TO_EDGE)
        pi3d.opengles.glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16,
                                   self.ix, self.iy, 0, GL_DEPTH_COMPONENT,
                                   GL_UNSIGNED_SHORT, None)
        pi3d.opengles.glGenerateMipmap(GL_TEXTURE_2D)
        pi3d.opengles.glBindTexture(GL_TEXTURE_2D, 0)
        pi3d.opengles.glEnable(GL_TEXTURE_2D)

        self.color = TextureShell(color, self.ix, self.iy, False, True)
        self.depth = TextureShell(depth, self.ix, self.iy, False, True)
예제 #7
0
파일: Shader.py 프로젝트: xuwumin/pi3d
 def showprogramlog(self, shader):
   """Prints the compile log for a program"""
   N = 1024
   log = (ctypes.c_char * N)()
   loglen = GLsizei()
   opengles.glGetProgramInfoLog(
     shader, N, ctypes.byref(loglen), log)
예제 #8
0
    def __init__(self, name):
        """ calls Texture.__init__ but doesn't need to set file name as
    texture generated from the framebuffer
    """
        super(OffScreenTexture, self).__init__(name)
        from pi3d.Display import Display
        self.disp = Display.INSTANCE
        self.ix, self.iy = self.disp.width, self.disp.height
        self.image = np.zeros((self.iy, self.ix, 4), dtype=np.uint8)
        self.blend = False
        self.mipmap = False

        self._tex = GLuint()
        self.framebuffer = (GLuint * 1)()
        pi3d.opengles.glGenFramebuffers(GLsizei(1), self.framebuffer)
        self.depthbuffer = (GLuint * 1)()
        pi3d.opengles.glGenRenderbuffers(GLsizei(1), self.depthbuffer)
예제 #9
0
 def showshaderlog(self, shader, src):
     """Prints the compile log for a shader"""
     N = 1024
     log = (ctypes.c_char * N)()
     loglen = GLsizei()
     opengles.glGetShaderInfoLog(shader, N, ctypes.byref(loglen), log)
     if len(log.value) > 0:
         LOGGER.debug('shader(%s) %s, %s', shader, self.shfile, log.value)
예제 #10
0
파일: Buffer.py 프로젝트: xuwumin/pi3d
 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')
예제 #11
0
  def start_capture(self, side):
    """ after calling this method all object.draw()s will rendered
    to this texture and not appear on the display.

      *side*
        Either 0 or 1 to determine stereoscopic view
    """
    self.camera_3d.reset()
    offs = -self.offs if side == 0 else self.offs
    self.camera_3d.offset([offs, 0.0, 0.0])
    #self.camera_3d.mtrx = np.dot(self.forMtrx, self.camera_3d.mtrx)
    #self.camera_3d.position(self.position)
    tex = self.textures[side]
    tex._start()
    if self.interlace <= 0:
      xx = tex.ix / 4.0 # draw the middle only - half width
      yy = 0
      ww = tex.ix / 2.0
      hh = tex.iy
      opengles.glEnable(GL_SCISSOR_TEST)
      opengles.glScissor(GLint(int(xx)), GLint(int(yy)),
                    GLsizei(int(ww)), GLsizei(int(hh)))
예제 #12
0
    def check(self, grain=50):
        """ checks the pixels of the texture to see if there is any change from the
    first pixel sampled; in which case returns True else returns False.

    Keyword argument:
      *grain*
        Number of locations to check over the whole image
        NB this is no longer used - there are fixed 100 checks across the
        full width at the mid y position. This self.setp value is set in
        __init__()
    """
        opengles.glDisable(GL_SCISSOR_TEST)
        self.s_flg = False
        img = self.img  # alias to make code a bit less bulky!
        opengles.glReadPixels(GLint(0), GLint(self.y0), GLsizei(self.ix),
                              GLsizei(1), GL_RGBA, GL_UNSIGNED_BYTE,
                              img.ctypes.data_as(ctypes.POINTER(GLubyte)))

        if (np.any(img[::self.step, 0] != img[0, 0])
                or np.any(img[::self.step, 1] != img[0, 1])
                or np.any(img[::self.step, 2] != img[0, 2])):
            return True
        return False
예제 #13
0
 def delete_buffers(self):
     pi3d.opengles.glDeleteFramebuffers(GLsizei(1), self.framebuffer)
     pi3d.opengles.glDeleteRenderbuffers(GLsizei(1), self.depthbuffer)
예제 #14
0
    def create_surface(self, x=0, y=0, w=0, h=0, layer=0):
        #Set the viewport position and size
        dst_rect = c_ints((x, y, w, h))
        src_rect = c_ints((x, y, w << 16, h << 16))

        if PLATFORM == PLATFORM_ANDROID:
            self.surface = openegl.eglGetCurrentSurface(EGL_DRAW)
            # Get the width and height of the screen - TODO, this system returns 100x100
            time.sleep(0.2)  #give it a chance to find out the dimensions
            w = c_int()
            h = c_int()
            openegl.eglQuerySurface(self.display, self.surface, EGL_WIDTH,
                                    byref(w))
            openegl.eglQuerySurface(self.display, self.surface, EGL_HEIGHT,
                                    byref(h))
            self.width, self.height = w.value, h.value
        elif PLATFORM == PLATFORM_PI:
            self.dispman_display = bcm.vc_dispmanx_display_open(
                0)  #LCD setting
            self.dispman_update = bcm.vc_dispmanx_update_start(0)
            alpha = c_ints((DISPMANX_FLAGS_ALPHA_PREMULT, 0, 0))
            self.dispman_element = bcm.vc_dispmanx_element_add(
                self.dispman_update, self.dispman_display, layer, dst_rect, 0,
                src_rect, DISPMANX_PROTECTION_NONE, alpha, 0, 0)

            nativewindow = (GLint * 3)(self.dispman_element, w, h + 1)
            bcm.vc_dispmanx_update_submit_sync(self.dispman_update)

            self.nw_p = ctypes.pointer(nativewindow)
            ### NB changing the argtypes to allow passing of bcm native window is
            ### deeply unsatisfactory. But xlib defines Window as c_ulong and ctypes
            ### isn't happy about a pointer being cast to an int
            openegl.eglCreateWindowSurface.argtypes = [
                EGLDisplay, EGLConfig,
                POINTER((GLint * 3)), EGLint
            ]
            self.surface = openegl.eglCreateWindowSurface(
                self.display, self.config, self.nw_p, 0)

        elif pi3d.USE_PYGAME:
            import pygame
            flags = pygame.OPENGL
            wsize = (w, h)
            if w == self.width and h == self.height:  # i.e. full screen
                flags = pygame.FULLSCREEN | pygame.OPENGL
                wsize = (0, 0)
            if self.display_config & DISPLAY_CONFIG_NO_RESIZE:
                flags |= pygame.RESIZABLE
            if self.display_config & DISPLAY_CONFIG_NO_FRAME:
                flags |= pygame.NOFRAME
            if self.display_config & DISPLAY_CONFIG_FULLSCREEN:
                flags |= pygame.FULLSCREEN
            elif self.display_config & DISPLAY_CONFIG_MAXIMIZED:
                flags |= pygame.FULLSCREEN
                wsize = (0, 0)

            self.width, self.height = w, h
            self.d = pygame.display.set_mode(wsize, flags)
            self.window = pygame.display.get_wm_info()["window"]
            self.surface = openegl.eglCreateWindowSurface(
                self.display, self.config, self.window, 0)

        else:  # work on basis it's X11
            # Set some WM info
            self.root = xlib.XRootWindowOfScreen(self.screen)
            if self.use_glx:  # For drawing on X window with transparent background
                numfbconfigs = c_int()
                VisData = c_ints(
                    (glx.GLX_RENDER_TYPE, glx.GLX_RGBA_BIT,
                     glx.GLX_DRAWABLE_TYPE, glx.GLX_WINDOW_BIT,
                     glx.GLX_DOUBLEBUFFER, True, glx.GLX_RED_SIZE, 8,
                     glx.GLX_GREEN_SIZE, 8, glx.GLX_BLUE_SIZE, 8,
                     glx.GLX_ALPHA_SIZE, 8, glx.GLX_DEPTH_SIZE, 16, 0))
                glx_screen = xlib.XDefaultScreen(self.d)
                fbconfigs = glx.glXChooseFBConfig(self.d, glx_screen, VisData,
                                                  byref(numfbconfigs))
                fbconfig = 0
                for i in range(numfbconfigs.value):
                    visual = glx.glXGetVisualFromFBConfig(
                        self.d, fbconfigs[i]).contents
                    if not visual:
                        continue
                    pict_format = glx.XRenderFindVisualFormat(
                        self.d, visual.visual).contents
                    if not pict_format:
                        continue

                    fbconfig = fbconfigs[i]
                    if pict_format.direct.alphaMask > 0:
                        break

                if not fbconfig:
                    print("No matching FB config found")
                #/* Create a colormap - only needed on some X clients, eg. IRIX */
                cmap = xlib.XCreateColormap(self.d, self.root, visual.visual,
                                            AllocNone)
                attr = xlib.XSetWindowAttributes()
                attr.colormap = cmap
                attr.background_pixmap = 0
                attr.border_pixmap = 0
                attr.border_pixel = 0
                attr.event_mask = (StructureNotifyMask | EnterWindowMask
                                   | LeaveWindowMask | ExposureMask
                                   | ButtonPressMask | ButtonReleaseMask
                                   | OwnerGrabButtonMask | KeyPressMask
                                   | KeyReleaseMask)
                attr_mask = (  #  CWBackPixmap|
                    CWColormap | CWBorderPixel | CWEventMask)
                self.window = xlib.XCreateWindow(self.d, self.root, x, y, w, h,
                                                 0, visual.depth, 1,
                                                 visual.visual, attr_mask,
                                                 byref(attr))
            else:  # normal EGL created context
                self.window = xlib.XCreateSimpleWindow(self.d, self.root, x, y,
                                                       w, h, 1, 0, 0)

            s = ctypes.create_string_buffer(b'WM_DELETE_WINDOW')
            self.WM_DELETE_WINDOW = ctypes.c_ulong(
                xlib.XInternAtom(self.d, s, 0))

            # set window title
            title = ctypes.c_char_p(self.window_title)
            title_length = ctypes.c_int(len(self.window_title))
            wm_name_atom = ctypes.c_ulong(
                xlib.XInternAtom(self.d,
                                 ctypes.create_string_buffer(b'WM_NAME'), 0))
            string_atom = ctypes.c_ulong(
                xlib.XInternAtom(self.d,
                                 ctypes.create_string_buffer(b'STRING'), 0))
            xlib.XChangeProperty(self.d, self.window, wm_name_atom,
                                 string_atom, 8, xlib.PropModeReplace, title,
                                 title_length)

            if (w == self.width
                    and h == self.height) or (self.display_config
                                              & DISPLAY_CONFIG_FULLSCREEN):
                # set full-screen. Messy c function calls!
                wm_state = ctypes.c_ulong(
                    xlib.XInternAtom(self.d, b'_NET_WM_STATE', 0))
                fullscreen = ctypes.c_ulong(
                    xlib.XInternAtom(self.d, b'_NET_WM_STATE_FULLSCREEN', 0))
                fullscreen = ctypes.cast(ctypes.pointer(fullscreen),
                                         ctypes.c_char_p)
                XA_ATOM = 4
                xlib.XChangeProperty(self.d, self.window, wm_state, XA_ATOM,
                                     32, xlib.PropModeReplace, fullscreen, 1)

            self.width, self.height = w, h

            if self.display_config & DISPLAY_CONFIG_HIDE_CURSOR:
                black = xlib.XColor()
                black.red = 0
                black.green = 0
                black.blue = 0
                noData = ctypes.c_char_p(bytes([0, 0, 0, 0, 0, 0, 0, 0]))
                bitmapNoData = xlib.XCreateBitmapFromData(
                    self.d, self.window, noData, 8, 8)
                invisibleCursor = xlib.XCreatePixmapCursor(
                    self.d, bitmapNoData, bitmapNoData, black, black, 0, 0)
                xlib.XDefineCursor(self.d, self.window, invisibleCursor)

            #TODO add functions to xlib for these window manager libx11 functions
            #self.window.set_wm_name('pi3d xlib window')
            #self.window.set_wm_icon_name('pi3d')
            #self.window.set_wm_class('draw', 'XlibExample')

            xlib.XSetWMProtocols(self.d, self.window, self.WM_DELETE_WINDOW, 1)
            #self.window.set_wm_hints(flags = Xutil.StateHint,
            #                         initial_state = Xutil.NormalState)

            #self.window.set_wm_normal_hints(flags = (Xutil.PPosition | Xutil.PSize
            #                                         | Xutil.PMinSize),
            #                                min_width = 20,
            #                                min_height = 20)

            xlib.XSelectInput(
                self.d, self.window,
                KeyPressMask | KeyReleaseMask | ResizeRedirectMask)
            xlib.XMapWindow(self.d, self.window)
            #xlib.XMoveWindow(self.d, self.window, x, y) #TODO this has to happen later. Works after rendering first frame. Check when
            if self.use_glx:
                dummy = c_int()
                if not glx.glXQueryExtension(self.d, byref(dummy),
                                             byref(dummy)):
                    print("OpenGL not supported by X server\n")
                dummy_glx_context = ctypes.cast(0, glx.GLXContext)
                self.render_context = glx.glXCreateNewContext(
                    self.d, fbconfig, glx.GLX_RGBA_TYPE, dummy_glx_context,
                    True)
                if not self.render_context:
                    print("Failed to create a GL context\n")
                if not glx.glXMakeContextCurrent(
                        self.d, self.window, self.window, self.render_context):
                    print("glXMakeCurrent failed for window\n")
            else:
                self.surface = openegl.eglCreateWindowSurface(
                    self.display, self.config, self.window, 0)

        if not self.use_glx:
            assert self.surface != EGL_NO_SURFACE and self.surface is not None
            r = openegl.eglMakeCurrent(self.display, self.surface,
                                       self.surface, self.context)
            assert r

        #Create viewport
        opengles.glViewport(GLint(0), GLint(0), GLsizei(w), GLsizei(h))
예제 #15
0
파일: Buffer.py 프로젝트: xuwumin/pi3d
  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)
예제 #16
0
    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)
예제 #17
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("=====")