Skip to content

Commit

Permalink
client: Add GLClientWindow, a GtkGL window. OpenGL colorspace convers…
Browse files Browse the repository at this point in the history
…ion divides CPU usage by 2+.

This implementation is incomplete :
- it doesn't support JPEG and PNG encodings)
- do_expose_event isn't called automatically, for a reason I could not figure out.
- We use GLClientWindow by default but do not gracefully fall back on the previous system if GL is unavailable or too slow.
- this breaks the build on win32 (!)

git-svn-id: https://xpra.org/svn/Xpra/trunk@922 3bb7dfac-3a0b-4e04-842a-767bc560f471
  • Loading branch information
Arthur Huillet committed Jun 15, 2012
1 parent c0dcb9a commit fc01e9c
Showing 1 changed file with 343 additions and 0 deletions.
343 changes: 343 additions & 0 deletions src/xpra/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,349 @@ def _focus_change(self, *args):

gobject.type_register(ClientWindow)

from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GL.ARB.vertex_program import *
from OpenGL.GL.ARB.fragment_program import *

class GLClientWindow(ClientWindow):
def __init__(self, client, wid, x, y, w, h, metadata, override_redirect):
ClientWindow.__init__(self, client, wid, x, y, w, h, metadata, override_redirect)
display_mode = (gtk.gdkgl.MODE_RGB | gtk.gdkgl.MODE_SINGLE)
# We use single buffer because double doesn't work, figure out why
try:
self.glconfig = gtk.gdkgl.Config(mode=display_mode)
except gtk.gdkgl.NoMatches:
raise SystemExit

self.glarea = gtk.gtkgl.DrawingArea(self.glconfig)
self.glarea.set_size_request(w, h)
self.glarea.show()
self.add(self.glarea)
self._on_close = []
self.textures = [ 0 ]

def do_configure_event(self, event):
ClientWindow.do_configure_event(self, event)
drawable = self.glarea.get_gl_drawable()
context = self.glarea.get_gl_context()

self.use_openGL_CSC = True
self.yuv420_shader = None
self.current_mode = 0 # 0 = uninitialized 1 = RGB 2 = YUV

if not drawable.gl_begin(context):
raise SystemExit, "** Cannot create OpenGL rendering context!"

gl_major = glGetString(GL_VERSION)[0]
gl_minor = glGetString(GL_VERSION)[2]

if gl_major == 1 and gl_minor < 1:
raise SystemExit, "** OpenGL output requires OpenGL version 1.1 or greater"

w, h = self.get_size()
log.debug("Configure widget size size is %d, %d" % (w, h))
glViewport(0, 0, w, h)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glOrtho(0.0, w, h, 0.0, -1.0, 1.0);
glMatrixMode(GL_MODELVIEW)
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);

if self.textures[0] == 0:
self.textures = glGenTextures(3)

drawable.gl_end()


def do_expose_event(self, event):
log.debug("my_do_expose_event(%s) area=%s", event, event.area)
if not (self.flags() & gtk.MAPPED):
log.error("returning now")
return
x,y,_,_ = event.area
self.render_image()
self.glarea.window.invalidate_rect(self.glarea.allocation, False)
# Update window synchronously (fast).
self.glarea.window.process_updates(False)

def draw_region(self, x, y, width, height, coding, img_data, rowstride):
#log.error("draw_region(%s, %s, %s, %s, %s, %s bytes, %s)", x, y, width, height, coding, len(img_data), rowstride)
if coding == "mmap":
# No GL output for mmap
assert coding != "mmap"
elif coding == "rgb24":
if rowstride>0:
assert len(img_data) == rowstride * height
else:
assert len(img_data) == width * 3 * height
self.update_texture_rgb24(img_data, x, y, width, height, rowstride)
elif coding == "x264":
assert "x264" in ENCODINGS
from xpra.x264.codec import DECODERS, Decoder #@UnresolvedImport
self.paint_with_video_decoder(DECODERS, Decoder, "x264", img_data, x, y, width, height, rowstride)
elif coding == "vpx":
assert "vpx" in ENCODINGS
from xpra.vpx.codec import DECODERS, Decoder #@UnresolvedImport
self.paint_with_video_decoder(DECODERS, Decoder, "vpx", img_data, x, y, width, height, rowstride)
else:
raise SystemExit, "** No JPEG/PNG support for OpenGL"
queue_draw(self, x, y, width, height)

