-
-
Notifications
You must be signed in to change notification settings - Fork 171
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
client: Add GLClientWindow, a GtkGL window. OpenGL colorspace convers…
…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.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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__ = { | ||
|