diff --git a/src/tests/xpra/clients/fake_client.py b/src/tests/xpra/clients/fake_client.py index 1223d2d870..729ed79f09 100755 --- a/src/tests/xpra/clients/fake_client.py +++ b/src/tests/xpra/clients/fake_client.py @@ -52,9 +52,15 @@ def send_mouse_position(self, *args): def send_configure_event(self, skip_geometry): log.info("send_configure_event(%s)", skip_geometry) + def window_close_event(self, *args): + log.info("window_close_event%s", args) + def mask_to_names(self, *args): return [] + def get_current_modifiers(self, *args): + return [] + def get_mouse_position(self): return 0, 0 diff --git a/src/tests/xpra/clients/test_gl_window.py b/src/tests/xpra/clients/test_gl_window.py index 811c66ed58..ca6530dacf 100755 --- a/src/tests/xpra/clients/test_gl_window.py +++ b/src/tests/xpra/clients/test_gl_window.py @@ -4,48 +4,105 @@ # 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 math from xpra.log import Logger log = Logger() - +import glib import gtk from gtk import gdk -import glib from xpra.util import typedict from tests.xpra.clients.fake_client import FakeClient from xpra.client.gl.gtk2.gl_client_window import GLClientWindow - from xpra.codecs.loader import load_codecs load_codecs(encoders=False, decoders=True, csc=False) -def get_mouse_position(*args): - root = gdk.get_default_root_window() - p = root.get_pointer() - return p[0], p[1] - -def get_current_modifiers(*args): - #root = gdk.get_default_root_window() - #modifiers_mask = root.get_pointer()[-1] - #return self.mask_to_names(modifiers_mask) - return [] - -client = FakeClient() -client.get_mouse_position = get_mouse_position -client.get_current_modifiers = get_current_modifiers -client.source_remove = glib.source_remove -client.timeout_add = glib.timeout_add -client.idle_add = glib.idle_add - -W = 640 -H = 480 -window = GLClientWindow(client, None, 1, 10, 10, W, H, W, H, typedict({}), False, typedict({}), 0, None) -window.show() -def paint_window(): - img_data = "\0"*W*3*H - window.draw_region(0, 0, W, H, "rgb24", img_data, W*3, 0, typedict({}), []) - -glib.timeout_add(1000, paint_window) - -gtk.main() + +def fake_gtk_client(): + def get_mouse_position(*args): + root = gdk.get_default_root_window() + p = root.get_pointer() + return p[0], p[1] + + def get_current_modifiers(*args): + #root = gdk.get_default_root_window() + #modifiers_mask = root.get_pointer()[-1] + #return self.mask_to_names(modifiers_mask) + return [] + def window_close_event(*args): + gtk.main_quit() + client = FakeClient() + client.get_mouse_position = get_mouse_position + client.get_current_modifiers = get_current_modifiers + client.source_remove = glib.source_remove + client.timeout_add = glib.timeout_add + client.idle_add = glib.idle_add + client.window_close_event = window_close_event + return client + + +def paint_window(window): + import binascii + W, H = window.get_size() + img_data = binascii.unhexlify("89504e470d0a1a0a0000000d494844520000010000000100010300000066bc3a2500000003504c54" + "45b5d0d0630416ea0000001f494441546881edc1010d000000c2a0f74f6d0e37a000000000000000" + "00be0d210000019a60e1d50000000049454e44ae426082") + window.draw_region((W-256)//2, (H-256)//2, 256, 256, "png", img_data, W*4, 0, typedict(), []) + +def paint_rect(window, x=200, y=200, w=32, h=32, color=0x80, options=typedict()): + print("paint_rect%s" % ((x, y, w, h),)) + W, H = window.get_size() + img_data = chr(color)*w*4*h + window.draw_region(x, y, w, h, "rgb32", img_data, w*4, 0, typedict(options), []) + +def paint_and_scroll(window, ydelta=10, color=0xA0): + print("paint_and_scroll(%i, %#x)" % (ydelta, color)) + W, H = window.get_size() + if ydelta>0: + #scroll down, repaint the top: + client_options = {"scrolls" : ((0, 0, W, H-ydelta, ydelta),) } + window.draw_region(0, 0, W, H, "scroll", "", W*4, 0, typedict(client_options), []) + paint_rect(window, 0, 0, W, ydelta, color) + else: + #scroll up, repaint the bottom: + client_options = {"scrolls" : ((0, -ydelta, W, H+ydelta, ydelta),) } + window.draw_region(0, 0, W, H, "scroll", "", W*4, 0, typedict(client_options), []) + paint_rect(window, 0, H-ydelta, W, -ydelta, color) + +def split_scroll(window): + W, H = window.get_size() + scrolls = [ + (0, 1, W, H//2-1, -1), + (0, H//2, W, H//2-1, 1), + ] + window.draw_region(0, 0, W, H, "scroll", "", W*4, 0, typedict({"scrolls" : scrolls}), []) + + +def main(): + W = 640 + H = 480 + client = fake_gtk_client() + window = GLClientWindow(client, None, 1, 10, 10, W, H, W, H, typedict({}), False, typedict({}), 0, None) + window.show() + glib.timeout_add(0, paint_rect, window, 0, 0, W, H, 0xFF) + glib.timeout_add(0, paint_window, window) + for i in range(4): + glib.timeout_add(500, paint_rect, window, W//4*i + W//8, 100, 32, 32, 0x30*i) + for i in range(50): + glib.timeout_add(1000+i*20, paint_rect, window, int(W//3+math.sin(i/10.0)*64), H//2-32) + glib.timeout_add(1000+i*20, paint_and_scroll, window, -1) + glib.timeout_add(2000+i*20, paint_rect, window, int(W//3*2-math.sin(i/10.0)*64), H//2-16, 32, 32, 0x10) + glib.timeout_add(2000+i*20, paint_and_scroll, window, +1) + for i in range(200): + glib.timeout_add(4000+i*20, split_scroll, window) + try: + gtk.main() + except KeyboardInterrupt: + pass + + +if __name__ == "__main__": + + main() diff --git a/src/xpra/client/gl/gl_window_backing_base.py b/src/xpra/client/gl/gl_window_backing_base.py index e4e361af49..d5b03b6e85 100644 --- a/src/xpra/client/gl/gl_window_backing_base.py +++ b/src/xpra/client/gl/gl_window_backing_base.py @@ -90,12 +90,14 @@ def get_fcolor(encoding): glTexImage2D, \ glMultiTexCoord2i, \ glTexCoord2i, glVertex2i, glEnd, \ - glClear, glClearColor, glLineWidth, glColor4f + glClear, glClearColor, glLineWidth, glColor4f, \ + glDrawBuffer, glReadBuffer from OpenGL.GL.ARB.texture_rectangle import GL_TEXTURE_RECTANGLE_ARB from OpenGL.GL.ARB.vertex_program import glGenProgramsARB, \ glBindProgramARB, glProgramStringARB, GL_PROGRAM_ERROR_STRING_ARB, GL_PROGRAM_FORMAT_ASCII_ARB from OpenGL.GL.ARB.fragment_program import GL_FRAGMENT_PROGRAM_ARB -from OpenGL.GL.ARB.framebuffer_object import GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, glGenFramebuffers, glBindFramebuffer, glFramebufferTexture2D +from OpenGL.GL.ARB.framebuffer_object import GL_FRAMEBUFFER, GL_DRAW_FRAMEBUFFER, GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, \ + glGenFramebuffers, glBindFramebuffer, glFramebufferTexture2D, glBlitFramebuffer from ctypes import c_uint @@ -171,6 +173,7 @@ def py_gl_debug_callback(source, error_type, error_id, severity, length, message TEX_V = 2 TEX_RGB = 3 TEX_FBO = 4 #FBO texture (guaranteed up-to-date window contents) +TEX_TMP_FBO = 5 # Shader number assignment YUV2RGB_SHADER = 0 @@ -208,6 +211,7 @@ def __init__(self, wid, window_alpha): self.paint_spinner = False self.draw_needs_refresh = False self.offscreen_fbo = None + self.tmp_fbo = None self.pending_fbo_paint = [] self.last_flush = time.time() self.default_paint_box_line_width = OPENGL_PAINT_BOX or 1 @@ -256,6 +260,11 @@ def init_backing(self): self._alpha_enabled = False self._backing.set_events(self._backing.get_events() | POINTER_MOTION_MASK | POINTER_MOTION_HINT_MASK) + def get_encoding_properties(self): + props = GTKWindowBacking.get_encoding_properties(self) + props["encoding.scrolling"] = True + return props + def __repr__(self): return "GLWindowBacking(%s, %s, %s)" % (self.wid, self.size, self.pixel_format) @@ -316,14 +325,18 @@ def gl_init_debug(self): def gl_init_textures(self): assert self.offscreen_fbo is None assert self.shaders is None - self.textures = glGenTextures(5) + self.textures = glGenTextures(6) + self.offscreen_fbo = self._gen_fbo() + self.tmp_fbo = self._gen_fbo() + log("%s.gl_init_textures() textures: %s, offscreen fbo: %s, tmp fbo: %s", self, self.textures, self.offscreen_fbo, self.tmp_fbo) + + def _gen_fbo(self): if hasattr(glGenFramebuffers, "pyConverters") and len(glGenFramebuffers.pyConverters)==1: #single argument syntax: - self.offscreen_fbo = glGenFramebuffers(1) - else: - self.offscreen_fbo = c_uint(1) - glGenFramebuffers(1, self.offscreen_fbo) - log("%s.gl_init_textures() textures: %s, offscreen fbo: %s", self, self.textures, self.offscreen_fbo) + return glGenFramebuffers(1) + fbo = c_uint(1) + glGenFramebuffers(1, fbo) + return fbo def gl_init_shaders(self): assert self.shaders is None @@ -399,19 +412,22 @@ def gl_init(self): if self.textures is None: self.gl_init_textures() - # Define empty FBO texture and set rendering to FBO glEnable(GL_FRAGMENT_PROGRAM_ARB) + # Define empty tmp FBO + glBindTexture(GL_TEXTURE_RECTANGLE_ARB, self.textures[TEX_TMP_FBO]) + glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_BASE_LEVEL, 0) + glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAX_LEVEL, 0) + glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, self.texture_pixel_format, w, h, 0, self.texture_pixel_format, GL_UNSIGNED_BYTE, None) + glBindFramebuffer(GL_FRAMEBUFFER, self.tmp_fbo) + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ARB, self.textures[TEX_TMP_FBO], 0) + glClear(GL_COLOR_BUFFER_BIT) + + # Define empty FBO texture and set rendering to FBO glBindTexture(GL_TEXTURE_RECTANGLE_ARB, self.textures[TEX_FBO]) # nvidia needs this even though we don't use mipmaps (repeated through this file): glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_BASE_LEVEL, 0) glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAX_LEVEL, 0) - try: - glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, self.texture_pixel_format, w, h, 0, self.texture_pixel_format, GL_UNSIGNED_BYTE, None) - except Exception as e: - log.error("Error: cannot initialize %ix%i %s texture", w, h, CONSTANT_TO_PIXEL_FORMAT.get(self.texture_pixel_format, self.texture_pixel_format)) - log.error(" %r", e) - log("%s", self.gl_init, exc_info=True) - raise Exception("cannot initialize %ix%i %s texture: %r" % (w, h, CONSTANT_TO_PIXEL_FORMAT.get(self.texture_pixel_format, self.texture_pixel_format), e)) + glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, self.texture_pixel_format, w, h, 0, self.texture_pixel_format, GL_UNSIGNED_BYTE, None) glBindFramebuffer(GL_FRAMEBUFFER, self.offscreen_fbo) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ARB, self.textures[TEX_FBO], 0) glClear(GL_COLOR_BUFFER_BIT) @@ -439,6 +455,53 @@ def close(self): b.destroy() self.glconfig = None + def paint_scroll(self, x, y, w, h, options, callbacks): + scrolls = options.listget("scrolls") + self.idle_add(self.do_scroll_paints, scrolls) + fire_paint_callbacks(callbacks, True) + + def do_scroll_paints(self, scrolls, flush=0): + log.warn("do_scroll_paints%s", (scrolls, flush)) + 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) + + for x,y,w,h,ydelta in scrolls: + assert ydelta!=0 and abs(ydelta)0 and h>0 + assert x+w<=bw and y+h<=bh, "scroll rectangle overflows the buffer: %s vs %s" % ((x, y, w, h), self.size) + assert y+ydelta>=0 and y+h+ydelta<=bh, "invalid vertical scroll value %i for rectangle %s overflows the buffer size %s" % (ydelta, (x, y, w, h), self.size) + #invert Y coordinates (bh-?) + glBlitFramebuffer(x, bh-y, x+w, bh-(y+h), + x, bh-(y+ydelta), x+w, bh-(y+h+ydelta), + GL_COLOR_BUFFER_BIT, GL_NEAREST) + + #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: + glBindFramebuffer(GL_READ_FRAMEBUFFER, self.offscreen_fbo) + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, self.offscreen_fbo) + glBindFramebuffer(GL_FRAMEBUFFER, self.offscreen_fbo) + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ARB, self.textures[TEX_FBO], 0) + self.unset_rgb_paint_state() + bw, bh = self.size + if flush==0: + self.present_fbo(0, 0, bw, bh) + def set_rgb_paint_state(self): # Set GL state for RGB painting: # no fragment program diff --git a/src/xpra/client/window_backing_base.py b/src/xpra/client/window_backing_base.py index 9d9162ee73..a74cafda78 100644 --- a/src/xpra/client/window_backing_base.py +++ b/src/xpra/client/window_backing_base.py @@ -488,6 +488,9 @@ def paint_mmap(self, img_data, x, y, width, height, rowstride, options, callback raise Exception("invalid rgb format: %s" % rgb_format) return False + def paint_scroll(self, *args): + raise NotImplementedError("no paint scroll on %s" % type(self)) + def draw_region(self, x, y, width, height, coding, img_data, rowstride, options, callbacks): """ dispatches the paint to one of the paint_XXXX methods """ @@ -529,6 +532,8 @@ def draw_region(self, x, y, width, height, coding, img_data, rowstride, options, self.paint_webp(img_data, x, y, width, height, options, callbacks) elif coding in self._PIL_encodings: self.paint_image(coding, img_data, x, y, width, height, options, callbacks) + elif coding=="scroll": + self.paint_scroll(x, y, width, height, options, callbacks) else: self.do_draw_region(x, y, width, height, coding, img_data, rowstride, options, callbacks) except Exception: