Ejemplo n.º 1
0
    def swapBuffers(self):
        # first call the swap on the QGLWidget
        start = long(now()*1000)

        self.glw.swapBuffers()

        #self.glw.makeCurrent()

        # The following is taken from the PsychToolbox
        # Draw a single pixel in left-top area of back-buffer. 
        # This will wait/stall the rendering pipeline
        # until the buffer flip has happened, aka immediately after the VBL has started.
        # We need the pixel as "synchronization token", so the following glFinish() really
        # waits for VBL instead of just "falling through" due to the asynchronous nature of
        # OpenGL:
        glDrawBuffer(GL_BACK)
        # We draw our single pixel with an alpha-value of zero - so effectively it doesn't
        # change the color buffer - just the z-buffer if z-writes are enabled...
        glColor4f(0.0,0.0,0.0,0.0)
        glBegin(GL_POINTS)
        glVertex2i(10,10)
        glEnd()
        # This glFinish() will wait until point drawing is finished, ergo backbuffer was ready
        # for drawing, ergo buffer swap in sync with start of VBL has happened.
        glFinish()

        finish = long(now()*1000)
        fdiff = finish - self.last_finish
        self.last_finish = finish
        return (start,finish-start,fdiff)
Ejemplo n.º 2
0
    def bind_depth_texture(self, size):
        """Create depth texture for shadow map."""
        width, height = size
        texture_type = GL_TEXTURE_2D
        self.__depth_map_fbo = glGenFramebuffers(1)

        depth_map = self.__textures_ids[len(self.__textures)]
        glBindTexture(texture_type, depth_map)
        self.__textures.append(2)
        glTexImage2D(texture_type, 0, GL_DEPTH_COMPONENT, width, height, 0,
                     GL_DEPTH_COMPONENT, GL_FLOAT, None)
        glTexParameteri(texture_type, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
        glTexParameteri(texture_type, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
        glTexParameteri(texture_type, GL_TEXTURE_WRAP_S, GL_REPEAT)
        glTexParameteri(texture_type, GL_TEXTURE_WRAP_T, GL_REPEAT)

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE,
                        GL_COMPARE_REF_TO_TEXTURE)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LESS)

        glBindFramebuffer(GL_FRAMEBUFFER, self.__depth_map_fbo)
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
                               GL_TEXTURE_2D, depth_map, 0)
        glDrawBuffer(GL_NONE)
        glReadBuffer(GL_NONE)
        glBindFramebuffer(GL_FRAMEBUFFER, 0)
Ejemplo n.º 3
0
    def swapBuffers(self):
        # first call the swap on the QGLWidget
        start = long(now() * 1000)

        self.glw.swapBuffers()

        #self.glw.makeCurrent()

        # The following is taken from the PsychToolbox
        # Draw a single pixel in left-top area of back-buffer.
        # This will wait/stall the rendering pipeline
        # until the buffer flip has happened, aka immediately after the VBL has started.
        # We need the pixel as "synchronization token", so the following glFinish() really
        # waits for VBL instead of just "falling through" due to the asynchronous nature of
        # OpenGL:
        glDrawBuffer(GL_BACK)
        # We draw our single pixel with an alpha-value of zero - so effectively it doesn't
        # change the color buffer - just the z-buffer if z-writes are enabled...
        glColor4f(0.0, 0.0, 0.0, 0.0)
        glBegin(GL_POINTS)
        glVertex2i(10, 10)
        glEnd()
        # This glFinish() will wait until point drawing is finished, ergo backbuffer was ready
        # for drawing, ergo buffer swap in sync with start of VBL has happened.
        glFinish()

        finish = long(now() * 1000)
        fdiff = finish - self.last_finish
        self.last_finish = finish
        return (start, finish - start, fdiff)
