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# This file is part of Xpra. 

2# Copyright (C) 2013 Serviware (Arthur Huillet, <ahuillet@serviware.com>) 

3# Copyright (C) 2012-2020 Antoine Martin <antoine@xpra.org> 

4# Xpra is released under the terms of the GNU GPL v2, or, at your option, any 

5# later version. See the file COPYING for details. 

6 

7import os 

8import time 

9from ctypes import c_char_p 

10 

11from OpenGL import version as OpenGL_version 

12from OpenGL.error import GLError 

13from OpenGL.GL import ( 

14 GL_PROJECTION, GL_MODELVIEW, 

15 GL_UNPACK_ROW_LENGTH, GL_UNPACK_ALIGNMENT, 

16 GL_TEXTURE_MAG_FILTER, GL_TEXTURE_MIN_FILTER, GL_NEAREST, 

17 GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT, 

18 GL_LUMINANCE, GL_LINEAR, 

19 GL_TEXTURE0, GL_TEXTURE1, GL_TEXTURE2, GL_QUADS, GL_LINE_LOOP, GL_LINES, GL_COLOR_BUFFER_BIT, 

20 GL_TEXTURE_WRAP_S, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER, 

21 GL_DONT_CARE, GL_TRUE, GL_DEPTH_TEST, GL_SCISSOR_TEST, GL_LIGHTING, GL_DITHER, 

22 GL_RGB, GL_RGBA, GL_BGR, GL_BGRA, GL_RGBA8, GL_RGB8, GL_RGB10_A2, GL_RGB565, GL_RGB5_A1, GL_RGBA4, GL_RGBA16, 

23 GL_UNSIGNED_INT_2_10_10_10_REV, GL_UNSIGNED_INT_10_10_10_2, GL_UNSIGNED_SHORT_5_6_5, 

24 GL_BLEND, GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA, 

25 GL_TEXTURE_MAX_LEVEL, GL_TEXTURE_BASE_LEVEL, 

26 GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST, 

27 glTexEnvi, GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE, 

28 glHint, 

29 glBlendFunc, 

30 glActiveTexture, glTexSubImage2D, 

31 glGetString, glViewport, glMatrixMode, glLoadIdentity, glOrtho, 

32 glGenTextures, glDisable, 

33 glBindTexture, glPixelStorei, glEnable, glBegin, glFlush, 

34 glTexParameteri, 

35 glTexImage2D, 

36 glMultiTexCoord2i, 

37 glTexCoord2i, glVertex2i, glEnd, 

38 glClear, glClearColor, glLineWidth, glColor4f, 

39 glDrawBuffer, glReadBuffer, 

40 ) 

41from OpenGL.GL.ARB.texture_rectangle import GL_TEXTURE_RECTANGLE_ARB 

42from OpenGL.GL.ARB.vertex_program import ( 

43 glGenProgramsARB, glBindProgramARB, glProgramStringARB, 

44 GL_PROGRAM_ERROR_STRING_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, 

45 ) 

46from OpenGL.GL.ARB.fragment_program import GL_FRAGMENT_PROGRAM_ARB 

47from OpenGL.GL.ARB.framebuffer_object import ( 

48 GL_FRAMEBUFFER, GL_DRAW_FRAMEBUFFER, GL_READ_FRAMEBUFFER, 

49 GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, \ 

50 glGenFramebuffers, glBindFramebuffer, glFramebufferTexture2D, glBlitFramebuffer, 

51 ) 

52 

53from xpra.os_util import ( 

54 monotonic_time, strtobytes, hexstr, 

55 POSIX, 

56 DummyContextManager, 

57 ) 

58from xpra.util import envint, envbool, repr_ellipsized, first_time 

59from xpra.client.paint_colors import get_paint_box_color 

60from xpra.codecs.codec_constants import get_subsampling_divs 

61from xpra.client.window_backing_base import ( 

62 fire_paint_callbacks, WindowBackingBase, 

63 WEBP_PILLOW, SCROLL_ENCODING, 

64 ) 

65from xpra.client.gl.gl_check import GL_ALPHA_SUPPORTED, is_pyopengl_memoryview_safe, get_max_texture_size 

66from xpra.client.gl.gl_colorspace_conversions import YUV2RGB_shader, YUV2RGB_FULL_shader, RGBP2RGB_shader 

67from xpra.client.gl.gl_spinner import draw_spinner 

68from xpra.log import Logger 

69 

70log = Logger("opengl", "paint") 

71fpslog = Logger("opengl", "fps") 

72 

73OPENGL_DEBUG = envbool("XPRA_OPENGL_DEBUG", False) 

74PAINT_FLUSH = envbool("XPRA_PAINT_FLUSH", True) 

75JPEG_YUV = envbool("XPRA_JPEG_YUV", True) 

76WEBP_YUV = envbool("XPRA_WEBP_YUV", True) 

77FORCE_CLONE = envbool("XPRA_OPENGL_FORCE_CLONE", False) 

78DRAW_REFRESH = envbool("XPRA_OPENGL_DRAW_REFRESH", True) 

79FBO_RESIZE = envbool("XPRA_OPENGL_FBO_RESIZE", True) 

80FBO_RESIZE_DELAY = envint("XPRA_OPENGL_FBO_RESIZE_DELAY", -1) 

81CONTEXT_REINIT = envbool("XPRA_OPENGL_CONTEXT_REINIT", False) 

82 

83CURSOR_IDLE_TIMEOUT = envint("XPRA_CURSOR_IDLE_TIMEOUT", 6) 

84 

85SAVE_BUFFERS = os.environ.get("XPRA_OPENGL_SAVE_BUFFERS") 

86if SAVE_BUFFERS not in ("png", "jpeg", None): 

87 log.warn("invalid value for XPRA_OPENGL_SAVE_BUFFERS: must be 'png' or 'jpeg'") 

88 SAVE_BUFFERS = None 

89if SAVE_BUFFERS: 

90 from OpenGL.GL import glGetTexImage #pylint: disable=ungrouped-imports 

91 from PIL import Image, ImageOps 

92 

93 

94PIXEL_FORMAT_TO_CONSTANT = { 

95 "r210" : GL_BGRA, 

96 "R210" : GL_RGBA, 

97 "BGR" : GL_BGR, 

98 "RGB" : GL_RGB, 

99 "BGRA" : GL_BGRA, 

100 "BGRX" : GL_BGRA, 

101 "RGBA" : GL_RGBA, 

102 "RGBX" : GL_RGBA, 

103 "BGR565": GL_RGB, 

104 "RGB565": GL_RGB, 

105 } 

106PIXEL_FORMAT_TO_DATATYPE = { 

107 "r210" : GL_UNSIGNED_INT_2_10_10_10_REV, 

108 "R210" : GL_UNSIGNED_INT_10_10_10_2, 

109 "RGB565": GL_UNSIGNED_SHORT_5_6_5, 

110 "BGR565": GL_UNSIGNED_SHORT_5_6_5, 

111 "BGR" : GL_UNSIGNED_BYTE, 

112 "RGB" : GL_UNSIGNED_BYTE, 

113 "BGRA" : GL_UNSIGNED_BYTE, 

114 "BGRX" : GL_UNSIGNED_BYTE, 

115 "RGBA" : GL_UNSIGNED_BYTE, 

116 "RGBX" : GL_UNSIGNED_BYTE, 

117 "YUV420P" : GL_UNSIGNED_BYTE, 

118 "YUV422P" : GL_UNSIGNED_BYTE, 

119 "YUV444P" : GL_UNSIGNED_BYTE, 

120 "GBRP" : GL_UNSIGNED_BYTE, 

121 "GBRP16" : GL_UNSIGNED_SHORT, 

122 "YUV444P10" : GL_UNSIGNED_SHORT, 

123 "YUV444P16" : GL_UNSIGNED_SHORT, 

124 } 

125CONSTANT_TO_PIXEL_FORMAT = { 

126 GL_BGR : "BGR", 

127 GL_RGB : "RGB", 

128 GL_BGRA : "BGRA", 

129 GL_RGBA : "RGBA", 

130 } 

