Hide keyboard shortcuts

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. 

7 

8import sys 

9import logging 

10 

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 

15 

16log = Logger("opengl") 

17 

18required_extensions = ["GL_ARB_texture_rectangle", "GL_ARB_vertex_program"] 

19 

20 

21GL_ALPHA_SUPPORTED = envbool("XPRA_ALPHA", True) 

22DOUBLE_BUFFERED = envbool("XPRA_OPENGL_DOUBLE_BUFFERED", True) 

23 

24CRASH = envbool("XPRA_OPENGL_FORCE_CRASH", False) 

25TIMEOUT = envint("XPRA_OPENGL_FORCE_TIMEOUT", 0) 

26 

27 

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 

35 

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 

60 

61 

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)) 

80 

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) 

98 

99 

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') 

126 

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) 

156 

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 

168 

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) 

185 

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() 

197 

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 

203 

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)) 

236 

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 

246 

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 

276 

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 ) 

308 

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 

319 

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) 

328 

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) 

344 

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) 

351 

352 texture_size_limit = get_max_texture_size() 

353 props["texture-size-limit"] = int(texture_size_limit) 

354 

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) 

376 

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)) 

394 

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) 

401 

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)) 

418 

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) 

427 

428 

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) 

472 

473 

474if __name__ == "__main__": 

475 sys.exit(main())