Ejemplo n.º 4
0
    def bind_depth_texture(self, size):
        """Create depth texture for shadow map."""
        width, height = size
        texture_type = GL_TEXTURE_2D
        self.__depth_map_fbo = glGenFramebuffers(1)

        depth_map = self.__textures_ids[len(self.__textures)]
        glBindTexture(texture_type, depth_map)
        self.__textures.append(2)
        glTexImage2D(texture_type, 0, GL_DEPTH_COMPONENT,
                     width, height, 0, GL_DEPTH_COMPONENT,
                     GL_FLOAT, None)
        glTexParameteri(texture_type, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
        glTexParameteri(texture_type, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
        glTexParameteri(texture_type, GL_TEXTURE_WRAP_S, GL_REPEAT)
        glTexParameteri(texture_type, GL_TEXTURE_WRAP_T, GL_REPEAT)

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE,
                        GL_COMPARE_REF_TO_TEXTURE)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC,
                        GL_LESS)

        glBindFramebuffer(GL_FRAMEBUFFER, self.__depth_map_fbo)
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
                               GL_TEXTURE_2D, depth_map, 0)
        glDrawBuffer(GL_NONE)
        glReadBuffer(GL_NONE)
        glBindFramebuffer(GL_FRAMEBUFFER, 0)
Ejemplo n.º 5
0
 def create(self, app):
     """ creates resources. """
     self.app = app
     
     shadowMaps = self.shadowMaps
     
     # reserve a texture unit for shadow texture 
     self.textureUnit = app.reserveTextureUnit()
     
     # create a 3D texture using GL_TEXTURE_2D_ARRAY
     self.texture = DepthTexture3D(self.sizeI, self.sizeI)
     self.texture.targetType = GL_TEXTURE_2D_ARRAY
     self.texture.internalFormat = GL_DEPTH_COMPONENT24
     self.texture.numTextures = self.numShadowMaps
     self.texture.pixelType = GL_FLOAT
     self.texture.minFilterMode = GL_LINEAR
     self.texture.magFilterMode = GL_LINEAR
     self.texture.wrapMode = GL_CLAMP_TO_EDGE
     self.texture.compareFunc = GL_LEQUAL
     if self.textureType=="sampler2DArrayShadow":
         self.texture.compareMode = GL_COMPARE_R_TO_TEXTURE
     else:
         self.texture.compareMode = GL_NONE
     self.texture.create()
     
     # create a depth only fbo for the 3D texture
     self.fbo = glGenFramebuffers(1)
     glBindFramebuffer(GL_FRAMEBUFFER, self.fbo)
     glDrawBuffer(GL_NONE)
     glBindFramebuffer(GL_FRAMEBUFFER, 0)
     
     # the frustum slice far value must be accessable in the shader.
     # uniforms does not support array, so we split
     # the frustum slices in vec4 instances....
     numVecs = self.numShadowMaps/4; mod = self.numShadowMaps%4
     self.farVecs = [4]*numVecs
     if mod != 0:
         self.farVecs.append( mod )
         numVecs += 1
     
     # create shadow maps
     for i in range(self.numShadowMaps):
         shadowMap = shadowMaps[i]
         shadowMap.textureMatrixUnit = app.reserveTextureMatrixUnit()
         shadowMap.textureLayer = i
         shadowMap.create(app)
Ejemplo n.º 6
0
    def __init__(self, w, h, color_texture, depth_texture):
        self._gl_id = glGenFramebuffers(1);

        self._ct = color_texture

        self._dt = depth_texture
        with self:

            glDrawBuffer(GL_COLOR_ATTACHMENT0)
            glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, gl_id(color_texture), 0);
            glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, gl_id(depth_texture), 0);
            rid = glGenRenderbuffers(1)

            glBindRenderbuffer(GL_RENDERBUFFER, rid)
            glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, w, h)
            glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rid)
            glBindRenderbuffer(GL_RENDERBUFFER, 0)
            e = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER);
            if e != GL_FRAMEBUFFER_COMPLETE:
                raise Exception('GOT PROBLEMS {}'.format(e))