131INTERNAL_FORMAT_TO_STR = { 

132 GL_RGB10_A2 : "RGB10_A2", 

133 GL_RGBA8 : "RGBA8", 

134 GL_RGB8 : "RGB8", 

135 GL_RGB565 : "RGB565", 

136 GL_RGB5_A1 : "RGB5_A1", 

137 GL_RGBA4 : "RGBA4", 

138 GL_RGBA16 : "GL_RGBA16", 

139 } 

140DATATYPE_TO_STR = { 

141 GL_UNSIGNED_INT_2_10_10_10_REV : "UNSIGNED_INT_2_10_10_10_REV", 

142 GL_UNSIGNED_INT_10_10_10_2 : "UNSIGNED_INT_10_10_10_2", 

143 GL_UNSIGNED_BYTE : "UNSIGNED_BYTE", 

144 GL_UNSIGNED_SHORT : "UNSIGNED_SHORT", 

145 GL_UNSIGNED_SHORT_5_6_5 : "UNSIGNED_SHORT_5_6_5", 

146 } 

147 

148#debugging variables: 

149GL_DEBUG_OUTPUT = None 

150GL_DEBUG_OUTPUT_SYNCHRONOUS = None 

151gl_debug_callback = None 

152glInitStringMarkerGREMEDY = None 

153glStringMarkerGREMEDY = None 

154glInitFrameTerminatorGREMEDY = None 

155glFrameTerminatorGREMEDY = None 

156if OPENGL_DEBUG: 

157 try: 

158 from OpenGL.GL.KHR.debug import ( 

159 GL_DEBUG_OUTPUT, GL_DEBUG_OUTPUT_SYNCHRONOUS, 

160 glDebugMessageControl, glDebugMessageCallback, glInitDebugKHR, 

161 ) 

162 except ImportError: 

163 log("Unable to import GL_KHR_debug OpenGL extension. Debug output will be more limited.") 

164 try: 

165 from OpenGL.GL.GREMEDY.string_marker import glInitStringMarkerGREMEDY, glStringMarkerGREMEDY 

166 from OpenGL.GL.GREMEDY.frame_terminator import glInitFrameTerminatorGREMEDY, glFrameTerminatorGREMEDY 

167 from OpenGL.GL import GLDEBUGPROC #@UnresolvedImport 

168 def py_gl_debug_callback(source, error_type, error_id, severity, length, message, param): 

169 log.error("src %x type %x id %x severity %x length %d message %s, param=%s", 

170 source, error_type, error_id, severity, length, message, param) 

171 gl_debug_callback = GLDEBUGPROC(py_gl_debug_callback) 

172 except ImportError: 

173 # This is normal- GREMEDY_string_marker is only available with OpenGL debuggers 

174 log("Unable to import GREMEDY OpenGL extension. Debug output will be more limited.") 

175 log("OpenGL debugging settings:") 

176 log(" GL_DEBUG_OUTPUT=%s, GL_DEBUG_OUTPUT_SYNCHRONOUS=%s", GL_DEBUG_OUTPUT, GL_DEBUG_OUTPUT_SYNCHRONOUS) 

177 log(" gl_debug_callback=%s", gl_debug_callback) 

178 log(" glInitStringMarkerGREMEDY=%s, glStringMarkerGREMEDY=%s", 

179 glInitStringMarkerGREMEDY, glStringMarkerGREMEDY) 

180 log(" glInitFrameTerminatorGREMEDY=%s, glFrameTerminatorGREMEDY=%s", 

181 glInitFrameTerminatorGREMEDY, glFrameTerminatorGREMEDY) 

182 

183zerocopy_upload = False 

184if envbool("XPRA_ZEROCOPY_OPENGL_UPLOAD", True): 

185 try: 

186 import OpenGL_accelerate #@UnresolvedImport 

187 assert OpenGL_accelerate 

188 except ImportError: 

189 pass 

190 else: 

191 zerocopy_upload = is_pyopengl_memoryview_safe(OpenGL_version.__version__, OpenGL_accelerate.__version__) 

192 

193 

194if POSIX: 

195 from xpra.gtk_common.error import xsync 

196 paint_context_manager = xsync 

197else: 

198 paint_context_manager = DummyContextManager() 

199 

200 

201# Texture number assignment 

202# The first four are used to update the FBO, 

203# the FBO is what is painted on screen. 

204TEX_Y = 0 

205TEX_U = 1 

206TEX_V = 2 

207TEX_RGB = 3 

208TEX_FBO = 4 #FBO texture (guaranteed up-to-date window contents) 

209TEX_TMP_FBO = 5 

210TEX_CURSOR = 6 

211N_TEXTURES = 7 

212 

213# Shader number assignment 

214YUV2RGB_SHADER = 0 

215RGBP2RGB_SHADER = 1 

216YUV2RGB_FULL_SHADER = 2 

217 

218 

219""" 

220The logic is as follows: 

221 

222We create an OpenGL framebuffer object, which will be always up-to-date with the latest windows contents. 

223This framebuffer object is updated with YUV painting and RGB painting. It is presented on screen by drawing a 

224textured quad when requested, that is: after each YUV or RGB painting operation, and upon receiving an expose event. 

225The use of a intermediate framebuffer object is the only way to guarantee that the client keeps 

226an always fully up-to-date window image, which is critical because of backbuffer content losses upon buffer swaps 

227or offscreen window movement. 

228""" 

229class GLWindowBackingBase(WindowBackingBase): 

230 

231 RGB_MODES = ["YUV420P", "YUV422P", "YUV444P", "GBRP", "BGRA", "BGRX", "RGBA", "RGBX", "RGB", "BGR"] 

232 HAS_ALPHA = GL_ALPHA_SUPPORTED 

233 

234 def __init__(self, wid : int, window_alpha : bool, pixel_depth : int=0): 

235 self.wid = wid 

236 self.texture_pixel_format = None 

237 #this is the pixel format we are currently updating the fbo with 

238 #can be: "YUV420P", "YUV422P", "YUV444P", "GBRP" or None when not initialized yet. 

239 self.pixel_format = None 

240 self.textures = None # OpenGL texture IDs 

241 self.shaders = None 

242 self.texture_size = 0, 0 

243 self.gl_setup = False 

244 self.debug_setup = False 

245 self.border = None 

246 self.paint_screen = False 

247 self.paint_spinner = False 

248 self.offscreen_fbo = None 

249 self.tmp_fbo = None 

250 self.pending_fbo_paint = [] 

251 self.last_flush = monotonic_time() 

252 self.last_present_fbo_error = None 

253 

254 super().__init__(wid, window_alpha and self.HAS_ALPHA) 

255 self.init_gl_config() 

256 self.init_backing() 

257 self.bit_depth = self.get_bit_depth(pixel_depth) 

258 self.init_formats() 

259 self.draw_needs_refresh = DRAW_REFRESH 

260 #the correct check would be this: 

261 #self.repaint_all = self.is_double_buffered() or bw!=ww or bh!=wh 

262 #but we're meant to be using double-buffered everywhere, so don't bother: 

263 self.repaint_all = True 

264 self._backing.show() 

265 

266 def get_info(self): 

267 info = super().get_info() 

268 info.update({ 

269 "type" : "OpenGL", 

270 "bit-depth" : self.bit_depth, 

271 "pixel-format" : self.pixel_format, 

272 "texture-pixel-format" : CONSTANT_TO_PIXEL_FORMAT.get(self.texture_pixel_format, str(self.texture_pixel_format)), 

273 "internal-format" : INTERNAL_FORMAT_TO_STR.get(self.internal_format, str(self.internal_format)), 

274 }) 

275 return info 

276 

277 

278 def init_gl_config(self): 

279 raise NotImplementedError() 

280 

281 def init_backing(self): 

282 raise NotImplementedError() 

283 

284 def gl_context(self): 

285 raise NotImplementedError() 

286 

287 def do_gl_show(self, rect_count): 

288 raise NotImplementedError() 

289 

290 def is_double_buffered(self): 

291 raise NotImplementedError() 

292 

293 

294 def get_bit_depth(self, pixel_depth=0): 

295 return pixel_depth or 24 

296 

