diff --git a/xpra/client/gl/backing.py b/xpra/client/gl/backing.py index c6eab7f534..70cf000021 100644 --- a/xpra/client/gl/backing.py +++ b/xpra/client/gl/backing.py @@ -380,7 +380,10 @@ def gl_init_shaders(self) -> None: # Create and assign fragment programs from OpenGL.GL import GL_FRAGMENT_SHADER, GL_VERTEX_SHADER vertex_shader = self.gl_init_shader("vertex", GL_VERTEX_SHADER) - for name in ("YUV_to_RGB", "YUV_to_RGB_FULL", "NV12_to_RGB"): + from xpra.client.gl.shaders import SOURCE + for name, source in SOURCE.items(): + if name in ("overlay", "vertex"): + continue fragment_shader = self.gl_init_shader(name, GL_FRAGMENT_SHADER) self.gl_init_program(name, vertex_shader, fragment_shader) overlay_shader = self.gl_init_shader("overlay", GL_FRAGMENT_SHADER) @@ -976,7 +979,7 @@ def paint_nvdec(gl_context): flush = options.intget("flush", 0) w = img.get_width() h = img.get_height() - self.idle_add(self.gl_paint_planar, "YUV_to_RGB_FULL", flush, encoding, img, + self.idle_add(self.gl_paint_planar, "YUV420P_to_RGB_FULL", flush, encoding, img, x, y, w, h, width, height, options, callbacks) return if encoding == "jpeg": @@ -1126,7 +1129,7 @@ def paint_webp(self, img_data, x: int, y: int, width: int, height: int, flush = options.intget("flush", 0) w = img.get_width() h = img.get_height() - self.idle_add(self.gl_paint_planar, "YUV_to_RGB", flush, "webp", img, + self.idle_add(self.gl_paint_planar, f"{subsampling}_to_RGB", flush, "webp", img, x, y, w, h, width, height, options, callbacks) return super().paint_webp(img_data, x, y, width, height, options, callbacks) @@ -1140,7 +1143,7 @@ def paint_avif(self, img_data, x: int, y: int, width: int, height: int, w = img.get_width() h = img.get_height() if pixel_format.startswith("YUV"): - self.idle_add(self.gl_paint_planar, "YUV_to_RGB_FULL", flush, "avif", img, + self.idle_add(self.gl_paint_planar, f"{pixel_format}_to_RGB_FULL", flush, "avif", img, x, y, w, h, width, height, options, callbacks) else: self.idle_add(self.do_paint_rgb, pixel_format, img.get_pixels(), x, y, w, h, width, height, @@ -1253,7 +1256,7 @@ def do_video_paint(self, img, # which will end up calling paint rgb with r210 data super().do_video_paint(img, x, y, enc_width, enc_height, width, height, options, callbacks) return - shader = "NV12_to_RGB" if pixel_format == "NV12" else "YUV_to_RGB" + shader = f"{pixel_format}_to_RGB" self.idle_add(self.gl_paint_planar, shader, options.intget("flush", 0), options.strget("encoding"), img, x, y, enc_width, enc_height, width, height, options, callbacks) @@ -1389,7 +1392,7 @@ def update_planar_textures(self, width: int, height: int, img, pixel_format, sca # glActiveTexture(GL_TEXTURE0) #redundant, we always call render_planar_update afterwards def render_planar_update(self, rx: int, ry: int, rw: int, rh: int, width: int, height: int, - shader="YUV_to_RGB") -> None: + shader="YUV420P_to_RGB") -> None: log("%s.render_planar_update%s pixel_format=%s", self, (rx, ry, rw, rh, width, height, shader), self.planar_pixel_format) if self.planar_pixel_format not in ("YUV420P", "YUV422P", "YUV444P", "GBRP", "NV12", "GBRP16", "YUV444P16"): @@ -1423,14 +1426,16 @@ def clampy(v: int) -> int: glViewport(*viewport) log("viewport: %s for size=%s render_size=%s", viewport, self.size, self.render_size) - program = self.programs[shader] + program = self.programs.get(shader) + if not program: + raise RuntimeError(f"no {shader} found!") glUseProgram(program) for texture, tex_index in textures: glActiveTexture(texture) glBindTexture(target, self.textures[tex_index]) # TEX_Y is 0, so effectively index==tex_index index = tex_index-TEX_Y - plane_name = shader[index:index + 1] # ie: "YUV_to_RGB" 0 -> "Y" + plane_name = shader[index:index + 1] # ie: "YUV420P_to_RGB" 0 -> "Y" tex_loc = glGetUniformLocation(program, plane_name) # ie: "Y" -> 0 glUniform1i(tex_loc, index) # tell the shader where to find the texture: 0 -> TEXTURE_0 diff --git a/xpra/client/gl/shaders.py b/xpra/client/gl/shaders.py index b0972f6ef4..e08290aa30 100644 --- a/xpra/client/gl/shaders.py +++ b/xpra/client/gl/shaders.py @@ -24,6 +24,8 @@ # G = Y - (a * e / b) * Cr - (c * d / b) * Cb # B = Y + d * Cb +from xpra.codecs.constants import get_subsampling_divs + GLSL_VERSION = "330 core" CS_MULTIPLIERS = { @@ -33,7 +35,7 @@ } -def gen_YUV_to_RGB(cs="bt601", full_range=True): +def gen_YUV_to_RGB(divs: tuple[tuple[int, int], ...], cs="bt601", full_range=True): if cs not in CS_MULTIPLIERS: raise ValueError(f"unsupported colorspace {cs}") a, b, c, d, e = CS_MULTIPLIERS[cs] @@ -41,9 +43,27 @@ def gen_YUV_to_RGB(cs="bt601", full_range=True): g = - a * e / b ymult = "" if full_range else " * 1.1643835616438356" umult = vmult = "" if full_range else " * 1.1383928571428572" + defines = [] + + def add_div(name: str, xdiv=1, ydiv=1): + if xdiv == ydiv: + # just divide by the same integer: + # ie: "#define Ydiv 1" + value = xdiv + else: + # store each component in a vector: + # ie: "#define Udiv vec2(2, 1)" + value = f"vec2({xdiv}, {ydiv})" + defines.append(f"{name}div {value}") + + for i, div in enumerate(divs): + add_div("YUV"[i], *div) + + defines_str = "\n".join(f"#define {define}" for define in defines) return f""" #version {GLSL_VERSION} layout(origin_upper_left) in vec4 gl_FragCoord; +{defines_str} uniform vec2 viewport_pos; uniform vec2 scaling; uniform sampler2DRect Y; @@ -54,9 +74,9 @@ def gen_YUV_to_RGB(cs="bt601", full_range=True): void main() {{ vec2 pos = (gl_FragCoord.xy-viewport_pos.xy)/scaling; - highp float y = texture(Y, pos).r {ymult}; - highp float u = (texture(U, pos/2.0).r - 0.5) {umult}; - highp float v = (texture(V, pos/2.0).r - 0.5) {vmult}; + highp float y = texture(Y, pos/Ydiv).r {ymult}; + highp float u = (texture(U, pos/Udiv).r - 0.5) {umult}; + highp float v = (texture(V, pos/Vdiv).r - 0.5) {vmult}; highp float r = y + {e} * v; highp float g = y + {f} * u + {g} * v; @@ -129,6 +149,9 @@ def gen_NV12_to_RGB(cs="bt601"): "vertex": VERTEX_SHADER, "overlay": OVERLAY_SHADER, "NV12_to_RGB": gen_NV12_to_RGB(), - "YUV_to_RGB": gen_YUV_to_RGB(), - "YUV_to_RGB_FULL": gen_YUV_to_RGB(full_range=True), } + +for fmt in ("YUV420P", "YUV422P", "YUV444P"): + divs = get_subsampling_divs(fmt) + SOURCE[f"{fmt}_to_RGB"] = gen_YUV_to_RGB(divs, full_range=False) + SOURCE[f"{fmt}_to_RGB_FULL"] = gen_YUV_to_RGB(divs, full_range=True)