Ejemplo n.º 7
0
    def do_scroll_paints(self, scrolls, flush=0, callbacks=[]):
        log("do_scroll_paints%s", (scrolls, flush))
        context = self.gl_context()
        if not context:
            log.warn("Warning: cannot paint scroll, no OpenGL context!")
            return
        def fail(msg):
            log.error("Error: %s", msg)
            fire_paint_callbacks(callbacks, False, msg)
        with context:
            bw, bh = self.size
            self.set_rgb_paint_state()
            #paste from offscreen to tmp with delta offset:
            glBindFramebuffer(GL_READ_FRAMEBUFFER, self.offscreen_fbo)
            glBindTexture(GL_TEXTURE_RECTANGLE_ARB, self.textures[TEX_FBO])
            glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ARB, self.textures[TEX_FBO], 0)
            glReadBuffer(GL_COLOR_ATTACHMENT0)

            glBindFramebuffer(GL_DRAW_FRAMEBUFFER, self.tmp_fbo)
            glBindTexture(GL_TEXTURE_RECTANGLE_ARB, self.textures[TEX_TMP_FBO])
            glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_RECTANGLE_ARB, self.textures[TEX_TMP_FBO], 0)
            glDrawBuffer(GL_COLOR_ATTACHMENT1)

            #copy current fbo:
            glBlitFramebuffer(0, 0, bw, bh,
                              0, 0, bw, bh,
                              GL_COLOR_BUFFER_BIT, GL_NEAREST)

            for x,y,w,h,xdelta,ydelta in scrolls:
                if abs(xdelta)>=bw:
                    fail("invalid xdelta value: %i" % xdelta)
                    continue
                if abs(ydelta)>=bh:
                    fail("invalid ydelta value: %i" % ydelta)
                    continue
                if ydelta==0 and xdelta==0:
                    fail("scroll has no delta!")
                    continue
                if w<=0 or h<=0:
                    fail("invalid scroll area size: %ix%i" % (w, h))
                    continue
                #these should be errors,
                #but desktop-scaling can cause a mismatch between the backing size
                #and the real window size server-side.. so we clamp the dimensions instead
                if x+w>bw:
                    w = bw-x
                if y+h>bh:
                    h = bh-y
                if x+w+xdelta>bw:
                    w = bw-x-xdelta
                    if w<=0:
                        continue        #nothing left!
                if y+h+ydelta>bh:
                    h = bh-y-ydelta
                    if h<=0:
                        continue        #nothing left!
                if x+xdelta<0:
                    fail("horizontal scroll by %i: rectangle %s overflows the backing buffer size %s" % (xdelta, (x, y, w, h), self.size))
                    continue
                if y+ydelta<0:
                    fail("vertical scroll by %i: rectangle %s overflows the backing buffer size %s" % (ydelta, (x, y, w, h), self.size))
                    continue
                #opengl buffer is upside down, so we must invert Y coordinates: bh-(..)
                glBlitFramebuffer(x, bh-y, x+w, bh-(y+h),
                                  x+xdelta, bh-(y+ydelta), x+w+xdelta, bh-(y+h+ydelta),
                                  GL_COLOR_BUFFER_BIT, GL_NEAREST)
                glFlush()

            #now swap references to tmp and offscreen so tmp becomes the new offscreen:
            tmp = self.offscreen_fbo
            self.offscreen_fbo = self.tmp_fbo
            self.tmp_fbo = tmp
            tmp = self.textures[TEX_FBO]
            self.textures[TEX_FBO] = self.textures[TEX_TMP_FBO]
            self.textures[TEX_TMP_FBO] = tmp

            #restore normal paint state:
            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ARB, self.textures[TEX_FBO], 0)
            glBindFramebuffer(GL_READ_FRAMEBUFFER, self.offscreen_fbo)
            glBindFramebuffer(GL_DRAW_FRAMEBUFFER, self.offscreen_fbo)
            glBindFramebuffer(GL_FRAMEBUFFER, self.offscreen_fbo)

            self.unset_rgb_paint_state()
            self.paint_box("scroll", True, x+xdelta, y+ydelta, x+w+xdelta, y+h+ydelta)
            self.present_fbo(0, 0, bw, bh, flush)
            fire_paint_callbacks(callbacks, True)
Ejemplo n.º 8
0
 def attach(self):
     glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, self.texture,
                          0)
     glDrawBuffer(GL_NONE)
     glReadBuffer(GL_NONE)
    def OnPaint(self, event):
#         startTime = time.time()
        if not self.init:
            self.context = GLContext(self)
            self.SetCurrent(self.context)
            glEnable(GL_DEPTH_TEST);
            self.init = True
            
        if self._refreshAll:  
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
            glLoadIdentity()