297 def init_formats(self): 

298 self.RGB_MODES = list(GLWindowBackingBase.RGB_MODES) 

299 if self.bit_depth>32: 

300 self.internal_format = GL_RGBA16 

301 self.RGB_MODES.append("r210") 

302 #self.RGB_MODES.append("GBRP16") 

303 elif self.bit_depth==30: 

304 self.internal_format = GL_RGB10_A2 

305 self.RGB_MODES.append("r210") 

306 #self.RGB_MODES.append("GBRP16") 

307 elif 0<self.bit_depth<=16: 

308 if self._alpha_enabled: 

309 if envbool("XPRA_GL_RGBA4", True): 

310 self.internal_format = GL_RGBA4 

311 else: 

312 self.internal_format = GL_RGB5_A1 

313 #too much of a waste to enable? 

314 self.RGB_MODES.append("r210") 

315 else: 

316 self.internal_format = GL_RGB565 

317 self.RGB_MODES.append("BGR565") 

318 self.RGB_MODES.append("RGB565") 

319 else: 

320 if self.bit_depth not in (0, 24, 32) and first_time("bit-depth-%i" % self.bit_depth): 

321 log.warn("Warning: invalid bit depth %i, using 24", self.bit_depth) 

322 #assume 24: 

323 if self._alpha_enabled: 

324 self.internal_format = GL_RGBA8 

325 else: 

326 self.internal_format = GL_RGB8 

327 #(pixels are always stored in 32bpp - but this makes it clearer when we do/don't support alpha) 

328 if self._alpha_enabled: 

329 self.texture_pixel_format = GL_RGBA 

330 else: 

331 self.texture_pixel_format = GL_RGB 

332 log("init_formats() texture pixel format=%s, internal format=%s, rgb modes=%s", 

333 CONSTANT_TO_PIXEL_FORMAT.get(self.texture_pixel_format), 

334 INTERNAL_FORMAT_TO_STR.get(self.internal_format), 

335 self.RGB_MODES) 

336 

337 def get_encoding_properties(self): 

338 props = WindowBackingBase.get_encoding_properties(self) 

339 if SCROLL_ENCODING: 

340 props["encoding.scrolling"] = True 

341 props["encoding.bit-depth"] = self.bit_depth 

342 return props 

343 

344 

345 def __repr__(self): 

346 return "GLWindowBacking(%s, %s, %s)" % (self.wid, self.size, self.pixel_format) 

347 

348 def init(self, ww : int, wh : int, bw : int, bh : int): 

349 #re-init gl projection with new dimensions 

350 #(see gl_init) 

351 self.render_size = ww, wh 

352 if self.size!=(bw, bh): 

353 self.gl_setup = False 

354 oldw, oldh = self.size 

355 self.size = bw, bh 

356 if CONTEXT_REINIT: 

357 self.close_gl_config() 

358 self.init_gl_config() 

359 return 

360 if FBO_RESIZE: 

361 self.resize_fbo(oldw, oldh, bw, bh) 

362 

363 def resize_fbo(self, oldw : int, oldh : int, bw : int, bh : int): 

364 try: 

365 context = self.gl_context() 

366 except Exception: 

367 context = None 

368 log("resize_fbo%s context=%s, offscreen_fbo=%s", 

369 (oldw, oldh, bw, bh), context, self.offscreen_fbo) 

370 if context is None or self.offscreen_fbo is None: 

371 return 

372 #if we have a valid context and an existing offscreen fbo, 

373 #preserve the existing pixels by copying them onto the new tmp fbo (new size) 

374 #and then doing the gl_init() call but without initializing the offscreen fbo. 

375 sx, sy, dx, dy, w, h = self.gravity_copy_coords(oldw, oldh, bw, bh) 

376 with context: 

377 context.update_geometry() 

378 #invert Y coordinates for OpenGL: 

379 sy = (oldh-h)-sy 

380 dy = (bh-h)-dy 

381 #re-init our OpenGL context with the new size, 

382 #but leave offscreen fbo with the old size 

383 self.gl_init(True) 

384 #copy offscreen to new tmp: 

385 self.copy_fbo(w, h, sx, sy, dx, dy) 

386 #make tmp the new offscreen: 

387 self.swap_fbos() 

388 #now we don't need the old tmp fbo contents any more, 

389 #and we can re-initialize it with the correct size: 

390 mag_filter = self.get_init_magfilter() 

391 self.init_fbo(TEX_TMP_FBO, self.tmp_fbo, bw, bh, mag_filter) 

392 self._backing.queue_draw_area(0, 0, bw, bh) 

393 if FBO_RESIZE_DELAY>=0: 

394 def redraw(): 

395 context = self.gl_context() 

396 if not context: 

397 return 

398 with context: 

399 self.pending_fbo_paint = ((0, 0, bw, bh), ) 

400 self.do_present_fbo() 

401 from gi.repository import GLib 

402 GLib.timeout_add(FBO_RESIZE_DELAY, redraw) 

403 

404 

405 def gl_marker(self, *msg): 

406 log(*msg) 

407 if not bool(glStringMarkerGREMEDY): 

408 return 

409 s = str(msg) 

410 c_string = c_char_p(s) 

411 glStringMarkerGREMEDY(0, c_string) 

412 

413 def gl_frame_terminator(self): 

414 log("%s.gl_frame_terminator()", self) 

415 # Mark the end of the frame 

416 # This makes the debug output more readable especially when doing single-buffered rendering 

417 if not bool(glFrameTerminatorGREMEDY): 

418 return 

419 glFrameTerminatorGREMEDY() 

420 

421 def gl_init_debug(self): 

422 #ensure python knows which scope we're talking about: 

423 global glInitStringMarkerGREMEDY, glStringMarkerGREMEDY 

424 global glInitFrameTerminatorGREMEDY, glFrameTerminatorGREMEDY 

425 # Ask GL to send us all debug messages 

426 if GL_DEBUG_OUTPUT and gl_debug_callback and glInitDebugKHR() is True: 

427 glEnable(GL_DEBUG_OUTPUT) 

428 glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS) 

429 glDebugMessageCallback(gl_debug_callback, None) 

430 glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, None, GL_TRUE) 

431 # Initialize string_marker GL debugging extension if available 

432 if glInitStringMarkerGREMEDY and glInitStringMarkerGREMEDY() is True: 

433 log.info("Extension GL_GREMEDY_string_marker available. Will output detailed information about each frame.") 

434 else: 

435 # General case - running without debugger, extension not available 

436 glStringMarkerGREMEDY = None 

437 #don't bother trying again for another window: 

438 glInitStringMarkerGREMEDY = None 

439 # Initialize frame_terminator GL debugging extension if available 

440 if glInitFrameTerminatorGREMEDY and glInitFrameTerminatorGREMEDY() is True: 

441 log.info("Enabling GL frame terminator debugging.") 

442 else: 

443 glFrameTerminatorGREMEDY = None 

444 #don't bother trying again for another window: 

445 glInitFrameTerminatorGREMEDY = None 

446 

447 def gl_init_textures(self): 

448 assert self.offscreen_fbo is None 

449 assert self.shaders is None 

450 assert glGenFramebuffers, "no framebuffer support" 

451 self.textures = glGenTextures(N_TEXTURES) 

452 self.offscreen_fbo = glGenFramebuffers(1) 

453 self.tmp_fbo = glGenFramebuffers(1) 

454 log("%s.gl_init_textures() textures: %s, offscreen fbo: %s, tmp fbo: %s", 

455 self, self.textures, self.offscreen_fbo, self.tmp_fbo) 

456 

457 def gl_init_shaders(self): 

458 assert self.shaders is None 

459 # Create and assign fragment programs 

460 self.shaders = [ 1, 2, 3 ] 

461 glGenProgramsARB(3, self.shaders) 

462 for name, progid, progstr in ( 

463 ("YUV2RGB", YUV2RGB_SHADER, YUV2RGB_shader), 

464 ("YUV2RGBFULL", YUV2RGB_FULL_SHADER, YUV2RGB_FULL_shader), 

465 ("RGBP2RGB", RGBP2RGB_SHADER, RGBP2RGB_shader), 

466 ): 

