Skip to content

Commit

Permalink
#756: multi delta support:
Browse files Browse the repository at this point in the history
* 5 buckets by default (single bucket for older clients - as before)
* added to xpra info
* renamed "last_pixmap_data" to "delta_pixel_data"

git-svn-id: https://xpra.org/svn/Xpra/trunk@8285 3bb7dfac-3a0b-4e04-842a-767bc560f471
  • Loading branch information
totaam committed Dec 25, 2014
1 parent 8ef289a commit f28b4cf
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 24 deletions.
2 changes: 2 additions & 0 deletions src/xpra/client/gtk_base/gtk_client_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,8 @@ def make_hello(self):
#window icon bits
capabilities["encoding.icons.size"] = 64, 64 #size we want
capabilities["encoding.icons.max_size"] = 128, 128 #limit
from xpra.client.window_backing_base import DELTA_BUCKETS
capabilities["encoding.delta_buckets"] = DELTA_BUCKETS
return capabilities


Expand Down
17 changes: 11 additions & 6 deletions src/xpra/client/window_backing_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
# later version. See the file COPYING for details.

import os
from xpra.log import Logger
log = Logger("paint")

Expand All @@ -18,6 +19,8 @@
from xpra.codecs.xor.cyxor import xor_str #@UnresolvedImport
from xpra.codecs.argb.argb import unpremultiply_argb, unpremultiply_argb_in_place #@UnresolvedImport

DELTA_BUCKETS = int(os.environ.get("XPRA_DELTA_BUCKETS", "5"))

PIL = get_codec("PIL")

#ie:
Expand Down Expand Up @@ -73,7 +76,7 @@ def __init__(self, wid, window_alpha, idle_add):
self.idle_add = idle_add
self._alpha_enabled = window_alpha
self._backing = None
self._last_pixmap_data = None
self._delta_pixel_data = [None for _ in xrange(DELTA_BUCKETS)]
self._video_decoder = None
self._csc_decoder = None
self._decoder_lock = Lock()
Expand Down Expand Up @@ -170,17 +173,19 @@ def process_delta(self, raw_data, width, height, rowstride, options):
raise Exception("expected %s bytes for %sx%s with rowstride=%s but received %s (%s compressed)" %
(rowstride * height, width, height, rowstride, len(img_data), len(raw_data)))
delta = options.intget("delta", -1)
bucket = options.intget("bucket", 0)
rgb_data = img_data
if delta>=0:
if not self._last_pixmap_data:
raise Exception("delta region references pixmap data we do not have!")
lwidth, lheight, store, ldata = self._last_pixmap_data
assert width==lwidth and height==lheight and delta==store
assert bucket>=0 and bucket<DELTA_BUCKETS, "invalid delta bucket number: %s" % bucket
if self._delta_pixel_data[bucket] is None:
raise Exception("delta region bucket %s references pixmap data we do not have!" % bucket)
lwidth, lheight, store, ldata = self._delta_pixel_data[bucket]
assert width==lwidth and height==lheight and delta==store, "delta bucket %s data does not match: expected %s but got %s" % (bucket, (width, height, delta), (lwidth, lheight, store))
rgb_data = xor_str(img_data, ldata)
#store new pixels for next delta:
store = options.intget("store", -1)
if store>=0:
self._last_pixmap_data = width, height, store, rgb_data
self._delta_pixel_data[bucket] = width, height, store, rgb_data
return rgb_data


Expand Down
66 changes: 48 additions & 18 deletions src/xpra/server/window_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
MAX_PIXELS_PREFER_RGB = 4096

DELTA = os.environ.get("XPRA_DELTA", "1")=="1"
MAX_DELTA_SIZE = int(os.environ.get("XPRA_MAX_DELTA_SIZE", "10000"))
MAX_DELTA_SIZE = int(os.environ.get("XPRA_MAX_DELTA_SIZE", "32768"))
HAS_ALPHA = os.environ.get("XPRA_ALPHA", "1")=="1"
FORCE_BATCH = os.environ.get("XPRA_FORCE_BATCH", "0")=="1"
STRICT_MODE = os.environ.get("XPRA_ENCODING_STRICT_MODE", "0")=="1"
Expand Down Expand Up @@ -110,6 +110,9 @@ def __init__(self, queue_size, call_in_encode_thread, queue_packet, compressed_w
self.supports_delta = []
if not window.is_tray():
self.supports_delta = [x for x in encoding_options.strlistget("supports_delta", []) if x in ("png", "rgb24", "rgb32")]
if self.supports_delta:
self.delta_buckets = encoding_options.intget("delta_buckets", 1)
self.delta_pixel_data = [None for _ in range(self.delta_buckets)]
self.batch_config = batch_config
#auto-refresh:
self.auto_refresh_delay = auto_refresh_delay
Expand Down Expand Up @@ -205,7 +208,8 @@ def init_vars(self):
self.supports_transparency = False
self.full_frames_only = False
self.supports_delta = []
self.last_pixmap_data = None
self.delta_buckets = 0
self.delta_pixel_data = []
self.suspended = False
self.strict = STRICT_MODE
#
Expand All @@ -220,7 +224,7 @@ def init_vars(self):
self.soft_timer = None
self.soft_expired = 0
self.max_soft_expired = 5
self.min_delta_size = 512
self.min_delta_size = 1024
self.max_delta_size = MAX_DELTA_SIZE
self.is_OR = False
self.is_tray = False
Expand Down Expand Up @@ -287,7 +291,14 @@ def up(prefix, d):
"last_used" : self.encoding_last_used or "",
"full-frames-only" : self.full_frames_only,
"supports-transparency" : self.supports_transparency,
"delta" : self.supports_delta,
"delta.buckets" : self.delta_buckets,
})
now = time.time()
for i,x in enumerate(self.delta_pixel_data):
if x:
w, h, coding, store, dpixels, last_used = x
info["encoding.delta.bucket[%s]" % i] = w, h, coding, store, len(dpixels), int((now-last_used)*1000)
up("encoding", self.get_quality_speed_info())
try:
#ie: get_strict_encoding -> "strict_encoding"
Expand Down Expand Up @@ -490,7 +501,7 @@ def set_new_encoding(self, encoding, strict):
if self.encoding==encoding:
return
self.statistics.reset()
self.last_pixmap_data = None
self.delta_pixel_data = [None for _ in range(self.delta_buckets)]
self.update_encoding_selection(encoding)


