diff --git a/src/xpra/codec_constants.py b/src/xpra/codec_constants.py new file mode 100644 index 0000000000..0d8a3a9ef6 --- /dev/null +++ b/src/xpra/codec_constants.py @@ -0,0 +1,9 @@ +# coding=utf8 +# This file is part of Parti. +# Copyright (C) 2012 Antoine Martin +# Parti is released under the terms of the GNU GPL v2, or, at your option, any +# later version. See the file COPYING for details. + +YUV420P = 0 +YUV422P = 1 +YUV444P = 2 diff --git a/src/xpra/gl_client_window.py b/src/xpra/gl_client_window.py index a01cd6dcb6..1ba400a94f 100644 --- a/src/xpra/gl_client_window.py +++ b/src/xpra/gl_client_window.py @@ -30,12 +30,11 @@ gl_minor = int(glGetString(GL_VERSION)[2]) if gl_major<=1 and gl_minor<1: raise ImportError("** OpenGL output requires OpenGL version 1.1 or greater, not %s.%s" % (gl_major, gl_minor)) - log.info("found valid OpenGL: %s.%s", gl_major, gl_minor) + log("found valid OpenGL: %s.%s", gl_major, gl_minor) #this allows us to do CSC via OpenGL: #see http://www.opengl.org/registry/specs/ARB/fragment_program.txt - use_openGL_CSC = glInitFragmentProgramARB() - if not use_openGL_CSC: + if not glInitFragmentProgramARB(): raise ImportError("** OpenGL output requires glInitFragmentProgramARB") finally: gldrawable.gl_end() @@ -47,26 +46,26 @@ class GLClientWindow(ClientWindow): def __init__(self, client, wid, x, y, w, h, metadata, override_redirect, client_properties, auto_refresh_delay): - log.info("GLClientWindow(..)") + log("GLClientWindow(..)") self._configured = False ClientWindow.__init__(self, client, wid, x, y, w, h, metadata, override_redirect, client_properties, auto_refresh_delay) self.add(self._backing.glarea) def do_configure_event(self, event): - log.info("do_configure_event(%s)", event) + log("GL do_configure_event(%s)", event) self._configured = True ClientWindow.do_configure_event(self, event) w, h = self.get_size() self._backing.init(w, h) def do_expose_event(self, event): - log.info("do_expose_event(%s) area=%s, mapped=%s", event, event.area, self.flags() & gtk.MAPPED) + log("GL do_expose_event(%s) area=%s, mapped=%s", event, event.area, self.flags() & gtk.MAPPED) if not (self.flags() & gtk.MAPPED): return self._backing.render() def new_backing(self, w, h): - log.info("new_backing(%s, %s)", w, h) + log("GL new_backing(%s, %s)", w, h) #self._backing = new_backing(self._id, w, h, self._backing, self._client.supports_mmap, self._client.mmap) w = max(1, w) h = max(1, h) diff --git a/src/xpra/gl_window_backing.py b/src/xpra/gl_window_backing.py index a70a2e630c..6a58b9f038 100644 --- a/src/xpra/gl_window_backing.py +++ b/src/xpra/gl_window_backing.py @@ -13,6 +13,7 @@ from wimpiggy.log import Logger log = Logger() +from xpra.codec_constants import YUV420P, YUV422P, YUV444P from xpra.gl_colorspace_conversions import GL_COLORSPACE_CONVERSIONS from xpra.window_backing import PixmapBacking from OpenGL.GL import GL_PROJECTION, GL_MODELVIEW, GL_VERTEX_ARRAY, \ @@ -47,8 +48,10 @@ def __init__(self, wid, w, h, mmap_enabled, mmap): self.glarea.set_size_request(w, h) self.glarea.show() self.textures = None # OpenGL texture IDs - self.yuv420_shader = None + self.yuv_shader = None + self.pixel_format = None self.size = 0, 0 + self.current_mode = GLPixmapBacking.MODE_UNINITIALIZED def init(self, w, h): @@ -58,15 +61,16 @@ def init(self, w, h): drawable = self.glarea.get_gl_drawable() context = self.glarea.get_gl_context() - self.yuv420_shader = None + self.yuv_shader = None # Re-create textures + self.pixel_format = None self.current_mode = GLPixmapBacking.MODE_UNINITIALIZED if not drawable.gl_begin(context): raise Exception("** Cannot create OpenGL rendering context!") - log.info("GL Pixmap backing size: %d x %d", w, h) + log("GL Pixmap backing size: %d x %d", w, h) glViewport(0, 0, w, h) glMatrixMode(GL_PROJECTION) glLoadIdentity() @@ -82,14 +86,14 @@ def init(self, w, h): drawable.gl_end() def render(self): - log.info("GL render") + log("GL render") self.render_image() self.glarea.window.invalidate_rect(self.glarea.allocation, False) # Update window synchronously (fast). self.glarea.window.process_updates(False) def do_video_paint(self, coding, img_data, x, y, width, height, options, callbacks): - log.info("do_video_paint: options=%s, decoder=%s", options, type(self._video_decoder)) + log("do_video_paint: options=%s, decoder=%s", options, type(self._video_decoder)) err, rowstrides, img_data = self._video_decoder.decompress_image_to_yuv(img_data, options) success = err==0 and img_data and len(img_data)==3 if not success: @@ -98,12 +102,23 @@ def do_video_paint(self, coding, img_data, x, y, width, height, options, callbac self.fire_paint_callbacks(callbacks, False) return def do_paint(): - self.update_texture_yuv420(img_data, x, y, width, height, rowstrides) + csc_pixel_format = options.get("csc_pixel_format", -1) + pixel_format = self._video_decoder.get_pixel_format(csc_pixel_format) + self.update_texture_yuv(img_data, x, y, width, height, rowstrides, pixel_format) self.render_image() self.fire_paint_callbacks(callbacks, True) gobject.idle_add(do_paint) - def update_texture_yuv420(self, img_data, x, y, width, height, rowstrides): + def get_subsampling_divs(self, pixel_format): + if pixel_format==YUV420P: + return 1, 2, 2 + elif pixel_format==YUV422P: + return 1, 2, 1 + elif pixel_format==YUV444P: + return 1, 1, 1 + raise Exception("invalid pixel format: %s" % pixel_format) + + def update_texture_yuv(self, img_data, x, y, width, height, rowstrides, pixel_format): drawable = self.glarea.get_gl_drawable() context = self.glarea.get_gl_context() window_width, window_height = self.size @@ -111,46 +126,35 @@ def update_texture_yuv420(self, img_data, x, y, width, height, rowstrides): raise Exception("** Cannot create OpenGL rendering context!") assert self.textures is not None - if self.current_mode == GLPixmapBacking.MODE_UNINITIALIZED: - log.info("Creating new YUV textures") - + if self.pixel_format is None or self.pixel_format!=pixel_format: + self.pixel_format = pixel_format + divs = self.get_subsampling_divs(pixel_format) + log("GL creating new YUV textures for pixel format %s using divs=%s", pixel_format, divs) # 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.info("Assigning fragment program") + for texture, index in ((GL_TEXTURE0, 0), (GL_TEXTURE1, 1), (GL_TEXTURE2, 2)): + div = divs[index] + glActiveTexture(texture) + glBindTexture(GL_TEXTURE_RECTANGLE_ARB, self.textures[index]) + glEnable(GL_TEXTURE_RECTANGLE_ARB) + mag_filter = GL_NEAREST + if div>1: + mag_filter = GL_LINEAR + glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, mag_filter) + glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST) + glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_LUMINANCE, window_width/div, window_height/div, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, 0) + + log("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]) + if not self.yuv_shader: + self.yuv_shader = [ 1 ] + glGenProgramsARB(1, self.yuv_shader) + glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, self.yuv_shader[0]) prog = GL_COLORSPACE_CONVERSIONS 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 = GLPixmapBacking.MODE_YUV - else: - assert self.current_mode == GLPixmapBacking.MODE_YUV + glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, self.yuv_shader[0]) # Clamp width and height to the actual texture size if x + width > window_width: @@ -158,52 +162,31 @@ def update_texture_yuv420(self, img_data, x, y, width, height, rowstrides): 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]) + divs = self.get_subsampling_divs(pixel_format) + for texture, index in ((GL_TEXTURE0, 0), (GL_TEXTURE1, 1), (GL_TEXTURE2, 2)): + div = divs[index] + glActiveTexture(texture) + glBindTexture(GL_TEXTURE_RECTANGLE_ARB, self.textures[index]) + glPixelStorei(GL_UNPACK_ROW_LENGTH, rowstrides[index]) + glTexSubImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, x, y, width/div, height/div, GL_LUMINANCE, GL_UNSIGNED_BYTE, img_data[index]) drawable.gl_end() def render_image(self): drawable = self.glarea.get_gl_drawable() context = self.glarea.get_gl_context() - log.info("render_image() size=%s, using %s and %s", self.size, drawable, context) + log("GL render_image() size=%s", self.size) w, h = self.size if not drawable.gl_begin(context): raise Exception("** Cannot create OpenGL rendering context!") - assert self.current_mode == GLPixmapBacking.MODE_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); + divs = self.get_subsampling_divs(self.pixel_format) + for x,y in ((0, 0), (0, h), (w, h), (w, 0)): + for texture, index in ((GL_TEXTURE0, 0), (GL_TEXTURE1, 1), (GL_TEXTURE2, 2)): + div = divs[index] + glMultiTexCoord2i(texture, x/div, y/div) + glVertex2i(x, y) glEnd() drawable.swap_buffers() diff --git a/src/xpra/vpx/codec.pyx b/src/xpra/vpx/codec.pyx index f7f8ff4c5e..ea792fc108 100644 --- a/src/xpra/vpx/codec.pyx +++ b/src/xpra/vpx/codec.pyx @@ -5,6 +5,7 @@ import os from libc.stdlib cimport free +from xpra.codec_constants import YUV420P cdef extern from "Python.h": ctypedef int Py_ssize_t @@ -109,6 +110,11 @@ cdef class Decoder(xcoder): strides = [outstrides[0], outstrides[1], outstrides[2]] return i, strides, out + def get_pixel_format(self, csc_pixel_format): + #we only support 420 at present + assert csc_pixel_format==-1 + return YUV420P + def decompress_image_to_rgb(self, input, options): cdef uint8_t *yuvplanes[3] cdef uint8_t *dout diff --git a/src/xpra/x264/codec.pyx b/src/xpra/x264/codec.pyx index 375be053d9..2c6292462b 100644 --- a/src/xpra/x264/codec.pyx +++ b/src/xpra/x264/codec.pyx @@ -32,6 +32,7 @@ cdef extern from "x264lib.h": int compress_image(x264lib_ctx *ctx, x264_picture_t *pic_in, uint8_t **out, int *outsz, int quality_override) nogil int get_encoder_pixel_format(x264lib_ctx *ctx) int get_encoder_quality(x264lib_ctx *ctx) + int get_pixel_format(int csc_format) x264lib_ctx* init_decoder(int width, int height, int csc_fmt) void set_decoder_csc_format(x264lib_ctx *context, int csc_fmt) @@ -132,6 +133,9 @@ cdef class Decoder(xcoder): strides = [outstrides[0], outstrides[1], outstrides[2]] return i, strides, out + def get_pixel_format(self, csc_pixel_format): + return get_pixel_format(csc_pixel_format) + def decompress_image_to_rgb(self, input, options): cdef uint8_t *yuvplanes[3] cdef uint8_t *dout diff --git a/src/xpra/x264/x264lib.c b/src/xpra/x264/x264lib.c index 027e493b02..d1b9319bcc 100644 --- a/src/xpra/x264/x264lib.c +++ b/src/xpra/x264/x264lib.c @@ -119,12 +119,26 @@ int get_csc_format_for_x264_format(int i_csp) return PIX_FMT_YUV444P; #endif else { + fprintf(stderr, "invalid pixel format: %i\n", i_csp); return -1; - printf("invalid pixel format: %i", i_csp); } } #endif +//Given a csc colour sampling constant, +//return our own generic csc constant (see codec_constants.py) +int get_pixel_format(int csc) +{ + if (csc == PIX_FMT_YUV420P || csc < 0) + return 0; + else if (csc == PIX_FMT_YUV422P) + return 1; + else if (csc == PIX_FMT_YUV444P) + return 2; + else + return -1; +} + int get_csc_algo_for_quality(int initial_quality) { //always use the best quality as lower quality options //do not offer a significant speed improvement @@ -343,7 +357,7 @@ int compress_image(struct x264lib_ctx *ctx, x264_picture_t *pic_in, uint8_t **ou return 0; } #else -x264_picture_t* csc_image_rgb2yuv(struct x264lib_ctx *ctx, const uint8_t *in, int stride) +x264_picture_t* csc_image_rgb2yuv(struct x264lib_ctx *ctx, const uint8_t *in, int stride) { return NULL; } diff --git a/src/xpra/x264/x264lib.h b/src/xpra/x264/x264lib.h index 79f9925c7d..3659e2e99e 100644 --- a/src/xpra/x264/x264lib.h +++ b/src/xpra/x264/x264lib.h @@ -29,6 +29,9 @@ int get_encoder_pixel_format(struct x264lib_ctx *ctx); /** Expose current quality setting so we can tell the client how good the frame was */ int get_encoder_quality(struct x264lib_ctx *ctx); +/** Returns the pixel format using our own generic codec_constants */ +int get_pixel_format(int csc); + /** Create an encoding context for images of a given size. */ struct x264lib_ctx *init_encoder(int width, int height, int initial_quality, int supports_csc_option); @@ -80,7 +83,7 @@ int compress_image(struct x264lib_ctx *ctx, x264_picture_t *pic_in, uint8_t **ou /** Decompress an image using the given context. @param in: Input buffer, format is H264. @param size: Input size. - @param out: Will be filled to point to the output data in planar YUV420 format (3 planes). This data will be freed automatically upon next call to the decoder. + @param out: Will be filled to point to the output data in planar YUV420 format (3 planes). This data will be freed automatically upon next call to the decoder. @param outsize: Output size. @param outstride: Output strides (3 planes). */