467 glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, self.shaders[progid]) 

468 glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, len(progstr), progstr) 

469 err = glGetString(GL_PROGRAM_ERROR_STRING_ARB) 

470 if err: 

471 log.error("OpenGL shader %s failed:", name) 

472 log.error(" %s", err) 

473 raise Exception("OpenGL shader %s setup failure: %s" % (name, err)) 

474 log("%s shader initialized", name) 

475 

476 def gl_init(self, skip_fbo=False): 

477 #must be called within a context! 

478 #performs init if needed 

479 if not self.debug_setup: 

480 self.debug_setup = True 

481 self.gl_init_debug() 

482 

483 if self.gl_setup: 

484 return 

485 mt = get_max_texture_size() 

486 w, h = self.size 

487 if w>mt or h>mt: 

488 raise Exception("invalid texture dimensions %ix%i, maximum is %i" % (w, h, mt)) 

489 self.gl_marker("Initializing GL context for window size %s, backing size %s, max texture size=%i", 

490 self.render_size, self.size, mt) 

491 # Initialize viewport and matrices for 2D rendering 

492 x, _, _, y = self.offsets 

493 glViewport(x, y, w, h) 

494 glMatrixMode(GL_PROJECTION) 

495 glLoadIdentity() 

496 glOrtho(0.0, w, h, 0.0, -1.0, 1.0) 

497 glMatrixMode(GL_MODELVIEW) 

498 # Mesa docs claim: this hint can improve the speed of texturing 

499 #when perspective-correct texture coordinate interpolation isn't needed, 

500 #such as when using a glOrtho() projection: 

501 glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST) 

502 # Could be more optimal to use vertex arrays: 

503 # glEnableClientState(GL_VERTEX_ARRAY) 

504 # glEnableClientState(GL_TEXTURE_COORD_ARRAY) 

505 

506 # Clear background to transparent black 

507 glClearColor(0.0, 0.0, 0.0, 0.0) 

508 

509 # we don't use the depth (2D only): 

510 glDisable(GL_DEPTH_TEST) 

511 glDisable(GL_SCISSOR_TEST) 

512 glDisable(GL_LIGHTING) 

513 glDisable(GL_DITHER) 

514 # only do alpha blending in present_fbo: 

515 glDisable(GL_BLEND) 

516 

517 # Default state is good for YUV painting: 

518 # - fragment program enabled 

519 # - YUV fragment program bound 

520 # - render to offscreen FBO 

521 if self.textures is None: 

522 self.gl_init_textures() 

523 

524 mag_filter = self.get_init_magfilter() 

525 # Define empty tmp FBO 

526 self.init_fbo(TEX_TMP_FBO, self.tmp_fbo, w, h, mag_filter) 

527 if not skip_fbo: 

528 # Define empty FBO texture and set rendering to FBO 

529 self.init_fbo(TEX_FBO, self.offscreen_fbo, w, h, mag_filter) 

530 

531 target = GL_TEXTURE_RECTANGLE_ARB 

532 glBindTexture(target, 0) 

533 

534 # Create and assign fragment programs 

535 if not self.shaders: 

536 self.gl_init_shaders() 

537 

538 # Bind program 0 for YUV painting by default 

539 glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, self.shaders[YUV2RGB_SHADER]) 

540 self.gl_setup = True 

541 log("gl_init(%s) done", skip_fbo) 

542 

543 def get_init_magfilter(self): 

544 rw, rh = self.render_size 

545 w, h = self.size 

546 if rw/w!=rw//w or rh/h!=rh//h: 

547 #non integer scaling, use linear magnification filter: 

548 return GL_LINEAR 

549 return GL_NEAREST 

550 

551 

552 def init_fbo(self, texture_index : int, fbo, w : int, h : int, mag_filter): 

553 target = GL_TEXTURE_RECTANGLE_ARB 

554 glBindTexture(target, self.textures[texture_index]) 

555 # nvidia needs this even though we don't use mipmaps (repeated through this file): 

556 glTexParameteri(target, GL_TEXTURE_MAG_FILTER, mag_filter) 

557 glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST) 

558 glTexImage2D(target, 0, self.internal_format, w, h, 0, self.texture_pixel_format, GL_UNSIGNED_BYTE, None) 

559 glBindFramebuffer(GL_FRAMEBUFFER, fbo) 

560 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, self.textures[texture_index], 0) 

561 self.gl_clear_color_buffer() 

562 

563 def gl_clear_color_buffer(self): 

564 try: 

565 glClear(GL_COLOR_BUFFER_BIT) 

566 except Exception: 

567 log("ignoring glClear(GL_COLOR_BUFFER_BIT) error, buggy driver?", exc_info=True) 

568 

569 

570 def close_gl_config(self): 

571 pass 

572 

573 def close(self): 

574 self.close_gl_config() 

575 #This seems to cause problems, so we rely 

576 #on destroying the context to clear textures and fbos... 

577 #if self.offscreen_fbo is not None: 

578 # glDeleteFramebuffers(1, [self.offscreen_fbo]) 

579 # self.offscreen_fbo = None 

580 #if self.textures is not None: 

581 # glDeleteTextures(self.textures) 

582 # self.textures = None 

583 b = self._backing 

584 if b: 

585 self._backing = None 

586 b.destroy() 

587 

588 

589 def paint_scroll(self, scroll_data, options, callbacks): #pylint: disable=arguments-differ 

590 flush = options.intget("flush", 0) 

591 self.idle_add(self.do_scroll_paints, scroll_data, flush, callbacks) 

592 

593 def do_scroll_paints(self, scrolls, flush=0, callbacks=()): 

594 log("do_scroll_paints%s", (scrolls, flush)) 

595 context = self.gl_context() 

596 if not context: 

597 log("%s.do_scroll_paints(..) no context!", self) 

598 fire_paint_callbacks(callbacks, False, "no opengl context") 

599 return 

600 def fail(msg): 

601 log.error("Error: %s", msg) 

602 fire_paint_callbacks(callbacks, False, msg) 

603 bw, bh = self.size 

604 with context: 

605 self.copy_fbo(bw, bh) 

606 

607 for x,y,w,h,xdelta,ydelta in scrolls: 

608 if abs(xdelta)>=bw: 

609 fail("invalid xdelta value: %i, backing width is %i" % (xdelta, bw)) 

610 continue 

611 if abs(ydelta)>=bh: 

612 fail("invalid ydelta value: %i, backing height is %i" % (ydelta, bh)) 

613 continue 

614 if ydelta==0 and xdelta==0: 

615 fail("scroll has no delta!") 

616 continue 

617 if w<=0 or h<=0: 

618 fail("invalid scroll area size: %ix%i" % (w, h)) 

619 continue 

620 #these should be errors, 

621 #but desktop-scaling can cause a mismatch between the backing size 

622 #and the real window size server-side.. so we clamp the dimensions instead 

623 if x+w>bw: 

624 w = bw-x 

625 if y+h>bh: 

626 h = bh-y 

627 if x+w+xdelta>bw: 

628 w = bw-x-xdelta 

629 if w<=0: 

630 continue #nothing left! 

631 if y+h+ydelta>bh: 

632 h = bh-y-ydelta 

633 if h<=0: 

634 continue #nothing left! 

635 if x+xdelta<0: 

636 fail("horizontal scroll by %i:" % xdelta 

637 +" rectangle %s overflows the backing buffer size %s" % ((x, y, w, h), self.size)) 

638 continue 

639 if y+ydelta<0: 

640 fail("vertical scroll by %i:" % ydelta 

641 +" rectangle %s overflows the backing buffer size %s" % ((x, y, w, h), self.size)) 

642 continue 

643 #opengl buffer is upside down, so we must invert Y coordinates: bh-(..) 

644 glBlitFramebuffer(x, bh-y, x+w, bh-(y+h), 

645 x+xdelta, bh-(y+ydelta), x+w+xdelta, bh-(y+h+ydelta), 

646 GL_COLOR_BUFFER_BIT, GL_NEAREST) 

647 self.paint_box("scroll", x+xdelta, y+ydelta, x+w+xdelta, y+h+ydelta) 

