Skip to content

Commit

Permalink
#385: transparency support via opengl and more:
Browse files Browse the repository at this point in the history
* always try to create a GL context which supports transparency
* always store the RGB texture as RGBA, paint_rgb24 and paint_rgb32 only differ in the bytes-per-pixel
* add support for BGRA / BGR modes via simple GL data upload flag - which means we can handle mmap (transparent) data directly, and server side need to copy it either!

git-svn-id: https://xpra.org/svn/Xpra/trunk@4880 3bb7dfac-3a0b-4e04-842a-767bc560f471
  • Loading branch information
totaam committed Dec 8, 2013
1 parent 414b92e commit 80e5067
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 40 deletions.
52 changes: 38 additions & 14 deletions src/xpra/client/gl/gl_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,45 @@

BLACKLIST = {"vendor" : ["nouveau", "Humper"]}

#needed on win32?
DEFAULT_DOUBLE_BUFFERED=0
DEFAULT_DOUBLE_BUFFERED = 0
DEFAULT_ALPHA = 1
if sys.platform.startswith("win"):
DEFAULT_DOUBLE_BUFFERED=1
#needed on win32?
DEFAULT_DOUBLE_BUFFERED = 1
#does not work on win32:
DEFAULT_ALPHA = 0
DOUBLE_BUFFERED = os.environ.get("XPRA_OPENGL_DOUBLE_BUFFERED", str(DEFAULT_DOUBLE_BUFFERED))=="1"
ALPHA = os.environ.get("XPRA_OPENGL_ALPHA", str(DEFAULT_ALPHA))=="1"


def get_DISPLAY_MODE():
import gtk.gdkgl
#return gtk.gdkgl.MODE_RGB | gtk.gdkgl.MODE_DEPTH | gtk.gdkgl.MODE_DOUBLE
#gtk.gdkgl.MODE_DEPTH
mode = 0
if ALPHA:
mode = mode | gtk.gdkgl.MODE_RGBA | gtk.gdkgl.MODE_ALPHA
else:
mode = mode | gtk.gdkgl.MODE_RGB
if DOUBLE_BUFFERED:
return gtk.gdkgl.MODE_RGB | gtk.gdkgl.MODE_DOUBLE
return gtk.gdkgl.MODE_RGB | gtk.gdkgl.MODE_SINGLE
mode = mode | gtk.gdkgl.MODE_DOUBLE
else:
mode = mode | gtk.gdkgl.MODE_SINGLE
return mode

def get_MODE_names(mode):
import gtk.gdkgl
friendly_mode_names = {gtk.gdkgl.MODE_RGB : "RGB",
gtk.gdkgl.MODE_RGB : "RGBA",
gtk.gdkgl.MODE_ALPHA : "ALPHA",
gtk.gdkgl.MODE_DEPTH : "DEPTH",
gtk.gdkgl.MODE_DOUBLE : "DOUBLE",
gtk.gdkgl.MODE_SINGLE : "SINGLE"}
friendly_modes = [v for k,v in friendly_mode_names.items() if k>0 and (k&mode)==k]
#special case for single (value is zero!)
if not (mode&gtk.gdkgl.MODE_DOUBLE==gtk.gdkgl.MODE_DOUBLE):
friendly_modes.append("SINGLE")
return friendly_modes