#             prepareTime = time.time() - startTime
            if self.dataNum == 0:
                self.SwapBuffers()
                return
            
            eyePos = self.eyeDistance
            ex = 0.0
            ey = 0.0
            ez = 0.0
            dx = 0.0
            dy = 0.0
            dz = 0.0
            ux = 0.0
            uy = 0.0
            uz = 0.0
            xLen = max(abs(self.xMax), abs(self.xMin)) * 2
            yLen = max(abs(self.yMax), abs(self.yMin)) * 2
            zLen = max(abs(self.zMax), abs(self.zMin)) * 2
#             print 'transX, transY:', self.transX, self.transY
            if self.is3D:
                r2s = max(xLen, yLen, zLen) / self.size.width
                tX = self.transX * r2s
                tY = self.transY * r2s
                if not zLen == 0:
                    z = zLen / 2
                    if yLen / zLen > self.size.width / self.size.height:
                        z = yLen * self.size.height / self.size.width / 2
                    z *= 1.5
                    usedAngle = pi / 2 - viewPosAngle
                    cfovy1 = (eyePos - z * cos(usedAngle)) / sqrt(eyePos * eyePos + z * z - 2 * eyePos * z * cos(usedAngle))
                    cfovy2 = (eyePos - z * cos(pi - usedAngle)) / sqrt(eyePos * eyePos + z * z - 2 * eyePos * z * cos(pi - usedAngle))
                    fovy1 = degrees(acos(cfovy1))
                    fovy2 = degrees(acos(cfovy2))
                    self.fovy = 3 * max(fovy1, fovy2)
                angleX = viewPosAngle + radians(self.angleHorizontal)
                angleZ = viewPosAngle + radians(self.angleVertical)
                ex = eyePos * cos(angleZ) * cos(angleX)
                ey = eyePos * cos(angleZ) * sin(angleX)
                ez = eyePos * sin(angleZ)
                ux = 0.0
                uy = cos(pi / 2 - radians(self.angleFlat))
                uz = sin(pi / 2 - radians(self.angleFlat))
                flatAngle = radians(self.angleFlat)
                dx = -tX * cos(flatAngle) * sin(angleX) - tX * sin(flatAngle) * sin(angleZ) * sin(angleX)
                dx += tY * sin(flatAngle) * sin(angleX) - tY * cos(flatAngle) * sin(angleZ) * cos(angleX)
                dy = tX * cos(flatAngle) * cos(angleX) - tX * sin(flatAngle) * sin(angleZ) * cos(angleX)
                dy += -tY * sin(flatAngle) * cos(angleX) - tY * cos(flatAngle) * sin(angleZ) * sin(angleX)
                dz = tX * sin(flatAngle) * cos(angleZ)
                dz += tY * cos(flatAngle) * cos(angleZ)
            else:
                r2s = xLen / self.size.width
                dx = self.transX * r2s
                dy = self.transY * r2s
                dz = 0.0
                y = yLen / 2
                if xLen / yLen > self.size.width / self.size.height:
                    y = xLen * self.size.height / self.size.width / 2
                y += yLen / 10
                self.fovy = 2 * degrees(atan(y / eyePos))
                ez = eyePos
                ux = sin(radians(self.angleFlat))
                uy = cos(radians(self.angleFlat))
                uz = 0.0
            scale = 1
            if self.size != None:
                scale = float(self.size.width) / self.size.height               