648 glFlush() 

649 

650 self.swap_fbos() 

651 

652 target = GL_TEXTURE_RECTANGLE_ARB 

653 #restore normal paint state: 

654 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, self.textures[TEX_FBO], 0) 

655 glBindFramebuffer(GL_READ_FRAMEBUFFER, self.offscreen_fbo) 

656 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, self.offscreen_fbo) 

657 glBindFramebuffer(GL_FRAMEBUFFER, self.offscreen_fbo) 

658 

659 glBindTexture(target, 0) 

660 glDisable(target) 

661 fire_paint_callbacks(callbacks, True) 

662 if not self.draw_needs_refresh: 

663 self.present_fbo(0, 0, bw, bh, flush) 

664 

665 def copy_fbo(self, w : int, h : int, sx : int=0, sy : int=0, dx : int=0, dy : int=0): 

666 log("copy_fbo%s", (w, h, sx, sy, dx, dy)) 

667 #copy from offscreen to tmp: 

668 glBindFramebuffer(GL_READ_FRAMEBUFFER, self.offscreen_fbo) 

669 target = GL_TEXTURE_RECTANGLE_ARB 

670 glEnable(target) 

671 glBindTexture(target, self.textures[TEX_FBO]) 

672 glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, self.textures[TEX_FBO], 0) 

673 glReadBuffer(GL_COLOR_ATTACHMENT0) 

674 

675 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, self.tmp_fbo) 

676 glBindTexture(target, self.textures[TEX_TMP_FBO]) 

677 glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, target, self.textures[TEX_TMP_FBO], 0) 

678 glDrawBuffer(GL_COLOR_ATTACHMENT1) 

679 

680 glBlitFramebuffer(sx, sy, sx+w, sy+h, 

681 dx, dy, dx+w, dy+h, 

682 GL_COLOR_BUFFER_BIT, GL_NEAREST) 

683 

684 def swap_fbos(self): 

685 log("swap_fbos()") 

686 #swap references to tmp and offscreen so tmp becomes the new offscreen: 

687 tmp = self.offscreen_fbo 

688 self.offscreen_fbo = self.tmp_fbo 

689 self.tmp_fbo = tmp 

690 tmp = self.textures[TEX_FBO] 

691 self.textures[TEX_FBO] = self.textures[TEX_TMP_FBO] 

692 self.textures[TEX_TMP_FBO] = tmp 

693 

694 

695 def present_fbo(self, x : int, y : int, w : int, h : int, flush=0): 

696 log("present_fbo: adding %s to pending paint list (size=%i), flush=%s, paint_screen=%s", 

697 (x, y, w, h), len(self.pending_fbo_paint), flush, self.paint_screen) 

698 self.pending_fbo_paint.append((x, y, w, h)) 

699 if not self.paint_screen: 

700 return 

701 #flush>0 means we should wait for the final flush=0 paint 

702 if flush==0 or not PAINT_FLUSH: 

703 try: 

704 with paint_context_manager: 

705 self.do_present_fbo() 

706 except Exception as e: 

707 log.error("Error presenting FBO:") 

708 log.error(" %s", e) 

709 log("Error presenting FBO", exc_info=True) 

710 self.last_present_fbo_error = str(e) 

711 

712 def do_present_fbo(self): 

713 bw, bh = self.size 

714 ww, wh = self.render_size 

715 rect_count = len(self.pending_fbo_paint) 

716 if self.is_double_buffered() or bw!=ww or bh!=wh: 

717 #refresh the whole window: 

718 rectangles = ((0, 0, bw, bh), ) 

719 else: 

720 #paint just the rectangles we have accumulated: 

721 rectangles = self.pending_fbo_paint 

722 self.pending_fbo_paint = [] 

723 log("do_present_fbo: painting %s", rectangles) 

724 

725 if SAVE_BUFFERS: 

726 self.save_FBO() 

727 

728 self.gl_marker("Presenting FBO on screen") 

729 # Change state to target screen instead of our FBO 

730 glBindFramebuffer(GL_FRAMEBUFFER, 0) 

731 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0) 

732 

733 left, top, right, bottom = self.offsets 

734 

735 #viewport for clearing the whole window: 

736 glViewport(0, 0, left+ww+right, top+wh+bottom) 

737 if self._alpha_enabled: 

738 # transparent background: 

739 glClearColor(0.0, 0.0, 0.0, 0.0) 

740 else: 

741 # black, no alpha: 

742 glClearColor(0.0, 0.0, 0.0, 1.0) 

743 if left or top or right or bottom: 

744 self.gl_clear_color_buffer() 

745 

746 #from now on, take the offsets into account: 

747 glViewport(left, top, ww, wh) 

748 target = GL_TEXTURE_RECTANGLE_ARB 

749 if ww!=bw or wh!=bh: 

750 glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR) 

751 glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR) 

752 

753 # Draw FBO texture on screen 

754 glEnable(target) #redundant - done in rgb paint state 

755 glBindTexture(target, self.textures[TEX_FBO]) 

756 if self._alpha_enabled: 

757 # support alpha channel if present: 

758 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) 

759 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE) 

760 glBegin(GL_QUADS) 

761 for x,y,w,h in rectangles: 

762 #note how we invert coordinates.. 

763 tx1, ty1, tx2, ty2 = x, bh-y, x+w, bh-y-h 

764 vx1, vy1, vx2, vy2 = x, y, x+w, y+h 

765 glTexCoord2i(tx1, ty1) 

766 glVertex2i(vx1, vy1) #top-left of window viewport 

767 glTexCoord2i(tx1, ty2) 

768 glVertex2i(vx1, vy2) #bottom-left of window viewport 

769 glTexCoord2i(tx2, ty2) 

770 glVertex2i(vx2, vy2) #bottom-right of window viewport 

771 glTexCoord2i(tx2, ty1) 

772 glVertex2i(vx2, vy1) #top-right of window viewport 

773 glEnd() 

774 glBindTexture(target, 0) 

775 glDisable(target) 

776 

777 if self.pointer_overlay: 

778 self.draw_pointer() 

779 

780 if self.paint_spinner: 

781 #add spinner: 

782 self.draw_spinner() 

783 

784 if self.border and self.border.shown: 

785 self.draw_border() 

786 

787 # Show the backbuffer on screen 

788 glFlush() 

789 self.gl_show(rect_count) 

790 self.gl_frame_terminator() 

791 

792 #restore pbo viewport 

793 glViewport(0, 0, bw, bh) 

794 glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST) 

795 glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST) 

796 log("%s(%s, %s)", glBindFramebuffer, GL_FRAMEBUFFER, self.offscreen_fbo) 

797 glBindFramebuffer(GL_FRAMEBUFFER, self.offscreen_fbo) 

798 log("%s.do_present_fbo() done", self) 

799 

800 def save_FBO(self): 

801 target = GL_TEXTURE_RECTANGLE_ARB 

802 bw, bh = self.size 

803 glEnable(target) 

804 glBindFramebuffer(GL_READ_FRAMEBUFFER, self.offscreen_fbo) 

805 glBindTexture(target, self.textures[TEX_FBO]) 

806 glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, self.textures[TEX_FBO], 0) 

807 glReadBuffer(GL_COLOR_ATTACHMENT0) 

808 glViewport(0, 0, bw, bh) 

809 size = bw*bh*4 

810 from xpra.buffers.membuf import get_membuf #@UnresolvedImport 

811 membuf = get_membuf(size) 

812 glGetTexImage(target, 0, GL_BGRA, GL_UNSIGNED_BYTE, membuf.get_mem_ptr()) 

813 pixels = memoryview(membuf).tobytes() 

814 img = Image.frombuffer("RGBA", (bw, bh), pixels, "raw", "BGRA", bw*4) 

815 img = ImageOps.flip(img) 

816 kwargs = {} 

817 if not self._alpha_enabled or SAVE_BUFFERS=="jpeg": 

818 img = img.convert("RGB") 

819 if SAVE_BUFFERS=="jpeg": 

820 kwargs = { 

821 "quality" : 0, 

822 "optimize" : False, 

823 } 

