def render(self): '''Render the scene and return image as buffer. Returns ------- image: HxWxD array where D is 4 when `mode=='RGBA'` else 3. ''' with self.offscreen.bind(): self.offscreen.draw_view3d( bpy.context.scene, bpy.context.view_layer, self.space, #bpy.context.space_data self.region, #bpy.context.region self.camera.view_matrix, self.camera.proj_matrix) bgl.glActiveTexture(bgl.GL_TEXTURE0) bgl.glBindTexture(bgl.GL_TEXTURE_2D, self.offscreen.color_texture) # np.asarray seems slow, because bgl.buffer does not support the python buffer protocol # bgl.glGetTexImage(bgl.GL_TEXTURE_2D, 0, bgl.GL_RGB, bgl.GL_UNSIGNED_BYTE, self.buffer) # https://docs.blender.org/api/blender2.8/gpu.html # That's why we use PyOpenGL at this point instead. glGetTexImage(bgl.GL_TEXTURE_2D, 0, self.mode, bgl.GL_UNSIGNED_BYTE, self.buffer) buffer = self.buffer if self.origin == 'upper-left': buffer = np.flipud(buffer) if self.gamma_coeff: buffer = self._color_correct(buffer, self.gamma_coeff) return buffer
def save_FBO(self): bw, bh = self.size glEnable(GL_TEXTURE_RECTANGLE_ARB) 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) glViewport(0, 0, bw, bh) size = bw*bh*4 data = numpy.empty(size) img_data = glGetTexImage(GL_TEXTURE_RECTANGLE_ARB, 0, GL_BGRA, GL_UNSIGNED_BYTE, data) img = Image.frombuffer("RGBA", (bw, bh), img_data, "raw", "BGRA", bw*4) img = ImageOps.flip(img) kwargs = {} if SAVE_BUFFERS=="jpeg": kwargs = { "quality" : 0, "optimize" : False, } t = time.time() tstr = time.strftime("%H-%M-%S", time.localtime(t)) filename = "./W%i-FBO-%s.%03i.%s" % (self.wid, tstr, (t*1000)%1000, SAVE_BUFFERS) log("do_present_fbo: saving %4ix%-4i pixels, %7i bytes to %s", bw, bh, size, filename) img.save(filename, SAVE_BUFFERS, **kwargs) glBindFramebuffer(GL_READ_FRAMEBUFFER, 0) glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0) glDisable(GL_TEXTURE_RECTANGLE_ARB)
def __record(self): diag = QDialog() fId = gSettings.value('RecordFPS', 2) rId = gSettings.value('RecordResolution', 3) layout = QGridLayout() diag.setLayout(layout) layout.addWidget(QLabel('FPS: '), 0, 0) fps = QComboBox() fps.addItems(['12', '24', '30', '48', '60', '120']) fps.setCurrentIndex(fId) layout.addWidget(fps, 0, 1) layout.addWidget(QLabel('Vertical resolution: '), 1, 0) resolution = QComboBox() resolution.addItems(['144', '288', '360', '720', '1080', '2160']) resolution.setCurrentIndex(rId) layout.addWidget(resolution, 1, 1) ok = QPushButton('Ok') ok.clicked.connect(diag.accept) cancel = QPushButton('Cancel') cancel.clicked.connect(diag.reject) layout.addWidget(ok, 2, 0) layout.addWidget(cancel, 2, 1) diag.exec_() if diag.result() != QDialog.Accepted: return gSettings.setValue('RecordFPS', fps.currentIndex()) gSettings.setValue('RecordResolution', resolution.currentIndex()) FPS = int(fps.currentText()) HEIGHT = int(resolution.currentText()) WIDTH = (HEIGHT * 16) / 9 FMT = 'jpg' data = (ctypes.c_ubyte * (WIDTH * HEIGHT * 3))() # alloc buffer once flooredStart = self._timer.secondsToBeats(int(self._timer.beatsToSeconds(self._timer.start) * FPS) / float(FPS)) duration = self._timer.beatsToSeconds(self._timer.end - flooredStart) if not fileutil.exists('capture'): os.makedirs('capture') progress = QProgressDialog(self) progress.setMaximum(int(duration * FPS)) prevFrame = 0 for frame in xrange(int(duration * FPS)): deltaTime = (frame - prevFrame) / float(FPS) prevFrame = frame progress.setValue(frame) QApplication.processEvents() if progress.wasCanceled(): break beats = flooredStart + self._timer.secondsToBeats(frame / float(FPS)) shot = self.__shotsManager.shotAtTime(beats) if shot is None: continue sceneFile = os.path.join(ScenesPath(), shot.sceneName + SCENE_EXT) scene = Scene.getScene(sceneFile) scene.setSize(WIDTH, HEIGHT) uniforms = self.__shotsManager.evaluate(beats) textureUniforms = self.__shotsManager.additionalTextures(beats) self.__sceneView._cameraInput.setData(*(uniforms['uOrigin'] + uniforms['uAngles'])) # feed animation into camera so animationprocessor can read it again cameraData = self.__sceneView._cameraInput.data() modifier = os.path.join(ProjectDir(), 'animationprocessor.py') if fileutil.exists(modifier): execfile(modifier, globals(), locals()) for name in self.__sceneView._textures: uniforms[name] = self.__sceneView._textures[name]._id scene.drawToScreen(self._timer.beatsToSeconds(beats), beats, uniforms, (0, 0, WIDTH, HEIGHT), textureUniforms) scene.colorBuffers[-1][0].use() from OpenGL.GL import glGetTexImage, GL_TEXTURE_2D, GL_RGB, GL_UNSIGNED_BYTE glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, data) QImage(data, WIDTH, HEIGHT, QImage.Format_RGB888).mirrored(False, True).save('capture/dump_%s_%05d.%s' % (FPS, int(self._timer.beatsToSeconds(self._timer.start) * FPS) + frame, FMT)) progress.close() if not fileutil.exists('convertcapture'): os.makedirs('convertcapture') with fileutil.edit('convertcapture/convert.bat') as fh: start = '' start2 = '' if int(self._timer.start * FPS) > 0: start = '-start_number {} '.format(int(self._timer.beatsToSeconds(self._timer.start) * FPS)) start2 = '-vframes {} '.format(int(self._timer.beatsToSeconds(self._timer.end - self._timer.start) * FPS)) fh.write('cd "../capture"\n"../convertcapture/ffmpeg.exe" -framerate {} {}-i dump_{}_%%05d.{} {}-c:v libx264 -r {} -pix_fmt yuv420p "../convertcapture/output.mp4"'.format(FPS, start, FPS, FMT, start2, FPS)) with fileutil.edit('convertcapture/convertGif.bat') as fh: start = '' start2 = '' if int(self._timer.start * FPS) > 0: start = '-start_number {} '.format(int(self._timer.beatsToSeconds(self._timer.start) * FPS)) start2 = '-vframes {} '.format(int(self._timer.beatsToSeconds(self._timer.end - self._timer.start) * FPS)) fh.write('cd "../capture"\n"../convertcapture/ffmpeg.exe" -framerate {} {}-i dump_{}_%%05d.{} {}-r {} "../convertcapture/output.gif"'.format(FPS, start, FPS, FMT, start2, FPS)) sound = self.timeSlider.soundtrackPath() if not sound: return with fileutil.edit('convertcapture/merge.bat') as fh: startSeconds = self._timer.beatsToSeconds(self._timer.start) fh.write('ffmpeg -i output.mp4 -itsoffset {} -i "{}" -vcodec copy -shortest merged.mp4'.format(-startSeconds, sound))
def do_present_fbo(self): bw, bh = self.size ww, wh = self.render_size self.gl_marker("Presenting FBO on screen") # Change state to target screen instead of our FBO glBindFramebuffer(GL_FRAMEBUFFER, 0) glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0) left, top, right, bottom = self.offsets #viewport for clearing the whole window: glViewport(0, 0, left+ww+right, top+wh+bottom) if self._alpha_enabled: # transparent background: glClearColor(0.0, 0.0, 0.0, 0.0) else: # black, no alpha: glClearColor(0.0, 0.0, 0.0, 1.0) if left or top or right or bottom: try: glClear(GL_COLOR_BUFFER_BIT) except: log("ignoring glClear(GL_COLOR_BUFFER_BIT) error, buggy driver?", exc_info=True) #viewport for painting to window: glViewport(left, top, ww, wh) # Draw FBO texture on screen self.set_rgb_paint_state() rect_count = len(self.pending_fbo_paint) if self.glconfig.is_double_buffered() or bw!=ww or bh!=wh: #refresh the whole window: rectangles = ((0, 0, bw, bh), ) else: #paint just the rectangles we have accumulated: rectangles = self.pending_fbo_paint self.pending_fbo_paint = [] log("do_present_fbo: painting %s", rectangles) glEnable(GL_TEXTURE_RECTANGLE_ARB) glBindTexture(GL_TEXTURE_RECTANGLE_ARB, self.textures[TEX_FBO]) if self._alpha_enabled: # support alpha channel if present: glEnablei(GL_BLEND, self.textures[TEX_FBO]) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE) if SAVE_BUFFERS: 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) glViewport(0, 0, bw, bh) from OpenGL.GL import glGetTexImage size = bw*bh*4 import numpy data = numpy.empty(size) img_data = glGetTexImage(GL_TEXTURE_RECTANGLE_ARB, 0, GL_BGRA, GL_UNSIGNED_BYTE, data) from PIL import Image, ImageOps img = Image.frombuffer("RGBA", (bw, bh), img_data, "raw", "BGRA", bw*4) img = ImageOps.flip(img) kwargs = {} if SAVE_BUFFERS=="jpeg": kwargs = { "quality" : 0, "optimize" : False, } t = time.time() tstr = time.strftime("%H-%M-%S", time.localtime(t)) filename = "./W%i-FBO-%s.%03i.%s" % (self.wid, tstr, (t*1000)%1000, SAVE_BUFFERS) log("do_present_fbo: saving %4ix%-4i pixels, %7i bytes to %s", bw, bh, size, filename) img.save(filename, SAVE_BUFFERS, **kwargs) glBindFramebuffer(GL_READ_FRAMEBUFFER, 0) if ww!=bw or wh!=bh: glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_LINEAR) glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_LINEAR) glBegin(GL_QUADS) for x,y,w,h in rectangles: #note how we invert coordinates.. tx1, ty1, tx2, ty2 = x, bh-y, x+w, bh-y-h vx1, vy1, vx2, vy2 = x, y, x+w, y+h glTexCoord2i(tx1, ty1) glVertex2i(vx1, vy1) #top-left of window viewport glTexCoord2i(tx1, ty2) glVertex2i(vx1, vy2) #bottom-left of window viewport glTexCoord2i(tx2, ty2) glVertex2i(vx2, vy2) #bottom-right of window viewport glTexCoord2i(tx2, ty1) glVertex2i(vx2, vy1) #top-right of window viewport glEnd() glDisable(GL_TEXTURE_RECTANGLE_ARB) if self.paint_spinner: #add spinner: dim = min(bw/3.0, bh/3.0) t = time.time() count = int(t*4.0) bx = bw//2 by = bh//2 for i in range(8): #8 lines glBegin(GL_POLYGON) c = cv.trs[count%8][i] glColor4f(c, c, c, 1) mi1 = math.pi*i/4-math.pi/16 mi2 = math.pi*i/4+math.pi/16 glVertex2i(int(bx+math.sin(mi1)*10), int(by+math.cos(mi1)*10)) glVertex2i(int(bx+math.sin(mi1)*dim), int(by+math.cos(mi1)*dim)) glVertex2i(int(bx+math.sin(mi2)*dim), int(by+math.cos(mi2)*dim)) glVertex2i(int(bx+math.sin(mi2)*10), int(by+math.cos(mi2)*10)) glEnd() #if desired, paint window border if self.border and self.border.shown: #double size since half the line will be off-screen glLineWidth(self.border.size*2) glBegin(GL_LINE_LOOP) glColor4f(self.border.red, self.border.green, self.border.blue, self.border.alpha) for px,py in ((0, 0), (bw, 0), (bw, bh), (0, bh)): glVertex2i(px, py) glEnd() if self.pointer_overlay: x, y, _, _, size, start_time = self.pointer_overlay elapsed = time.time()-start_time if elapsed<6: alpha = max(0, (5.0-elapsed)/5.0) glLineWidth(1) glBegin(GL_LINES) glColor4f(0, 0, 0, alpha) glVertex2i(x-size, y) glVertex2i(x+size, y) glVertex2i(x, y-size) glVertex2i(x, y+size) glEnd() else: self.pointer_overlay = None # Show the backbuffer on screen self.gl_show(rect_count) self.gl_frame_terminator() #restore pbo viewport glViewport(0, 0, bw, bh) glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST) glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST) self.unset_rgb_paint_state() log("%s(%s, %s)", glBindFramebuffer, GL_FRAMEBUFFER, self.offscreen_fbo) glBindFramebuffer(GL_FRAMEBUFFER, self.offscreen_fbo) log("%s.do_present_fbo() done", self)
def setupTransforms(self, transforms): # note: this is only called from test_drawing.py (as of before 090302) """ Fill a block of transforms. Depending on the setting of TEXTURE_XFORMS and UNIFORM_XFORMS, the transforms are either in texture memory, or in a uniform array of mat4s ("constant memory"), or unsupported (error if we need any here). @param transforms: A list of transform matrices, where each transform is a flattened list (or Numpy array) of 16 numbers. """ self.n_transforms = nTransforms = len(transforms) if not self.supports_transforms(): assert not nTransforms, "%r doesn't support transforms" % self return self.setActive(True) # Must activate before setting uniforms. assert self._has_uniform("n_transforms") # redundant with following glUniform1iARB(self._uniform("n_transforms"), self.n_transforms) # The shader bypasses transform logic if n_transforms is 0. # (Then location coordinates are in global modeling coordinates.) if nTransforms > 0: if UNIFORM_XFORMS: # Load into constant memory. The GL_EXT_bindable_uniform # extension supports sharing this array of mat4s through a VBO. # XXX Need to bank-switch this data if more than N_CONST_XFORMS. C_transforms = numpy.array(transforms, dtype = numpy.float32) glUniformMatrix4fvARB(self._uniform("transforms"), # Don't over-run the array size. min(len(transforms), N_CONST_XFORMS), GL_TRUE, # Transpose. C_transforms) elif TEXTURE_XFORMS: # Generate a texture ID and bind the texture unit to it. self.transform_memory = glGenTextures(1) glBindTexture(GL_TEXTURE_2D, self.transform_memory) ## These seem to have no effect with a vertex shader. ## glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) ## glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) # XXX Needed? glEnable(GL_TEXTURE_2D) # Load the transform data into the texture. # # Problem: SIGSEGV kills Python in gleTextureImagePut under # glTexImage2D with more than 250 transforms (16,000 bytes.) # Maybe there's a 16-bit signed size calculation underthere, that's # overflowing the sign bit... Work around by sending transforms to # the texture unit in batches with glTexSubImage2D.) glTexImage2D(GL_TEXTURE_2D, 0, # Level zero - base image, no mipmap. GL_RGBA32F_ARB, # Internal format is floating point. # Column major storage: width = N, height = 4 * RGBA. nTransforms, 4 * 4, 0, # No border. # Data format and type, null pointer to allocate space. GL_RGBA, GL_FLOAT, None) # XXX Split this off into a setTransforms method. batchSize = 250 nBatches = (nTransforms + batchSize-1) / batchSize for i in range(nBatches): xStart = batchSize * i xEnd = min(nTransforms, xStart + batchSize) xSize = xEnd - xStart glTexSubImage2D(GL_TEXTURE_2D, 0, # Subimage x and y offsets and sizes. xStart, 0, xSize, 4 * 4, # List of matrices is flattened into a sequence. GL_RGBA, GL_FLOAT, transforms[xStart:xEnd]) continue # Read back to check proper loading. if CHECK_TEXTURE_XFORM_LOADING: mats = glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_FLOAT) nMats = len(mats) print "setupTransforms\n[[", for i in range(nMats): nElts = len(mats[i]) perLine = 8 nLines = (nElts + perLine-1) / perLine for line in range(nLines): jStart = perLine * line jEnd = min(nElts, jStart + perLine) for j in range(jStart, jEnd): print "%.2f" % mats[i][j], continue if line < nLines-1: print "\n ", pass if i < nMats-1: print "]\n [", pass continue print "]]" pass else: # should never happen if SUPPORTS_XFORMS is defined correctly assert 0, "can't setupTransforms unless UNIFORM_XFORMS or TEXTURE_XFORMS is set" pass self.setActive(False) # Deactivate again. return
def __record(self): diag = QDialog() fId = gSettings.value('RecordFPS', 2) rId = gSettings.value('RecordResolution', 3) layout = QGridLayout() diag.setLayout(layout) layout.addWidget(QLabel('FPS: '), 0, 0) fps = QComboBox() fps.addItems(['12', '24', '30', '48', '60', '120']) fps.setCurrentIndex(fId) layout.addWidget(fps, 0, 1) layout.addWidget(QLabel('Vertical resolution: '), 1, 0) resolution = QComboBox() resolution.addItems(['144', '288', '360', '720', '1080', '2160']) resolution.setCurrentIndex(rId) layout.addWidget(resolution, 1, 1) ok = QPushButton('Ok') ok.clicked.connect(diag.accept) cancel = QPushButton('Cancel') cancel.clicked.connect(diag.reject) layout.addWidget(ok, 2, 0) layout.addWidget(cancel, 2, 1) diag.exec_() if diag.result() != QDialog.Accepted: return gSettings.setValue('RecordFPS', fps.currentIndex()) gSettings.setValue('RecordResolution', resolution.currentIndex()) FPS = int(fps.currentText()) HEIGHT = int(resolution.currentText()) WIDTH = (HEIGHT * 16) / 9 FMT = 'jpg' data = (ctypes.c_ubyte * (WIDTH * HEIGHT * 3))() # alloc buffer once flooredStart = self._timer.secondsToBeats( int(self._timer.beatsToSeconds(self._timer.start) * FPS) / float(FPS)) duration = self._timer.beatsToSeconds(self._timer.end - flooredStart) captureDir = currentProjectDirectory().join('capture') captureDir.ensureExists(isFolder=True) progress = QProgressDialog(self) progress.setMaximum(int(duration * FPS)) prevFrame = 0 for frame in range(int(duration * FPS)): deltaTime = (frame - prevFrame) / float(FPS) prevFrame = frame progress.setValue(frame) QApplication.processEvents() if progress.wasCanceled(): break beats = flooredStart + self._timer.secondsToBeats( frame / float(FPS)) shot = self.__shotsManager.shotAtTime(beats) if shot is None: continue sceneFile = currentScenesDirectory().join( shot.sceneName).ensureExt(SCENE_EXT) scene = Scene.getScene(sceneFile) scene.setSize(WIDTH, HEIGHT) uniforms = self.__shotsManager.evaluate(beats) textureUniforms = self.__shotsManager.additionalTextures(beats) self.__sceneView._cameraInput.setData( *(uniforms['uOrigin'] + uniforms['uAngles']) ) # feed animation into camera so animationprocessor can read it again cameraData = self.__sceneView._cameraInput.data() modifier = currentProjectDirectory().join('animationprocessor.py') if modifier.exists(): execfile(str(modifier), globals(), locals()) for name in self.__sceneView._textures: uniforms[name] = self.__sceneView._textures[name]._id scene.drawToScreen(self._timer.beatsToSeconds(beats), beats, uniforms, (0, 0, WIDTH, HEIGHT), textureUniforms) scene.colorBuffers[-1][0].use() from OpenGL.GL import glGetTexImage, GL_TEXTURE_2D, GL_RGB, GL_UNSIGNED_BYTE glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, data) captureDir = currentProjectDirectory().join('capture') QImage(data, WIDTH, HEIGHT, QImage.Format_RGB888).mirrored( False, True ).save( captureDir.join( 'dump_%s_%05d.%s' % (FPS, int(self._timer.beatsToSeconds(self._timer.start) * FPS) + frame, FMT))) progress.close() convertCaptureDir = currentProjectDirectory().join('convertcapture') convertCaptureDir.ensureExists(isFolder=True) with convertCaptureDir.join('convert.bat').edit() as fh: start = '' start2 = '' if int(self._timer.start * FPS) > 0: start = '-start_number {} '.format( int(self._timer.beatsToSeconds(self._timer.start) * FPS)) start2 = '-vframes {} '.format( int( self._timer.beatsToSeconds(self._timer.end - self._timer.start) * FPS)) fh.write( 'cd "../capture"\n"{}" -framerate {} {}-i dump_{}_%%05d.{} {}-c:v libx264 -r {} -pix_fmt yuv420p "../convertcapture/output.mp4"' .format(FFMPEG_PATH, FPS, start, FPS, FMT, start2, FPS)) with convertCaptureDir.join('convertGif.bat').edit() as fh: start = '' start2 = '' iln = '' if int(self._timer.start * FPS) > 0: start = '-start_number {} '.format( int(self._timer.beatsToSeconds(self._timer.start) * FPS)) start2 = '-vframes {} '.format( int( self._timer.beatsToSeconds(self._timer.end - self._timer.start) * FPS)) iln = '-t {:03f} '.format( self._timer.beatsToSeconds(self._timer.end - self._timer.start)) fh.write( 'REM File format is actually %5d but in a .bat file we need to escape % or something, so you can\'t copy paste this into a cmd prompt without fixing up the %%05d to be %5d.\n' ) fh.write( 'cd "../capture"\n"{}" -framerate {} {}{}-i dump_{}_%%05d.{} -vf "fps={},scale={}:-1:flags=lanczos,palettegen" palette.png\n' .format(FFMPEG_PATH, FPS, start, iln, FPS, FMT, FPS, HEIGHT)) fh.write( '"{}" -framerate {} {}-i dump_{}_%%05d.{} -i "palette.png" -filter_complex "fps=12,scale=360:-1:flags=lanczos[x];[x][1:v]paletteuse" {}-r {} "../convertcapture/output.gif"' .format(FFMPEG_PATH, FPS, start, FPS, FMT, start2, FPS)) sound = self.timeSlider.soundtrackPath() if not sound: return with convertCaptureDir.join('merge.bat').edit() as fh: startSeconds = self._timer.beatsToSeconds(self._timer.start) fh.write( '{} -i output.mp4 -itsoffset {} -i "{}" -vcodec copy -shortest merged.mp4' .format(FFMPEG_PATH, -startSeconds, sound))
def do_present_fbo(self): bw, bh = self.size ww, wh = self.render_size self.gl_marker("Presenting FBO on screen") # Change state to target screen instead of our FBO glBindFramebuffer(GL_FRAMEBUFFER, 0) glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0) if self._alpha_enabled: # transparent background: glClearColor(0.0, 0.0, 0.0, 0.0) else: # plain white no alpha: glClearColor(1.0, 1.0, 1.0, 1.0) # Draw FBO texture on screen self.set_rgb_paint_state() rect_count = len(self.pending_fbo_paint) if self.glconfig.is_double_buffered() or bw!=ww or bh!=wh: #refresh the whole window: rectangles = ((0, 0, bw, bh), ) else: #paint just the rectangles we have accumulated: rectangles = self.pending_fbo_paint self.pending_fbo_paint = [] log("do_present_fbo: painting %s", rectangles) glEnable(GL_TEXTURE_RECTANGLE_ARB) glBindTexture(GL_TEXTURE_RECTANGLE_ARB, self.textures[TEX_FBO]) if self._alpha_enabled: # support alpha channel if present: glEnablei(GL_BLEND, self.textures[TEX_FBO]) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE) if SAVE_BUFFERS: 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) glViewport(0, 0, bw, bh) from OpenGL.GL import glGetTexImage size = bw*bh*4 import numpy data = numpy.empty(size) img_data = glGetTexImage(GL_TEXTURE_RECTANGLE_ARB, 0, GL_BGRA, GL_UNSIGNED_BYTE, data) from PIL import Image, ImageOps img = Image.frombuffer("RGBA", (bw, bh), img_data, "raw", "BGRA", bw*4) img = ImageOps.flip(img) kwargs = {} if SAVE_BUFFERS=="jpeg": kwargs = { "quality" : 0, "optimize" : False, } t = time.time() tstr = time.strftime("%H-%M-%S", time.localtime(t)) filename = "./W%i-FBO-%s.%03i.%s" % (self.wid, tstr, (t*1000)%1000, SAVE_BUFFERS) log("do_present_fbo: saving %4ix%-4i pixels, %7i bytes to %s", bw, bh, size, filename) img.save(filename, SAVE_BUFFERS, **kwargs) glBindFramebuffer(GL_READ_FRAMEBUFFER, 0) #viewport for painting to window: glViewport(0, 0, ww, wh) if ww!=bw or wh!=bh: glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_LINEAR) glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_LINEAR) glBegin(GL_QUADS) for x,y,w,h in rectangles: #note how we invert coordinates.. tx1, ty1, tx2, ty2 = x, bh-y, x+w, bh-y-h vx1, vy1, vx2, vy2 = x, y, x+w, y+h glTexCoord2i(tx1, ty1) glVertex2i(vx1, vy1) #top-left of window viewport glTexCoord2i(tx1, ty2) glVertex2i(vx1, vy2) #bottom-left of window viewport glTexCoord2i(tx2, ty2) glVertex2i(vx2, vy2) #bottom-right of window viewport glTexCoord2i(tx2, ty1) glVertex2i(vx2, vy1) #top-right of window viewport glEnd() glDisable(GL_TEXTURE_RECTANGLE_ARB) if self.paint_spinner: #add spinner: dim = min(bw/3.0, bh/3.0) t = time.time() count = int(t*4.0) bx = bw//2 by = bh//2 for i in range(8): #8 lines glBegin(GL_POLYGON) c = cv.trs[count%8][i] glColor4f(c, c, c, 1) mi1 = math.pi*i/4-math.pi/16 mi2 = math.pi*i/4+math.pi/16 glVertex2i(int(bx+math.sin(mi1)*10), int(by+math.cos(mi1)*10)) glVertex2i(int(bx+math.sin(mi1)*dim), int(by+math.cos(mi1)*dim)) glVertex2i(int(bx+math.sin(mi2)*dim), int(by+math.cos(mi2)*dim)) glVertex2i(int(bx+math.sin(mi2)*10), int(by+math.cos(mi2)*10)) glEnd() #if desired, paint window border if self.border and self.border.shown: #double size since half the line will be off-screen glLineWidth(self.border.size*2) glBegin(GL_LINE_LOOP) glColor4f(self.border.red, self.border.green, self.border.blue, self.border.alpha) for px,py in ((0, 0), (bw, 0), (bw, bh), (0, bh)): glVertex2i(px, py) glEnd() if self.pointer_overlay: x, y, _, _, size, start_time = self.pointer_overlay elapsed = time.time()-start_time if elapsed<6: alpha = max(0, (5.0-elapsed)/5.0) glLineWidth(1) glBegin(GL_LINES) glColor4f(0, 0, 0, alpha) glVertex2i(x-size, y) glVertex2i(x+size, y) glVertex2i(x, y-size) glVertex2i(x, y+size) glEnd() else: self.pointer_overlay = None # Show the backbuffer on screen self.gl_show(rect_count) self.gl_frame_terminator() #restore pbo viewport glViewport(0, 0, bw, bh) glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST) glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST) self.unset_rgb_paint_state() log("%s(%s, %s)", glBindFramebuffer, GL_FRAMEBUFFER, self.offscreen_fbo) glBindFramebuffer(GL_FRAMEBUFFER, self.offscreen_fbo) log("%s.do_present_fbo() done", self)