Skip to content

Commit

Permalink
#2467 opengl switch can be used to choose a backend
Browse files Browse the repository at this point in the history
and add a 'gtk' backend
  • Loading branch information
totaam committed Oct 25, 2023
1 parent 6016ba6 commit 345a1cc
Show file tree
Hide file tree
Showing 7 changed files with 190 additions and 28 deletions.
148 changes: 148 additions & 0 deletions xpra/client/gl/gtk3/glarea_window.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# This file is part of Xpra.
# Copyright (C) 2023 Antoine Martin <antoine@xpra.org>
# 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 {}
41 changes: 33 additions & 8 deletions xpra/client/gl/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
Expand Down Expand Up @@ -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
Expand Down
8 changes: 2 additions & 6 deletions xpra/client/gtk3/client_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion xpra/client/gui/widget_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion xpra/gtk/examples/opengl.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 1 addition & 5 deletions xpra/platform/posix/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 ""


Expand Down
10 changes: 3 additions & 7 deletions xpra/scripts/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 345a1cc

Please sign in to comment.