824 t = time.time() 

825 tstr = time.strftime("%H-%M-%S", time.localtime(t)) 

826 filename = "./W%i-FBO-%s.%03i.%s" % (self.wid, tstr, (t*1000)%1000, SAVE_BUFFERS) 

827 log("do_present_fbo: saving %4ix%-4i pixels, %7i bytes to %s", bw, bh, size, filename) 

828 img.save(filename, SAVE_BUFFERS, **kwargs) 

829 glBindFramebuffer(GL_READ_FRAMEBUFFER, 0) 

830 glBindTexture(target, 0) 

831 glDisable(target) 

832 

833 def draw_pointer(self): 

834 px, py, _, _, size, start_time = self.pointer_overlay 

835 elapsed = monotonic_time()-start_time 

836 log("pointer_overlay=%s, elapsed=%.1f, timeout=%s, cursor-data=%s", 

837 self.pointer_overlay, elapsed, CURSOR_IDLE_TIMEOUT, (self.cursor_data or [])[:7]) 

838 if elapsed>=CURSOR_IDLE_TIMEOUT: 

839 #timeout - stop showing it: 

840 self.pointer_overlay = None 

841 return 

842 x = px 

843 y = py 

844 if not self.cursor_data: 

845 #paint a fake one: 

846 alpha = max(0, (5.0-elapsed)/5.0) 

847 lw = 2 

848 glLineWidth(lw) 

849 glBegin(GL_LINES) 

850 glColor4f(0, 0, 0, alpha) 

851 glVertex2i(x-size, y-lw//2) 

852 glVertex2i(x+size, y-lw//2) 

853 glVertex2i(x, y-size) 

854 glVertex2i(x, y+size) 

855 glEnd() 

856 return 

857 

858 cw = self.cursor_data[3] 

859 ch = self.cursor_data[4] 

860 xhot = self.cursor_data[5] 

861 yhot = self.cursor_data[6] 

862 x = px-xhot 

863 y = py-yhot 

864 #paint the texture containing the cursor: 

865 glActiveTexture(GL_TEXTURE0) 

866 target = GL_TEXTURE_RECTANGLE_ARB 

867 glEnable(target) 

868 glBindTexture(target, self.textures[TEX_CURSOR]) 

869 glEnable(GL_BLEND) 

870 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA) 

871 #glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE) 

872 

873 glBegin(GL_QUADS) 

874 glTexCoord2i(0, 0) 

875 glVertex2i(x, y) 

876 glTexCoord2i(0, ch) 

877 glVertex2i(x, y+ch) 

878 glTexCoord2i(cw, ch) 

879 glVertex2i(x+cw, y+ch) 

880 glTexCoord2i(cw, 0) 

881 glVertex2i(x+cw, y) 

882 glEnd() 

883 

884 glDisable(GL_BLEND) 

885 glBindTexture(target, 0) 

886 glDisable(target) 

887 

888 def draw_spinner(self): 

889 bw, bh = self.size 

890 draw_spinner(bw, bh) 

891 

892 def draw_border(self): 

893 bw, bh = self.size 

894 #double size since half the line will be off-screen 

895 log("draw_border: %s", self.border) 

896 glLineWidth(self.border.size*2) 

897 glBegin(GL_LINE_LOOP) 

898 glColor4f(self.border.red, self.border.green, self.border.blue, self.border.alpha) 

899 for px,py in ((0, 0), (bw, 0), (bw, bh), (0, bh)): 

900 glVertex2i(px, py) 

901 glEnd() 

902 

903 

904 def validate_cursor(self): 

905 cursor_data = self.cursor_data 

906 cw = cursor_data[3] 

907 ch = cursor_data[4] 

908 pixels = cursor_data[8] 

909 blen = cw*ch*4 

910 if len(pixels)!=blen: 

911 log.error("Error: invalid cursor pixel buffer for %ix%i", cw, ch) 

912 log.error(" expected %i bytes but got %i (%s)", blen, len(pixels), type(pixels)) 

913 log.error(" %s", repr_ellipsized(hexstr(pixels))) 

914 return False 

915 return True 

916 

917 def set_cursor_data(self, cursor_data): 

918 if (not cursor_data or len(cursor_data)==1) and self.default_cursor_data: 

919 cursor_data = ["raw"] + list(self.default_cursor_data) 

920 if not cursor_data: 

921 return 

922 self.cursor_data = cursor_data 

923 if not cursor_data: 

924 return 

925 cw = cursor_data[3] 

926 ch = cursor_data[4] 

927 pixels = cursor_data[8] 

928 if not self.validate_cursor(): 

929 return 

930 context = self.gl_context() 

931 if not context: 

932 return 

933 with context: 

934 self.gl_init() 

935 self.upload_cursor_texture(cw, ch, pixels) 

936 

937 def upload_cursor_texture(self, width : int, height : int, pixels): 

938 upload, pixel_data = self.pixels_for_upload(pixels) 

939 rgb_format = "RGBA" 

940 glActiveTexture(GL_TEXTURE0) 

941 target = GL_TEXTURE_RECTANGLE_ARB 

942 glEnable(target) 

943 glBindTexture(target, self.textures[TEX_CURSOR]) 

944 self.set_alignment(width, width*4, rgb_format) 

945 glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST) 

946 glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST) 

947 glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER) 

948 glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER) 

949 glTexImage2D(target, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixel_data) 

950 log("GL cursor %ix%i uploaded %i bytes of %s pixel data using %s", 

951 width, height, len(pixels), rgb_format, upload) 

952 glBindTexture(target, 0) 

953 glDisable(target) 

954 

955 def paint_box(self, encoding : str, x : int, y : int, w : int, h : int): 

956 #show region being painted if debug paint box is enabled only: 

957 if self.paint_box_line_width<=0: 

958 return 

959 glLineWidth(self.paint_box_line_width+0.5+int(encoding=="scroll")*2) 

960 glBegin(GL_LINE_LOOP) 

961 color = get_paint_box_color(encoding) 

962 log("Painting colored box around %s screen update using: %s", encoding, color) 

963 glColor4f(*color) 

964 for px,py in ((x, y), (x+w, y), (x+w, y+h), (x, y+h)): 

965 glVertex2i(px, py) 

966 glEnd() 

967 

968 

969 def pixels_for_upload(self, img_data): 

970 #prepare the pixel buffer for upload: 

971 if isinstance(img_data, memoryview): 

972 if not zerocopy_upload: 

973 #not safe, make a copy :( 

974 return "copy:memoryview.tobytes", img_data.tobytes() 

975 return "zerocopy:memoryview", img_data 

976 if isinstance(img_data, bytes) and zerocopy_upload: 

977 #we can zerocopy if we wrap it: 

978 return "zerocopy:bytes-as-memoryview", memoryview(img_data) 

979 if isinstance(img_data, bytes): 

980 return "copy:bytes", img_data 

981 if hasattr(img_data, "raw"): 

982 return "zerocopy:mmap", img_data.raw 

983 #everything else.. copy to bytes (aka str): 

984 return "copy:bytes(%s)" % type(img_data), strtobytes(img_data) 

985 

986 def set_alignment(self, width : int, rowstride : int, pixel_format): 

987 bytes_per_pixel = len(pixel_format) #ie: BGRX -> 4, Y -> 1, YY -> 2 

988 # Compute alignment and row length 

989 row_length = 0 

990 alignment = 1 

991 for a in (2, 4, 8): 

992 # Check if we are a-aligned - ! (var & 0x1) means 2-aligned or better, 0x3 - 4-aligned and so on 

993 if (rowstride & a-1) == 0: 

994 alignment = a 

995 # If number of extra bytes is greater than the alignment value, 

996 # then we also have to set row_length 

997 # Otherwise it remains at 0 (= width implicitely) 

998 if (rowstride - width * bytes_per_pixel) >= alignment: 

999 row_length = width + (rowstride - width * bytes_per_pixel) // bytes_per_pixel 

1000 glPixelStorei(GL_UNPACK_ROW_LENGTH, row_length) 

