xpra icon
Bug tracker and wiki

This bug tracker and wiki are being discontinued
please use https://github.com/Xpra-org/xpra instead.


Ticket #147: gl_window_backing.py

File gl_window_backing.py, 10.5 KB (added by Antoine Martin, 8 years ago)

work in progress to make it work with nvidia driver.. not too far

Line 
1# This file is part of Parti.
2# Copyright (C) 2012 Antoine Martin <antoine@devloop.org.uk>
3# Parti is released under the terms of the GNU GPL v2, or, at your option, any
4# later version. See the file COPYING for details.
5
6#only works with gtk2:
7from gtk import gdk
8assert gdk
9import gtk.gdkgl, gtk.gtkgl         #@UnresolvedImport
10assert gtk.gdkgl is not None and gtk.gtkgl is not None
11import gobject
12
13from wimpiggy.log import Logger
14log = Logger()
15
16from xpra.codec_constants import YUV420P, YUV422P, YUV444P
17from xpra.gl.gl_colorspace_conversions import GL_COLORSPACE_CONVERSIONS
18from xpra.window_backing import PixmapBacking
19from OpenGL.GL import GL_PROJECTION, GL_MODELVIEW, GL_VERTEX_ARRAY, \
20    GL_TEXTURE_COORD_ARRAY, GL_FRAGMENT_PROGRAM_ARB, \
21    GL_PROGRAM_ERROR_STRING_ARB, GL_RGB, GL_PROGRAM_FORMAT_ASCII_ARB, \
22    GL_TEXTURE_RECTANGLE, GL_TEXTURE_RECTANGLE_ARB, GL_UNPACK_ROW_LENGTH, \
23    GL_TEXTURE_MAG_FILTER, GL_TEXTURE_MIN_FILTER, GL_NEAREST, \
24    GL_UNSIGNED_BYTE, GL_LUMINANCE, GL_LINEAR, \
25    GL_TEXTURE0, GL_TEXTURE1, GL_TEXTURE2, GL_QUADS, \
26    glActiveTexture, glTexSubImage2D, glTexCoord2i, \
27    glGetString, glViewport, glMatrixMode, glLoadIdentity, glOrtho, \
28    glEnableClientState, glGenTextures, glDisable, \
29    glBindTexture, glPixelStorei, glEnable, glBegin, glFlush, \
30    glTexParameteri, \
31    glTexImage2D, \
32    glMultiTexCoord2i, \
33    glVertex2i, glEnd
34from OpenGL.GL.ARB.vertex_program import glGenProgramsARB, glDeleteProgramsARB, glBindProgramARB, glProgramStringARB
35
36"""
37This is the gtk2 + OpenGL version.
38"""
39class GLPixmapBacking(PixmapBacking):
40    RGB24 = 1   #make sure this never clashes with codec_constants!
41
42    def __init__(self, wid, w, h, mmap_enabled, mmap):
43        PixmapBacking.__init__(self, wid, w, h, mmap_enabled, mmap)
44        display_mode = (gtk.gdkgl.MODE_RGB | gtk.gdkgl.MODE_SINGLE)
45        self.glconfig = gtk.gdkgl.Config(mode=display_mode)
46        self.glarea = gtk.gtkgl.DrawingArea(self.glconfig)
47        self.glarea.show()
48        self.glarea.connect("expose_event", self.gl_expose_event)
49        self.textures = None # OpenGL texture IDs
50        self.yuv_shader = None
51        self.pixel_format = None
52        self.size = 0, 0
53
54    def init(self, w, h):
55        #also init the pixmap as backup:
56        self.size = w, h
57        self.yuv_shader = None
58        # Re-create textures
59        self.pixel_format = None
60        PixmapBacking.init(self, w, h)
61        self.drawable = self.gl_begin()
62
63        log("GL Pixmap backing size: %d x %d", w, h)
64        glViewport(0, 0, w, h)
65        glMatrixMode(GL_PROJECTION)
66        glLoadIdentity()
67        glOrtho(0.0, w, h, 0.0, -1.0, 1.0)
68        glMatrixMode(GL_MODELVIEW)
69        glEnableClientState(GL_VERTEX_ARRAY)
70        glEnableClientState(GL_TEXTURE_COORD_ARRAY)
71        glDisable(GL_FRAGMENT_PROGRAM_ARB)
72
73        if self.textures is None:
74            self.textures = glGenTextures(3)
75
76    def close(self):
77        PixmapBacking.close(self)
78        self.remove_shader()
79        self.glarea = None
80        self.glconfig = None
81
82    def remove_shader(self):
83        if self.yuv_shader:
84            glDisable(GL_FRAGMENT_PROGRAM_ARB)
85            glDeleteProgramsARB(1, self.yuv_shader)
86            self.yuv_shader = None
87
88    def gl_begin(self):
89        if self.glarea is None:
90            return None     #closed already
91        drawable = self.glarea.get_gl_drawable()
92        context = self.glarea.get_gl_context()
93        if drawable is None or context is None:
94            log.error("OpenGL error: no drawable or context!")
95            return None
96        if not drawable.gl_begin(context):
97            log.error("OpenGL error: cannot create rendering context!")
98        return drawable
99
100    def gl_end(self):
101        glFlush()
102        self.drawable.gl_end()
103
104    def gl_expose_event(self, glarea, event):
105        log("gl_expose_event(%s, %s)", glarea, event)
106        area = event.area
107        x, y, w, h = area.x, area.y, area.width, area.height
108        if not self.drawable:
109            return
110        self.render_image(x, y, w, h)
111
112    def _do_paint_rgb24(self, img_data, x, y, width, height, rowstride, options, callbacks):
113        log.info("do_paint_rgb24(%s bytes, %s, %s, %s, %s, %s, %s, %s)", len(img_data), x, y, width, height, rowstride, options, callbacks)
114        assert self.textures is not None
115        #cleanup if we were doing yuv previously:
116        if self.pixel_format!=GLPixmapBacking.RGB24:
117            self.remove_shader()
118            self.pixel_format = GLPixmapBacking.RGB24
119            # Create textures of the same size as the window's
120            glEnable(GL_TEXTURE_RECTANGLE_ARB)
121            glActiveTexture(GL_TEXTURE0)
122            glBindTexture(GL_TEXTURE_RECTANGLE_ARB, self.textures[0])
123            glEnable(GL_TEXTURE_RECTANGLE_ARB)
124            glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
125            glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
126            window_width, window_height = self.size
127            glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_LUMINANCE, window_width, window_height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, img_data)
128
129            log("Assigning fragment program")
130            glDisable(GL_FRAGMENT_PROGRAM_ARB)
131
132        glBindTexture(GL_TEXTURE_RECTANGLE_ARB, self.textures[0])
133        glPixelStorei(GL_UNPACK_ROW_LENGTH, rowstride/3)
134        for texture in (GL_TEXTURE1, GL_TEXTURE2):
135            glActiveTexture(texture)
136            glDisable(GL_TEXTURE_RECTANGLE_ARB)
137
138        glActiveTexture(GL_TEXTURE0)
139        glEnable(GL_TEXTURE_RECTANGLE_ARB)
140
141        glBegin(GL_QUADS)
142        for rx,ry in ((x, y), (x, y+height), (x+width, y+height), (x+width, y)):
143            glTexCoord2i(rx, ry)
144            glVertex2i(rx, ry)
145        glEnd()
146        glFlush()
147
148    def do_video_paint(self, coding, img_data, x, y, width, height, options, callbacks):
149        log.info("do_video_paint: options=%s, decoder=%s", options, type(self._video_decoder))
150        err, rowstrides, img_data = self._video_decoder.decompress_image_to_yuv(img_data, options)
151        success = err==0 and img_data and len(img_data)==3
152        if not success:
153            log.error("do_video_paint: %s decompression error %s on %s bytes of picture data for %sx%s pixels, options=%s",
154                      coding, err, len(img_data), width, height, options)
155            self.fire_paint_callbacks(callbacks, False)
156            return
157        csc_pixel_format = options.get("csc_pixel_format", -1)
158        pixel_format = self._video_decoder.get_pixel_format(csc_pixel_format)
159        def do_paint():
160            if not self.drawable:
161                return
162            try:
163                self.update_texture_yuv(img_data, x, y, width, height, rowstrides, pixel_format)
164                w, h = self.size
165                self.render_image(0, 0, w, h)
166                self.fire_paint_callbacks(callbacks, True)
167            except Exception, e:
168                log.error("OpenGL paint error: %s", e, exc_info=True)
169                self.fire_paint_callbacks(callbacks, False)
170        gobject.idle_add(do_paint)
171
172    def get_subsampling_divs(self, pixel_format):
173        if pixel_format==YUV420P:
174            return 1, 2, 2
175        elif pixel_format==YUV422P:
176            return 1, 2, 1
177        elif pixel_format==YUV444P:
178            return 1, 1, 1
179        raise Exception("invalid pixel format: %s" % pixel_format)
180
181    def update_texture_yuv(self, img_data, x, y, width, height, rowstrides, pixel_format):
182        window_width, window_height = self.size
183        assert self.textures is not None, "no OpenGL textures!"
184
185        if self.pixel_format is None or self.pixel_format!=pixel_format:
186            self.pixel_format = pixel_format
187            divs = self.get_subsampling_divs(pixel_format)
188            log("GL creating new YUV textures for pixel format %s using divs=%s", pixel_format, divs)
189            # Create textures of the same size as the window's
190            glEnable(GL_TEXTURE_RECTANGLE_ARB)
191
192            for texture, index in ((GL_TEXTURE0, 0), (GL_TEXTURE1, 1), (GL_TEXTURE2, 2)):
193                div = divs[index]
194                glActiveTexture(texture)
195                glBindTexture(GL_TEXTURE_RECTANGLE_ARB, self.textures[index])
196                glEnable(GL_TEXTURE_RECTANGLE_ARB)
197                mag_filter = GL_NEAREST
198                if div>1:
199                    mag_filter = GL_LINEAR
200                glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, mag_filter)
201                glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
202                glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_LUMINANCE, window_width/div, window_height/div, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, 0)
203
204            log("Assigning fragment program")
205            glEnable(GL_FRAGMENT_PROGRAM_ARB)
206            if not self.yuv_shader:
207                self.yuv_shader = [ 1 ]
208                glGenProgramsARB(1, self.yuv_shader)
209                glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, self.yuv_shader[0])
210                prog = GL_COLORSPACE_CONVERSIONS
211                glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, len(prog), prog)
212                log.error(glGetString(GL_PROGRAM_ERROR_STRING_ARB))
213                glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, self.yuv_shader[0])
214
215        # Clamp width and height to the actual texture size
216        if x + width > window_width:
217            width = window_width - x
218        if y + height > window_height:
219            height = window_height - y
220
221        divs = self.get_subsampling_divs(pixel_format)
222        for texture, index in ((GL_TEXTURE0, 0), (GL_TEXTURE1, 1), (GL_TEXTURE2, 2)):
223            div = divs[index]
224            glActiveTexture(texture)
225            glBindTexture(GL_TEXTURE_RECTANGLE_ARB, self.textures[index])
226            glPixelStorei(GL_UNPACK_ROW_LENGTH, rowstrides[index])
227            glTexSubImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, x, y, width/div, height/div, GL_LUMINANCE, GL_UNSIGNED_BYTE, img_data[index])
228        glFlush()
229
230    def render_image(self, rx, ry, rw, rh):
231        log("render_image %sx%s at %sx%s pixel_format=%s", rw, rh, rx, ry, self.pixel_format)
232        if self.pixel_format not in (YUV420P, YUV422P, YUV444P):
233            #not ready to render yet
234            return
235        divs = self.get_subsampling_divs(self.pixel_format)
236        glEnable(GL_FRAGMENT_PROGRAM_ARB)
237        glBegin(GL_QUADS)
238        for x,y in ((rx, ry), (rx, ry+rh), (rx+rw, ry+rh), (rx+rw, ry)):
239            for texture, index in ((GL_TEXTURE0, 0), (GL_TEXTURE1, 1), (GL_TEXTURE2, 2)):
240                div = divs[index]
241                glMultiTexCoord2i(texture, x/div, y/div)
242            glVertex2i(x, y)
243        glEnd()
244        glFlush()