#by default, we raise an ImportError as soon as we find something missing:
def raise_error(msg):
Expand Down Expand Up @@ -213,17 +239,15 @@ def check_support(min_texture_size=0, force_enable=False):
display_mode = get_DISPLAY_MODE()
try:
glconfig = gtk.gdkgl.Config(mode=display_mode)
except gtk.gdkgl.NoMatches:
except gtk.gdkgl.NoMatches, e:
debug("no match: %s, toggling double-buffering", e)
display_mode &= ~gtk.gdkgl.MODE_DOUBLE
glconfig = gtk.gdkgl.Config(mode=display_mode)
friendly_mode_names = {gtk.gdkgl.MODE_RGB : "RGB",
gtk.gdkgl.MODE_DEPTH : "DEPTH",
gtk.gdkgl.MODE_DOUBLE : "DOUBLE",
gtk.gdkgl.MODE_SINGLE : "SINGLE"}
friendly_modes = [v for k,v in friendly_mode_names.items() if (k&display_mode)==k]
debug("using display mode: %s", friendly_modes)
props["display_mode"] = friendly_modes
props["display_mode"] = get_MODE_names(display_mode)
props["glconfig"] = glconfig
props["has_alpha"] = glconfig.has_alpha()
props["rgba"] = glconfig.is_rgba()
debug("GL props=%s", props)
assert gtk.gdkgl.query_extension()
glcontext, gldrawable, glext, w = None, None, None, None
try:
Expand Down
11 changes: 11 additions & 0 deletions src/xpra/client/gl/gl_client_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ def __str__(self):
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):
if not self._backing.paint_screen or not self._backing._backing or not self.can_have_spinner():
return
Expand Down
76 changes: 54 additions & 22 deletions src/xpra/client/gl/gl_window_backing.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,16 @@
from OpenGL.GL import GL_PROJECTION, GL_MODELVIEW, \
GL_UNPACK_ROW_LENGTH, GL_UNPACK_ALIGNMENT, \
GL_TEXTURE_MAG_FILTER, GL_TEXTURE_MIN_FILTER, GL_NEAREST, \
GL_UNSIGNED_BYTE, GL_LUMINANCE, GL_RGB, GL_LINEAR, \
GL_UNSIGNED_BYTE, GL_LUMINANCE, GL_LINEAR, \
GL_TEXTURE0, GL_TEXTURE1, GL_TEXTURE2, GL_QUADS, GL_COLOR_BUFFER_BIT, \
GL_DONT_CARE, GL_TRUE,\
GL_DONT_CARE, GL_TRUE, \
GL_RGB, GL_RGBA, GL_BGR, GL_BGRA, \
GL_BLEND, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, \
glActiveTexture, glTexSubImage2D, \
glGetString, glViewport, glMatrixMode, glLoadIdentity, glOrtho, \
glGenTextures, glDisable, \
glBindTexture, glPixelStorei, glEnable, glBegin, glFlush, \
glBlendFunc, \
glTexParameteri, \
glTexImage2D, \
glMultiTexCoord2i, \
Expand Down Expand Up @@ -79,6 +82,8 @@ def py_gl_debug_callback(source, error_type, error_id, severity, length, message
# 3 = V plane
# 4 = RGB updates
# 5 = FBO texture (guaranteed up-to-date window contents)
# The first four are used to update the FBO,
# the FBO is what is painted on screen.
TEX_Y = 0
TEX_U = 1
TEX_V = 2
Expand Down Expand Up @@ -108,6 +113,9 @@ def __init__(self, wid, w, h, 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)
rgba = self._backing.get_screen().get_rgba_colormap()
if rgba:
self._backing.set_colormap(rgba)
self._backing.show()
self._backing.connect("expose_event", self.gl_expose_event)
self.textures = None # OpenGL texture IDs
Expand Down Expand Up @@ -189,8 +197,8 @@ def gl_init(self):
# glEnableClientState(GL_VERTEX_ARRAY)
# glEnableClientState(GL_TEXTURE_COORD_ARRAY)

# Clear to white
glClearColor(1.0, 1.0, 1.0, 1.0)
# Clear background to transparent white
glClearColor(1.0, 1.0, 1.0, 0.0)

# Default state is good for YUV painting:
# - fragment program enabled
Expand All @@ -205,7 +213,7 @@ def gl_init(self):

# Define empty FBO texture and set rendering to FBO
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, self.textures[TEX_FBO])
glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, None)
glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA, w, h, 0, GL_RGBA, 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 @@ -246,19 +254,19 @@ def gl_begin(self):
return None
return drawable

def set_rgb24_paint_state(self):
# Set GL state for RGB24 painting:
def set_rgb_paint_state(self):
# Set GL state for RGB painting:
# no fragment program
# only tex unit #0 active
self.gl_marker("Switching to RGB24 paint state")
self.gl_marker("Switching to RGB paint state")
glDisable(GL_FRAGMENT_PROGRAM_ARB);
for texture in (GL_TEXTURE1, GL_TEXTURE2):
glActiveTexture(texture)
glDisable(GL_TEXTURE_RECTANGLE_ARB)
glActiveTexture(GL_TEXTURE0);
glEnable(GL_TEXTURE_RECTANGLE_ARB)

def unset_rgb24_paint_state(self):
def unset_rgb_paint_state(self):
# Reset state to our default
self.gl_marker("Switching back to YUV paint state")
glEnable(GL_FRAGMENT_PROGRAM_ARB)
Expand All @@ -279,10 +287,16 @@ def present_fbo(self, drawable):
# Change state to target screen instead of our FBO
glBindFramebuffer(GL_FRAMEBUFFER, 0)

# transparent background:
glClearColor(1.0, 0.0, 1.0, 0)

# Draw FBO texture on screen
self.set_rgb24_paint_state()
self.set_rgb_paint_state()

glBindTexture(GL_TEXTURE_RECTANGLE_ARB, self.textures[TEX_FBO])
# support alpha channel if present:
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)

w, h = self.size
glBegin(GL_QUADS)
Expand All @@ -306,7 +320,7 @@ def present_fbo(self, drawable):
glFlush()
self.gl_frame_terminator()

self.unset_rgb24_paint_state()
self.unset_rgb_paint_state()
glBindFramebuffer(GL_FRAMEBUFFER, self.offscreen_fbo)
debug("%s.present_fbo() done", self)

Expand All @@ -320,20 +334,23 @@ def gl_expose_event(self, glarea, event):
finally:
drawable.gl_end()