1001 glPixelStorei(GL_UNPACK_ALIGNMENT, alignment) 

1002 self.gl_marker("set_alignment%s GL_UNPACK_ROW_LENGTH=%i, GL_UNPACK_ALIGNMENT=%i", 

1003 (width, rowstride, pixel_format), row_length, alignment) 

1004 

1005 

1006 def paint_jpeg(self, img_data, x : int, y : int, width : int, height : int, options, callbacks): 

1007 if JPEG_YUV and width>=2 and height>=2: 

1008 img = self.jpeg_decoder.decompress_to_yuv(img_data) 

1009 flush = options.intget("flush", 0) 

1010 w = img.get_width() 

1011 h = img.get_height() 

1012 self.idle_add(self.gl_paint_planar, YUV2RGB_FULL_SHADER, flush, "jpeg", img, 

1013 x, y, w, h, width, height, options, callbacks) 

1014 else: 

1015 img = self.jpeg_decoder.decompress_to_rgb("BGRX", img_data) 

1016 self.idle_add(self.do_paint_rgb, "BGRX", img.get_pixels(), x, y, width, height, img.get_rowstride(), options, callbacks) 

1017 

1018 def paint_webp(self, img_data, x : int, y : int, width : int, height : int, options, callbacks): 

1019 subsampling = options.strget("subsampling") 

1020 has_alpha = options.boolget("has_alpha") 

1021 if subsampling=="YUV420P" and WEBP_YUV and self.webp_decoder and not WEBP_PILLOW and not has_alpha and width>=2 and height>=2: 

1022 img = self.webp_decoder.decompress_yuv(img_data) 

1023 flush = options.intget("flush", 0) 

1024 w = img.get_width() 

1025 h = img.get_height() 

1026 self.idle_add(self.gl_paint_planar, YUV2RGB_SHADER, flush, "webp", img, 

1027 x, y, w, h, width, height, options, callbacks) 

1028 return 

1029 super().paint_webp(img_data, x, y, width, height, options, callbacks) 

1030 

1031 def do_paint_rgb(self, rgb_format, img_data, 

1032 x : int, y : int, width : int, height : int, render_width : int, render_height : int, 

1033 rowstride, options, callbacks): 

1034 log("%s.do_paint_rgb(%s, %s bytes, x=%d, y=%d, width=%d, height=%d, rowstride=%d, options=%s)", 

1035 self, rgb_format, len(img_data), x, y, width, height, rowstride, options) 

1036 x, y = self.gravity_adjust(x, y, options) 

1037 context = self.gl_context() 

1038 if not context: 

1039 log("%s._do_paint_rgb(..) no context!", self) 

1040 fire_paint_callbacks(callbacks, False, "no opengl context") 

1041 return 

1042 if not options.boolget("paint", True): 

1043 fire_paint_callbacks(callbacks) 

1044 return 

1045 try: 

1046 rgb_format = rgb_format.decode("utf-8") 

1047 except (AttributeError, UnicodeDecodeError): 

1048 pass 

1049 try: 

1050 upload, img_data = self.pixels_for_upload(img_data) 

1051 

1052 with context: 

1053 self.gl_init() 

1054 scaling = width!=render_width or height!=render_height 

1055 

1056 #convert it to a GL constant: 

1057 pformat = PIXEL_FORMAT_TO_CONSTANT.get(rgb_format) 

1058 assert pformat is not None, "could not find pixel format for %s" % rgb_format 

1059 ptype = PIXEL_FORMAT_TO_DATATYPE.get(rgb_format) 

1060 assert pformat is not None, "could not find pixel type for %s" % rgb_format 

1061 

1062 self.gl_marker("%s update at (%d,%d) size %dx%d (%s bytes) to %dx%d, using GL %s format=%s / %s to internal format=%s", 

1063 rgb_format, x, y, width, height, len(img_data), render_width, render_height, 

1064 upload, CONSTANT_TO_PIXEL_FORMAT.get(pformat), DATATYPE_TO_STR.get(ptype), INTERNAL_FORMAT_TO_STR.get(self.internal_format)) 

1065 

1066 # Upload data as temporary RGB texture 

1067 target = GL_TEXTURE_RECTANGLE_ARB 

1068 glEnable(target) 

1069 glBindTexture(target, self.textures[TEX_RGB]) 

1070 self.set_alignment(width, rowstride, rgb_format) 

1071 mag_filter = GL_NEAREST 

1072 if scaling: 

1073 mag_filter = GL_LINEAR 

1074 glTexParameteri(target, GL_TEXTURE_MAG_FILTER, mag_filter) 

1075 glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST) 

1076 glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER) 

1077 glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER) 

1078 glTexImage2D(target, 0, self.internal_format, width, height, 0, pformat, ptype, img_data) 

1079 

1080 # Draw textured RGB quad at the right coordinates 

1081 glBegin(GL_QUADS) 

1082 glTexCoord2i(0, 0) 

1083 glVertex2i(x, y) 

1084 glTexCoord2i(0, height) 

1085 glVertex2i(x, y+render_height) 

1086 glTexCoord2i(width, height) 

1087 glVertex2i(x+render_width, y+render_height) 

1088 glTexCoord2i(width, 0) 

1089 glVertex2i(x+render_width, y) 

1090 glEnd() 

1091 

1092 glBindTexture(target, 0) 

1093 glDisable(target) 

1094 self.paint_box(options.strget("encoding"), x, y, render_width, render_height) 

1095 # Present update to screen 

1096 if not self.draw_needs_refresh: 

1097 self.present_fbo(x, y, render_width, render_height, options.intget("flush", 0)) 

1098 # present_fbo has reset state already 

1099 fire_paint_callbacks(callbacks) 

1100 return 

1101 except GLError as e: 

1102 message = "OpenGL %s paint failed: %r" % (rgb_format, e) 

1103 log("Error in %s paint of %i bytes, options=%s", rgb_format, len(img_data), options, exc_info=True) 

1104 except Exception as e: 

1105 message = "OpenGL %s paint error: %s" % (rgb_format, e) 

1106 log("Error in %s paint of %i bytes, options=%s", rgb_format, len(img_data), options, exc_info=True) 

1107 fire_paint_callbacks(callbacks, False, message) 

1108 

1109 

1110 def do_video_paint(self, img, x : int, y : int, enc_width : int, enc_height : int, width : int, height : int, options, callbacks): 

1111 if not zerocopy_upload or FORCE_CLONE: 

1112 #copy so the data will be usable (usually a str) 

1113 img.clone_pixel_data() 

1114 pixel_format = img.get_pixel_format() 

1115 if pixel_format in ("GBRP10", "YUV444P10"): 

1116 #call superclass to handle csc 

1117 #which will end up calling paint rgb with r210 data 

1118 return super().do_video_paint(img, x, y, enc_width, enc_height, width, height, options, callbacks) 

1119 if pixel_format.startswith("GBRP"): 

1120 shader = RGBP2RGB_SHADER 

1121 else: 

1122 shader = YUV2RGB_SHADER 

1123 self.idle_add(self.gl_paint_planar, shader, options.intget("flush", 0), options.strget("encoding"), img, 

1124 x, y, enc_width, enc_height, width, height, options, callbacks) 

1125 

1126 def gl_paint_planar(self, shader, flush, encoding, img, 

1127 x : int, y : int, enc_width : int, enc_height : int, width : int, height : int, 

1128 options, callbacks): 

1129 #this function runs in the UI thread, no video_decoder lock held 

1130 log("gl_paint_planar%s", (flush, encoding, img, x, y, enc_width, enc_height, width, height, options, callbacks)) 

1131 x, y = self.gravity_adjust(x, y, options) 

1132 try: 

1133 pixel_format = img.get_pixel_format() 

1134 assert pixel_format in ("YUV420P", "YUV422P", "YUV444P", "GBRP", "GBRP16", "YUV444P16"), \ 

1135 "sorry the GL backing does not handle pixel format '%s' yet!" % (pixel_format) 

1136 

1137 context = self.gl_context() 

1138 if not context: 

1139 log("%s._do_paint_rgb(..) no context!", self) 

1140 fire_paint_callbacks(callbacks, False, "failed to get a gl context") 

