Skip to content

Commit

Permalink
#1232: part 2, handle "scroll" encoding in the GL backing, includes t…
Browse files Browse the repository at this point in the history
…est class for visualizing it

git-svn-id: https://xpra.org/svn/Xpra/trunk@12878 3bb7dfac-3a0b-4e04-842a-767bc560f471
  • Loading branch information
totaam committed Jun 21, 2016
1 parent fc21061 commit fde4418
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 48 deletions.
6 changes: 6 additions & 0 deletions src/tests/xpra/clients/fake_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
121 changes: 89 additions & 32 deletions src/tests/xpra/clients/test_gl_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
95 changes: 79 additions & 16 deletions src/xpra/client/gl/gl_window_backing_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)<bh, "invalid ydelta value: %i" % ydelta
assert w>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
Expand Down
5 changes: 5 additions & 0 deletions src/xpra/client/window_backing_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 """
Expand Down Expand Up @@ -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:
Expand Down

0 comments on commit fde4418

Please sign in to comment.