Skip to content

Commit f643928

Browse files
authored
Update gl support test to use glmark2 (New) (#2087)
* feat: migrate to glmark2 * refactor: fail early * doc: comments * fix: trim prefix * feat: add override option * fix: restrict choices * style: flip condition * style: return type * cleanup: unused variables * style: naming * test: add new test data and remove unused ones * test: new tests * style: formatting * test: increase coverage * fix: patched the wrong thing * test: cover the version-too-old path * test: increase coverage * test: increase coverage * test: increase coverage * fix: style guide * fix: inconsistent comments and missing path in tests * fix: bad name * style: formatting * refactor: use path objects * test: update test with new name * refactor: use path obj methods * doc: outdated comment * fix: broken tests * refactor: remove unnecessary get_desktop_env * test: remove unused tests * fix: bad test patches * fix: change how envs are patched * fix: spelling
1 parent 8d08caa commit f643928

11 files changed

+450
-143
lines changed

providers/base/bin/gl_support.py

Lines changed: 221 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,45 +17,239 @@
1717
# You should have received a copy of the GNU General Public License
1818
# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
1919

20-
import subprocess
21-
import re
20+
import shutil
21+
import subprocess as sp
22+
import typing as T
2223
import os
24+
import platform
25+
import argparse
26+
from pathlib import Path
2327

2428

25-
class GLSupport:
26-
"""
27-
This is a simple class to use unity_support_test to verify
28-
OpenGL is supported or not
29-
"""
29+
# Checkbox could run in a snap container, so we need to prepend this root path
30+
try:
31+
CHECKBOX_RUNTIME = Path(os.environ["CHECKBOX_RUNTIME"])
32+
except KeyError: # from indexing os.environ
33+
CHECKBOX_RUNTIME = None
34+
GLMARK2_DATA_PATH = Path("/usr/share/glmark2")
3035

31-
def remove_color_code(self, string: str) -> str:
36+
37+
class GLSupportTester:
38+
39+
def pick_glmark2_executable(
40+
self, xdg_session_type: str, cpu_arch: str
41+
) -> str:
42+
"""
43+
Pure function that picks a glmark2 executable based on xdg_session_type
44+
and cpu arch
45+
46+
:param xdg_session_type: the $XDG_SESSION_TYPE variable
47+
:param cpu_arch: the `uname -m` value like x86_64
48+
:return: glmark2 command to use. Caller is responsible for checking if
49+
the command exists
50+
"""
51+
if cpu_arch in ("x86_64", "amd64"):
52+
# x86 DUTs should run the version that uses the full opengl api
53+
glmark2_executable = "glmark2"
54+
else:
55+
# default to es2 as the common denominator
56+
# TODO: explicitly check for aarch64?
57+
glmark2_executable = "glmark2-es2"
58+
59+
if xdg_session_type == "wayland":
60+
glmark2_executable += "-wayland"
61+
# if x11, don't add anything
62+
return glmark2_executable
63+
64+
def gl_renderer_str_is_hardware_renderer(self, gl_renderer: str) -> bool:
65+
"""Checks if gl_renderer is produced by a hardware renderer.
66+
67+
This uses the same logic as unity_support_test. Details:
68+
https://github.com/canonical/checkbox/issues/1630#issuecomment-2540843110
69+
70+
:param gl_renderer: the GL_RENDERER string.
71+
https://registry.khronos.org/OpenGL-Refpages/gl4/html/glGetString.xhtml
72+
:return: whether GL_RENDERER is produced by a hardware renderer
73+
"""
74+
# These 2 values are carried over from unity_support_test
75+
# never seen this before on devices after ubuntu 16
76+
if gl_renderer in ("Software Rasterizer", "Mesa X11"):
77+
return False
78+
# https://docs.mesa3d.org/envvars.html#envvar-GALLIUM_DRIVER
79+
# it's almost always the 'llvmpipe' case if we find software rendering
80+
if "llvmpipe" in gl_renderer or "softpipe" in gl_renderer:
81+
return False
82+
83+
return True
84+
85+
def extract_gl_variable(
86+
self,
87+
glmark2_validate_output: str,
88+
gl_variable_name: "T.Literal['GL_VERSION', 'GL_RENDERER']",
89+
) -> str:
90+
"""Attempts to extract the specified gl variable from
91+
`glmark2 --validate`'s output
92+
93+
:param glmark2_validate_output: stdout of `glmark2 --validate`
94+
:param gl_variable_name: the variable to get
95+
:raises SystemExit: when the value of this variable doesn't appear in
96+
glmark2_validate_output
97+
:return: value of gl_variable_name, trimmed
98+
"""
99+
gl_renderer_line = None # type: str | None
100+
for line in glmark2_validate_output.splitlines():
101+
if gl_variable_name in line:
102+
gl_renderer_line = line.strip()
103+
break
104+
105+
if gl_renderer_line is None:
106+
raise SystemExit(
107+
"{} was not in glmark2's output".format(gl_variable_name)
108+
)
109+
110+
return gl_renderer_line.split(":")[-1].strip()
111+
112+
def call_glmark2_validate(
113+
self, glmark2_executable_override: "str | None" = None
114+
) -> str:
32115
"""
33-
Use to make the color code removing could be unit tested
116+
Calls 'glmark2 --validate --offscreen' with the symlink hack,
117+
but allow errors to be thrown unlike reboot_check_test.py
34118
35-
:param string: the string that you would like to remove color code
119+
:raises SystemExit: when XDG_SESSION_TYPE is not x11/wayland
120+
:return: stdout of `glmark2 --validate`
36121
"""
37122

38-
return re.sub(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])", "", string)
123+
XDG_SESSION_TYPE = os.environ.get("XDG_SESSION_TYPE")
124+
if XDG_SESSION_TYPE not in ("x11", "wayland"):
125+
# usually it's tty if we get here,
126+
# happens when gnome failed to start or not using graphical session
127+
raise SystemExit(
128+
"Unsupported session type: '{}'. ".format(XDG_SESSION_TYPE)
129+
+ "Expected either 'x11' or 'wayland'"
130+
)
131+
132+
print("XDG_SESSION type used by the desktop is:", XDG_SESSION_TYPE)
133+
134+
if glmark2_executable_override is not None:
135+
if shutil.which(glmark2_executable_override) is None:
136+
raise SystemExit(
137+
"Override command '{}' doesn't exist".format(
138+
glmark2_executable_override
139+
)
140+
)
141+
glmark2_executable = glmark2_executable_override
142+
else:
143+
glmark2_executable = self.pick_glmark2_executable(
144+
XDG_SESSION_TYPE, platform.uname().machine
145+
)
39146

