Ticket #1426: scroll-cythonized-v3.patch
File scroll-cythonized-v3.patch, 34.5 KB (added by , 4 years ago) |
---|
-
unittests/unit/server/motion_test.py
1 1 #!/usr/bin/env python 2 2 # This file is part of Xpra. 3 # Copyright (C) 201 1-2014Antoine Martin <antoine@devloop.org.uk>3 # Copyright (C) 2016-2017 Antoine Martin <antoine@devloop.org.uk> 4 4 # Xpra is released under the terms of the GNU GPL v2, or, at your option, any 5 5 # later version. See the file COPYING for details. 6 6 … … 20 20 21 21 class TestMotion(unittest.TestCase): 22 22 23 def calculate_distances(self, array1, array2, min_hits=2, max_distance=1): 24 sd = motion.scroll_distances(array1, array2, max_distance) 25 return sd.get_best_scroll_values(min_hits) 26 23 27 def test_match_distance(self): 24 28 def t(a1, a2, distance, matches): 25 lines = motion.match_distance(a1, a2, distance) 26 assert len(lines)==matches, "expected %i matches for distance=%i but got %i for a1=%s, a2=%s, result=%s" % (matches, distance, len(lines), a1, a2, lines) 29 sd = motion.scroll_distances(a1, a2) 30 line_defs = sd.match_distance(distance) 31 linecount = sum(line_defs.values()) 32 assert linecount==matches, "expected %i matches for distance=%i but got %i for a1=%s, a2=%s, result=%s" % (matches, distance, linecount, a1, a2, line_defs) 27 33 for N in (1, 10, 100): 28 a = range( N)34 a = range(1, N+1) 29 35 t(a, a, 0, N) #identity: all match 30 36 31 a = [ 0]*N37 a = [1]*N 32 38 t(a, a, 0, N) 33 39 for M in range(N): 34 40 t(a, a, M, N-M) 35 41 42 #from a1 to a2: shift by 2, get 2 hits 43 t([3, 4, 5, 6], [1, 2, 3, 4], 2, 2) 36 44 #from a2 to a1: shift by -2, get 2 hits 37 t([ 0, 1, 2, 3], [2, 3, 4, 5], -2, 2)45 t([1, 2, 3, 4], [3, 4, 5, 6], -2, 2) 38 46 N = 100 39 a1 = range(N) 40 for M in (1, 2, 10, 100): 47 S = 1 48 a1 = range(S, S+N) 49 for M in (2, 3, 10, 100): 41 50 a2 = range(M, M+N) 42 t(a1, a2, -M, N-abs(M)) 43 a2 = range(-M, N-M) 44 t(a1, a2, M, N-abs(M)) 45 for M in (-10, -20): 46 a2 = range(M, N+M) 47 t(a1, a2, -M, N-abs(M)) 51 t(a1, a2, S-M, S+N-M) 52 t(a2, a1, M-S, S+N-M) 48 53 49 def test_consecutive_lines(self):50 def f(v):51 try:52 motion.consecutive_lines(v)53 except:54 pass55 else:56 raise Exception("consecutive_lines should have failed for value %s" % v)57 f(None)58 f("")59 f([])60 f(1)61 def t(v, e):62 r = motion.consecutive_lines(v)63 assert r==e, "expected %s but got %s for input=%s" % (e, r, v)64 t([5, 10], [(5, 1), (10, 1)])65 t([5], [(5, 1)])66 t([1,2,3], [(1,3)])67 t(range(100), [(0, 100),])68 t([1,2,3,100,200,201], [(1,3), (100, 1), (200, 2)])69 70 54 def test_calculate_distances(self): 71 55 array1 = [crc32(str(x)) for x in (1234, "abc", 99999)] 72 array2 = array1 73 d = motion.calculate_distances(array1, array2, 1)74 assert len(d)==1 and d[0]==356 array2 = array1[:] 57 d = self.calculate_distances(array1, array2, 1) 58 assert len(d)==1 and sum(d[0].values())==len(array1), "expected %i matches but got %s" % (len(array1), d[0]) 75 59 76 array1 = range(0, 3) 77 array2 = range(1, 4) 78 d = motion.calculate_distances(array1, array2, 1) 79 assert len(d)==1, "expected 1 match but got: %s" % d 80 assert d.get(1)==2, "expected distance of 1 with 2 hits but got: %s" % d 60 array1 = range(1, 5) 61 array2 = range(2, 6) 62 d = self.calculate_distances(array1, array2) 63 assert len(d)==1, "expected 1 match but got: %s" % len(d) 64 common = set(array1).intersection(set(array2)) 65 assert -1 in d, "expected distance of -1 but got: %s" % d.keys() 66 linecount = sum(d.get(-1, {}).values()) 67 assert linecount==len(common), "expected %i hits but got: %s" % (len(common), linecount) 81 68 def cdf(v1, v2): 82 69 try: 83 motion.calculate_distances(v1, v2, 1)70 self.calculate_distances(v1, v2, 1) 84 71 except: 85 72 return 86 73 raise Exception("calculate_distances should have failed for values: %s" % (v1, v2)) … … 88 75 cdf([], None) 89 76 cdf(None, []) 90 77 cdf([1, 2], [1]) 91 assert len(motion.calculate_distances([], [], 1))==092 78 93 79 #performance: 94 80 N = 4096 … … 95 81 start = time.time() 96 82 array1 = range(N) 97 83 array2 = [N*2-x*2 for x in range(N)] 98 d = motion.calculate_distances(array1, array2, 1)84 d = self.calculate_distances(array1, array2, 1) 99 85 end = time.time() 100 86 if SHOW_PERF: 101 87 print("calculate_distances %4i^2 in %5.1f ms" % (N, (end-start)*1000)) … … 137 123 print("\nCRC_Image %ix%i (%.1fMB) in %4.2f ms" % (W, H, len(buf2)//1024//1024, 1000.0*(end-start))) 138 124 assert len(ov2)==H 139 125 start = time.time() 140 distances = motion.calculate_distances(ov1, ov2, min_score=1)126 distances = self.calculate_distances(ov1, ov2, min_hits=1) 141 127 end = time.time() 142 128 if SHOW_PERF: 143 129 print("calculate_distances %4i^2 in %5.2f ms" % (H, 1000.0*(end-start))) 144 linecount = distances.get(N, 0) 130 line_defs = distances.get(-N, {}) 131 linecount = sum(line_defs.values()) 145 132 assert linecount>0, "could not find distance %i" % N 146 133 assert linecount == (H-N), "expected to match %i lines but got %i" % (H-N, linecount) 147 134 if False: … … 154 141 print("ov1:\n%s" % (ov1, )) 155 142 print("ov2:\n%s" % (ov2, )) 156 143 144 def test_csum_data(self): 145 a1=[ 146 5992220345606009987L, 15040563112965825180L, 420530012284267555L, 3380071419019115782L, 14243596304267993264L, 834861281570233459L, 10803583843784306120L, 1379296002677236226L, 147 11874402007024898787L, 18061820378193118025L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 148 14669207905734636057L, 14669207905734636057L, 6048597477520792617L, 2736806572525204051L, 16630099595908746458L, 10194355114249600963L, 16726784880639428445L, 10866892264854763364L, 149 6367321356510949102L, 16626509354687956371L, 6309605599425761357L, 6893409879058778343L, 5414245501850544038L, 10339135854757169820L, 8701041795744152980L, 3604633436491088815L, 150 9865399393235410477L, 10031306284568036792L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 151 14669207905734636057L, 14669207905734636057L, 11266963446837574547L, 17157005122993541799L, 5218869126146608853L, 13274228147453099388L, 16342723934713827717L, 2435034235422505275L, 152 3689766606612767057L, 13721141386368216492L, 14859793948180065358L, 6883776362280179367L, 14582348771255332968L, 15418692344756373599L, 10241123668249748621L, 197976484773286461L, 153 14610077842739908751L, 9629342716869811747L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 154 14669207905734636057L, 14669207905734636057L, 6301677547777858738L, 13481745547040629090L, 11082728931134194933L, 3515047519092751608L, 17530992646520472518L, 11525573497958613731L, 155 6186650688264051723L, 10053681394182111520L, 7507461626261938488L, 3136410141592758381L, 18320341500820189028L, 7224279069641644876L, 76220613438872403L, 12174575413544881100L, 156 7769327179604108765L, 4993163530803732307L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 157 14669207905734636057L, 14669207905734636057L, 1011212212406598056L, 12369511552952147752L, 17332471340354818353L, 5562967289984763417L, 7276816103432910616L, 9095502394548196500L, 158 3966866363266810705L, 15115893782344445994L, 2470115778756702218L, 11300572931034497831L, 13356453083734411092L, 12682463388000998283L, 12461900100761490812L, 16565659067973398797L, 159 16700371844333341655L, 13475749720883007409L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 15095743182479501355L, 16652551598896547263L, 160 18117428461752083731L, 16517651160080181273L, 16482769665263024512L, 16482769665263024512L, 16482769665263024512L, 16482769665263024512L, 16482769665263024512L, 16482769665263024512L, 161 16482769665263024512L, 16482769665263024512L, 16482769665263024512L, 16482769665263024512L, 2620400469557574299L, 7552116755125697612L, 3191732720857892986L, 15697817096682717297L, 162 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 163 ] 164 a2 = [ 165 16517651160080181273, 16482769665263024512, 16482769665263024512, 16482769665263024512, 16482769665263024512, 16482769665263024512, 16482769665263024512, 16482769665263024512, 166 16482769665263024512, 16482769665263024512, 16482769665263024512, 2620400469557574299, 7552116755125697612, 3191732720857892986, 15697817096682717297, 14669207905734636057, 167 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 168 14669207905734636057, 7425237873317005741, 15881577514219781533, 5244943483479698162, 1645884179624549962, 6833306483329956671, 3142507118889544939, 14496593126061659900, 169 4782446320116037220, 11121580325383588737, 5128902123802403342, 14539804846999948736, 3999126996485638007, 6071163207581089360, 275311871111368509, 1419512211527079444, 170 16496147506624837932, 9366935943282992292, 16641602392096942222, 5312414525355881355, 6512670471206739810, 14669207905734636057, 14669207905734636057, 9515221130600033946, 171 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 172 16962147477217322879, 17777684172941730501, 5134598006302276024, 4495650412094508491, 14496320858648784912, 5882882193233282408, 13142401013874562815, 17213868142308207279, 173 5589927236057965940, 4529401611344340209, 3205874171513572790, 9555164747562437240, 14669207905734636057, 14669207905734636057, 14669207905734636057, 9080427549593249618, 174 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 8165205005918492527, 175 13352578771313229684, 11590125678725701957, 2006171165294962460, 5731472049560910928, 7815231195191982982, 5992220345606009987, 15040563112965825180, 420530012284267555, 176 3380071419019115782, 14243596304267993264, 834861281570233459, 10803583843784306120, 1379296002677236226, 11874402007024898787, 18061820378193118025, 14669207905734636057, 177 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 6048597477520792617, 178 2736806572525204051, 16630099595908746458, 10194355114249600963, 16726784880639428445, 10866892264854763364, 6367321356510949102, 16626509354687956371, 6309605599425761357, 179 6893409879058778343, 5414245501850544038, 10339135854757169820, 8701041795744152980, 3604633436491088815, 9865399393235410477, 10031306284568036792, 14669207905734636057, 180 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 11266963446837574547, 181 17157005122993541799, 5218869126146608853, 13274228147453099388, 16342723934713827717, 2435034235422505275, 3689766606612767057, 13721141386368216492, 14859793948180065358, 182 ] 183 #distances = motion.scroll_distances(a1[100:400], a2[100:400], 2, 1000) 184 distances = motion.scroll_distances(a1, a2, 2, 1000) 185 scroll, count = distances.get_best_match() 186 wh = len(a1) 187 print("best match: %s" % ((scroll, count),)) 188 x, y, w, h = 0, 0, 1050, 1151 189 raw_scroll = distances.get_best_scroll_values() 190 #non_scroll = distances.get_remaining_areas() 191 scrolls = [] 192 def h(v): 193 return hex(v).lstrip("0x").rstrip("L") 194 for i in range(wh): 195 print("%2i: %16s %16s" % (i, h(a1[i]), h(a2[i]))) 196 for scroll, line_defs in raw_scroll.items(): 197 if scroll==0: 198 continue 199 for line, count in line_defs.items(): 200 assert y+line+scroll>=0, "cannot scroll rectangle by %i lines from %i+%i" % (scroll, y, line) 201 assert y+line+scroll<=wh, "cannot scroll rectangle %i high by %i lines from %i+%i (window height is %i)" % (count, scroll, y, line, wh) 202 scrolls.append((x, y+line, w, count, 0, scroll)) 203 204 205 157 206 def main(): 158 207 if motion: 159 208 unittest.main() -
xpra/server/window/motion.pyx
9 9 10 10 import os 11 11 import time 12 import collections 12 13 13 14 from xpra.util import envbool 14 15 from xpra.log import Logger 15 log ger = Logger("encoding")16 log = Logger("encoding", "scroll") 16 17 17 from xpra.buffers.membuf cimport memalign 18 from xpra.buffers.membuf cimport object_as_buffer 18 from xpra.buffers.membuf cimport memalign, object_as_buffer 19 19 20 import zlib 20 cdef int DEBUG = envbool("XPRA_SCROLL_DEBUG", False) 21 21 hashfn = None 22 22 if envbool("XPRA_XXHASH", True): 23 23 try: … … 25 25 def hashfn(x): 26 26 return xxhash.xxh64(x).intdigest() 27 27 except ImportError as e: 28 log ger.warn("Warning: xxhash python bindings not found")28 log.warn("Warning: xxhash python bindings not found") 29 29 else: 30 log ger.warn("Warning: xxhash disabled")30 log.warn("Warning: xxhash disabled") 31 31 if hashfn is None: 32 log ger.warn(" no scrolling detection")32 log.warn(" no scrolling detection") 33 33 34 34 35 cdef extern from "math.h": 36 double log(double x) 35 from libc.stdint cimport int32_t, uint8_t, uint16_t, int16_t, uint32_t, int64_t 37 36 38 from libc.stdint cimport int32_t, uint8_t, uint32_t, int64_t39 40 cdef extern from "stdlib.h":41 int abs(int number)42 43 37 cdef extern from "string.h": 44 38 void free(void * ptr) nogil 45 39 void *memset(void * ptr, int value, size_t num) nogil 46 int memcmp(const void *a1, const void *a2, size_t size)47 40 48 41 49 42 def CRC_Image(pixels, unsigned int width, unsigned int height, unsigned int rowstride, unsigned char bpp=4): … … 73 66 #assert v>=0, "invalid int to cast: %s" % v 74 67 return v 75 68 76 def calculate_distances(array1, array2, int min_score=0, int max_distance=1000): 77 #print("calculate_distances(..)") 78 assert len(array1)==len(array2) 79 cdef int l = len(array1) 80 cdef int i, y1, y2, miny, maxy, d 81 #we want fast array access, 82 #so cache both arrays in C arrays: 83 assert sizeof(int64_t)==64//8, "uint64_t is not 64-bit: %i!" % sizeof(int64_t) 84 cdef size_t asize = l*(sizeof(int64_t)) 85 cdef int64_t *a1 = NULL 86 cdef int64_t *a2 = NULL 87 cdef int64_t a2v = 0 88 cdef int32_t *distances = NULL 89 #print("calculate_distances(%s, %s, %i, %i)" % (array1, array2, elen, min_score)) 90 try: 91 a1 = <int64_t*> memalign(asize) 92 a2 = <int64_t*> memalign(asize) 93 assert a1!=NULL and a2!=NULL, "failed to allocate %i bytes of scroll array memory" % asize 94 for i in range(l): 95 a1[i] = castint64(array1[i]) 96 a2[i] = castint64(array2[i]) 69 cdef inline da(int64_t *a, uint16_t l): 70 return [a[i] for i in range(l)] 71 72 cdef inline dd(uint16_t *d, uint16_t l): 73 return [d[i] for i in range(l)] 74 75 cdef inline ds(int16_t *d, uint16_t l): 76 return [d[i] for i in range(l)] 77 78 79 assert sizeof(int64_t)==64//8, "uint64_t is not 64-bit: %i!" % sizeof(int64_t) 80 81 82 cdef class ScrollDistances: 83 84 cdef object __weakref__ 85 #for each distance, keep track of the hit count: 86 cdef uint16_t* distances 87 cdef uint16_t l 88 cdef int64_t *a1 89 cdef int64_t *a2 90 91 def init(self, array1, array2, uint16_t max_distance=1000): 92 assert len(array1)==len(array2) 93 assert len(array1)<2**15 and len(array1)>0, "invalid array length: %i" % len(array1) 94 self.l = len(array1) 95 self.distances = <uint16_t*> memalign(2*self.l*sizeof(uint16_t)) 96 cdef size_t asize = self.l*(sizeof(int64_t)) 97 self.a1 = <int64_t*> memalign(asize) 98 self.a2 = <int64_t*> memalign(asize) 99 assert self.distances!=NULL and self.a1!=NULL and self.a2!=NULL, "scroll memory allocation failed" 100 for i in range(self.l): 101 self.a1[i] = castint64(array1[i]) 102 self.a2[i] = castint64(array2[i]) 97 103 #now compare all the values 98 distances = <int32_t*> memalign(2*l*sizeof(int32_t)) 99 assert distances!=NULL 104 self.calculate(max_distance) 105 106 def __repr__(self): 107 return "ScrollDistances(%i)" % self.l 108 109 cdef calculate(self, uint16_t max_distance=1000): 110 cdef int64_t *a1 = self.a1 111 cdef int64_t *a2 = self.a2 112 cdef uint16_t l = self.l 113 cdef uint16_t y1, y2 114 cdef uint16_t miny=0, maxy=0 115 cdef int64_t a2v 100 116 with nogil: 101 memset( <void*> distances, 0, 2*l*sizeof(int32_t))117 memset(self.distances, 0, 2*self.l*sizeof(uint16_t)) 102 118 for y2 in range(l): 103 miny = max(0, y2-max_distance) 104 maxy = min(l, y2+max_distance) 119 #miny = max(0, y2-max_distance): 120 if y2>max_distance: 121 miny = y2-max_distance 122 else: 123 miny = 0 124 #maxy = min(l, y2+max_distance) 125 if y2+max_distance<l: 126 maxy = y2+max_distance 127 else: 128 maxy = l 105 129 a2v = a2[y2] 106 130 if a2v==0: 107 131 continue … … 108 132 for y1 in range(miny, maxy): 109 133 if a1[y1]==a2v: 110 134 #distance = y1-y2 111 distances[l+y1-y2] += 1 112 r = {} 113 for i in range(2*l): 114 d = distances[i] 115 if abs(d)>=min_score: 116 r[i-l] = d 117 return r 118 finally: 119 if a1!=NULL: 120 free(a1) 121 if a2!=NULL: 122 free(a2) 123 if distances!=NULL: 124 free(distances) 135 self.distances[l-(y1-y2)] += 1 136 if DEBUG: 137 log("ScrollDistance: l=%i, calculate(%s, %s, %i)=%s", self.l, da(self.a1, self.l), da(self.a2, self.l), max_distance, dd(self.distances, self.l*2)) 125 138 126 def match_distance(array1, array2, int distance): 127 assert len(array1)==len(array2) 128 l = len(array1) 129 if distance>=0: 130 return [i for i,v in enumerate(array1) if (i+distance)<l and array2[i+distance]==v] 131 distance = abs(distance) 132 return [i+distance for i,v in enumerate(array2) if (i+distance)<l and array1[i+distance]==v] 139 def get_best_scroll_values(self, uint16_t min_hits=2): 140 DEF MAX_MATCHES = 20 141 cdef uint16_t m_arr[MAX_MATCHES] #number of hits 142 cdef int16_t s_arr[MAX_MATCHES] #scroll distance 143 cdef int16_t i 144 cdef uint8_t j 145 memset(m_arr, 0, MAX_MATCHES*sizeof(uint16_t)) 146 memset(s_arr, 0, MAX_MATCHES*sizeof(int16_t)) 147 cdef int16_t low = 0 148 cdef int16_t matches 149 cdef uint16_t* distances = self.distances 150 cdef uint16_t l = self.l 151 with nogil: 152 for i in range(2*l): 153 matches = distances[i] 154 if matches>low and matches>min_hits: 155 #add this candidate match to the arrays: 156 for j in range(MAX_MATCHES): 157 if m_arr[j]==low: 158 break 159 m_arr[j] = matches 160 s_arr[j] = i-l 161 #find the new lowest value: 162 low = matches 163 for j in range(MAX_MATCHES): 164 if m_arr[j]<low: 165 low = m_arr[j] 166 if low==0: 167 break 168 if DEBUG: 169 log("get_best_scroll_values: arrays: matches=%s, scroll=%s", dd(m_arr, MAX_MATCHES), ds(s_arr, MAX_MATCHES)) 170 #first collect the list of distances sorted by highest number of matches: 171 #(there can be more than one distance value for each match count): 172 scroll_hits = {} 173 for i in range(MAX_MATCHES): 174 if m_arr[i]>min_hits: 175 scroll_hits.setdefault(m_arr[i], []).append(s_arr[i]) 176 if DEBUG: 177 log("scroll hits=%s", scroll_hits) 178 #return a dict with the scroll distance as key, 179 #and the list of matching lines in a dictionary: 180 # {line-start : count, ..} 181 #this is destructive as we clear the checksums after use in match_distance() 182 scrolls = collections.OrderedDict() 183 cdef uint16_t m 184 #starting with the highest matches 185 for m in reversed(sorted(scroll_hits.keys())): 186 v = scroll_hits[m] 187 for scroll in v: 188 #find matching lines: 189 line_defs = self.match_distance(scroll) 190 if line_defs: 191 scrolls[scroll] = line_defs 192 return scrolls 133 193 134 def consecutive_lines(line_numbers): 135 #print("line_numbers_to_rectangles(%s)" % (line_numbers, )) 136 #aggregates consecutive lines: 137 #[1,2,3,4,8,9] -> [(1,3), (8,1)] 138 assert len(line_numbers)>0 139 if len(line_numbers)==1: 140 return [(line_numbers[0], 1)] 141 cdef int start = line_numbers[0] 142 cdef int last = start 143 cdef int line = 0 144 r = [] 145 for line in line_numbers[1:]: 146 if line!=last+1: 147 #new rectangle 148 r.append((start, last-start+1)) 149 start = line 150 last = line 151 if last!=line_numbers[0]: 152 r.append((start, last-start+1)) 153 return r 194 def get_remaining_areas(self): 195 #all the lines which have not been zeroed out 196 #when we matched them in match_distance 197 cdef int64_t *a2 = self.a2 198 cdef uint16_t i, start = 0, count = 0 199 line_defs = collections.OrderedDict() 200 for i in range(self.l): 201 if a2[i]!=0: 202 if count==0: 203 start = i 204 count += 1 205 elif count>0: 206 line_defs[start] = count 207 count = 0 208 if count>0: 209 line_defs[start] = count 210 return line_defs 211 212 def match_distance(self, int16_t distance): 213 """ find the lines that match the given scroll distance """ 214 cdef int64_t *a1 = self.a1 215 cdef int64_t *a2 = self.a2 216 cdef char swap = 0 217 if distance<0: 218 #swap order: 219 swap = 1 220 a1 = self.a2 221 a2 = self.a1 222 distance = -distance 223 if DEBUG: 224 log("match_distance(%i) l=%i, a1=%s, a2=%s", distance, self.l, da(a1, self.l), da(a2, self.l)) 225 assert distance<self.l, "invalid distance %i for size %i" % (distance, self.l) 226 cdef uint16_t i, start = 0, count = 0 227 line_defs = collections.OrderedDict() 228 for i in range(self.l-distance): 229 #if DEBUG: 230 # log("%i: a1=%i / a2=%i", i, a1[i], a2[i+distance]) 231 if a1[i]!=0 and a1[i]==a2[i+distance]: 232 #if DEBUG: 233 # log("match at %i: %i", i, a1[i]) 234 if count==0: 235 #first match 236 if swap: 237 start = i+distance 238 else: 239 start = i 240 count += 1 241 #mark the target line as dealt with: 242 if swap: 243 a1[i] = 0 244 else: 245 a2[i+distance] = 0 246 elif count>0: 247 #we had a match 248 line_defs[start] = count 249 count = 0 250 if count>0: 251 #last few lines ended as a match: 252 line_defs[start] = count 253 if DEBUG: 254 log("match_distance(%i)=%s", distance, line_defs) 255 return line_defs 256 257 258 def get_best_match(self): 259 cdef int16_t max = 0 260 cdef int d = 0 261 cdef unsigned int i 262 for i in range(2*self.l): 263 if self.distances[i]>max: 264 max = self.distances[i] 265 d = i-self.l 266 return d, max 267 268 def __dealloc__(self): 269 cdef void* ptr = <void*> self.distances 270 if ptr: 271 self.distances = NULL 272 free(ptr) 273 ptr = <void*> self.a1 274 if ptr: 275 self.a1 = NULL 276 free(ptr) 277 ptr = <void*> self.a2 278 if ptr: 279 self.a2 = NULL 280 free(ptr) 281 282 283 def scroll_distances(array1, array2, unsigned int min_score=0, uint16_t max_distance=1000): 284 cdef ScrollDistances sd = ScrollDistances() 285 sd.init(array1, array2, max_distance) 286 return sd -
xpra/server/window/window_video_source.py
14 14 from xpra.codecs.codec_constants import TransientCodecException, RGB_FORMATS, PIXEL_SUBSAMPLING 15 15 from xpra.server.window.window_source import WindowSource, STRICT_MODE, AUTO_REFRESH_SPEED, AUTO_REFRESH_QUALITY 16 16 from xpra.server.window.region import rectangle, merge_all #@UnresolvedImport 17 from xpra.server.window.motion import match_distance, consecutive_lines, calculate_distances, CRC_Image #@UnresolvedImport17 from xpra.server.window.motion import scroll_distances, CRC_Image #@UnresolvedImport 18 18 from xpra.server.window.video_subregion import VideoSubregion, VIDEO_SUBREGION 19 19 from xpra.server.window.video_scoring import get_pipeline_score 20 20 from xpra.codecs.loader import PREFERED_ENCODING_ORDER, EDGE_ENCODING_ORDER … … 1453 1453 return packet 1454 1454 1455 1455 1456 def encode_scrolling(self, image, distances, o ld_csums, csums, options):1456 def encode_scrolling(self, image, distances, options, saved): 1457 1457 start = time.time() 1458 1458 try: 1459 1459 del options["av-sync"] … … 1460 1460 except: 1461 1461 pass 1462 1462 #tells make_data_packet not to invalidate the scroll data: 1463 scrolllog("encode_scrolling(%s, {..}, [], [], %s) window-dimensions=%s", image, options, self.window_dimensions) 1463 ww, wh = self.window_dimensions 1464 scrolllog("encode_scrolling(%s, %s, [], [], %s) window-dimensions=%s", image, distances, options, (ww, wh)) 1464 1465 x, y, w, h = image.get_geometry()[:4] 1465 yscroll_values = [] 1466 max_scroll_regions = 50 1467 #process distances with the highest score first: 1468 for hits in reversed(sorted(distances.values())): 1469 for scroll in (d for d,v in distances.items() if v==hits): 1470 assert scroll<h 1471 yscroll_values.append(scroll) 1472 if len(yscroll_values)>=max_scroll_regions: 1473 break 1474 assert yscroll_values 1475 #always add zero=no-change so we can drop those updates! 1476 if 0 not in yscroll_values and 0 in distances: 1477 #(but do this last so we don't end up cutting too many rectangles) 1478 yscroll_values.append(0) 1479 scrolllog(" will send scroll packets for yscroll=%s", yscroll_values) 1480 #keep track of the lines we have handled already: 1481 #(the same line may be available from multiple scroll directions) 1482 handled = set() 1466 raw_scroll = distances.get_best_scroll_values() 1467 non_scroll = distances.get_remaining_areas() 1468 scrolllog(" will send scroll data=%s, non-scroll=%s", raw_scroll, non_scroll) 1469 flush = len(non_scroll) 1470 assert raw_scroll, "failed to detect scroll values" 1471 #convert to a screen rectangle list for the client: 1483 1472 scrolls = [] 1484 max_scrolls = 1000 1485 for s in yscroll_values: 1486 #find all the lines that scroll by this much: 1487 slines = match_distance(old_csums, csums, s) 1488 assert slines, "no lines matching distance %i" % s 1489 #remove any lines we have already handled: 1490 lines = [v+s for v in slines if ((v+s) not in handled and v not in handled)] 1491 if not lines: 1473 for scroll, line_defs in raw_scroll.items(): 1474 if scroll==0: 1492 1475 continue 1493 #and them to the handled set so we don't try to paint them again: 1494 handled = handled.union(set(lines)) 1495 if s==0: 1496 scrolllog(" %i lines have not changed", len(lines)) 1497 else: 1498 #things have actually moved 1499 #aggregate consecutive lines into rectangles: 1500 cl = consecutive_lines(lines) 1501 #scrolllog(" scroll groups for distance=%i : %s=%s", s, lines, cl) 1502 scrolllog(" scroll groups for distance=%i : %s", s, cl) 1503 for sy,sh in cl: 1504 #new rectangle 1505 scrolls.append((x, y+sy-s, w, sh, 0, s)) 1506 if len(scrolls)>max_scrolls: 1507 break 1508 if len(scrolls)>max_scrolls: 1509 break 1510 1511 non_scroll = [] 1512 remaining = set(range(h))-handled 1513 if remaining: 1514 damaged_lines = sorted(list(remaining)) 1515 non_scroll = consecutive_lines(damaged_lines) 1516 scrolllog(" non scroll: %i packets: %s", len(non_scroll), non_scroll) 1517 flush = len(non_scroll) 1518 #send as scroll paints packets: 1519 if scrolls: 1476 for line, count in line_defs.items(): 1477 try: 1478 assert y+line+scroll>=0, "cannot scroll rectangle by %i lines from %i+%i" % (scroll, y, line) 1479 assert y+line+scroll<=wh, "cannot scroll rectangle %i high by %i lines from %i+%i (window height is %i)" % (count, scroll, y, line, wh) 1480 except Exception as e: 1481 log.error("Error: invalid scroll data:") 1482 log.error("LAST=%s", saved[0]) 1483 log.error("CURR=%s", saved[1]) 1484 scrolls.append((x, y+line, w, count, 0, scroll)) 1485 #send the scrolls if we have any 1486 #(zero change scrolls have been removed - so maybe there are none) 1487 if len(scrolls)>0: 1520 1488 client_options = options.copy() 1489 try: 1490 del client_options["scroll"] 1491 except: 1492 pass 1521 1493 if flush>0 and self.supports_flush: 1522 1494 client_options["flush"] = flush 1523 1495 coding = "scroll" … … 1534 1506 else: 1535 1507 #slower but can be lossless: 1536 1508 encoding = self.get_video_fallback_encoding(PREFERED_ENCODING_ORDER) 1509 client_options = options.copy() 1537 1510 if encoding: 1538 1511 encode_fn = self._encoders[encoding] 1539 for sy, sh in non_scroll: 1512 for sy, sh in non_scroll.items(): 1513 #s = time.time() 1540 1514 sub = image.get_sub_image(0, sy, w, sh) 1541 flush -= 11515 #scrolllog("sub_image %i pixels: %.1fms", (w*sh), (time.time()-s)*1000) 1542 1516 ret = encode_fn(encoding, sub, options) 1543 1517 if not ret: 1544 1518 #cancelled? … … 1545 1519 return None 1546 1520 coding, data, client_options, outw, outh, outstride, _ = ret 1547 1521 assert data 1548 client_options = options.copy()1549 if flush>0 and self.supports_flush:1522 flush -= 1 1523 if self.supports_flush and flush>0: 1550 1524 client_options["flush"] = flush 1551 1525 packet = self.make_draw_packet(sub.get_x(), sub.get_y(), outw, outh, coding, data, outstride, client_options, options) 1552 1526 self.queue_damage_packet(packet) … … 1556 1530 compresslog("compress: %5.1fms for %4ix%-4i pixels at %4i,%-4i for wid=%-5i using %6s with ratio %5.1f%% (%5iKB to %5iKB), sequence %5i, client_options=%s", 1557 1531 (end-nsstart)*1000.0, w, sh, 0, sy, self.wid, coding, 100.0*csize/psize, psize/1024, csize/1024, self._damage_packet_sequence, client_options) 1558 1532 scrolllog("non-scroll encoding using %s (quality=%i, speed=%i) took %ims for %i rectangles", encoding, self._current_quality, self._current_speed, (time.time()-nsstart)*1000, len(non_scroll)) 1533 else: 1534 #we can't send the non-scroll areas, ouch! 1535 flush = 0 1559 1536 assert flush==0 1560 1537 self.last_scroll_time = time.time() 1561 1538 scrolllog("scroll encoding total time: %ims", (self.last_scroll_time-start)*1000) … … 1633 1610 if csums: 1634 1611 self.scroll_data = rectangle(x, y, w, h), csums 1635 1612 options["scroll"] = True 1636 scrolllog("updated scroll data , previously set: %s", bool(lsd))1613 scrolllog("updated scroll data with %s, previously set: %s", self.scroll_data[0], (lsd or [None])[0]) 1637 1614 if lsd and csums: 1638 1615 rect, lcsums = lsd 1639 1616 if rect.x!=x or rect.y!=y: … … 1644 1621 else: 1645 1622 #same size, try to find scrolling value 1646 1623 assert len(csums)==len(lcsums), "mismatch between checksums lists: %i vs %i items!" % (len(csums), len(lcsums)) 1647 distances = calculate_distances(csums, lcsums, 2, 1000) 1648 if len(distances)==0: 1624 saved = lcsums[:], csums[:] 1625 max_distance = min(1000, (100-SCROLL_MIN_PERCENT)*h//100) 1626 distances = scroll_distances(lcsums, csums, 2, max_distance) 1627 scroll, count = distances.get_best_match() 1628 if count==0: 1649 1629 scrolllog("no scroll distances found") 1650 1630 else: 1651 best = max(distances.values())1652 scroll = distances.keys()[distances.values().index(best)]1653 1631 end = time.time() 1654 best_pct = int(100*best/h)1655 scrolllog("best scroll guess took %ims, matches %i%% of %i lines: %s", (end-start)*1000, best_pct, h, scroll)1632 match_pct = int(100*count/h) 1633 scrolllog("best scroll guess took %ims, matches %i%% of %i lines: %s", (end-start)*1000, match_pct, h, scroll) 1656 1634 #if enough scrolling is detected, use scroll encoding for this frame: 1657 if best_pct>=SCROLL_MIN_PERCENT:1658 return self.encode_scrolling(image, distances, lcsums, csums, options)1635 if match_pct>=SCROLL_MIN_PERCENT: 1636 return self.encode_scrolling(image, distances, options, saved) 1659 1637 except Exception: 1660 1638 scrolllog.error("Error during scrolling detection!", exc_info=True) 1661 1639