def draw_region(self, x, y, width, height, coding, img_data, rowstride, packet_sequence, options, callbacks): """ Note: this runs from the draw thread (not UI thread) """ backing = self._backing if not backing: log("draw_region: window %s has no backing, gone?", self._id) from xpra.client.window_backing_base import fire_paint_callbacks fire_paint_callbacks(callbacks, -1, "no backing") return def after_draw_refresh(success, message=""): plog( "after_draw_refresh(%s, %s) %sx%s at %sx%s encoding=%s, options=%s", success, message, width, height, x, y, coding, options) if success <= 0: return backing = self._backing if backing and backing.draw_needs_refresh: if REPAINT_ALL == "1" or self._client.xscale != 1 or self._client.yscale != 1: rw, rh = self.get_size() rx, ry = 0, 0 else: rx, ry, rw, rh = self._client.srect(x, y, width, height) if self.window_offset: rx += self.window_offset[0] ry += self.window_offset[1] self.idle_add(self.queue_draw, rx, ry, rw, rh) #only register this callback if we actually need it: if backing.draw_needs_refresh: callbacks.append(after_draw_refresh) backing.draw_region(x, y, width, height, coding, img_data, rowstride, options, callbacks)
def paint_png(self, img_data, x, y, width, height, rowstride, options, callbacks): """ must be called from UI thread """ if self._backing is None: fire_paint_callbacks(callbacks, False) return buf = data_to_buffer(img_data) self.do_paint_png(buf, x, y, width, height, rowstride, options, callbacks)
def do_paint_png(self, buf, x, y, width, height, rowstride, options, callbacks): surf = cairo.ImageSurface.create_from_png(buf) gc = cairo.Context(self._backing) gc.set_source_surface(surf) gc.paint() surf.finish() fire_paint_callbacks(callbacks, True)
def paint_pixbuf_gdk(self, coding, img_data, x, y, width, height, options, callbacks): """ must be called from UI thread """ loader = gdk.PixbufLoader(coding) loader.write(img_data, len(img_data)) loader.close() pixbuf = loader.get_pixbuf() if not pixbuf: log.error("failed %s pixbuf=%s data len=%s" % (coding, pixbuf, len(img_data))) fire_paint_callbacks(callbacks, False) return False raw_data = pixbuf.get_pixels() rowstride = pixbuf.get_rowstride() img_data = self.process_delta(raw_data, width, height, rowstride, options) n = pixbuf.get_n_channels() if n == 3: self.do_paint_rgb24(img_data, x, y, width, height, rowstride, options, callbacks) else: assert n == 4, "invalid number of channels: %s" % n self.do_paint_rgb32(img_data, x, y, width, height, rowstride, options, callbacks) return False
def paint_png(self, img_data, x, y, width, height, rowstride, options, callbacks): """ must be called from UI thread """ if self._backing is None: fire_paint_callbacks(callbacks, False) return buf = data_to_buffer(img_data) self.do_paint_png(buf, x, y, width, height, rowstride, options, callbacks)
def draw_region(self, x, y, width, height, coding, img_data, rowstride, packet_sequence, options, callbacks): """ Note: this runs from the draw thread (not UI thread) """ backing = self._backing if not backing: log("draw_region: window %s has no backing, gone?", self._id) from xpra.client.window_backing_base import fire_paint_callbacks fire_paint_callbacks(callbacks, False) return def after_draw_refresh(success): plog( "after_draw_refresh(%s) %sx%s at %sx%s encoding=%s, options=%s", success, width, height, x, y, coding, options) if not success: return backing = self._backing if backing and backing.draw_needs_refresh: self.queue_draw(x, y, width, height) #only register this callback if we actually need it: if backing.draw_needs_refresh: callbacks.append(after_draw_refresh) self._backing.draw_region(x, y, width, height, coding, img_data, rowstride, options, callbacks)
def paint_pixbuf_gdk(self, coding, img_data, x, y, width, height, options, callbacks): """ must be called from UI thread """ if coding.startswith("png"): coding = "png" else: assert coding=="jpeg" loader = gdk.PixbufLoader(coding) loader.write(img_data, len(img_data)) loader.close() pixbuf = loader.get_pixbuf() if not pixbuf: msg = "failed to load a pixbuf from %i bytes of %s data" % (len(img_data), coding) log.error("Error: %s", msg) fire_paint_callbacks(callbacks, False, msg) return False raw_data = pixbuf.get_pixels() rowstride = pixbuf.get_rowstride() img_data = self.process_delta(raw_data, width, height, rowstride, options) n = pixbuf.get_n_channels() if n==3: self.do_paint_rgb24(img_data, x, y, width, height, rowstride, options, callbacks) else: assert n==4, "invalid number of channels: %s" % n self.do_paint_rgb32(img_data, x, y, width, height, rowstride, options, callbacks) return False
def do_paint_png(self, buf, x, y, width, height, rowstride, options, callbacks): surf = cairo.ImageSurface.create_from_png(buf) gc = cairo.Context(self._backing) gc.set_source_surface(surf) gc.paint() surf.finish() fire_paint_callbacks(callbacks, True)
def do_paint_scroll(self, scrolls, callbacks): old_backing = self._backing self.do_init_new_backing_instance() self.copy_backing(old_backing) gc = self._backing.new_gc() for sx, sy, sw, sh, xdelta, ydelta in scrolls: self._backing.draw_drawable(gc, old_backing, sx, sy, sx + xdelta, sy + ydelta, sw, sh) fire_paint_callbacks(callbacks)
def do_paint_scroll(self, scrolls, callbacks): old_backing = self._backing w, h = self.size ww, wh = self.render_size self.init(ww, wh, w, h) gc = gdk_cairo_context(cairo.Context(self._backing)) gc.set_operator(cairo.OPERATOR_SOURCE) for sx, sy, sw, sh, xdelta, ydelta in scrolls: gc.set_source_surface(old_backing, xdelta, ydelta) gc.rectangle(sx + xdelta, sy + ydelta, sw, sh) gc.fill() fire_paint_callbacks(callbacks)
def paint_pixbuf_gdk(self, coding, img_data, x, y, width, height, options, callbacks): """ must be called from UI thread """ loader = gdk.PixbufLoader(coding) loader.write(img_data, len(img_data)) loader.close() pixbuf = loader.get_pixbuf() if not pixbuf: log.error("failed %s pixbuf=%s data len=%s" % (coding, pixbuf, len(img_data))) fire_paint_callbacks(callbacks, False) return False raw_data = pixbuf.get_pixels() rowstride = pixbuf.get_rowstride() img_data = self.process_delta(raw_data, width, height, rowstride, options) self.do_paint_rgb24(img_data, x, y, width, height, rowstride, options, callbacks) return False
def do_paint_rgb(self, rgb_format, img_data, x, y, width, height, rowstride, options, callbacks): log("%s.do_paint_rgb(%s, %s bytes, x=%d, y=%d, width=%d, height=%d, rowstride=%d, options=%s)", self, rgb_format, len(img_data), x, y, width, height, rowstride, options) context = self.gl_context() if not context: log("%s._do_paint_rgb(..) no context!", self) fire_paint_callbacks(callbacks, False, "no opengl context") return if not options.get("paint", True): fire_paint_callbacks(callbacks) return try: upload, img_data = self.pixels_for_upload(img_data) with context: self.gl_init() self.set_rgb_paint_state() #convert it to a GL constant: pformat = PIXEL_FORMAT_TO_CONSTANT.get(rgb_format.decode()) assert pformat is not None, "could not find pixel format for %s" % rgb_format self.gl_marker("%s update at (%d,%d) size %dx%d (%s bytes), using GL %s format=%s", rgb_format, x, y, width, height, len(img_data), upload, CONSTANT_TO_PIXEL_FORMAT.get(pformat)) # Upload data as temporary RGB texture glBindTexture(GL_TEXTURE_RECTANGLE_ARB, self.textures[TEX_RGB]) self.set_alignment(width, rowstride, rgb_format) glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST) glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST) set_texture_level() glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER) glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER) glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, self.texture_pixel_format, width, height, 0, pformat, GL_UNSIGNED_BYTE, img_data) # Draw textured RGB quad at the right coordinates glBegin(GL_QUADS) glTexCoord2i(0, 0) glVertex2i(x, y) glTexCoord2i(0, height) glVertex2i(x, y+height) glTexCoord2i(width, height) glVertex2i(x+width, y+height) glTexCoord2i(width, 0) glVertex2i(x+width, y) glEnd() self.paint_box(options.get("encoding"), options.get("delta", -1)>=0, x, y, width, height) # Present update to screen self.present_fbo(x, y, width, height, options.get("flush", 0)) # present_fbo has reset state already fire_paint_callbacks(callbacks) except Exception as e: log("Error in %s paint of %i bytes, options=%s)", rgb_format, len(img_data), options) fire_paint_callbacks(callbacks, False, "OpenGL %s paint error: %s" % (rgb_format, e))
def draw_region(self, x, y, width, height, coding, img_data, rowstride, _packet_sequence, options, callbacks): """ Note: this runs from the draw thread (not UI thread) """ backing = self._backing if not backing: log("draw_region: window %s has no backing, gone?", self._id) from xpra.client.window_backing_base import fire_paint_callbacks fire_paint_callbacks(callbacks, -1, "no backing") return #only register this callback if we actually need it: if backing.draw_needs_refresh: if not backing.repaint_all: self.pending_refresh.append((x, y, width, height)) if options.intget("flush", 0) == 0: callbacks.append(self.after_draw_refresh) backing.draw_region(x, y, width, height, coding, img_data, rowstride, options, callbacks)
def do_paint_scroll(self, scrolls, callbacks): old_backing = self._backing gc = self.create_surface() if not gc: fire_paint_callbacks(callbacks, False, message="no context") return gc.set_operator(OPERATOR_SOURCE) for sx, sy, sw, sh, xdelta, ydelta in scrolls: gc.set_source_surface(old_backing, xdelta, ydelta) x = sx + xdelta y = sy + ydelta gc.rectangle(x, y, sw, sh) gc.fill() if self.paint_box_line_width > 0: self.cairo_paint_box(gc, "scroll", x, y, sw, sh) del gc self._backing.flush() fire_paint_callbacks(callbacks)
def do_paint_rgb24(self, img_data, x, y, width, height, rowstride, options, callbacks): """ must be called from UI thread """ if DRAW_DEBUG: log.info("cairo_paint_rgb24(..,%s,%s,%s,%s,%s,%s,%s)", x, y, width, height, rowstride, options, callbacks) if self._backing is None: fire_paint_callbacks(callbacks, False) return False assert PIL, "cannot paint without PIL!" if rowstride==0: rowstride = width * 3 im = PIL.Image.frombuffer("RGB", (width, height), img_data, "raw", "RGB", rowstride) buf = BytesIOClass() im.save(buf, "PNG") data = buf.getvalue() buf.close() img_data = BytesIOClass(data) self.do_paint_png(img_data, x, y, width, height, rowstride, options, callbacks) return False
def draw_region(self, x, y, width, height, coding, img_data, rowstride, packet_sequence, options, callbacks): """ Note: this runs from the draw thread (not UI thread) """ backing = self._backing if not backing: log("draw_region: window %s has no backing, gone?", self._id) from xpra.client.window_backing_base import fire_paint_callbacks fire_paint_callbacks(callbacks, False) return def after_draw_refresh(success): plog("after_draw_refresh(%s) %sx%s at %sx%s encoding=%s, options=%s", success, width, height, x, y, coding, options) if not success: return backing = self._backing if backing and backing.draw_needs_refresh: self.queue_draw(x, y, width, height) #only register this callback if we actually need it: if backing.draw_needs_refresh: callbacks.append(after_draw_refresh) self._backing.draw_region(x, y, width, height, coding, img_data, rowstride, options, callbacks)
def do_paint_rgb24(self, img_data, x, y, width, height, rowstride, options, callbacks): """ must be called from UI thread """ if DRAW_DEBUG: log.info("cairo_paint_rgb24(..,%s,%s,%s,%s,%s,%s,%s)", x, y, width, height, rowstride, options, callbacks) if self._backing is None: fire_paint_callbacks(callbacks, False) return False PIL = get_codec("PIL") assert PIL, "cannot paint without PIL!" if rowstride==0: rowstride = width * 3 im = PIL.Image.frombuffer("RGB", (width, height), img_data, "raw", "RGB", rowstride) buf = BytesIOClass() im.save(buf, "PNG") data = buf.getvalue() buf.close() img_data = BytesIOClass(data) self.do_paint_png(img_data, x, y, width, height, rowstride, options, callbacks) return False
def ui_paint_image(): if not self._backing: fire_paint_callbacks(callbacks, False) return try: if coding.startswith("png"): reader = BytesIOClass(img_data) img = cairo.ImageSurface.create_from_png(reader) success = self.cairo_paint_surface(img, x, y) else: assert coding == "jpeg" pbl = PixbufLoader() pbl.write(img_data) pbl.close() pixbuf = pbl.get_pixbuf() del pbl success = self.cairo_paint_pixbuf(pixbuf, x, y) except: log.error("cairo error during paint", exc_info=True) success = False fire_paint_callbacks(callbacks, success)
def ui_paint_image(): if not self._backing: fire_paint_callbacks(callbacks, False) return try: if coding.startswith("png"): reader = BytesIOClass(img_data) img = cairo.ImageSurface.create_from_png(reader) success = self.cairo_paint_surface(img, x, y) else: assert coding == "jpeg" pbl = PixbufLoader() pbl.write(img_data) pbl.close() pixbuf = pbl.get_pixbuf() del pbl success = self.cairo_paint_pixbuf(pixbuf, x, y) except: log.error("cairo error during paint", exc_info=True) success = False fire_paint_callbacks(callbacks, success)
def gl_paint_planar(self, flush, encoding, img, x, y, enc_width, enc_height, width, height, callbacks): #this function runs in the UI thread, no video_decoder lock held log("gl_paint_planar%s", (flush, encoding, img, x, y, enc_width, enc_height, width, height, callbacks)) try: pixel_format = img.get_pixel_format() assert pixel_format in ("YUV420P", "YUV422P", "YUV444P", "GBRP"), "sorry the GL backing does not handle pixel format '%s' yet!" % (pixel_format) context = self.gl_context() if not context: log("%s._do_paint_rgb(..) no context!", self) fire_paint_callbacks(callbacks, False, "failed to get a gl context") return with context: self.gl_init() self.update_planar_textures(x, y, enc_width, enc_height, img, pixel_format, scaling=(enc_width!=width or enc_height!=height)) img.free() # Update FBO texture x_scale, y_scale = 1, 1 if width!=enc_width or height!=enc_height: x_scale = float(width)/enc_width y_scale = float(height)/enc_height self.render_planar_update(x, y, enc_width, enc_height, x_scale, y_scale) self.paint_box(encoding, False, x, y, width, height) # Present it on screen self.present_fbo(x, y, width, height, flush) fire_paint_callbacks(callbacks, True) return except GLError as e: message = "gl_paint_planar error: %r" % e except Exception as e: message = "gl_paint_planar error: %s" % e log.error("%s.gl_paint_planar(..) error: %s", self, e, exc_info=True) fire_paint_callbacks(callbacks, False, message)
def gl_paint_planar(self, flush, encoding, img, x, y, enc_width, enc_height, width, height, callbacks): #this function runs in the UI thread, no video_decoder lock held log("gl_paint_planar%s", (flush, encoding, img, x, y, enc_width, enc_height, width, height, callbacks)) try: pixel_format = img.get_pixel_format() assert pixel_format in ("YUV420P", "YUV422P", "YUV444P", "GBRP"), "sorry the GL backing does not handle pixel format '%s' yet!" % (pixel_format) context = self.gl_context() if not context: log("%s._do_paint_rgb(..) no context!", self) fire_paint_callbacks(callbacks, False, "failed to get a gl context") return with context: self.gl_init() self.update_planar_textures(x, y, enc_width, enc_height, img, pixel_format, scaling=(enc_width!=width or enc_height!=height)) # Update FBO texture x_scale, y_scale = 1, 1 if width!=enc_width or height!=enc_height: x_scale = float(width)/enc_width y_scale = float(height)/enc_height self.render_planar_update(x, y, enc_width, enc_height, x_scale, y_scale) self.paint_box(encoding, False, x, y, width, height) # Present it on screen self.present_fbo(x, y, width, height, flush) img.free() fire_paint_callbacks(callbacks, True) return except GLError as e: message = "OpenGL %s paint error: %r" % (encoding, e) except Exception as e: message = "OpenGL %s paint error: %s" % (encoding, e) log.error("%s.gl_paint_planar(..) error: %s", self, e, exc_info=True) fire_paint_callbacks(callbacks, False, message)
def paint_pixbuf_gdk(self, coding, img_data, x, y, width, height, options, callbacks): """ must be called from UI thread """ if coding.startswith("png"): coding = "png" loader = gdk.PixbufLoader(coding) loader.write(img_data, len(img_data)) loader.close() pixbuf = loader.get_pixbuf() if not pixbuf: log.error("failed %s pixbuf=%s data len=%s" % (coding, pixbuf, len(img_data))) fire_paint_callbacks(callbacks, False) return False raw_data = pixbuf.get_pixels() rowstride = pixbuf.get_rowstride() img_data = self.process_delta(raw_data, width, height, rowstride, options) n = pixbuf.get_n_channels() if n == 3: self.do_paint_rgb24(img_data, x, y, width, height, rowstride, options, callbacks) else: assert n == 4, "invalid number of channels: %s" % n self.do_paint_rgb32(img_data, x, y, width, height, rowstride, options, callbacks) return False
def draw_region(self, x, y, width, height, coding, img_data, rowstride, packet_sequence, options, callbacks): """ Note: this runs from the draw thread (not UI thread) """ backing = self._backing if not backing: log("draw_region: window %s has no backing, gone?", self._id) from xpra.client.window_backing_base import fire_paint_callbacks fire_paint_callbacks(callbacks, -1, "no backing") return def after_draw_refresh(success, message=""): plog("after_draw_refresh(%s, %s) %sx%s at %sx%s encoding=%s, options=%s", success, message, width, height, x, y, coding, options) if success<=0: return backing = self._backing if backing and backing.draw_needs_refresh: if REPAINT_ALL=="1" or self._client.xscale!=1 or self._client.yscale!=1: w, h = self.get_size() self.queue_draw(0, 0, w, h) else: self.queue_draw(*self._client.srect(x, y, width, height)) #only register this callback if we actually need it: if backing.draw_needs_refresh: callbacks.append(after_draw_refresh) backing.draw_region(x, y, width, height, coding, img_data, rowstride, options, callbacks)
def do_paint_rgb(self, rgb_format, img_data, x, y, width, height, rowstride, options, callbacks): log( "%s.do_paint_rgb(%s, %s bytes, x=%d, y=%d, width=%d, height=%d, rowstride=%d, options=%s)", self, rgb_format, len(img_data), x, y, width, height, rowstride, options) context = self.gl_context() if not context: log("%s._do_paint_rgb(..) no context!", self) fire_paint_callbacks(callbacks, False, "no opengl context") return if not options.get("paint", True): fire_paint_callbacks(callbacks) return try: upload, img_data = self.pixels_for_upload(img_data) with context: self.gl_init() self.set_rgb_paint_state() #convert it to a GL constant: pformat = PIXEL_FORMAT_TO_CONSTANT.get(rgb_format.decode()) assert pformat is not None, "could not find pixel format for %s" % rgb_format bytes_per_pixel = len(rgb_format) #ie: BGRX -> 4 # Compute alignment and row length row_length = 0 alignment = 1 for a in [2, 4, 8]: # Check if we are a-aligned - ! (var & 0x1) means 2-aligned or better, 0x3 - 4-aligned and so on if (rowstride & a - 1) == 0: alignment = a # If number of extra bytes is greater than the alignment value, # then we also have to set row_length # Otherwise it remains at 0 (= width implicitely) if (rowstride - width * bytes_per_pixel) >= alignment: row_length = width + ( rowstride - width * bytes_per_pixel) // bytes_per_pixel self.gl_marker( "%s update at (%d,%d) size %dx%d (%s bytes), stride=%d, row length %d, alignment %d, using GL %s format=%s", rgb_format, x, y, width, height, len(img_data), rowstride, row_length, alignment, upload, CONSTANT_TO_PIXEL_FORMAT.get(pformat)) # Upload data as temporary RGB texture glBindTexture(GL_TEXTURE_RECTANGLE_ARB, self.textures[TEX_RGB]) glPixelStorei(GL_UNPACK_ROW_LENGTH, row_length) glPixelStorei(GL_UNPACK_ALIGNMENT, alignment) glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST) glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST) set_texture_level() glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER) glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER) glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, self.texture_pixel_format, width, height, 0, pformat, GL_UNSIGNED_BYTE, img_data) # Draw textured RGB quad at the right coordinates glBegin(GL_QUADS) glTexCoord2i(0, 0) glVertex2i(x, y) glTexCoord2i(0, height) glVertex2i(x, y + height) glTexCoord2i(width, height) glVertex2i(x + width, y + height) glTexCoord2i(width, 0) glVertex2i(x + width, y) glEnd() self.paint_box(options.get("encoding"), options.get("delta", -1) >= 0, x, y, width, height) # Present update to screen self.present_fbo(x, y, width, height, options.get("flush", 0)) # present_fbo has reset state already fire_paint_callbacks(callbacks) except Exception as e: log("Error in %s paint of %i bytes, options=%s)", rgb_format, len(img_data), options) fire_paint_callbacks(callbacks, False, "opengl %s paint error: %s" % (rgb_format, e))
def do_paint_scroll(self, x, y, w, h, scrolls, options, callbacks): gc = self._backing.new_gc() for sx,sy,sw,sh,xdelta,ydelta in scrolls: self._backing.draw_drawable(gc, self._backing, sx, sy, sx+xdelta, sy+ydelta, sw, sh) fire_paint_callbacks(callbacks)
def do_paint_scroll(self, x, y, w, h, scrolls, options, callbacks): gc = self._backing.new_gc() for sx,sy,sw,sh,xdelta,ydelta in scrolls: self._backing.draw_drawable(gc, self._backing, sx, sy, sx+xdelta, sy+ydelta, sw, sh) fire_paint_callbacks(callbacks)
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)
def fail(msg): log.error("Error: %s", msg) fire_paint_callbacks(callbacks, False, msg)
def fail(msg): log.error("Error: %s", msg) fire_paint_callbacks(callbacks, False, msg)
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)
def do_paint_rgb(self, rgb_format, img_data, x, y, width, height, rowstride, options, callbacks): log("%s.do_paint_rgb(%s, %s bytes, x=%d, y=%d, width=%d, height=%d, rowstride=%d, options=%s)", self, rgb_format, len(img_data), x, y, width, height, rowstride, options) context = self.gl_context() if not context: log("%s._do_paint_rgb(..) no context!", self) fire_paint_callbacks(callbacks, False, "no opengl context") return if not options.get("paint", True): fire_paint_callbacks(callbacks) return try: upload, img_data = self.pixels_for_upload(img_data) with context: self.gl_init() self.set_rgb_paint_state() #convert it to a GL constant: pformat = PIXEL_FORMAT_TO_CONSTANT.get(rgb_format.decode()) assert pformat is not None, "could not find pixel format for %s" % rgb_format bytes_per_pixel = len(rgb_format) #ie: BGRX -> 4 # Compute alignment and row length row_length = 0 alignment = 1 for a in [2, 4, 8]: # Check if we are a-aligned - ! (var & 0x1) means 2-aligned or better, 0x3 - 4-aligned and so on if (rowstride & a-1) == 0: alignment = a # If number of extra bytes is greater than the alignment value, # then we also have to set row_length # Otherwise it remains at 0 (= width implicitely) if (rowstride - width * bytes_per_pixel) >= alignment: row_length = width + (rowstride - width * bytes_per_pixel) // bytes_per_pixel self.gl_marker("%s update at (%d,%d) size %dx%d (%s bytes), stride=%d, row length %d, alignment %d, using GL %s format=%s", rgb_format, x, y, width, height, len(img_data), rowstride, row_length, alignment, upload, CONSTANT_TO_PIXEL_FORMAT.get(pformat)) # Upload data as temporary RGB texture glBindTexture(GL_TEXTURE_RECTANGLE_ARB, self.textures[TEX_RGB]) glPixelStorei(GL_UNPACK_ROW_LENGTH, row_length) glPixelStorei(GL_UNPACK_ALIGNMENT, alignment) glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST) glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST) set_texture_level() glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER) glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER) glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, self.texture_pixel_format, width, height, 0, pformat, GL_UNSIGNED_BYTE, img_data) # Draw textured RGB quad at the right coordinates glBegin(GL_QUADS) glTexCoord2i(0, 0) glVertex2i(x, y) glTexCoord2i(0, height) glVertex2i(x, y+height) glTexCoord2i(width, height) glVertex2i(x+width, y+height) glTexCoord2i(width, 0) glVertex2i(x+width, y) glEnd() self.paint_box(options.get("encoding"), options.get("delta", -1)>=0, x, y, width, height) # Present update to screen self.present_fbo(x, y, width, height, options.get("flush", 0)) # present_fbo has reset state already fire_paint_callbacks(callbacks) except Exception as e: log("Error in %s paint of %i bytes, options=%s)", rgb_format, len(img_data), options) fire_paint_callbacks(callbacks, False, "OpenGL %s paint error: %s" % (rgb_format, e))
def do_paint_rgb(self, rgb_format, img_data, x, y, width, height, rowstride, options, callbacks): log("%s.do_paint_rgb(%s, %s bytes, x=%d, y=%d, width=%d, height=%d, rowstride=%d, options=%s)", self, rgb_format, len(img_data), x, y, width, height, rowstride, options) context = self.gl_context() if not context: log("%s._do_paint_rgb(..) no context!", self) fire_paint_callbacks(callbacks, False, "no opengl context") return if not options.boolget("paint", True): fire_paint_callbacks(callbacks) return try: rgb_format = rgb_format.decode() except: pass try: upload, img_data = self.pixels_for_upload(img_data) with context: self.gl_init() #convert it to a GL constant: pformat = PIXEL_FORMAT_TO_CONSTANT.get(rgb_format) assert pformat is not None, "could not find pixel format for %s" % rgb_format ptype = PIXEL_FORMAT_TO_DATATYPE.get(rgb_format) assert pformat is not None, "could not find pixel type for %s" % rgb_format self.gl_marker("%s update at (%d,%d) size %dx%d (%s bytes), using GL %s format=%s / %s to internal format=%s", rgb_format, x, y, width, height, len(img_data), upload, CONSTANT_TO_PIXEL_FORMAT.get(pformat), DATATYPE_TO_STR.get(ptype), INTERNAL_FORMAT_TO_STR.get(self.internal_format)) # Upload data as temporary RGB texture target = GL_TEXTURE_RECTANGLE_ARB glEnable(target) glBindTexture(target, self.textures[TEX_RGB]) self.set_alignment(width, rowstride, rgb_format) glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST) glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST) glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER) glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER) glTexImage2D(target, 0, self.internal_format, width, height, 0, pformat, ptype, img_data) # Draw textured RGB quad at the right coordinates glBegin(GL_QUADS) glTexCoord2i(0, 0) glVertex2i(x, y) glTexCoord2i(0, height) glVertex2i(x, y+height) glTexCoord2i(width, height) glVertex2i(x+width, y+height) glTexCoord2i(width, 0) glVertex2i(x+width, y) glEnd() glBindTexture(target, 0) glDisable(target) self.paint_box(options.strget("encoding"), options.intget("delta", -1)>=0, x, y, width, height) fire_paint_callbacks(callbacks) # Present update to screen self.present_fbo(x, y, width, height, options.intget("flush", 0)) # present_fbo has reset state already return except GLError as e: message = "OpenGL %s paint failed: %r" % (rgb_format, e) log("Error in %s paint of %i bytes, options=%s", rgb_format, len(img_data), options, exc_info=True) except Exception as e: message = "OpenGL %s paint error: %s" % (rgb_format, e) log("Error in %s paint of %i bytes, options=%s", rgb_format, len(img_data), options, exc_info=True) fire_paint_callbacks(callbacks, False, message)