Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/client/gl/gl_check.py : 53%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#!/usr/bin/env python
2# This file is part of Xpra.
3# Copyright (C) 2012 Serviware (Arthur Huillet, <ahuillet@serviware.com>)
4# Copyright (C) 2012-2019 Antoine Martin <antoine@xpra.org>
5# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
6# later version. See the file COPYING for details.
8import sys
9import logging
11from xpra.util import envbool, envint, csv
12from xpra.os_util import POSIX, OSX, bytestostr
13from xpra.log import Logger, CaptureHandler
14from xpra.client.gl.gl_drivers import WHITELIST, GREYLIST, VERSION_REQ, BLACKLIST, OpenGLFatalError
16log = Logger("opengl")
18required_extensions = ["GL_ARB_texture_rectangle", "GL_ARB_vertex_program"]
21GL_ALPHA_SUPPORTED = envbool("XPRA_ALPHA", True)
22DOUBLE_BUFFERED = envbool("XPRA_OPENGL_DOUBLE_BUFFERED", True)
24CRASH = envbool("XPRA_OPENGL_FORCE_CRASH", False)
25TIMEOUT = envint("XPRA_OPENGL_FORCE_TIMEOUT", 0)
28#by default, we raise an ImportError as soon as we find something missing:
29def raise_error(msg):
30 raise ImportError(msg)
31def raise_fatal_error(msg):
32 raise OpenGLFatalError(msg)
33gl_check_error = raise_error
34gl_fatal_error = raise_fatal_error
36_version_warning_shown = False
37#support for memory views requires Python 2.7 and PyOpenGL 3.1
38def is_pyopengl_memoryview_safe(pyopengl_version, accel_version) -> bool:
39 if accel_version is not None and pyopengl_version!=accel_version:
40 #mismatch is not safe!
41 return False
42 vsplit = pyopengl_version.split('.')
43 if vsplit[:2]<['3','1']:
44 #requires PyOpenGL >= 3.1, earlier versions will not work
45 return False
46 if vsplit[:2]>=['3','2']:
47 #assume that newer versions are OK too
48 return True
49 #at this point, we know we have a 3.1.x version, but which one?
50 if len(vsplit)<3:
51 #not enough parts to know for sure, assume it's not supported
52 return False
53 micro = vsplit[2]
54 #ie: '0', '1' or '0b2'
55 if micro=='0':
56 return True #3.1.0 is OK
57 if micro>='1':
58 return True #3.1.1 onwards should be too
59 return False #probably something like '0b2' which is broken
62def check_functions(force_enable, *functions):
63 missing = []
64 available = []
65 for x in functions:
66 try:
67 name = x.__name__
68 except AttributeError:
69 name = str(x)
70 if not bool(x):
71 missing.append(name)
72 else:
73 available.append(name)
74 if missing:
75 if not force_enable:
76 raise_fatal_error("some required OpenGL functions are missing:\n%s" % csv(missing))
77 log("some functions are missing: %s", csv(missing))
78 else:
79 log("All the required OpenGL functions are available: %s " % csv(available))
81def get_max_texture_size() -> int:
82 from OpenGL.GL import glGetInteger, GL_MAX_TEXTURE_SIZE
83 texture_size = glGetInteger(GL_MAX_TEXTURE_SIZE)
84 log("GL_MAX_TEXTURE_SIZE=%s", texture_size)
85 #this one may be missing?
86 rect_texture_size = texture_size
87 try:
88 from OpenGL.GL import GL_MAX_RECTANGLE_TEXTURE_SIZE
89 rect_texture_size = glGetInteger(GL_MAX_RECTANGLE_TEXTURE_SIZE)
90 except ImportError as e:
91 log("OpenGL: %s", e)
92 log("using GL_MAX_TEXTURE_SIZE=%s as default", texture_size)
93 except Exception as e:
94 log("failed to query GL_MAX_RECTANGLE_TEXTURE_SIZE: %s", e)
95 else:
96 log("Texture size GL_MAX_RECTANGLE_TEXTURE_SIZE=%s", rect_texture_size)
97 return min(rect_texture_size, texture_size)
100def check_PyOpenGL_support(force_enable) -> dict:
101 props = {}
102 def unsafe():
103 props["safe"] = False
104 try:
105 if CRASH:
106 import ctypes
107 ctypes.string_at(0)
108 raise Exception("should have crashed!")
109 if TIMEOUT>0:
110 import time
111 time.sleep(TIMEOUT)
112 #log redirection:
113 def redirect_log(logger_name):
114 logger = logging.getLogger(logger_name)
115 assert logger is not None
116 logger.saved_handlers = logger.handlers
117 logger.saved_propagate = logger.propagate
118 logger.handlers = [CaptureHandler()]
119 logger.propagate = 0
120 return logger
121 fhlogger = redirect_log('OpenGL.formathandler')
122 elogger = redirect_log('OpenGL.extensions')
123 alogger = redirect_log('OpenGL.acceleratesupport')
124 arlogger = redirect_log('OpenGL.arrays')
125 clogger = redirect_log('OpenGL.converters')
127 import OpenGL
128 props["pyopengl"] = OpenGL.__version__
129 from OpenGL.GL import GL_VERSION, GL_EXTENSIONS
130 from OpenGL.GL import glGetString, glGetIntegerv
131 gl_version_str = glGetString(GL_VERSION)
132 if gl_version_str is None and not force_enable:
133 raise_fatal_error("OpenGL version is missing - cannot continue")
134 return props
135 #b'4.6.0 NVIDIA 440.59' -> ['4', '6', '0 NVIDIA...']
136 log("GL_VERSION=%s", bytestostr(gl_version_str))
137 vparts = bytestostr(gl_version_str).split(" ", 1)[0].split(".")
138 try:
139 gl_major = int(vparts[0])
140 gl_minor = int(vparts[1])
141 except (IndexError, ValueError) as e:
142 log("failed to parse gl version '%s': %s", bytestostr(gl_version_str), e)
143 log(" assuming this is at least 1.1 to continue")
144 unsafe()
145 else:
146 props["opengl"] = gl_major, gl_minor
147 MIN_VERSION = (1,1)
148 if (gl_major, gl_minor) < MIN_VERSION:
149 if not force_enable:
150 raise_fatal_error("OpenGL output requires version %s or greater, not %s.%s" %
151 (".".join([str(x) for x in MIN_VERSION]), gl_major, gl_minor))
152 return props
153 unsafe()
154 else:
155 log("found valid OpenGL version: %s.%s", gl_major, gl_minor)
157 from OpenGL import version as OpenGL_version
158 pyopengl_version = OpenGL_version.__version__
159 try:
160 import OpenGL_accelerate #@UnresolvedImport
161 accel_version = OpenGL_accelerate.__version__
162 props["accelerate"] = accel_version
163 log("OpenGL_accelerate version %s", accel_version)
164 except ImportError:
165 log("OpenGL_accelerate not found")
166 OpenGL_accelerate = None
167 accel_version = None
169 if accel_version is not None and pyopengl_version!=accel_version:
170 global _version_warning_shown
171 if not _version_warning_shown:
172 log.warn("Warning: version mismatch between PyOpenGL and PyOpenGL-accelerate")
173 log.warn(" %s vs %s", pyopengl_version, accel_version)
174 log.warn(" this may cause crashes")
175 _version_warning_shown = True
176 gl_check_error("PyOpenGL vs accelerate version mismatch: %s vs %s" % (pyopengl_version, accel_version))
177 vsplit = pyopengl_version.split('.')
178 #we now require PyOpenGL 3.1 or later
179 if vsplit[:3]<['3','1']:
180 if not force_enable:
181 raise_fatal_error("PyOpenGL version %s is too old and buggy" % pyopengl_version)
182 return {}
183 unsafe()
184 props["zerocopy"] = bool(OpenGL_accelerate) and is_pyopengl_memoryview_safe(pyopengl_version, accel_version)
186 try:
187 extensions = glGetString(GL_EXTENSIONS).decode().split(" ")
188 log("OpenGL extensions found: %s", csv(extensions))
189 props["extensions"] = extensions
190 except Exception:
191 log("error querying extensions", exc_info=True)
192 extensions = []
193 if not force_enable:
194 raise_fatal_error("OpenGL could not find the list of GL extensions -"+
195 " does the graphics driver support OpenGL?")
196 unsafe()
198 from OpenGL.arrays.arraydatatype import ArrayDatatype
199 try:
200 log("found the following array handlers: %s", set(ArrayDatatype.getRegistry().values()))
201 except Exception:
202 pass
204 from OpenGL.GL import GL_RENDERER, GL_VENDOR, GL_SHADING_LANGUAGE_VERSION
205 def fixstring(v):
206 try:
207 return str(v).strip()
208 except Exception:
209 return str(v)
210 for d,s,fatal in (("vendor", GL_VENDOR, True),
211 ("renderer", GL_RENDERER, True),
212 ("shading-language-version", GL_SHADING_LANGUAGE_VERSION, False)):
213 try:
214 v = glGetString(s)
215 v = fixstring(v.decode())
216 log("%s: %s", d, v)
217 except Exception:
218 if fatal and not force_enable:
219 gl_check_error("OpenGL property '%s' is missing" % d)
220 else:
221 log("OpenGL property '%s' is missing", d)
222 v = ""
223 props[d] = v
224 vendor = props["vendor"]
225 version_req = VERSION_REQ.get(vendor)
226 if version_req:
227 req_maj, req_min = version_req
228 if gl_major<req_maj or (gl_major==req_maj and gl_minor<req_min):
229 if force_enable:
230 log.warn("Warning: '%s' OpenGL driver requires version %i.%i", vendor, req_maj, req_min)
231 log.warn(" version %i.%i was found", gl_major, gl_minor)
232 unsafe()
233 else:
234 gl_check_error("OpenGL version %i.%i is too old, %i.%i is required for %s" % (
235 gl_major, gl_minor, req_maj, req_min, vendor))
237 from OpenGL.GLU import gluGetString, GLU_VERSION, GLU_EXTENSIONS
238 #maybe we can continue without?
239 if not bool(gluGetString):
240 raise_fatal_error("no OpenGL GLU support")
241 for d,s in {"GLU.version": GLU_VERSION, "GLU.extensions":GLU_EXTENSIONS}.items():
242 v = gluGetString(s)
243 v = v.decode()
244 log("%s: %s", d, v)
245 props[d] = v
247 def match_list(thelist, listname):
248 for k,vlist in thelist.items():
249 v = props.get(k)
250 matches = [x for x in vlist if v.find(x)>=0]
251 if matches:
252 log("%s '%s' found in %s: %s", k, v, listname, vlist)
253 return (k, v)
254 log("%s '%s' not found in %s: %s", k, v, listname, vlist)
255 return None
256 blacklisted = match_list(BLACKLIST, "blacklist")
257 greylisted = match_list(GREYLIST, "greylist")
258 whitelisted = match_list(WHITELIST, "whitelist")
259 if blacklisted:
260 if whitelisted:
261 log.info("%s '%s' enabled (found in both blacklist and whitelist)", *whitelisted)
262 elif force_enable:
263 log.warn("Warning: %s '%s' is blacklisted!", *blacklisted)
264 log.warn(" force enabled by option")
265 else:
266 if force_enable:
267 log.warn("%s '%s' is blacklisted!" % (blacklisted))
268 else:
269 raise_fatal_error("%s '%s' is blacklisted!" % (blacklisted))
270 safe = bool(whitelisted) or not bool(blacklisted)
271 if greylisted and not whitelisted:
272 log.warn("Warning: %s '%s' is greylisted,", *greylisted)
273 log.warn(" you may want to turn off OpenGL if you encounter bugs")
274 if props.get("safe") is None:
275 props["safe"] = safe
277 #check for specific functions we need:
278 from OpenGL.GL import (
279 glActiveTexture, glTexSubImage2D, glTexCoord2i,
280 glViewport, glMatrixMode, glLoadIdentity, glOrtho,
281 glEnableClientState, glGenTextures, glDisable,
282 glBindTexture, glPixelStorei, glEnable, glBegin, glFlush,
283 glTexParameteri, glTexEnvi, glHint, glBlendFunc, glLineStipple,
284 glTexImage2D,
285 glMultiTexCoord2i,
286 glVertex2i, glEnd,
287 )
288 check_functions(force_enable,
289 glActiveTexture, glTexSubImage2D, glTexCoord2i,
290 glViewport, glMatrixMode, glLoadIdentity, glOrtho,
291 glEnableClientState, glGenTextures, glDisable,
292 glBindTexture, glPixelStorei, glEnable, glBegin, glFlush,
293 glTexParameteri, glTexEnvi, glHint, glBlendFunc, glLineStipple,
294 glTexImage2D,
295 glMultiTexCoord2i,
296 glVertex2i, glEnd)
297 #check for framebuffer functions we need:
298 from OpenGL.GL.ARB.framebuffer_object import (
299 GL_FRAMEBUFFER,
300 GL_COLOR_ATTACHMENT0,
301 glGenFramebuffers, glBindFramebuffer, glFramebufferTexture2D,
302 )
303 check_functions(force_enable,
304 GL_FRAMEBUFFER,
305 GL_COLOR_ATTACHMENT0,
306 glGenFramebuffers, glBindFramebuffer, glFramebufferTexture2D,
307 )
309 glEnablei = None
310 try:
311 from OpenGL.GL import glEnablei
312 except ImportError:
313 pass
314 if not bool(glEnablei):
315 log.warn("OpenGL glEnablei is not available, disabling transparency")
316 global GL_ALPHA_SUPPORTED
317 GL_ALPHA_SUPPORTED = False
318 props["transparency"] = GL_ALPHA_SUPPORTED
320 missing_extensions = [ext for ext in required_extensions if ext not in extensions]
321 if missing_extensions:
322 if not force_enable:
323 raise_fatal_error("OpenGL driver lacks support for extension: %s" % csv(missing_extensions))
324 log("some extensions are missing: %s", csv(missing_extensions))
325 unsafe()
326 else:
327 log("All required extensions are present: %s", required_extensions)
329 #this allows us to do CSC via OpenGL:
330 #see http://www.opengl.org/registry/specs/ARB/fragment_program.txt
331 from OpenGL.GL.ARB.fragment_program import glInitFragmentProgramARB
332 from OpenGL.GL.ARB.texture_rectangle import glInitTextureRectangleARB
333 for name, fn in {
334 "glInitFragmentProgramARB" : glInitFragmentProgramARB,
335 "glInitTextureRectangleARB" : glInitTextureRectangleARB,
336 }.items():
337 if not fn():
338 if not force_enable:
339 raise_fatal_error("OpenGL output requires %s" % name)
340 log("%s missing", name)
341 unsafe()
342 else:
343 log("%s found", name)
345 from OpenGL.GL.ARB.vertex_program import (
346 glGenProgramsARB, glDeleteProgramsARB,
347 glBindProgramARB, glProgramStringARB,
348 )
349 check_functions(force_enable,
350 glGenProgramsARB, glDeleteProgramsARB, glBindProgramARB, glProgramStringARB)
352 texture_size_limit = get_max_texture_size()
353 props["texture-size-limit"] = int(texture_size_limit)
355 try:
356 from OpenGL.GL import GL_MAX_VIEWPORT_DIMS
357 v = glGetIntegerv(GL_MAX_VIEWPORT_DIMS)
358 max_viewport_dims = int(v[0]), int(v[1])
359 assert max_viewport_dims[0]>=texture_size_limit and max_viewport_dims[1]>=texture_size_limit
360 log("GL_MAX_VIEWPORT_DIMS=%s", max_viewport_dims)
361 except ImportError as e:
362 log.error("Error querying max viewport dims: %s", e)
363 max_viewport_dims = texture_size_limit, texture_size_limit
364 props["max-viewport-dims"] = max_viewport_dims
365 return props
366 finally:
367 for x in alogger.handlers[0].records:
368 #strip default message prefix:
369 msg = x.getMessage().replace("No OpenGL_accelerate module loaded: ", "")
370 if msg=="No module named OpenGL_accelerate":
371 msg = "missing accelerate module"
372 if msg=="OpenGL_accelerate module loaded":
373 log.info(msg)
374 else:
375 log.warn("PyOpenGL warning: %s", msg)
377 #format handler messages:
378 STRIP_LOG_MESSAGE = "Unable to load registered array format handler "
379 missing_handlers = []
380 for x in fhlogger.handlers[0].records:
381 msg = x.getMessage()
382 p = msg.find(STRIP_LOG_MESSAGE)
383 if p<0:
384 #unknown message, log it:
385 log.info(msg)
386 continue
387 format_handler = msg[p+len(STRIP_LOG_MESSAGE):]
388 p = format_handler.find(":")
389 if p>0:
390 format_handler = format_handler[:p]
391 missing_handlers.append(format_handler)
392 if missing_handlers:
393 log.warn("PyOpenGL warning: missing array format handlers: %s", csv(missing_handlers))
395 for x in elogger.handlers[0].records:
396 msg = x.getMessage()
397 #ignore extension messages:
398 p = msg.startswith("GL Extension ") and msg.endswith("available")
399 if not p:
400 log.info(msg)
402 missing_accelerators = []
403 STRIP_AR_HEAD = "Unable to load"
404 STRIP_AR_TAIL = "from OpenGL_accelerate"
405 for x in arlogger.handlers[0].records+clogger.handlers[0].records:
406 msg = x.getMessage()
407 if msg.startswith(STRIP_AR_HEAD) and msg.endswith(STRIP_AR_TAIL):
408 m = msg[len(STRIP_AR_HEAD):-len(STRIP_AR_TAIL)].strip()
409 m = m.replace("accelerators", "").replace("accelerator", "").strip()
410 missing_accelerators.append(m)
411 continue
412 elif msg.startswith("Using accelerated"):
413 log(msg)
414 else:
415 log.info(msg)
416 if missing_accelerators:
417 log.info("OpenGL accelerate missing: %s", csv(missing_accelerators))
419 def restore_logger(logger):
420 logger.handlers = logger.saved_handlers
421 logger.propagate = logger.saved_propagate
422 restore_logger(fhlogger)
423 restore_logger(elogger)
424 restore_logger(alogger)
425 restore_logger(arlogger)
426 restore_logger(clogger)
429def main():
430 from xpra.platform import program_context
431 from xpra.platform.gui import init as gui_init
432 from xpra.util import print_nested_dict
433 from xpra.log import enable_color
434 with program_context("OpenGL-Check"):
435 gui_init()
436 enable_color()
437 verbose = "-v" in sys.argv or "--verbose" in sys.argv
438 if verbose:
439 log.enable_debug()
440 if POSIX and not OSX:
441 from xpra.x11.gtk_x11.gdk_display_source import init_gdk_display_source
442 init_gdk_display_source()
443 force_enable = "-f" in sys.argv or "--force" in sys.argv
444 from xpra.platform.gl_context import GLContext
445 log("testing %s", GLContext)
446 gl_context = GLContext() #pylint: disable=not-callable
447 log("GLContext=%s", gl_context)
448 #replace ImportError with a log message:
449 global gl_check_error, gl_fatal_error
450 errors = []
451 def log_error(msg):
452 log.error("ERROR: %s", msg)
453 errors.append(msg)
454 gl_check_error = log_error
455 gl_fatal_error = log_error
456 try:
457 props = gl_context.check_support(force_enable)
458 except Exception as e:
459 props = {}
460 log("check_support", exc_info=True)
461 errors.append(e)
462 log.info("")
463 if errors:
464 log.info("OpenGL errors:")
465 for e in errors:
466 log.info(" %s", e)
467 if props:
468 log.info("")
469 log.info("OpenGL properties:")
470 print_nested_dict(props)
471 return len(errors)
474if __name__ == "__main__":
475 sys.exit(main())