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.2.py

File gl_window_backing.2.py, 11.6 KB (added by Antoine Martin, 9 years ago)

version which creates the fragment program only once and uses glEnable / glDisable

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