40-
def is_support_opengl(self):
41-
cr = os.getenv("CHECKBOX_RUNTIME", default="")
42-
cmd = [
43-
"{}/usr/lib/nux/unity_support_test".format(cr),
44-
"-p",
45-
]
46147
try:
47-
rv = subprocess.run(
48-
cmd,
148+
if CHECKBOX_RUNTIME and not os.path.exists(GLMARK2_DATA_PATH):
149+
# the official way to specify the location of the data files
150+
# is "--data-path path/to/data/files"
151+
# but 16, 18, 20 doesn't have this option
152+
# and the /usr/share/glmark2 path is hard-coded inside glmark2
153+
# by the GLMARK_DATA_PATH build macro
154+
src = CHECKBOX_RUNTIME / GLMARK2_DATA_PATH
155+
dst = GLMARK2_DATA_PATH
156+
print(
157+
"[ DEBUG ] Symlinking glmark2 data dir ({} -> {})".format(
158+
src, dst
159+
)
160+
)
161+
os.symlink(src, dst, target_is_directory=True)
162+
# override is needed for snaps on classic ubuntu
163+
# to allow the glmark2 command itself to be discovered
164+
# in debian version of checkbox this line does nothing
165+
glmark2_output = sp.check_output(
166+
# all glmark2 programs share the same args
167+
[glmark2_executable, "--off-screen", "--validate"],
49168
universal_newlines=True,
50-
stdout=subprocess.PIPE,
51-
stderr=subprocess.STDOUT,
169+
# be more relaxed on this timeout in case
170+
# the device needs a lot of time to wake up the GPU
171+
timeout=120,
52172
)
53-
except (subprocess.CalledProcessError, FileNotFoundError) as e:
54-
raise SystemExit("running cmd:[{}] fail:{}".format(cmd, repr(e)))
55-
print(self.remove_color_code(rv.stdout))
56-
if rv.returncode != 0:
57-
raise SystemExit("Some OpenGL functions might not be supported")
173+
return glmark2_output
174+
finally:
175+
# immediately cleanup
176+
if CHECKBOX_RUNTIME and os.path.islink(GLMARK2_DATA_PATH):
177+
print("[ DEBUG ] Un-symlinking glmark2 data")
178+
os.unlink(GLMARK2_DATA_PATH)
179+
180+
181+
def remove_prefix(s: str, prefix: str) -> str:
182+
"""3.8 and older doesn't have <str>.removeprefix()"""
183+
if s.startswith(prefix):
184+
return s[len(prefix) :]
185+
return s
186+
187+
188+
def parse_args():
189+
parser = argparse.ArgumentParser()
190+
parser.add_argument(
191+
"--glmark2-override",
192+
help=(
193+
"Override the glmark2 executable to use, "
194+
"even if it might be unsupported on this platform"
195+
),
196+
choices=(
197+
"glmark2",
198+
"glmark2-wayland",
199+
"glmark2-es2",
200+
"glmark2-es2-wayland",
201+
),
202+
required=False,
203+
)
204+
return parser.parse_args()
205+
206+
207+
def main() -> None:
208+
args = parse_args()
209+
tester = GLSupportTester()
210+
glmark2_output = tester.call_glmark2_validate(args.glmark2_override)
211+
212+
gl_version_str = (
213+
remove_prefix(
214+
tester.extract_gl_variable(
215+
glmark2_output, "GL_VERSION"
216+
), # 4.6 (Compatibility Profile) Mesa 25.0.7-0ubuntu0.25.04.1
217+
"OpenGL ES", # OpenGL ES 3.0 Mesa 18.0.5
218+
)
219+
.strip() # technically not needed but might as well be careful
220+
.split()[0] # 4.6
221+
.strip() # final cleanup
222+
)
223+
# Mesa Intel(R) Graphics (LNL)
224+
gl_renderer = tester.extract_gl_variable(glmark2_output, "GL_RENDERER")
225+
226+
print("GL_VERSION:", gl_version_str)
227+
print("GL_RENDERER:", gl_renderer)
228+
229+
# check if it's newer than 3.0
230+
# we don't have to check the minor version
231+
# since it would be just comparing a positive int to 0
232+
if int(gl_version_str.split(".")[0]) < 3:
233+
raise SystemExit(
234+
"The minimum required OpenGL version is 3.0, but got {}".format(
235+
gl_version_str
236+
)
237+
)
238+
239+
if not tester.gl_renderer_str_is_hardware_renderer(gl_renderer):
240+
raise SystemExit(
241+
"This machine is not using a hardware renderer. "
242+
+ "Got GL_RENDERER={}".format(gl_renderer)
243+
)
244+
245+
print(
246+
"OK! This machine meets the minimum OpenGL version requirement",
247+
"({} >= 3.0)".format(gl_version_str),
248+
"and is using a hardware renderer for {} apps".format(
249+
os.environ["XDG_SESSION_TYPE"]
250+
), # wayland working doesn't necessarily imply Xwayland working
251+
)
58252

59253

60254
if __name__ == "__main__":
61-
GLSupport().is_support_opengl()
255+
main()

providers/base/tests/test_data/gl_support_fail.txt

Lines changed: 0 additions & 16 deletions
This file was deleted.

providers/base/tests/test_data/gl_support_fail_changed.txt

Lines changed: 0 additions & 16 deletions
This file was deleted.

providers/base/tests/test_data/gl_support_succ.txt

Lines changed: 0 additions & 16 deletions
This file was deleted.

providers/base/tests/test_data/gl_support_succ_changed.txt

Lines changed: 0 additions & 16 deletions
This file was deleted.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
=======================================================
2+
glmark2 2014.03+git20150611.fa71af2d
3+
=======================================================
4+
OpenGL Information
5+
GL_VENDOR: VMware, Inc.
6+
GL_RENDERER: llvmpipe (LLVM 6.0, 256 bits)
7+
GL_VERSION: OpenGL ES 3.0 Mesa 18.0.5
8+
=======================================================
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
=======================================================
2+
glmark2 2023.01
3+
=======================================================
4+
OpenGL Information
5+
GL_VENDOR: Intel
6+
GL_RENDERER: Mesa Intel(R) Graphics (LNL)
7+
GL_VERSION: OpenGL ES 3.2 Mesa 25.0.7-0ubuntu0.25.04.1
8+
Surface Config: buf=32 r=8 g=8 b=8 a=8 depth=24 stencil=0 samples=0
9+
Surface Size: 800x600 windowed
10+
=======================================================
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
** GLX does not support GLX_EXT_swap_control or GLX_MESA_swap_control!
2+
** Failed to set swap interval. Results may be bounded above by refresh rate.
3+
=======================================================
4+
glmark2 2014.03+git20150611.fa71af2d
5+
=======================================================
6+
OpenGL Information
7+
GL_VENDOR: VMware, Inc.
8+
GL_RENDERER: llvmpipe (LLVM 6.0, 256 bits)
9+
GL_VERSION: 3.0 Mesa 18.0.5
10+
=======================================================
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
=======================================================
2+
glmark2 2023.01
3+
=======================================================
4+
OpenGL Information
5+
GL_VENDOR: Intel
6+
GL_RENDERER: Mesa Intel(R) Graphics (LNL)
7+
GL_VERSION: 4.6 (Compatibility Profile) Mesa 25.0.7-0ubuntu0.25.04.1
8+
Surface Config: buf=32 r=8 g=8 b=8 a=8 depth=24 stencil=0 samples=0
9+
Surface Size: 800x600 windowed
10+
=======================================================
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
=======================================================
2+
glmark2 2010.01
3+
=======================================================
4+
OpenGL Information
5+
GL_VENDOR: Intel
6+
GL_RENDERER: Mesa Intel(R) Graphics
7+
GL_VERSION: OpenGL ES 2.1 Mesa 25.0.7-0ubuntu0.25.04.1
8+
Surface Config: buf=32 r=8 g=8 b=8 a=8 depth=24 stencil=0 samples=0
9+
Surface Size: 800x600 windowed
10+
=======================================================

0 commit comments

Comments
 (0)