# This is a copypaste from window_backing.py...
def paint_with_video_decoder(self, decoders, factory, coding, img_data, x, y, width, height, rowstride):
assert x==0 and y==0
decoder = decoders.get(self._id)
if decoder and (decoder.get_width()!=width or decoder.get_height()!=height):
log("paint_with_video_decoder: window dimensions have changed from %s to %s", (decoder.get_width(), decoder.get_height()), (width, height))
decoder.clean()
decoder.init(width, height)
if decoder is None:
decoder = factory()
decoder.init(width, height)
decoders[self._id] = decoder
def close_decoder():
log("closing %s decoder for window %s", coding, self._id)
decoder.clean()
del decoders[self._id]
self._on_close.append(close_decoder)
try:
if self.use_openGL_CSC == True:
err, outstride, data = decoder.decompress_image_to_yuv(img_data)
if err!=0:
log.error("paint_with_video_decoder: ouch, decompression error %s", err)
return
if not data:
log.error("paint_with_video_decoder: ouch, no data from %s decoder", coding)
return
log("paint_with_video_decoder: decompressed %s to %s bytes (%s%%) of rgb24 (%s*%s*3=%s) (outstride: %s)", len(img_data), len(data), int(100*len(img_data)/len(data)),width, height, width*height*3, outstride)
self.update_texture_yuv420(data, x, y, width, height, outstride)
else:
err, outstride, data = decoder.decompress_image_to_rgb(img_data)
if err!=0:
log.error("paint_with_video_decoder: ouch, decompression error %s", err)
return
if not data:
log.error("paint_with_video_decoder: ouch, no data from %s decoder", coding)
return
log("paint_with_video_decoder: decompressed %s to %s bytes (%s%%) of rgb24 (%s*%s*3=%s) (outstride: %s)", len(img_data), len(data), int(100*len(img_data)/len(data)),width, height, width*height*3, outstride)
self.update_texture_rgb24(data, x, y, width, height, outstride)
finally:
if self.use_openGL_CSC == False:
decoder.free_image()
log("done")

def update_texture_rgb24(self, img_data, x, y, width, height, rowstride):
drawable = self.glarea.get_gl_drawable()
context = self.glarea.get_gl_context()
if not drawable.gl_begin(context):
raise SystemExit, "** Cannot create OpenGL rendering context!"

glBindTexture(GL_TEXTURE_RECTANGLE_ARB, self.textures[0])
glPixelStorei(GL_UNPACK_ROW_LENGTH, rowstride/3)

if self.current_mode == 2:
raise SystemExit, "** YUV -> RGB mode change unimplemented!"
elif self.current_mode == 0:
log.error("Creating new RGB texture")
w, h = self.get_size()
# First time we draw must be full image
assert w == width and h == height
glEnable(GL_TEXTURE_RECTANGLE_ARB)
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, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, 0)
self.current_mode = 1
log.debug("Updating RGB texture")
glTexSubImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, img_data)
drawable.gl_end()
self.render_image()

def update_texture_yuv420(self, img_data, x, y, width, height, rowstrides):
drawable = self.glarea.get_gl_drawable()
context = self.glarea.get_gl_context()
window_width, window_height = self.get_size()
if not drawable.gl_begin(context):
raise SystemExit, "** Cannot create OpenGL rendering context!"

if self.current_mode == 1:
raise SystemExit, "** RGB -> YUV mode change unimplemented!"
elif self.current_mode == 0:
log.debug("Creating new YUV textures")

# Create textures of the same size as the window's
glEnable(GL_TEXTURE_RECTANGLE_ARB)
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, self.textures[0])
glEnable(GL_TEXTURE_RECTANGLE_ARB)
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, GL_LUMINANCE, window_width, window_height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, 0);

glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, self.textures[1])
glEnable(GL_TEXTURE_RECTANGLE_ARB)
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_LUMINANCE, window_width/2, window_height/2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, 0);

glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, self.textures[2])
glEnable(GL_TEXTURE_RECTANGLE_ARB)
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_LUMINANCE, window_width/2, window_height/2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, 0);