#             userCalculationTime = time.time() - startTime - prepareTime
        
            glMatrixMode(GL_PROJECTION)
            glLoadIdentity()
            if self.is3D:
                gluPerspective(self.fovy * self.scaleFactor, scale, 0.1, self.eyeDistance * 2)
            else:
                gluPerspective(self.fovy * self.scaleFactor, scale,
                               self.eyeDistance - self.zMax - 10, self.eyeDistance - self.zMin + 10)
            glMatrixMode(GL_MODELVIEW)
            glLoadIdentity()
            gluLookAt(ex, ey, ez, -dx, -dy, -dz, ux, uy, uz)
            self._startPointIdx = 0
            
        if self.isDragging:
            # draw the frame instead of all data
            glLineWidth(1.0)
            glColor3f(0.0, 0.5, 0.0)
            glBegin(GL_LINES)
            glVertex3f(self.xMin, self.yMin, self.zMin)  # 1
            glVertex3f(self.xMax, self.yMin, self.zMin)  # 2
            
            glVertex3f(self.xMax, self.yMin, self.zMin)  # 2
            glVertex3f(self.xMax, self.yMax, self.zMin)  # 3
            
            glVertex3f(self.xMax, self.yMax, self.zMin)  # 3
            glVertex3f(self.xMin, self.yMax, self.zMin)  # 4
            
            glVertex3f(self.xMin, self.yMax, self.zMin)  # 4
            glVertex3f(self.xMin, self.yMin, self.zMin)  # 1
            
            glVertex3f(self.xMin, self.yMin, self.zMin)  # 1
            glVertex3f(self.xMin, self.yMin, self.zMax)  # 5
            
            glVertex3f(self.xMin, self.yMin, self.zMax)  # 5
            glVertex3f(self.xMax, self.yMin, self.zMax)  # 6
            
            glVertex3f(self.xMax, self.yMin, self.zMax)  # 6
            glVertex3f(self.xMax, self.yMax, self.zMax)  # 7
            
            glVertex3f(self.xMax, self.yMax, self.zMax)  # 7
            glVertex3f(self.xMin, self.yMax, self.zMax)  # 8
            
            glVertex3f(self.xMin, self.yMax, self.zMax)  # 8
            glVertex3f(self.xMin, self.yMin, self.zMax)  # 5
            
            glVertex3f(self.xMax, self.yMin, self.zMin)  # 2
            glVertex3f(self.xMax, self.yMin, self.zMax)  # 6
            
            glVertex3f(self.xMax, self.yMax, self.zMin)  # 3
            glVertex3f(self.xMax, self.yMax, self.zMax)  # 7
            
            glVertex3f(self.xMin, self.yMax, self.zMin)  # 4
            glVertex3f(self.xMin, self.yMax, self.zMax)  # 8
            
            glEnd()
            glFlush()
            self.SwapBuffers()
            return
            
        glEnableClientState(GL_VERTEX_ARRAY)
        if self.colorsNum >= self.dataNum:
            glEnableClientState(GL_COLOR_ARRAY)
            
        glDrawBuffer(GL_FRONT_AND_BACK)
                
        if self._startPointIdx + self._drawnPointsNum >= self.dataNum:
            glDrawArrays(GL_POINTS, self._startPointIdx, self.dataNum - self._startPointIdx)
            if self.displaySelected:
                selNum = len(self.displayedSelPoints)
                if selNum >= 2:
                    glLineWidth(2.0)
                    glColor3f(0.0, 1.0, 0.0)
                    glBegin(GL_LINES)
                    for i in xrange(1, selNum):
                        glVertex3f(self.displayedSelPoints[i - 1][0],
                                   self.displayedSelPoints[i - 1][1],
                                   self.displayedSelPoints[i - 1][2])
                        glVertex3f(self.displayedSelPoints[i][0],
                                   self.displayedSelPoints[i][1],
                                   self.displayedSelPoints[i][2])
                    glVertex3f(self.displayedSelPoints[selNum - 1][0],
                               self.displayedSelPoints[selNum - 1][1],
                               self.displayedSelPoints[selNum - 1][2])
                    glVertex3f(self.displayedSelPoints[0][0],
                               self.displayedSelPoints[0][1],
                               self.displayedSelPoints[0][2])
                    glEnd()
            self._refreshAll = True
        else:
            glDrawArrays(GL_POINTS, self._startPointIdx, self._drawnPointsNum)
            self._refreshAll = False
            self._isFront = not self._isFront
            self._startPointIdx += self._drawnPointsNum if self._isFront else 0
            
        glDisableClientState(GL_VERTEX_ARRAY)
        if self.colorsNum >= self.dataNum:
            glDisableClientState(GL_COLOR_ARRAY)
        glFlush()
        self.SwapBuffers()
#         drawingTime = time.time() - startTime - prepareTime - userCalculationTime
#         print "preparation time:", str(prepareTime), "user calculation time:",\
#             str(userCalculationTime), "drawing time:", str(drawingTime)
        if not self._refreshAll:
            PostEvent(self, PaintEvent())
        event.Skip()
