From 345a1ccd67df48ccad5df216cbdbbc00bae2a217 Mon Sep 17 00:00:00 2001 From: totaam Date: Wed, 25 Oct 2023 23:29:50 +0700 Subject: [PATCH] #2467 opengl switch can be used to choose a backend and add a 'gtk' backend --- xpra/client/gl/gtk3/glarea_window.py | 148 +++++++++++++++++++++++++++ xpra/client/gl/window.py | 41 ++++++-- xpra/client/gtk3/client_base.py | 8 +- xpra/client/gui/widget_base.py | 3 +- xpra/gtk/examples/opengl.py | 2 +- xpra/platform/posix/gui.py | 6 +- xpra/scripts/main.py | 10 +- 7 files changed, 190 insertions(+), 28 deletions(-) create mode 100644 xpra/client/gl/gtk3/glarea_window.py diff --git a/xpra/client/gl/gtk3/glarea_window.py b/xpra/client/gl/gtk3/glarea_window.py new file mode 100644 index 0000000000..be3dd30e9a --- /dev/null +++ b/xpra/client/gl/gtk3/glarea_window.py @@ -0,0 +1,148 @@ +# This file is part of Xpra. +# Copyright (C) 2023 Antoine Martin +# Xpra is released under the terms of the GNU GPL v2, or, at your option, any +# later version. See the file COPYING for details. + +from typing import Any +from collections.abc import Callable +from gi.repository import Gtk, Gdk, GObject + +from xpra.util.str_fn import ellipsizer +from xpra.client.gl.gtk3.client_window import GLClientWindowBase +from xpra.client.gl.backing import GLWindowBackingBase +from xpra.log import Logger + +log = Logger("opengl", "paint") + + +class GLAreaBacking(GLWindowBackingBase): + + def __init__(self, wid : int, window_alpha : bool, pixel_depth : int=0): + self.on_realize_cb : list[tuple[Callable,tuple[Any,...]]] = [] + super().__init__(wid, window_alpha, pixel_depth) + + def __repr__(self): + return "GLAreaBacking(%#x, %s, %s)" % (self.wid, self.size, self.pixel_format) + + def init_gl_config(self) -> None: + pass + + def is_double_buffered(self) -> bool: + return True + + def init_backing(self) -> None: + da = Gtk.GLArea() + da.set_auto_render(True) + da.set_has_alpha(self._alpha_enabled) + da.set_has_depth_buffer(False) + da.set_has_stencil_buffer(False) + da.set_required_version(3, 2) + da.connect("realize", self.on_realize) + da.connect("render", self.on_render) + da.set_size_request(*self.size) + da.set_events(da.get_events() | Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.POINTER_MOTION_HINT_MASK) + da.show() + self._backing = da + + def on_realize(self, *args) -> None: + onrcb = self.on_realize_cb + log("GLAreaBacking.on_realize%s callbacks=%s", args, tuple(ellipsizer(x) for x in onrcb)) + self.on_realize_cb = [] + gl_context = self.gl_context() + log("gl context version %s", gl_context.get_version()) + gl_context.make_current() + for x, args in onrcb: + with log.trap_error("Error calling realize callback %s", ellipsizer(x)): + x(gl_context, *args) + self.gl_init(gl_context) + + def with_gl_context(self, cb:Callable, *args): + da = self._backing + if da and da.get_mapped(): + gl_context = self.gl_context() + gl_context.make_current() + cb(gl_context, *args) + else: + log("GLAreaBacking.with_gl_context delayed: %s%s", cb, ellipsizer(args)) + self.on_realize_cb.append((cb, args)) + + def get_bit_depth(self, pixel_depth=0) -> int: + return pixel_depth or 24 + + def gl_context(self): + return self._backing.get_context() + + def do_gl_show(self, rect_count) -> None: + log.warn(f"do_gl_show({rect_count})") + self._backing.queue_render() + + def close_gl_config(self) -> None: + pass + + def draw_fbo(self, context) -> bool: + log.warn(f"draw_fbo({context})") + #window = self._backing.get_window() + #from xpra.client.gl.backing import TEX_FBO + #from OpenGL.GL import GL_TEXTURE + #w, h = self.render_size + #self.textures[TEX_FBO] + #Gdk.cairo_draw_from_gl(context, window, self.textures[TEX_FBO], GL_TEXTURE, 1, 0, 0, w, h) + return False + + def on_render(self, glarea, glcontext): + log(f"render({glarea}, {glcontext}) {self.textures=}, {self.offscreen_fbo=}") + if self.textures is None or self.offscreen_fbo is None: + self.gl_init(glcontext) + return False + w, h = self.render_size + from xpra.client.gl.backing import TEX_FBO + if False: + def noscale(): + return 1 + glcontext.get_scale_factor = noscale + self.managed_present_fbo(glcontext) + else: + # TODO: handle widget scaling! + #https://discourse.gnome.org/t/solved-framebuffer-issue-render-to-texture-with-gtk3-glarea-vs-glfw-identical-opengl-program-works-in-glfw-but-not-gtk3s-glarea/3597 + #current = glGetIntegerv(GL_FRAMEBUFFER_BINDING) + from OpenGL.GL import GL_COLOR_BUFFER_BIT, GL_NEAREST, glReadBuffer, glClear, glClearColor + from OpenGL.GL.ARB.texture_rectangle import GL_TEXTURE_RECTANGLE_ARB + from OpenGL.GL.ARB.framebuffer_object import glBindFramebuffer, GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, glBlitFramebuffer, glFramebufferTexture2D + glClearColor(0, 0, 0, 0) + glClear(GL_COLOR_BUFFER_BIT) + target = GL_TEXTURE_RECTANGLE_ARB + glBindFramebuffer(GL_READ_FRAMEBUFFER, self.offscreen_fbo) + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, self.textures[TEX_FBO], 0) + glReadBuffer(GL_COLOR_ATTACHMENT0) + glBlitFramebuffer(0, 0, w, h, + 0, 0, w, h, + GL_COLOR_BUFFER_BIT, GL_NEAREST) + return True + + +class GLClientWindow(GLClientWindowBase): + + def get_backing_class(self): + return GLAreaBacking + + def repaint(self, x:int, y:int, w:int, h:int) -> None: + widget = self.drawing_area + log.error(f"repaint%s {widget=}", (x, y, w, h)) + if widget: + widget.queue_render() + +GObject.type_register(GLClientWindow) + + +def check_support(force_enable=False): + if True: + from xpra.client.gl.window import test_gl_client_window + return test_gl_client_window(GLClientWindow) + window = Gtk.Window(title="opengl-check") + window.set_default_size(400, 400) + gl_area = GLAreaBacking() + gl_area.set_has_depth_buffer(False) + gl_area.set_has_stencil_buffer(False) + window.add(gl_area) + gl_area.realize() + return {} diff --git a/xpra/client/gl/window.py b/xpra/client/gl/window.py index 3996fbea6a..522888b18e 100755 --- a/xpra/client/gl/window.py +++ b/xpra/client/gl/window.py @@ -10,6 +10,7 @@ from collections.abc import Callable from xpra.common import noop +from xpra.scripts.config import FALSE_OPTIONS from xpra.util.types import AtomicInteger, typedict from xpra.util.env import envint from xpra.os_util import WIN32, load_binary_file @@ -20,22 +21,45 @@ log = Logger("opengl", "paint") -def get_gl_client_window_module(force_enable=False) -> tuple[dict,Any]: - log("get_gl_client_window_module()") +def get_gl_client_window_module(opengl="on") -> tuple[dict,Any]: + log(f"get_gl_client_window_module({opengl})") + # ie: "auto", "no", "probe-success", "yes:gtk", "gtk", "yes:native", "native" + parts = opengl.lower().split(":") + if parts[0].lower() in FALSE_OPTIONS: + return {}, None + arg = parts[-1] + if arg in ("gtk", "glarea"): + module_names = ("glarea", ) + elif arg == "native": + module_names = ("native", ) + else: + module_names = ("native", "glarea", ) + force_enable = parts[0]=="force" + for module_name in module_names: + props, window_module = test_window_module(module_name, force_enable) + if window_module: + return props, window_module + return {}, None + + +def test_window_module(module_name="glarea", force_enable=False) -> tuple[dict,Any]: + from importlib import import_module try: - from xpra.client.gl.gtk3 import native_window + mod = import_module(f"xpra.client.gl.gtk3.{module_name}_window") + log(f"gl client window module {module_name!r}={mod}") except ImportError as e: - log("cannot import opengl window module", exc_info=True) - log.warn("Warning: cannot import native OpenGL module") + log(f"cannot import opengl window module {module_name}", exc_info=True) + log.warn(f"Warning: cannot import OpenGL window module {module_name}") log.warn(" %s", e) return {}, None - opengl_props = native_window.check_support(force_enable) - log("check_support(%s)=%s", force_enable, opengl_props) + opengl_props = mod.check_support(force_enable) + log(f"{mod}.check_support({force_enable})={opengl_props}") if opengl_props: - return opengl_props, native_window + return opengl_props, mod return {}, None + def get_test_gl_icon(): data = b"" encoding = "png" @@ -74,6 +98,7 @@ def no_idle_add(fn, *args, **kwargs): fn(*args, **kwargs) def test_gl_client_window(gl_client_window_class : Callable, max_window_size=(1024, 1024), pixel_depth=24, show=False): + show = True #try to render using a temporary window: draw_result = {} window = None diff --git a/xpra/client/gtk3/client_base.py b/xpra/client/gtk3/client_base.py index b6280af9da..a1ca6fc02d 100644 --- a/xpra/client/gtk3/client_base.py +++ b/xpra/client/gtk3/client_base.py @@ -1256,12 +1256,8 @@ def err(msg, e): try: opengllog("init_opengl: going to import xpra.client.gl") __import__("xpra.client.gl", {}, {}, []) - from xpra.client.gl.window import ( - get_gl_client_window_module, - test_gl_client_window, - ) - force_enable = self.opengl_force or (enable_option in TRUE_OPTIONS) - self.opengl_props, gl_client_window_module = get_gl_client_window_module(force_enable) + from xpra.client.gl.window import get_gl_client_window_module, test_gl_client_window + self.opengl_props, gl_client_window_module = get_gl_client_window_module(enable_opengl) if not gl_client_window_module: opengllog.warn("Warning: no OpenGL backend module found") self.client_supports_opengl = False diff --git a/xpra/client/gui/widget_base.py b/xpra/client/gui/widget_base.py index b1f0042703..4f968a8e9c 100644 --- a/xpra/client/gui/widget_base.py +++ b/xpra/client/gui/widget_base.py @@ -6,6 +6,7 @@ # later version. See the file COPYING for details. from typing import Any +from collections.abc import Callable from xpra.util.env import envbool from xpra.log import Logger @@ -45,7 +46,7 @@ def get_info(self) -> dict[str,Any]: info["backing"] = b.get_info() return info - def make_new_backing(self, backing_class:type, ww:int, wh:int, bw:int, bh:int): + def make_new_backing(self, backing_class:Callable, ww:int, wh:int, bw:int, bh:int): # size of the backing, which should be the same as the server's window source: bw = max(1, bw) bh = max(1, bh) diff --git a/xpra/gtk/examples/opengl.py b/xpra/gtk/examples/opengl.py index 53f993a99d..149581dea8 100755 --- a/xpra/gtk/examples/opengl.py +++ b/xpra/gtk/examples/opengl.py @@ -16,7 +16,7 @@ def main(argv=()): init_gdk_display_source() if "-v" in argv or "--verbose" in argv: log.enable_debug() - opengl_props, gl_client_window_module = get_gl_client_window_module(True) + opengl_props, gl_client_window_module = get_gl_client_window_module("force") log("do_run_glcheck() opengl_props=%s, gl_client_window_module=%s", opengl_props, gl_client_window_module) gl_client_window_class = gl_client_window_module.GLClientWindow pixel_depth = 0 diff --git a/xpra/platform/posix/gui.py b/xpra/platform/posix/gui.py index fb63087f30..c3c85d4488 100644 --- a/xpra/platform/posix/gui.py +++ b/xpra/platform/posix/gui.py @@ -11,7 +11,7 @@ from xpra.os_util import ( bytestostr, get_saved_env, - is_X11, is_Wayland, get_saved_env_var, first_time, + is_X11, get_saved_env_var, first_time, ) from xpra.util.str_fn import csv from xpra.util.env import envint, envbool @@ -72,10 +72,6 @@ def X11XI2Bindings(): def gl_check() -> str: - if not is_X11() and is_Wayland(): - return "disabled under wayland with GTK3 (buggy)" - if is_X11() and not x11_bindings(): - return "X11 bindings are missing" return "" diff --git a/xpra/scripts/main.py b/xpra/scripts/main.py index acc15e87f2..05249471ac 100755 --- a/xpra/scripts/main.py +++ b/xpra/scripts/main.py @@ -2418,15 +2418,11 @@ def do_run_glcheck(opts, show=False) -> dict[str,Any]: saved_level = logging.root.getEffectiveLevel() logging.root.setLevel(logging.WARN) try: - from xpra.client.gl.window import ( - get_gl_client_window_module, - test_gl_client_window, - ) + from xpra.client.gl.window import get_gl_client_window_module, test_gl_client_window opengl_str = (opts.opengl or "").lower() - force_enable = opengl_str.split(":")[0] in TRUE_OPTIONS - opengl_props, gl_client_window_module = get_gl_client_window_module(force_enable) + opengl_props, gl_client_window_module = get_gl_client_window_module(opengl_str) log("do_run_glcheck() opengl_props=%s, gl_client_window_module=%s", opengl_props, gl_client_window_module) - if gl_client_window_module and (opengl_props.get("safe", False) or force_enable): + if gl_client_window_module and (opengl_props.get("safe", False) or opengl_str.startswith("force")): gl_client_window_class = gl_client_window_module.GLClientWindow pixel_depth = int(opts.pixel_depth) log("do_run_glcheck() gl_client_window_class=%s, pixel_depth=%s", gl_client_window_class, pixel_depth)