log.debug("Assigning fragment program")
glEnable(GL_FRAGMENT_PROGRAM_ARB)
if not self.yuv420_shader:
self.yuv420_shader = [ 1 ]
glGenProgramsARB(1, self.yuv420_shader)
glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, self.yuv420_shader[0])
# The following fragprog is:
# * MIT X11 license, Copyright (c) 2007 by:
# * Michael Dominic K. <[email protected]>
#http://www.mdk.org.pl/2007/11/17/gl-colorspace-conversions
prog = """!!ARBfp1.0
# cgc version 3.1.0010, build date Feb 10 2012
# command line args: -profile arbfp1
# source file: yuv.cg
#vendor NVIDIA Corporation
#version 3.1.0.10
#profile arbfp1
#program main
#semantic main.IN
#var float2 IN.texcoord1 : $vin.TEXCOORD0 : TEX0 : 0 : 1
#var float2 IN.texcoord2 : $vin.TEXCOORD1 : TEX1 : 0 : 1
#var float2 IN.texcoord3 : $vin.TEXCOORD2 : TEX2 : 0 : 1
#var samplerRECT IN.texture1 : TEXUNIT0 : texunit 0 : 0 : 1
#var samplerRECT IN.texture2 : TEXUNIT1 : texunit 1 : 0 : 1
#var samplerRECT IN.texture3 : TEXUNIT2 : texunit 2 : 0 : 1
#var float4 IN.color : $vin.COLOR0 : COL0 : 0 : 1
#var float4 main.color : $vout.COLOR0 : COL : -1 : 1
#const c[0] = 1.1643835 2.017231 0 0.5
#const c[1] = 0.0625 1.1643835 -0.3917616 -0.81296802
#const c[2] = 1.1643835 0 1.5960271
PARAM c[3] = { { 1.1643835, 2.017231, 0, 0.5 },
{ 0.0625, 1.1643835, -0.3917616, -0.81296802 },
{ 1.1643835, 0, 1.5960271 } };
TEMP R0;
TEMP R1;
TEX R0.x, fragment.texcoord[2], texture[2], RECT;
ADD R1.z, R0.x, -c[0].w;
TEX R1.x, fragment.texcoord[0], texture[0], RECT;
TEX R0.x, fragment.texcoord[1], texture[1], RECT;
ADD R1.x, R1, -c[1];
ADD R1.y, R0.x, -c[0].w;
DP3 result.color.z, R1, c[0];
DP3 result.color.y, R1, c[1].yzww;
DP3 result.color.x, R1, c[2];
MOV result.color.w, fragment.color.primary;
END
# 10 instructions, 2 R-regs
"""
glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, len(prog), prog)
log.error(glGetString(GL_PROGRAM_ERROR_STRING_ARB))
glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, self.yuv420_shader[0])

self.current_mode = 2

# Clamp width and height to the actual texture size
if x + width > window_width:
width = window_width - x
if y + height > window_height:
height = window_height - y

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, self.textures[0])
glPixelStorei(GL_UNPACK_ROW_LENGTH, rowstrides[0])
glTexSubImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, x, y, width, height, GL_LUMINANCE, GL_UNSIGNED_BYTE, img_data[0])

glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, self.textures[1])
glPixelStorei(GL_UNPACK_ROW_LENGTH, rowstrides[1])
glTexSubImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, x, y, width/2, height/2, GL_LUMINANCE, GL_UNSIGNED_BYTE, img_data[1])

glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, self.textures[2])
glPixelStorei(GL_UNPACK_ROW_LENGTH, rowstrides[2])
glTexSubImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, x, y, width/2, height/2, GL_LUMINANCE, GL_UNSIGNED_BYTE, img_data[2])

drawable.gl_end()
self.render_image()



def render_image(self):
drawable = self.glarea.get_gl_drawable()
context = self.glarea.get_gl_context()
w, h = self.get_size()
if not drawable.gl_begin(context):
raise SystemExit, "** Cannot create OpenGL rendering context!"
texcoords = [ [ 0, 0 ],
[ 0, h ],
[ w, h ],
[ w, 0 ] ]
texcoords_half = [ [ 0, 0 ],
[ 0, h/2 ],
[ w/2, h/2 ],
[ w/2, 0 ] ]
vtxcoords = texcoords

if self.current_mode == 1: #RGB
glVertexPointeri(vtxcoords)
glTexCoordPointeri(texcoords)
glDrawArrays(GL_QUADS, 0, 4);
elif self.current_mode == 2: #YUV
glEnable(GL_FRAGMENT_PROGRAM_ARB)
glBegin(GL_QUADS);
glMultiTexCoord2i(GL_TEXTURE0, 0, 0);
glMultiTexCoord2i(GL_TEXTURE1, 0, 0);
glMultiTexCoord2i(GL_TEXTURE2, 0, 0);
glVertex2i(0, 0);

glMultiTexCoord2i(GL_TEXTURE0, 0, h);
glMultiTexCoord2i(GL_TEXTURE1, 0, h/2);
glMultiTexCoord2i(GL_TEXTURE2, 0, h/2);
glVertex2i(0, h);

glMultiTexCoord2i(GL_TEXTURE0, w, h);
glMultiTexCoord2i(GL_TEXTURE1, w/2, h/2);
glMultiTexCoord2i(GL_TEXTURE2, w/2, h/2);
glVertex2i(w, h);

glMultiTexCoord2i(GL_TEXTURE0, w, 0);
glMultiTexCoord2i(GL_TEXTURE1, w/2, 0);
glMultiTexCoord2i(GL_TEXTURE2, w/2, 0);
glVertex2i(w, 0);
glEnd()
drawable.swap_buffers()
drawable.gl_end()

def move_resize(self, x, y, w, h):
assert self._override_redirect
self.window.move_resize(x, y, w, h)

def destroy(self):
self._unfocus()
self.glarea.destroy()
gtk.Window.destroy(self)
for cb in self._on_close:
try:
log("calling %s", cb)
cb()
except:
log.error("error on close callback %s", cb, exc_info=True)
self._on_close = []








class XpraClient(XpraClientBase):
__gsignals__ = {
Expand Down

0 comments on commit fc01e9c

Please sign in to comment.