diff --git a/src/setup.py b/src/setup.py index d7c442def5..4d9b84b6fd 100755 --- a/src/setup.py +++ b/src/setup.py @@ -1615,8 +1615,14 @@ def cython_add(*args, **kwargs): toggle_packages(client_ENABLED and gtk3_ENABLED, "xpra.client.gtk3", "gi") toggle_packages(client_ENABLED and qt4_ENABLED, "xpra.client.qt4", "PyQt4") toggle_packages(client_ENABLED and (gtk2_ENABLED or gtk3_ENABLED), "xpra.client.gtk_base") +toggle_packages(client_ENABLED and opengl_ENABLED and gtk2_ENABLED, "xpra.client.gl.gtk2") +toggle_packages(client_ENABLED and opengl_ENABLED and gtk3_ENABLED, "xpra.client.gl.gtk3") +#we can't just include "xpra.client.gl" because cx_freeze and py2exe then do the wrong thing +#and try to include both gtk3 and gtk2, and fail hard.. +for x in ("gl_check", "gl_colorspace_conversions", "gl_window_backing_base", "gtk_compat"): + toggle_packages(client_ENABLED and opengl_ENABLED, "xpra.client.gl.%s" % x) + toggle_packages(sound_ENABLED, "xpra.sound") -toggle_packages(client_ENABLED and opengl_ENABLED, "xpra.client.gl") toggle_packages(clipboard_ENABLED, "xpra.clipboard") if clipboard_ENABLED: diff --git a/src/xpra/client/gl/gl_check.py b/src/xpra/client/gl/gl_check.py index 3f7d6eed1a..d93bbe0271 100755 --- a/src/xpra/client/gl/gl_check.py +++ b/src/xpra/client/gl/gl_check.py @@ -23,13 +23,14 @@ BLACKLIST["vendor"].append("Intel Inc.") WHITELIST["renderer"] = ["Intel HD Graphics 4000 OpenGL Engine"] - -DEFAULT_ALPHA = not sys.platform.startswith("win") and not sys.platform.startswith("darwin") +#alpha requires gtk3 or *nix only for gtk2: +DEFAULT_ALPHA = sys.version>'3' or (not sys.platform.startswith("win") and not sys.platform.startswith("darwin")) GL_ALPHA_SUPPORTED = os.environ.get("XPRA_ALPHA", DEFAULT_ALPHA) in (True, "1") DEFAULT_DOUBLE_BUFFERED = 0 -if sys.platform.startswith("win"): - #needed on win32? - DEFAULT_DOUBLE_BUFFERED = 1 +#not working with gtk3 yet? +CAN_DOUBLE_BUFFER = True +#needed on win32?: +DEFAULT_DOUBLE_BUFFERED = sys.platform.startswith("win") and CAN_DOUBLE_BUFFER DOUBLE_BUFFERED = os.environ.get("XPRA_OPENGL_DOUBLE_BUFFERED", str(DEFAULT_DOUBLE_BUFFERED))=="1" @@ -116,16 +117,9 @@ def check_functions(*functions): #sanity checks: OpenGL version and fragment program support: def check_GL_support(widget, min_texture_size=0, force_enable=False): - from xpra.client.gl.gtk_compat import begin_gl, end_gl - try: - if not begin_gl(widget): - raise ImportError("failed to get an opengl context") - except Exception as e: - raise ImportError("error getting an opengl context: %s" % e) - try: + from xpra.client.gl.gtk_compat import GLContextManager + with GLContextManager(widget): return do_check_GL_support(min_texture_size, force_enable) - finally: - end_gl(widget) def do_check_GL_support(min_texture_size, force_enable): props = {} @@ -369,16 +363,16 @@ def check_support(min_texture_size=0, force_enable=False, check_colormap=False): props = {} #this will import gtk.gtkgl / gdkgl or gi.repository.GtkGLExt / GdkGLExt: - from xpra.client.gl.gtk_compat import get_info, gtkgl, gdkgl, Config_new_by_mode, MODE_DOUBLE, RGBA_TYPE + from xpra.client.gl.gtk_compat import get_info, gdkgl, Config_new_by_mode, MODE_DOUBLE, GLDrawingArea props.update(get_info()) display_mode = get_DISPLAY_MODE() glconfig = Config_new_by_mode(display_mode) - if glconfig is None: + if glconfig is None and CAN_DOUBLE_BUFFER: log("trying to toggle double-buffering") display_mode &= ~MODE_DOUBLE glconfig = Config_new_by_mode(display_mode) - if not glconfig: - raise Exception("cannot setup an OpenGL context") + if not glconfig: + raise Exception("cannot setup an OpenGL context") props["display_mode"] = get_MODE_names(display_mode) props["glconfig"] = glconfig props["has_alpha"] = glconfig.has_alpha() @@ -400,9 +394,7 @@ def check_support(min_texture_size=0, force_enable=False, check_colormap=False): w = gtk.Window() w.set_decorated(False) vbox = gtk.VBox() - glarea = gtk.DrawingArea() - # Set OpenGL-capability to the widget - gtkgl.widget_set_gl_capability(glarea, glconfig, None, True, RGBA_TYPE) + glarea = GLDrawingArea(glconfig) glarea.set_size_request(32, 32) vbox.add(glarea) vbox.show_all() diff --git a/src/xpra/client/gl/gl_colorspace_conversions.py b/src/xpra/client/gl/gl_colorspace_conversions.py index 9ac4b3dff5..360fcb4e77 100644 --- a/src/xpra/client/gl/gl_colorspace_conversions.py +++ b/src/xpra/client/gl/gl_colorspace_conversions.py @@ -3,7 +3,7 @@ # * Michael Dominic K. #http://www.mdk.org.pl/2007/11/17/gl-colorspace-conversions -YUV2RGB_shader = """!!ARBfp1.0 +YUV2RGB_shader = b"""!!ARBfp1.0 # cgc version 3.1.0010, build date Feb 10 2012 # command line args: -profile arbfp1 # source file: yuv.cg @@ -71,7 +71,7 @@ # return OUT; #} -RGBP2RGB_shader = """!!ARBfp1.0 +RGBP2RGB_shader = b"""!!ARBfp1.0 # cgc version 3.1.0013, build date Apr 24 2012 # command line args: -profile arbfp1 # source file: a.cg diff --git a/src/xpra/client/gl/gl_window_backing.py b/src/xpra/client/gl/gl_window_backing_base.py similarity index 88% rename from src/xpra/client/gl/gl_window_backing.py rename to src/xpra/client/gl/gl_window_backing_base.py index 1d71a02896..c50f10fc80 100644 --- a/src/xpra/client/gl/gl_window_backing.py +++ b/src/xpra/client/gl/gl_window_backing_base.py @@ -4,23 +4,22 @@ # Xpra is released under the terms of the GNU GPL v2, or, at your option, any # later version. See the file COPYING for details. -#only works with gtk2: import sys import os -from gtk import gdk -assert gdk -import gtk.gdkgl, gtk.gtkgl #@UnresolvedImport -assert gtk.gdkgl is not None and gtk.gtkgl is not None -import gobject from xpra.log import Logger log = Logger("opengl", "paint") OPENGL_DEBUG = os.environ.get("XPRA_OPENGL_DEBUG", "0")=="1" +from xpra.gtk_common.gtk_util import import_gobject +idle_add = import_gobject().idle_add + from xpra.codecs.codec_constants import get_subsampling_divs -from xpra.client.gl.gl_check import get_DISPLAY_MODE, GL_ALPHA_SUPPORTED +from xpra.client.window_backing_base import fire_paint_callbacks +from xpra.client.gtk_base.gtk_window_backing_base import GTKWindowBacking +from xpra.client.gl.gtk_compat import Config_new_by_mode, MODE_DOUBLE, GLContextManager, GLDrawingArea +from xpra.client.gl.gl_check import get_DISPLAY_MODE, GL_ALPHA_SUPPORTED, CAN_DOUBLE_BUFFER from xpra.client.gl.gl_colorspace_conversions import YUV2RGB_shader, RGBP2RGB_shader -from xpra.client.gtk2.window_backing import GTK2WindowBacking, fire_paint_callbacks from OpenGL import version as OpenGL_version from OpenGL.GL import \ GL_PROJECTION, GL_MODELVIEW, \ @@ -104,6 +103,11 @@ def py_gl_debug_callback(source, error_type, error_id, severity, length, message memoryview_type = None if sys.version_info[:2]>=(2,7) and OpenGL_version.__version__.split('.')[:2]>=['3','1']: memoryview_type = memoryview +try: + buffer_type = buffer +except: + #not defined in py3k.. + buffer_type = None # Texture number assignment @@ -125,7 +129,6 @@ def py_gl_debug_callback(source, error_type, error_id, severity, length, message RGBP2RGB_SHADER = 1 """ -This is the gtk2 + OpenGL version. The logic is as follows: We create an OpenGL framebuffer object, which will be always up-to-date with the latest windows contents. @@ -134,29 +137,57 @@ def py_gl_debug_callback(source, error_type, error_id, severity, length, message The use of a intermediate framebuffer object is the only way to guarantee that the client keeps an always fully up-to-date window image, which is critical because of backbuffer content losses upon buffer swaps or offscreen window movement. """ -class GLPixmapBacking(GTK2WindowBacking): +class GLWindowBackingBase(GTKWindowBacking): RGB_MODES = ["YUV420P", "YUV422P", "YUV444P", "GBRP", "BGRA", "BGRX", "RGBA", "RGBX", "RGB", "BGR"] HAS_ALPHA = GL_ALPHA_SUPPORTED def __init__(self, wid, w, h, window_alpha): - #initialize those early as we use them in __repr__ self.wid = wid self.size = 0, 0 self.pixel_format = None + self.texture_pixel_format = None + #this is the pixel format we are currently updating the fbo with + #can be: "YUV420P", "YUV422P", "YUV444P", "GBRP" or None when not initialized yet. + self.pixel_format = None + self.textures = None # OpenGL texture IDs + self.shaders = None + self.size = 0, 0 + self.texture_size = 0, 0 + self.gl_setup = False + self.debug_setup = False + self.border = None + self.paint_screen = False + self.draw_needs_refresh = False + self.offscreen_fbo = None + + GTKWindowBacking.__init__(self, wid, window_alpha) + self.init_gl_config(window_alpha) + self.init_backing() + #this is how many bpp we keep in the texture + #(pixels are always stored in 32bpp - but this makes it clearer when we do/don't support alpha) + if self._alpha_enabled: + self.texture_pixel_format = GL_RGBA + else: + self.texture_pixel_format = GL_RGB + self._backing.show() + + def init_gl_config(self, window_alpha): + #setup gl config: alpha = GL_ALPHA_SUPPORTED and window_alpha display_mode = get_DISPLAY_MODE(want_alpha=alpha) - try: - self.glconfig = gtk.gdkgl.Config(mode=display_mode) - except gtk.gdkgl.NoMatches: - #toggle double buffering and try again: - display_mode &= ~gtk.gdkgl.MODE_DOUBLE - log.warn("failed to initialize gl context: trying again %s double buffering", (bool(display_mode & gtk.gdkgl.MODE_DOUBLE) and "with") or "without") - self.glconfig = gtk.gdkgl.Config(mode=display_mode) - GTK2WindowBacking.__init__(self, wid, alpha and self.glconfig.has_alpha()) - self._backing = gtk.gtkgl.DrawingArea(self.glconfig) - #restoring missed masks: - self._backing.set_events(self._backing.get_events() | gdk.POINTER_MOTION_MASK | gdk.POINTER_MOTION_HINT_MASK) + self.glconfig = Config_new_by_mode(display_mode) + if self.glconfig is None and CAN_DOUBLE_BUFFER: + log("trying to toggle double-buffering") + display_mode &= ~MODE_DOUBLE + self.glconfig = Config_new_by_mode(display_mode) + if not self.glconfig: + raise Exception("cannot setup an OpenGL context") + + def init_backing(self): + self._backing = GLDrawingArea(self.glconfig) + #must be overriden in subclasses to setup self._backing + assert self._backing if self._alpha_enabled: assert GL_ALPHA_SUPPORTED, "BUG: cannot enable alpha if GL backing does not support it!" screen = self._backing.get_screen() @@ -171,31 +202,10 @@ def __init__(self, wid, w, h, window_alpha): else: log.warn("failed to enable transparency on screen %s", screen) self._alpha_enabled = False - #this is how many bpp we keep in the texture - #(pixels are always stored in 32bpp - but this makes it clearer when we do/don't support alpha) - if self._alpha_enabled: - self.texture_pixel_format = GL_RGBA - else: - self.texture_pixel_format = GL_RGB - #this is the pixel format we are currently updating the fbo with - #can be: "YUV420P", "YUV422P", "YUV444P", "GBRP" or None when not initialized yet. - self.pixel_format = None - self._backing.show() - self._backing.connect("expose_event", self.gl_expose_event) - self.textures = None # OpenGL texture IDs - self.shaders = None - self.size = 0, 0 - self.texture_size = 0, 0 - self.gl_setup = False - self.debug_setup = False - self.border = None - self.paint_screen = False - self._video_use_swscale = False - self.draw_needs_refresh = False - self.offscreen_fbo = None + def __repr__(self): - return "GLPixmapBacking(%s, %s, %s)" % (self.wid, self.size, self.pixel_format) + return "GLWindowBacking(%s, %s, %s)" % (self.wid, self.size, self.pixel_format) def init(self, w, h): #re-init gl projection with new dimensions @@ -270,18 +280,23 @@ def gl_init_shaders(self): #FIXME: maybe we should do something else here? log.error(err) - def gl_init(self): - drawable = self.gl_begin() + def gl_context(self): + if not self._backing: + return None w, h = self.size - log("%s.gl_init() GL Pixmap backing size: %d x %d, drawable=%s", self, w, h, drawable) - if not drawable: - return None + context = GLContextManager(self._backing) + log("%s.gl_context() GL Pixmap backing size: %d x %d, context=%s", self, w, h, context) + return context + def gl_init(self): + #must be called within a context! + #performs init if needed if not self.debug_setup: self.debug_setup = True self.gl_init_debug() if not self.gl_setup: + w, h = self.size self.gl_marker("Initializing GL context for window size %d x %d" % (w, h)) # Initialize viewport and matrices for 2D rendering glViewport(0, 0, w, h) @@ -325,7 +340,6 @@ def gl_init(self): # Bind program 0 for YUV painting by default glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, self.shaders[YUV2RGB_SHADER]) self.gl_setup = True - return drawable def close(self): #This seems to cause problems, so we rely @@ -338,22 +352,9 @@ def close(self): # self.textures = None if self._backing: self._backing.destroy() - GTK2WindowBacking.close(self) + self._backing = None self.glconfig = None - def gl_begin(self): - if self._backing is None: - return None #closed already - drawable = self._backing.get_gl_drawable() - context = self._backing.get_gl_context() - if drawable is None or context is None: - log.error("%s.gl_begin() no drawable or context!", self) - return None - if not drawable.gl_begin(context): - log.error("%s.gl_begin() cannot create rendering context!", self) - return None - return drawable - def set_rgb_paint_state(self): # Set GL state for RGB painting: # no fragment program @@ -381,11 +382,10 @@ def unset_rgbP_paint_state(self): # change fragment program glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, self.shaders[YUV2RGB_SHADER]) - def present_fbo(self, drawable): + def present_fbo(self): if not self.paint_screen: return - self.gl_marker("Presenting FBO on screen for drawable %s" % drawable) - assert drawable + self.gl_marker("Presenting FBO on screen") # Change state to target screen instead of our FBO glBindFramebuffer(GL_FRAMEBUFFER, 0) @@ -430,28 +430,26 @@ def present_fbo(self, drawable): glColor4f(1.0, 1.0, 1.0, 1.0) # Show the backbuffer on screen - if drawable.is_double_buffered(): - log("%s.present_fbo() swapping buffers now", self) - drawable.swap_buffers() - # Clear the new backbuffer to illustrate that its contents are undefined - glClear(GL_COLOR_BUFFER_BIT) - else: - glFlush() + self.gl_show() self.gl_frame_terminator() self.unset_rgb_paint_state() + log("%s(%s, %s)", glBindFramebuffer, GL_FRAMEBUFFER, self.offscreen_fbo) glBindFramebuffer(GL_FRAMEBUFFER, self.offscreen_fbo) log("%s.present_fbo() done", self) - def gl_expose_event(self, glarea, event): - log("%s.gl_expose_event(%s, %s)", self, glarea, event) - drawable = self.gl_init() - if not drawable: - return - try: - self.present_fbo(drawable) - finally: - drawable.gl_end() + def gl_show(self): + if self.glconfig.is_double_buffered(): + # Show the backbuffer on screen + log("%s.present_fbo() swapping buffers now", self) + gldrawable = self.get_gl_drawable() + gldrawable.swap_buffers() + # Clear the new backbuffer to illustrate that its contents are undefined + glClear(GL_COLOR_BUFFER_BIT) + else: + #just ensure stuff gets painted: + glFlush() + def _do_paint_rgb32(self, img_data, x, y, width, height, rowstride, options): return self._do_paint_rgb(32, img_data, x, y, width, height, rowstride, options) @@ -460,23 +458,23 @@ def _do_paint_rgb24(self, img_data, x, y, width, height, rowstride, options): return self._do_paint_rgb(24, img_data, x, y, width, height, rowstride, options) def _do_paint_rgb(self, bpp, img_data, x, y, width, height, rowstride, options): - log("%s._do_paint_rgb(%s, %s bytes, x=%d, y=%d, width=%d, height=%d, rowstride=%d)", self, bpp, len(img_data), x, y, width, height, rowstride) - drawable = self.gl_init() - if not drawable: - log("%s._do_paint_rgb(..) drawable is not set!", self) - return False - + log("%s._do_paint_rgb(%s, %s bytes, x=%d, y=%d, width=%d, height=%d, rowstride=%d, options=%s)", self, bpp, len(img_data), x, y, width, height, rowstride, options) #deal with buffers uploads by wrapping them if we can, or copy to a string: - if type(img_data)==buffer: + if type(img_data)==buffer_type: if memoryview_type is not None: img_data = memoryview_type(img_data) else: img_data = str(img_data) - try: + context = self.gl_context() + if not context: + log("%s._do_paint_rgb(..) no context!", self) + return False + with context: + self.gl_init() self.set_rgb_paint_state() - rgb_format = options.get("rgb_format") + rgb_format = options.get(b"rgb_format") if not rgb_format: #Older servers may not tell us the pixel format, so we must infer it: if bpp==24: @@ -484,8 +482,11 @@ def _do_paint_rgb(self, bpp, img_data, x, y, width, height, rowstride, options): else: assert bpp==32 rgb_format = "RGBA" + else: + rgb_format = rgb_format.decode() #convert it to a GL constant: pformat = PIXEL_FORMAT_TO_CONSTANT.get(rgb_format) + log("pixel format(%s)=%s", rgb_format, pformat) assert pformat is not None, "could not find pixel format for %s (bpp=%s)" % (rgb_format, bpp) bytes_per_pixel = len(rgb_format) #ie: BGRX -> 4 @@ -500,7 +501,7 @@ def _do_paint_rgb(self, bpp, img_data, x, y, width, height, rowstride, options): # 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 + row_length = width + (rowstride - width * bytes_per_pixel) // bytes_per_pixel self.gl_marker("%s %sbpp update at (%d,%d) size %dx%d (%s bytes), stride=%d, row length %d, alignment %d, using GL upload format=%s" % (rgb_format, bpp, x, y, width, height, len(img_data), rowstride, row_length, alignment, CONSTANT_TO_PIXEL_FORMAT.get(pformat))) @@ -528,10 +529,8 @@ def _do_paint_rgb(self, bpp, img_data, x, y, width, height, rowstride, options): glEnd() # Present update to screen - self.present_fbo(drawable) + self.present_fbo() # present_fbo has reset state already - finally: - drawable.gl_end() return True def do_video_paint(self, img, x, y, enc_width, enc_height, width, height, options, callbacks): @@ -547,7 +546,7 @@ def do_video_paint(self, img, x, y, enc_width, enc_height, width, height, option plane_type = plane_types[0] if plane_type==memoryview_type: clone = False - elif plane_type in (str, buffer): + elif plane_type in (str, buffer_type): #wrap with a memoryview that pyopengl can use: views = [] for i in range(3): @@ -557,7 +556,7 @@ def do_video_paint(self, img, x, y, enc_width, enc_height, width, height, option if clone: #copy so the data will be usable (usually a str) img.clone_pixel_data() - gobject.idle_add(self.gl_paint_planar, img, x, y, enc_width, enc_height, width, height, callbacks) + idle_add(self.gl_paint_planar, img, x, y, enc_width, enc_height, width, height, callbacks) def gl_paint_planar(self, img, x, y, enc_width, enc_height, width, height, callbacks): #this function runs in the UI thread, no video_decoder lock held @@ -565,12 +564,14 @@ def gl_paint_planar(self, img, x, y, enc_width, enc_height, width, height, callb 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) - drawable = self.gl_init() - if not drawable: - log("%s.gl_paint_planar() drawable is not set!", self) + + context = self.gl_context() + if not context: + log("%s._do_paint_rgb(..) not context!", self) fire_paint_callbacks(callbacks, False) return - try: + 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() @@ -581,9 +582,7 @@ def gl_paint_planar(self, img, x, y, enc_width, enc_height, width, height, callb y_scale = float(height)/enc_height self.render_planar_update(x, y, enc_width, enc_height, x_scale, y_scale) # Present it on screen - self.present_fbo(drawable) - finally: - drawable.gl_end() + self.present_fbo() fire_paint_callbacks(callbacks, True) except Exception as e: log.error("%s.gl_paint_planar(..) error: %s", self, e, exc_info=True) diff --git a/src/xpra/client/gl/gtk2/__init__.py b/src/xpra/client/gl/gtk2/__init__.py new file mode 100644 index 0000000000..4d4d0e5522 --- /dev/null +++ b/src/xpra/client/gl/gtk2/__init__.py @@ -0,0 +1,4 @@ +# This file is part of Xpra. +# Copyright (C) 2014 Antoine Martin +# Xpra is released under the terms of the GNU GPL v2, or, at your option, any +# later version. See the file COPYING for details. diff --git a/src/xpra/client/gl/gl_client_window.py b/src/xpra/client/gl/gtk2/gl_client_window.py similarity index 97% rename from src/xpra/client/gl/gl_client_window.py rename to src/xpra/client/gl/gtk2/gl_client_window.py index 37bed5330f..c61f976b87 100644 --- a/src/xpra/client/gl/gl_client_window.py +++ b/src/xpra/client/gl/gtk2/gl_client_window.py @@ -11,7 +11,7 @@ import gobject from xpra.client.gtk2.gtk2_window_base import GTK2WindowBase -from xpra.client.gl.gl_window_backing import GLPixmapBacking +from xpra.client.gl.gtk2.gl_window_backing import GLPixmapBacking class GLClientWindow(GTK2WindowBase): diff --git a/src/xpra/client/gl/gtk2/gl_window_backing.py b/src/xpra/client/gl/gtk2/gl_window_backing.py new file mode 100644 index 0000000000..6e15889053 --- /dev/null +++ b/src/xpra/client/gl/gtk2/gl_window_backing.py @@ -0,0 +1,40 @@ +# This file is part of Xpra. +# Copyright (C) 2013 Serviware (Arthur Huillet, ) +# Copyright (C) 2012-2014 Antoine Martin +# Xpra is released under the terms of the GNU GPL v2, or, at your option, any +# later version. See the file COPYING for details. + +from gtk import gdk +assert gdk +from gtk import gtkgl #@UnresolvedImport +assert gtkgl is not None + +from xpra.log import Logger +log = Logger("opengl", "paint") + +from xpra.client.gl.gl_window_backing_base import GLWindowBackingBase + + +""" +This is the gtk2 pygtkglext version. +""" +class GLPixmapBacking(GLWindowBackingBase): + + def init_backing(self): + GLWindowBackingBase.init_backing(self) + self._backing.connect("expose_event", self.gl_expose_event) + + def __repr__(self): + return "gtk2."+GLWindowBackingBase.__repr__(self) + + def get_gl_drawable(self): + return gtkgl.widget_get_gl_drawable(self._backing) + + def gl_expose_event(self, glarea, event): + context = self.gl_context() + log("%s.gl_expose_event(%s, %s) context=%s", self, glarea, event, context) + if not context: + return + with context: + self.gl_init() + self.present_fbo() diff --git a/src/xpra/client/gl/gtk3/__init__.py b/src/xpra/client/gl/gtk3/__init__.py new file mode 100644 index 0000000000..4d4d0e5522 --- /dev/null +++ b/src/xpra/client/gl/gtk3/__init__.py @@ -0,0 +1,4 @@ +# This file is part of Xpra. +# Copyright (C) 2014 Antoine Martin +# Xpra is released under the terms of the GNU GPL v2, or, at your option, any +# later version. See the file COPYING for details. diff --git a/src/xpra/client/gl/gtk3/gl_client_window.py b/src/xpra/client/gl/gtk3/gl_client_window.py new file mode 100644 index 0000000000..7f0ac59881 --- /dev/null +++ b/src/xpra/client/gl/gtk3/gl_client_window.py @@ -0,0 +1,67 @@ +# This file is part of Xpra. +# Copyright (C) 2012 Serviware (Arthur Huillet, ) +# Copyright (C) 2012-2014 Antoine Martin +# Xpra is released under the terms of the GNU GPL v2, or, at your option, any +# later version. See the file COPYING for details. + +from xpra.log import Logger +log = Logger("opengl", "window") + +from xpra.client.gtk3.client_window import ClientWindow +from xpra.client.gl.gtk3.gl_window_backing import GLPixmapBacking + + +class GLClientWindow(ClientWindow): + + def __init__(self, *args): + log("GLClientWindow(..)") + ClientWindow.__init__(self, *args) + self.add(self._backing._backing) + + def get_backing_class(self): + return GLPixmapBacking + + def setup_window(self): + self._client_properties["encoding.uses_swscale"] = False + ClientWindow.setup_window(self) + + + def __str__(self): + return "GLClientWindow(%s : %s)" % (self._id, self._backing) + + def is_GL(self): + return True + + def set_alpha(self): + ClientWindow.set_alpha(self) + rgb_formats = self._client_properties.get("encodings.rgb_formats", []) + #gl_window_backing supports BGR(A) too: + if "RGBA" in rgb_formats: + rgb_formats.append("BGRA") + if "RGB" in rgb_formats: + rgb_formats.append("BGR") + #TODO: we could handle BGRX as BGRA too... + #rgb_formats.append("BGRX") + + def spinner(self, ok): + #TODO + pass + + def do_expose_event(self, event): + log("GL do_expose_event(%s)", event) + + def do_configure_event(self, event): + log("GL do_configure_event(%s)", event) + ClientWindow.do_configure_event(self, event) + self._backing.paint_screen = True + + def destroy(self): + self._backing.paint_screen = False + ClientWindow.destroy(self) + + def magic_key(self, *args): + if self.border: + self.border.shown = (not self.border.shown) + self.queue_draw(0, 0, *self._size) + +#gobject.type_register(GLClientWindow) diff --git a/src/xpra/client/gl/gtk3/gl_window_backing.py b/src/xpra/client/gl/gtk3/gl_window_backing.py new file mode 100644 index 0000000000..37e5f64a21 --- /dev/null +++ b/src/xpra/client/gl/gtk3/gl_window_backing.py @@ -0,0 +1,31 @@ +# This file is part of Xpra. +# Copyright (C) 2013 Serviware (Arthur Huillet, ) +# Copyright (C) 2012-2014 Antoine Martin +# Xpra is released under the terms of the GNU GPL v2, or, at your option, any +# later version. See the file COPYING for details. + +from xpra.log import Logger +log = Logger("opengl", "paint") + +from xpra.client.gl.gl_window_backing_base import GLWindowBackingBase +from gi.repository import GdkGLExt #@UnresolvedImport + + +""" +This is the gtk3 GObject Introspection version. +""" +class GLPixmapBacking(GLWindowBackingBase): + + def init_backing(self): + GLWindowBackingBase.init_backing(self) + + def __repr__(self): + return "gtk3."+GLWindowBackingBase.__repr__(self) + + def get_gl_drawable(self): + window = self._backing.get_window() + #probably not the right place to be doing this! + return GdkGLExt.Window.new(self.glconfig, window, 0) + + def cairo_draw(self, context): + log("cairo_draw(%s)", context) diff --git a/src/xpra/client/gl/gtk_compat.py b/src/xpra/client/gl/gtk_compat.py index 0af57c85e5..d40e74035e 100644 --- a/src/xpra/client/gl/gtk_compat.py +++ b/src/xpra/client/gl/gtk_compat.py @@ -12,7 +12,10 @@ if is_gtk3(): - from gi.repository import GdkGLExt, GtkGLExt #@UnresolvedImport + from gi.repository import Gtk, GdkGLExt, GtkGLExt #@UnresolvedImport + gtk = Gtk + GdkGLExt.init_check(0, "") + GtkGLExt.init_check(0, "") MODE_DEPTH = GdkGLExt.ConfigMode.DEPTH MODE_RGBA = GdkGLExt.ConfigMode.RGBA MODE_ALPHA = GdkGLExt.ConfigMode.ALPHA @@ -23,7 +26,8 @@ RGBA_TYPE = GdkGLExt.RenderType.RGBA_TYPE def get_info(): - return {"gdkgl_version" : GdkGLExt._version, + return { + "gdkgl_version" : GdkGLExt._version, "gtkgl_version" : GtkGLExt._version, } gdkgl = GdkGLExt @@ -35,13 +39,21 @@ def Config_new_by_mode(display_mode): log("no configuration for mode: %s", e) return None - def begin_gl(widget): - return GtkGLExt.widget_begin_gl(widget) + class GLContextManager(object): - def end_gl(widget): - GtkGLExt.widget_end_gl(widget, False) + def __init__(self, widget): + self.widget = widget + def __enter__(self): + #self.context = GtkGLExt.widget_create_gl_context(self.widget) + assert GtkGLExt.widget_begin_gl(self.widget) + #log("dir(%s)=%s", self.widget, dir(self.widget)) + def __exit__(self, exc_type, exc_val, exc_tb): + #doing this crashes! + #GtkGLExt.widget_end_gl(self.widget, False) + pass else: + import gtk from gtk import gdkgl, gtkgl MODE_DEPTH = gdkgl.MODE_DEPTH MODE_RGBA = gdkgl.MODE_RGBA @@ -67,11 +79,20 @@ def get_info(): "gdkgl_version" : gdkgl.query_version() } - def begin_gl(widget): - gldrawable = gtkgl.widget_get_gl_drawable(widget) - glcontext = gtkgl.widget_get_gl_context(widget) - return gldrawable.gl_begin(glcontext) + class GLContextManager(object): + + def __init__(self, widget): + self.widget = widget + def __enter__(self): + gldrawable = gtkgl.widget_get_gl_drawable(self.widget) + glcontext = gtkgl.widget_get_gl_context(self.widget) + assert gldrawable.gl_begin(glcontext) + def __exit__(self, exc_type, exc_val, exc_tb): + gldrawable = gtkgl.widget_get_gl_drawable(self.widget) + gldrawable.gl_end() - def end_gl(widget): - gldrawable = gtkgl.widget_get_gl_drawable(widget) - gldrawable.gl_end() +def GLDrawingArea(glconfig): + glarea = gtk.DrawingArea() + # Set OpenGL-capability to the widget + gtkgl.widget_set_gl_capability(glarea, glconfig, None, True, RGBA_TYPE) + return glarea diff --git a/src/xpra/client/gtk2/client.py b/src/xpra/client/gtk2/client.py index ec9a9e55a7..6aba05719b 100644 --- a/src/xpra/client/gtk2/client.py +++ b/src/xpra/client/gtk2/client.py @@ -49,7 +49,6 @@ class XpraClient(GTKXpraClient): def __init__(self): GTKXpraClient.__init__(self) self.border = None - self.GLClientWindowClass = None self.local_clipboard_requests = 0 self.remote_clipboard_requests = 0 @@ -61,13 +60,12 @@ def __init__(self): def init(self, opts): GTKXpraClient.init(self, opts) - self.ClientWindowClass = None if opts.window_layout: assert opts.window_layout in WINDOW_LAYOUTS self.ClientWindowClass = WINDOW_LAYOUTS.get(opts.window_layout) if self.ClientWindowClass: log.info("window layout '%s' specified, disabling OpenGL", opts.window_layout) - opts.opengl= False + opts.opengl = False else: self.ClientWindowClass = BorderClientWindow log("init(..) ClientWindowClass=%s", self.ClientWindowClass) @@ -350,45 +348,6 @@ def window_ungrab(self): gtk.gdk.keyboard_ungrab() - def init_opengl(self, enable_opengl): - #enable_opengl can be True, False or None (auto-detect) - self.client_supports_opengl = False - self.opengl_enabled = False - self.GLClientWindowClass = None - self.opengl_props = {} - if enable_opengl is False: - self.opengl_props["info"] = "disabled by configuration" - return - from xpra.scripts.config import OpenGL_safety_check - warning = OpenGL_safety_check() - if warning: - if enable_opengl is True: - log.warn("OpenGL safety warning (enabled at your own risk): %s", warning) - self.opengl_props["info"] = "forced enabled despite: %s" % warning - else: - log.warn("OpenGL disabled: %s", warning) - self.opengl_props["info"] = "disabled: %s" % warning - return - self.opengl_props["info"] = "" - try: - __import__("xpra.client.gl", {}, {}, []) - __import__("xpra.client.gl.gtk_compat", {}, {}, []) - gl_check = __import__("xpra.client.gl.gl_check", {}, {}, ["check_support"]) - w, h = self.get_root_size() - min_texture_size = max(w, h) - self.opengl_props = gl_check.check_support(min_texture_size, force_enable=(enable_opengl is True)) - gl_client_window = __import__("xpra.client.gl.gl_client_window", {}, {}, ["GLClientWindow"]) - self.GLClientWindowClass = gl_client_window.GLClientWindow - self.client_supports_opengl = True - self.opengl_enabled = True - except ImportError as e: - log.warn("OpenGL support could not be enabled:") - log.warn(" %s", e) - self.opengl_props["info"] = str(e) - except Exception as e: - log.error("Error loading OpenGL support:") - log.error(" %s", e, exc_info=True) - self.opengl_props["info"] = str(e) def get_group_leader(self, metadata, override_redirect): if not self.supports_group_leader: @@ -461,71 +420,4 @@ def destroy_window(self, wid, window): group_leader.destroy() - def get_client_window_classes(self, metadata, override_redirect): - log("get_client_window_class(%s, %s) GLClientWindowClass=%s, opengl_enabled=%s, mmap_enabled=%s, encoding=%s", metadata, override_redirect, self.GLClientWindowClass, self.opengl_enabled, self.mmap_enabled, self.encoding) - if self.GLClientWindowClass is None or not self.opengl_enabled: - return [self.ClientWindowClass] - return [self.GLClientWindowClass, self.ClientWindowClass] - - def toggle_opengl(self, *args): - assert self.window_unmap, "server support for 'window_unmap' is required for toggling opengl at runtime" - self.opengl_enabled = not self.opengl_enabled - log("opengl_toggled: %s", self.opengl_enabled) - def fake_send(*args): - log("fake_send(%s)", args) - #now replace all the windows with new ones: - for wid, window in self._id_to_window.items(): - if window.is_tray(): - #trays are never GL enabled, so don't bother re-creating them - #(might cause problems anyway if we did) - continue - #ignore packets from old window: - window.send = fake_send - #copy attributes: - x, y = window._pos - w, h = window._size - client_properties = window._client_properties - metadata = window._metadata - override_redirect = window._override_redirect - backing = window._backing - video_decoder = None - csc_decoder = None - decoder_lock = None - try: - if backing: - video_decoder = backing._video_decoder - csc_decoder = backing._csc_decoder - decoder_lock = backing._decoder_lock - if decoder_lock: - decoder_lock.acquire() - log("toggle_opengl() will preserve video=%s and csc=%s for %s", video_decoder, csc_decoder, wid) - backing._video_decoder = None - backing._csc_decoder = None - backing._decoder_lock = None - - #now we can unmap it: - self.destroy_window(wid, window) - #explicitly tell the server we have unmapped it: - #(so it will reset the video encoders, etc) - self.send("unmap-window", wid) - try: - del self._id_to_window[wid] - except: - pass - try: - del self._window_to_id[window] - except: - pass - #create the new window, which should honour the new state of the opengl_enabled flag: - window = self.make_new_window(wid, x, y, w, h, metadata, override_redirect, client_properties) - if video_decoder or csc_decoder: - backing = window._backing - backing._video_decoder = video_decoder - backing._csc_decoder = csc_decoder - backing._decoder_lock = decoder_lock - finally: - if decoder_lock: - decoder_lock.release() - log("replaced all the windows with opengl=%s: %s", self.opengl_enabled, self._id_to_window) - gobject.type_register(XpraClient) diff --git a/src/xpra/client/gtk3/client.py b/src/xpra/client/gtk3/client.py index 8a40ec9370..6c9c82edcf 100644 --- a/src/xpra/client/gtk3/client.py +++ b/src/xpra/client/gtk3/client.py @@ -99,9 +99,4 @@ def get_root_size(self): w, h = 1920, 1080 return w, h - def init_opengl(self, enable_opengl): - self.opengl_enabled = False - self.opengl_props = {"info" : "GTK3 does not support OpenGL"} - - GObject.type_register(XpraClient) diff --git a/src/xpra/client/gtk_base/gtk_client_base.py b/src/xpra/client/gtk_base/gtk_client_base.py index afeabfdde1..8143c7f3a3 100644 --- a/src/xpra/client/gtk_base/gtk_client_base.py +++ b/src/xpra/client/gtk_base/gtk_client_base.py @@ -6,7 +6,7 @@ # later version. See the file COPYING for details. import sys -from xpra.gtk_common.gobject_compat import import_gobject, import_gtk, import_gdk +from xpra.gtk_common.gobject_compat import import_gobject, import_gtk, import_gdk, is_gtk3 gobject = import_gobject() gtk = import_gtk() gdk = import_gdk() @@ -14,6 +14,7 @@ from xpra.log import Logger log = Logger("gtk", "main") +opengllog = Logger("gtk", "opengl") cursorlog = Logger("gtk", "client", "cursor") from xpra.gtk_common.quit import (gtk_main_quit_really, @@ -34,11 +35,18 @@ class GTKXpraClient(UIXpraClient, GObjectXpraClient): __gsignals__ = UIXpraClient.__gsignals__ + ClientWindowClass = None + GLClientWindowClass = None + def __init__(self): GObjectXpraClient.__init__(self) UIXpraClient.__init__(self) self.session_info = None self.bug_report = None + #opengl bits: + self.client_supports_opengl = False + self.opengl_enabled = False + self.opengl_props = {} def init(self, opts): GObjectXpraClient.init(self, opts) @@ -310,3 +318,113 @@ def window_bell(self, window, device, percent, pitch, duration, bell_class, bell if not system_bell(gdkwindow, device, percent, pitch, duration, bell_class, bell_id, bell_name): #fallback to simple beep: gdk.beep() + + + #OpenGL bits: + def init_opengl(self, enable_opengl): + opengllog("init_opengl(%s)", enable_opengl) + #enable_opengl can be True, False or None (auto-detect) + if enable_opengl is False: + self.opengl_props["info"] = "disabled by configuration" + return + from xpra.scripts.config import OpenGL_safety_check + warning = OpenGL_safety_check() + if warning: + if enable_opengl is True: + opengllog.warn("OpenGL safety warning (enabled at your own risk): %s", warning) + self.opengl_props["info"] = "forced enabled despite: %s" % warning + else: + opengllog.warn("OpenGL disabled: %s", warning) + self.opengl_props["info"] = "disabled: %s" % warning + return + self.opengl_props["info"] = "" + try: + opengllog("init_opengl: going to import xpra.client.gl") + __import__("xpra.client.gl", {}, {}, []) + __import__("xpra.client.gl.gtk_compat", {}, {}, []) + gl_check = __import__("xpra.client.gl.gl_check", {}, {}, ["check_support"]) + opengllog("init_opengl: gl_check=%s", gl_check) + w, h = self.get_root_size() + min_texture_size = max(w, h) + self.opengl_props = gl_check.check_support(min_texture_size, force_enable=(enable_opengl is True)) + GTK_GL_CLIENT_WINDOW_MODULE = "xpra.client.gl.gtk%s.gl_client_window" % (2+int(is_gtk3())) + opengllog("init_opengl: trying to load GL client window module '%s'", GTK_GL_CLIENT_WINDOW_MODULE) + gl_client_window = __import__(GTK_GL_CLIENT_WINDOW_MODULE, {}, {}, ["GLClientWindow"]) + self.GLClientWindowClass = gl_client_window.GLClientWindow + self.client_supports_opengl = True + self.opengl_enabled = True + except ImportError as e: + opengllog.warn("OpenGL support could not be enabled:") + opengllog.warn(" %s", e) + self.opengl_props["info"] = str(e) + except Exception as e: + opengllog.error("Error loading OpenGL support:") + opengllog.error(" %s", e, exc_info=True) + self.opengl_props["info"] = str(e) + + def get_client_window_classes(self, metadata, override_redirect): + log("get_client_window_class(%s, %s) GLClientWindowClass=%s, opengl_enabled=%s, mmap_enabled=%s, encoding=%s", metadata, override_redirect, self.GLClientWindowClass, self.opengl_enabled, self.mmap_enabled, self.encoding) + if self.GLClientWindowClass is None or not self.opengl_enabled: + return [self.ClientWindowClass] + return [self.GLClientWindowClass, self.ClientWindowClass] + + def toggle_opengl(self, *args): + assert self.window_unmap, "server support for 'window_unmap' is required for toggling opengl at runtime" + self.opengl_enabled = not self.opengl_enabled + opengllog("opengl_toggled: %s", self.opengl_enabled) + def fake_send(*args): + opengllog("fake_send(%s)", args) + #now replace all the windows with new ones: + for wid, window in self._id_to_window.items(): + if window.is_tray(): + #trays are never GL enabled, so don't bother re-creating them + #(might cause problems anyway if we did) + continue + #ignore packets from old window: + window.send = fake_send + #copy attributes: + x, y = window._pos + w, h = window._size + client_properties = window._client_properties + metadata = window._metadata + override_redirect = window._override_redirect + backing = window._backing + video_decoder = None + csc_decoder = None + decoder_lock = None + try: + if backing: + video_decoder = backing._video_decoder + csc_decoder = backing._csc_decoder + decoder_lock = backing._decoder_lock + if decoder_lock: + decoder_lock.acquire() + opengllog("toggle_opengl() will preserve video=%s and csc=%s for %s", video_decoder, csc_decoder, wid) + backing._video_decoder = None + backing._csc_decoder = None + backing._decoder_lock = None + + #now we can unmap it: + self.destroy_window(wid, window) + #explicitly tell the server we have unmapped it: + #(so it will reset the video encoders, etc) + self.send("unmap-window", wid) + try: + del self._id_to_window[wid] + except: + pass + try: + del self._window_to_id[window] + except: + pass + #create the new window, which should honour the new state of the opengl_enabled flag: + window = self.make_new_window(wid, x, y, w, h, metadata, override_redirect, client_properties) + if video_decoder or csc_decoder: + backing = window._backing + backing._video_decoder = video_decoder + backing._csc_decoder = csc_decoder + backing._decoder_lock = decoder_lock + finally: + if decoder_lock: + decoder_lock.release() + opengllog("replaced all the windows with opengl=%s: %s", self.opengl_enabled, self._id_to_window)