def _do_paint_rgb32(self, *args):
#FIXME #385: add transparency to GL
raise Exception("bug: it should be impossible to get here: gl backing does not handle transparency")
def _do_paint_rgb32(self, img_data, x, y, width, height, rowstride, options, callbacks):
self._do_paint_rgb(32, img_data, x, y, width, height, rowstride, options, callbacks)

def _do_paint_rgb24(self, img_data, x, y, width, height, rowstride, options, callbacks):
debug("%s._do_paint_rgb24(x=%d, y=%d, width=%d, height=%d, rowstride=%d)", self, x, y, width, height, rowstride)
self._do_paint_rgb(24, img_data, x, y, width, height, rowstride, options, callbacks)

def _do_paint_rgb(self, bpp, img_data, x, y, width, height, rowstride, options, callbacks):
debug("%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:
debug("%s._do_paint_rgb24(..) drawable is not set!", self)
debug("%s._do_paint_rgb(..) drawable is not set!", self)
return False

try:
self.set_rgb24_paint_state()
self.set_rgb_paint_state()

bytes_per_pixel = bpp/8
# Compute alignment and row length
row_length = 0
alignment = 1
Expand All @@ -344,17 +361,33 @@ def _do_paint_rgb24(self, img_data, x, y, width, height, rowstride, options, cal
# If number of extra bytes is greater than the alignment value,
# then we also have to set row_length
# Otherwise it remains at 0 (= width implicitely)
if (rowstride - width * 3) > a:
row_length = width + (rowstride - width * 3) / 3
if (rowstride - width * bytes_per_pixel) > a:
row_length = width + (rowstride - width * bytes_per_pixel) / bytes_per_pixel

self.gl_marker("RGB24 update at %d,%d, size %d,%d, stride is %d, row length %d, alignment %d" % (x, y, width, height, rowstride, row_length, alignment))
self.gl_marker("RGB%s update at %d,%d, size %d,%d, stride is %d, row length %d, alignment %d" % (bpp, x, y, width, height, rowstride, row_length, alignment))
# Upload data as temporary RGB texture
rgb_format = options.get("rgb_format", "RGB")
log.info("options=%s, rgb format=%s", options, rgb_format)
if bpp==24:
if rgb_format=="BGR":
pformat = GL_BGR
else:
assert rgb_format=="RGB"
pformat = GL_RGB
else:
assert bpp==32
if rgb_format=="BGRA":
pformat = GL_BGRA
else:
assert rgb_format=="RGBA"
pformat = GL_RGBA

glBindTexture(GL_TEXTURE_RECTANGLE_ARB, self.textures[TEX_RGB])
glPixelStorei(GL_UNPACK_ROW_LENGTH, row_length)
glPixelStorei(GL_UNPACK_ALIGNMENT, alignment)
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, 4, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, img_data)
glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA, width, height, 0, pformat, GL_UNSIGNED_BYTE, img_data)

# Draw textured RGB quad at the right coordinates
glBegin(GL_QUADS)
Expand All @@ -371,7 +404,6 @@ def _do_paint_rgb24(self, img_data, x, y, width, height, rowstride, options, cal
# Present update to screen
self.present_fbo(drawable)
# present_fbo has reset state already

finally:
drawable.gl_end()
return True
Expand Down
4 changes: 2 additions & 2 deletions src/xpra/client/gtk2/client_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def setup_window(self):

def set_alpha(self):
#by default, only RGB (no transparency):
self._client_properties["encodings.rgb_formats"] = ("RGB",)
self._client_properties["encodings.rgb_formats"] = ["RGB"]
if sys.platform.startswith("win"):
return
if self._has_alpha and not self.is_realized():
Expand All @@ -101,7 +101,7 @@ def set_alpha(self):
else:
self.debug("set_alpha() using rgba colormap for %s, realized=%s", self._id, self.is_realized())
self.set_colormap(rgba)
self._client_properties["encodings.rgb_formats"] = ("RGBA",)
self._client_properties["encodings.rgb_formats"] = ["RGBA"]

def set_modal(self, modal):
#with gtk2 setting the window as modal would prevent
Expand Down
5 changes: 3 additions & 2 deletions src/xpra/client/window_backing_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -445,9 +445,10 @@ def paint_mmap(self, img_data, x, y, width, height, rowstride, options, callback
assert self.mmap_enabled
data = mmap_read(self.mmap, img_data)
rgb_format = options.get("rgb_format", "rgb24")
if rgb_format=="RGB":
#Note: BGR(A) is only handled by gl_window_backing
if rgb_format in ("RGB", "BGR"):
self.do_paint_rgb24(data, x, y, width, height, rowstride, options, callbacks)
elif rgb_format=="RGBA":
elif rgb_format in ("RGBA", "BGRA"):
self.do_paint_rgb32(data, x, y, width, height, rowstride, options, callbacks)
else:
raise Exception("invalid rgb format: %s" % rgb_format)
Expand Down

0 comments on commit 80e5067

Please sign in to comment.