Expand Down Expand Up @@ -623,7 +634,7 @@ def cancel_damage(self):
self.refresh_regions = []
self._damage_delayed = None
self._damage_delayed_expired = False
self.last_pixmap_data = None
self.delta_pixel_data = [None for _ in range(self.delta_buckets)]
#make sure we don't account for those as they will get dropped
#(generally before encoding - only one may still get encoded):
for sequence in self.statistics.encoding_pending.keys():
Expand Down Expand Up @@ -1383,7 +1394,7 @@ def damage_packet_acked(self, damage_packet_sequence, width, height, decode_time
def client_decode_error(self, error):
self.global_statistics.decode_errors += 1
#something failed client-side, so we can't rely on the delta being available
self.last_pixmap_data = None
self.delta_pixel_data = [None for _ in range(self.delta_buckets)]


def make_data_packet(self, damage_time, process_damage_time, wid, image, coding, sequence, options):
Expand Down Expand Up @@ -1422,22 +1433,25 @@ def make_data_packet(self, damage_time, process_damage_time, wid, image, coding,
options["mmap_data"] = data

#if client supports delta pre-compression for this encoding, use it if we can:
delta = -1
store = -1
isize = image.get_width() * image.get_height()
if DELTA and w>2 and h>2 and not (self._mmap and self._mmap_size>0) and (coding in self.supports_delta) and self.min_delta_size<isize<self.max_delta_size:
#we need to copy the pixels because some delta encodings
#will modify the pixel array in-place!
delta, store, bucket = -1, -1, -1
isize = image.get_width()*image.get_height()
if DELTA and not (self._mmap and self._mmap_size>0) and self.delta_buckets>0 and (coding in self.supports_delta) and self.min_delta_size<isize<self.max_delta_size:
#we need to copy the pixels because some encodings
#may modify the pixel array in-place!
dpixels = image.get_pixels()[:]
store = sequence
lpd = self.last_pixmap_data
if lpd is not None:
lw, lh, lcoding, lsequence, ldata = lpd
for i, dr in enumerate(list(self.delta_pixel_data)):
if dr is None:
continue
lw, lh, lcoding, lsequence, ldata, _ = dr
if lw==w and lh==h and lcoding==coding and len(ldata)==len(dpixels):
#xor with the last frame:
#xor with this matching delta bucket:
delta = lsequence
bucket = i
data = xor_str(dpixels, ldata)
image.set_pixels(data)
dr[-1] = time.time() #update last used time
break

#by default, don't set rowstride (the container format will take care of providing it):
encoder = self._encoders.get(coding)
Expand All @@ -1460,17 +1474,33 @@ def make_data_packet(self, damage_time, process_damage_time, wid, image, coding,
#tell client about delta/store for this pixmap:
if delta>=0:
client_options["delta"] = delta
client_options["bucket"] = bucket
csize = len(data)
if store>0:
if delta>0 and csize>=psize/3:
#compressed size is more than 33% of the original
#maybe delta is not helping us, so clear it:
self.last_pixmap_data = None
self.delta_pixel_data[bucket] = None
#TODO: could tell the clients they can clear it too
#(add a new client capability and send it a zero store value)
else:
self.last_pixmap_data = w, h, coding, store, dpixels
#find the bucket to use:
if bucket<0:
lpd = self.delta_pixel_data
try:
bucket = lpd.index(None)
except:
#find a bucket which has not been used recently
t = 0
bucket = 0
for i,dr in enumerate(lpd):
if dr and (t==0 or dr[-1]<t):
t = dr[-1]
bucket = i
self.delta_pixel_data[bucket] = [w, h, coding, store, dpixels, time.time()]
client_options["store"] = store
client_options["bucket"] = bucket
log("delta client options: %s (for region %s)", client_options, (x, y, w, h))
encoding = coding
if not self.generic_encodings:
#old clients use non-generic encoding names:
Expand Down

0 comments on commit f28b4cf

Please sign in to comment.