Ejemplo n.º 10
0
    def do_scroll_paints(self, scrolls, flush=0, callbacks=[]):
        log("do_scroll_paints%s", (scrolls, flush))
        context = self.gl_context()
        if not context:
            log.warn("Warning: cannot paint scroll, no OpenGL context!")
            return
        def fail(msg):
            log.error("Error: %s", msg)
            fire_paint_callbacks(callbacks, False, msg)
        with context:
            bw, bh = self.size
            self.set_rgb_paint_state()
            #paste from offscreen to tmp with delta offset:
            glBindFramebuffer(GL_READ_FRAMEBUFFER, self.offscreen_fbo)
            glBindTexture(GL_TEXTURE_RECTANGLE_ARB, self.textures[TEX_FBO])
            glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ARB, self.textures[TEX_FBO], 0)
            glReadBuffer(GL_COLOR_ATTACHMENT0)

            glBindFramebuffer(GL_DRAW_FRAMEBUFFER, self.tmp_fbo)
            glBindTexture(GL_TEXTURE_RECTANGLE_ARB, self.textures[TEX_TMP_FBO])
            glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_RECTANGLE_ARB, self.textures[TEX_TMP_FBO], 0)
            glDrawBuffer(GL_COLOR_ATTACHMENT1)

            #copy current fbo:
            glBlitFramebuffer(0, 0, bw, bh,
                              0, 0, bw, bh,
                              GL_COLOR_BUFFER_BIT, GL_NEAREST)

            for x,y,w,h,xdelta,ydelta in scrolls:
                if abs(xdelta)>=bw:
                    fail("invalid xdelta value: %i" % xdelta)
                    continue
                if abs(ydelta)>=bh:
                    fail("invalid ydelta value: %i" % ydelta)
                    continue
                if ydelta==0 and xdelta==0:
                    fail("scroll has no delta!")
                    continue
                if w<=0 or h<=0:
                    fail("invalid scroll area size: %ix%i" % (w, h))
                    continue
                #these should be errors,
                #but desktop-scaling can cause a mismatch between the backing size
                #and the real window size server-side.. so we clamp the dimensions instead
                if x+w>bw:
                    w = bw-x
                if y+h>bh:
                    h = bh-y
                if x+w+xdelta>bw:
                    w = bw-x-xdelta
                    if w<=0:
                        continue        #nothing left!
                if y+h+ydelta>bh:
                    h = bh-y-ydelta
                    if h<=0:
                        continue        #nothing left!
                if x+xdelta<0:
                    fail("horizontal scroll by %i: rectangle %s overflows the backing buffer size %s" % (xdelta, (x, y, w, h), self.size))
                    continue
                if y+ydelta<0:
                    fail("vertical scroll by %i: rectangle %s overflows the backing buffer size %s" % (ydelta, (x, y, w, h), self.size))
                    continue
                #opengl buffer is upside down, so we must invert Y coordinates: bh-(..)
                glBlitFramebuffer(x, bh-y, x+w, bh-(y+h),
                                  x+xdelta, bh-(y+ydelta), x+w+xdelta, bh-(y+h+ydelta),
                                  GL_COLOR_BUFFER_BIT, GL_NEAREST)
                glFlush()

            #now swap references to tmp and offscreen so tmp becomes the new offscreen:
            tmp = self.offscreen_fbo
            self.offscreen_fbo = self.tmp_fbo
            self.tmp_fbo = tmp
            tmp = self.textures[TEX_FBO]
            self.textures[TEX_FBO] = self.textures[TEX_TMP_FBO]
            self.textures[TEX_TMP_FBO] = tmp

            #restore normal paint state:
            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ARB, self.textures[TEX_FBO], 0)
            glBindFramebuffer(GL_READ_FRAMEBUFFER, self.offscreen_fbo)
            glBindFramebuffer(GL_DRAW_FRAMEBUFFER, self.offscreen_fbo)
            glBindFramebuffer(GL_FRAMEBUFFER, self.offscreen_fbo)

            self.unset_rgb_paint_state()
            self.paint_box("scroll", True, x+xdelta, y+ydelta, x+w+xdelta, y+h+ydelta)
            self.present_fbo(0, 0, bw, bh, flush)
            fire_paint_callbacks(callbacks, True)