1141 return 

1142 with context: 

1143 self.gl_init() 

1144 scaling = enc_width!=width or enc_height!=height 

1145 self.update_planar_textures(enc_width, enc_height, img, pixel_format, scaling=scaling) 

1146 

1147 # Update FBO texture 

1148 x_scale, y_scale = 1, 1 

1149 if scaling: 

1150 x_scale = width/enc_width 

1151 y_scale = height/enc_height 

1152 

1153 self.render_planar_update(x, y, enc_width, enc_height, x_scale, y_scale, shader) 

1154 self.paint_box(encoding, x, y, width, height) 

1155 fire_paint_callbacks(callbacks, True) 

1156 # Present it on screen 

1157 if not self.draw_needs_refresh: 

1158 self.present_fbo(x, y, width, height, flush) 

1159 img.free() 

1160 return 

1161 except GLError as e: 

1162 message = "OpenGL %s paint failed: %r" % (encoding, e) 

1163 log.error("Error painting planar update", exc_info=True) 

1164 except Exception as e: 

1165 message = "OpenGL %s paint failed: %s" % (encoding, e) 

1166 log.error("Error painting planar update", exc_info=True) 

1167 log.error(" flush=%i, image=%s, coords=%s, size=%ix%i", 

1168 flush, img, (x, y, enc_width, enc_height), width, height) 

1169 fire_paint_callbacks(callbacks, False, message) 

1170 

1171 def update_planar_textures(self, width : int, height : int, img, pixel_format, scaling=False): 

1172 assert self.textures is not None, "no OpenGL textures!" 

1173 log("%s.update_planar_textures%s", self, (width, height, img, pixel_format)) 

1174 

1175 upload_format = PIXEL_FORMAT_TO_DATATYPE[pixel_format] 

1176 divs = get_subsampling_divs(pixel_format) 

1177 if self.pixel_format is None or self.pixel_format!=pixel_format or self.texture_size!=(width, height): 

1178 self.gl_marker("Creating new planar textures, pixel format %s (was %s), texture size %s (was %s)", 

1179 pixel_format, self.pixel_format, (width, height), self.texture_size) 

1180 self.pixel_format = pixel_format 

1181 self.texture_size = (width, height) 

1182 # Create textures of the same size as the window's 

1183 for texture, index in ((GL_TEXTURE0, TEX_Y), (GL_TEXTURE1, TEX_U), (GL_TEXTURE2, TEX_V)): 

1184 (div_w, div_h) = divs[index] 

1185 glActiveTexture(texture) 

1186 target = GL_TEXTURE_RECTANGLE_ARB 

1187 glBindTexture(target, self.textures[index]) 

1188 mag_filter = GL_NEAREST 

1189 if scaling or (div_w > 1 or div_h > 1): 

1190 mag_filter = GL_LINEAR 

1191 glTexParameteri(target, GL_TEXTURE_MAG_FILTER, mag_filter) 

1192 glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST) 

1193 glTexImage2D(target, 0, GL_LUMINANCE, width//div_w, height//div_h, 0, GL_LUMINANCE, upload_format, None) 

1194 #glBindTexture(target, 0) #redundant: we rebind below: 

1195 

1196 self.gl_marker("updating planar textures: %sx%s %s", width, height, pixel_format) 

1197 rowstrides = img.get_rowstride() 

1198 img_data = img.get_pixels() 

1199 BPP = 2 if pixel_format.endswith("P16") else 1 

1200 assert len(rowstrides)==3 and len(img_data)==3 

1201 for texture, index, tex_name in ( 

1202 (GL_TEXTURE0, TEX_Y, pixel_format[0:1]*BPP), 

1203 (GL_TEXTURE1, TEX_U, pixel_format[1:2]*BPP), 

1204 (GL_TEXTURE2, TEX_V, pixel_format[2:3]*BPP), 

1205 ): 

1206 div_w, div_h = divs[index] 

1207 w = width//div_w 

1208 h = height//div_h 

1209 if w==0 or h==0: 

1210 log.error("Error: zero dimension %ix%i for %s planar texture %s", w, h, pixel_format, tex_name) 

1211 log.error(" screen update %s dropped", (width, height)) 

1212 continue 

1213 glActiveTexture(texture) 

1214 

1215 target = GL_TEXTURE_RECTANGLE_ARB 

1216 glBindTexture(target, self.textures[index]) 

1217 self.set_alignment(w, rowstrides[index], tex_name) 

1218 upload, pixel_data = self.pixels_for_upload(img_data[index]) 

1219 log("texture %s: div=%s, rowstride=%s, %sx%s, data=%s bytes, upload=%s", 

1220 index, divs[index], rowstrides[index], w, h, len(pixel_data), upload) 

1221 glTexParameteri(target, GL_TEXTURE_BASE_LEVEL, 0) 

1222 try: 

1223 glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, 0) 

1224 except Exception: 

1225 pass 

1226 glTexSubImage2D(target, 0, 0, 0, w, h, GL_LUMINANCE, upload_format, pixel_data) 

1227 glBindTexture(target, 0) 

1228 #glActiveTexture(GL_TEXTURE0) #redundant, we always call render_planar_update afterwards 

1229 

1230 def render_planar_update(self, rx : int, ry : int, rw : int, rh : int, x_scale=1, y_scale=1, shader=YUV2RGB_SHADER): 

1231 log("%s.render_planar_update%s pixel_format=%s", 

1232 self, (rx, ry, rw, rh, x_scale, y_scale, shader), self.pixel_format) 

1233 if self.pixel_format not in ("YUV420P", "YUV422P", "YUV444P", "GBRP", "GBRP16", "YUV444P16"): 

1234 #not ready to render yet 

1235 return 

1236 self.gl_marker("painting planar update, format %s", self.pixel_format) 

1237 glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, self.shaders[shader]) 

1238 glEnable(GL_FRAGMENT_PROGRAM_ARB) 

1239 for texture, index in ((GL_TEXTURE0, TEX_Y), (GL_TEXTURE1, TEX_U), (GL_TEXTURE2, TEX_V)): 

1240 glActiveTexture(texture) 

1241 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, self.textures[index]) 

1242 

1243 tw, th = self.texture_size 

1244 divs = get_subsampling_divs(self.pixel_format) 

1245 log("%s.render_planar_update(..) texture_size=%s, size=%s", self, self.texture_size, self.size) 

1246 glBegin(GL_QUADS) 

1247 for x,y in ((0, 0), (0, rh), (rw, rh), (rw, 0)): 

1248 ax = min(tw, x) 

1249 ay = min(th, y) 

1250 for texture, index in ((GL_TEXTURE0, TEX_Y), (GL_TEXTURE1, TEX_U), (GL_TEXTURE2, TEX_V)): 

1251 (div_w, div_h) = divs[index] 

1252 glMultiTexCoord2i(texture, ax//div_w, ay//div_h) 

1253 glVertex2i(int(rx+ax*x_scale), int(ry+ay*y_scale)) 

1254 glEnd() 

1255 for texture in (GL_TEXTURE0, GL_TEXTURE1, GL_TEXTURE2): 

1256 glActiveTexture(texture) 

1257 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0) 

1258 glDisable(GL_FRAGMENT_PROGRAM_ARB) 

1259 glActiveTexture(GL_TEXTURE0) 

1260 

1261 

1262 def gl_show(self, rect_count): 

1263 start = monotonic_time() 

1264 self.do_gl_show(rect_count) 

1265 end = monotonic_time() 

1266 flush_elapsed = end-self.last_flush 

1267 self.last_flush = end 

1268 fpslog("gl_show after %3ims took %2ims, %2i updates", flush_elapsed*1000, (end-start)*1000, rect_count) 

1269 

1270 

1271 def gl_expose_rect(self, rect=None): 

1272 if not self.paint_screen: 

1273 return 

1274 context = self.gl_context() 

1275 if not context: 

1276 return 

1277 if not rect: 

1278 w, h = self.size 

1279 rect = (0, 0, w, h) 

1280 with context: 

1281 self.gl_init() 

1282 self.present_fbo(*rect)