# ---------------------------------------------------------------------------- # pyglet # Copyright (c) 2006-2008 Alex Holkner # Copyright (c) 2008-2021 pyglet contributors # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in # the documentation and/or other materials provided with the # distribution. # * Neither the name of pyglet nor the names of its # contributors may be used to endorse or promote products # derived from this software without specific prior written # permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # ---------------------------------------------------------------------------- import warnings from ctypes import * from pyglet import gl from pyglet.canvas.xlib import XlibCanvas from pyglet.gl import glx from pyglet.gl import glx_info from pyglet.gl import glxext_arb from pyglet.gl import glxext_mesa from pyglet.gl import lib from .base import Config, CanvasConfig, Context class XlibConfig(Config): def match(self, canvas): if not isinstance(canvas, XlibCanvas): raise RuntimeError('Canvas must be an instance of XlibCanvas') x_display = canvas.display._display x_screen = canvas.display.x_screen info = glx_info.GLXInfo(x_display) have_13 = info.have_version(1, 3) if have_13: config_class = XlibCanvasConfig13 else: config_class = XlibCanvasConfig10 # Construct array of attributes attrs = [] for name, value in self.get_gl_attributes(): attr = config_class.attribute_ids.get(name, None) if attr and value is not None: attrs.extend([attr, int(value)]) if have_13: attrs.extend([glx.GLX_X_RENDERABLE, True]) else: attrs.extend([glx.GLX_RGBA, True]) attrs.extend([0, 0]) # attrib_list must be null terminated attrib_list = (c_int * len(attrs))(*attrs) if have_13: elements = c_int() configs = glx.glXChooseFBConfig(x_display, x_screen, attrib_list, byref(elements)) if not configs: return [] configs = cast(configs, POINTER(glx.GLXFBConfig * elements.value)).contents result = [config_class(canvas, info, c, self) for c in configs] # Can't free array until all XlibGLConfig13's are GC'd. Too much # hassle, live with leak. XXX # xlib.XFree(configs) return result else: try: return [config_class(canvas, info, attrib_list, self)] except gl.ContextException: return [] class BaseXlibCanvasConfig(CanvasConfig): # Common code shared between GLX 1.0 and GLX 1.3 configs. attribute_ids = { 'buffer_size': glx.GLX_BUFFER_SIZE, 'level': glx.GLX_LEVEL, # Not supported 'double_buffer': glx.GLX_DOUBLEBUFFER, 'stereo': glx.GLX_STEREO, 'aux_buffers': glx.GLX_AUX_BUFFERS, 'red_size': glx.GLX_RED_SIZE, 'green_size': glx.GLX_GREEN_SIZE, 'blue_size': glx.GLX_BLUE_SIZE, 'alpha_size': glx.GLX_ALPHA_SIZE, 'depth_size': glx.GLX_DEPTH_SIZE, 'stencil_size': glx.GLX_STENCIL_SIZE, 'accum_red_size': glx.GLX_ACCUM_RED_SIZE, 'accum_green_size': glx.GLX_ACCUM_GREEN_SIZE, 'accum_blue_size': glx.GLX_ACCUM_BLUE_SIZE, 'accum_alpha_size': glx.GLX_ACCUM_ALPHA_SIZE, } def __init__(self, canvas, glx_info, config): super(BaseXlibCanvasConfig, self).__init__(canvas, config) self.glx_info = glx_info def compatible(self, canvas): # TODO check more return isinstance(canvas, XlibCanvas) def _create_glx_context(self, share): raise NotImplementedError('abstract') def is_complete(self): return True def get_visual_info(self): raise NotImplementedError('abstract') class XlibCanvasConfig10(BaseXlibCanvasConfig): def __init__(self, canvas, glx_info, attrib_list, config): super(XlibCanvasConfig10, self).__init__(canvas, glx_info, config) x_display = canvas.display._display x_screen = canvas.display.x_screen self._visual_info = glx.glXChooseVisual( x_display, x_screen, attrib_list) if not self._visual_info: raise gl.ContextException('No conforming visual exists') for name, attr in self.attribute_ids.items(): value = c_int() result = glx.glXGetConfig( x_display, self._visual_info, attr, byref(value)) if result >= 0: setattr(self, name, value.value) self.sample_buffers = 0 self.samples = 0 def get_visual_info(self): return self._visual_info.contents def create_context(self, share): return XlibContext10(self, share) class XlibCanvasConfig13(BaseXlibCanvasConfig): attribute_ids = BaseXlibCanvasConfig.attribute_ids.copy() attribute_ids.update({ 'sample_buffers': glx.GLX_SAMPLE_BUFFERS, 'samples': glx.GLX_SAMPLES, # Not supported in current pyglet API: 'render_type': glx.GLX_RENDER_TYPE, 'config_caveat': glx.GLX_CONFIG_CAVEAT, 'transparent_type': glx.GLX_TRANSPARENT_TYPE, 'transparent_index_value': glx.GLX_TRANSPARENT_INDEX_VALUE, 'transparent_red_value': glx.GLX_TRANSPARENT_RED_VALUE, 'transparent_green_value': glx.GLX_TRANSPARENT_GREEN_VALUE, 'transparent_blue_value': glx.GLX_TRANSPARENT_BLUE_VALUE, 'transparent_alpha_value': glx.GLX_TRANSPARENT_ALPHA_VALUE, # Used internally 'x_renderable': glx.GLX_X_RENDERABLE, }) def __init__(self, canvas, glx_info, fbconfig, config): super(XlibCanvasConfig13, self).__init__(canvas, glx_info, config) x_display = canvas.display._display self._fbconfig = fbconfig for name, attr in self.attribute_ids.items(): value = c_int() result = glx.glXGetFBConfigAttrib( x_display, self._fbconfig, attr, byref(value)) if result >= 0: setattr(self, name, value.value) def get_visual_info(self): return glx.glXGetVisualFromFBConfig(self.canvas.display._display, self._fbconfig).contents def create_context(self, share): if self.glx_info.have_extension('GLX_ARB_create_context'): return XlibContextARB(self, share) else: return XlibContext13(self, share) class BaseXlibContext(Context): def __init__(self, config, share): super(BaseXlibContext, self).__init__(config, share) self.x_display = config.canvas.display._display self.glx_context = self._create_glx_context(share) if not self.glx_context: # TODO: Check Xlib error generated raise gl.ContextException('Could not create GL context') self._have_SGI_video_sync = config.glx_info.have_extension('GLX_SGI_video_sync') self._have_SGI_swap_control = config.glx_info.have_extension('GLX_SGI_swap_control') self._have_EXT_swap_control = config.glx_info.have_extension('GLX_EXT_swap_control') self._have_MESA_swap_control = config.glx_info.have_extension('GLX_MESA_swap_control') # In order of preference: # 1. GLX_EXT_swap_control (more likely to work where video_sync will not) # 2. GLX_MESA_swap_control (same as above, but supported by MESA drivers) # 3. GLX_SGI_video_sync (does not work on Intel 945GM, but that has EXT) # 4. GLX_SGI_swap_control (cannot be disabled once enabled) self._use_video_sync = (self._have_SGI_video_sync and not (self._have_EXT_swap_control or self._have_MESA_swap_control)) # XXX mandate that vsync defaults on across all platforms. self._vsync = True def is_direct(self): return glx.glXIsDirect(self.x_display, self.glx_context) def set_vsync(self, vsync=True): self._vsync = vsync interval = vsync and 1 or 0 try: if not self._use_video_sync and self._have_EXT_swap_control: glxext_arb.glXSwapIntervalEXT(self.x_display, glx.glXGetCurrentDrawable(), interval) elif not self._use_video_sync and self._have_MESA_swap_control: glxext_mesa.glXSwapIntervalMESA(interval) elif self._have_SGI_swap_control: glxext_arb.glXSwapIntervalSGI(interval) except lib.MissingFunctionException as e: warnings.warn(e.message) def get_vsync(self): return self._vsync def _wait_vsync(self): if self._vsync and self._have_SGI_video_sync and self._use_video_sync: count = c_uint() glxext_arb.glXGetVideoSyncSGI(byref(count)) glxext_arb.glXWaitVideoSyncSGI(2, (count.value + 1) % 2, byref(count)) class XlibContext10(BaseXlibContext): def __init__(self, config, share): super(XlibContext10, self).__init__(config, share) def _create_glx_context(self, share): if self.config.requires_gl_3(): raise gl.ContextException( 'Require GLX_ARB_create_context extension to create OpenGL 3 contexts.') if share: share_context = share.glx_context else: share_context = None return glx.glXCreateContext(self.config.canvas.display._display, self.config._visual_info, share_context, True) def attach(self, canvas): super(XlibContext10, self).attach(canvas) self.set_current() def set_current(self): glx.glXMakeCurrent(self.x_display, self.canvas.x_window, self.glx_context) super(XlibContext10, self).set_current() def detach(self): if not self.canvas: return self.set_current() gl.glFlush() glx.glXMakeCurrent(self.x_display, 0, None) super(XlibContext10, self).detach() def destroy(self): super(XlibContext10, self).destroy() glx.glXDestroyContext(self.x_display, self.glx_context) self.glx_context = None def flip(self): if not self.canvas: return if self._vsync: self._wait_vsync() glx.glXSwapBuffers(self.x_display, self.canvas.x_window) class XlibContext13(BaseXlibContext): def __init__(self, config, share): super(XlibContext13, self).__init__(config, share) self.glx_window = None def _create_glx_context(self, share): if self.config.requires_gl_3(): raise gl.ContextException( 'Require GLX_ARB_create_context extension to create ' + 'OpenGL 3 contexts.') if share: share_context = share.glx_context else: share_context = None return glx.glXCreateNewContext(self.config.canvas.display._display, self.config._fbconfig, glx.GLX_RGBA_TYPE, share_context, True) def attach(self, canvas): if canvas is self.canvas: return super(XlibContext13, self).attach(canvas) self.glx_window = glx.glXCreateWindow( self.x_display, self.config._fbconfig, canvas.x_window, None) self.set_current() def set_current(self): glx.glXMakeContextCurrent( self.x_display, self.glx_window, self.glx_window, self.glx_context) super(XlibContext13, self).set_current() def detach(self): if not self.canvas: return self.set_current() gl.glFlush() # needs to be in try/except? super(XlibContext13, self).detach() glx.glXMakeContextCurrent(self.x_display, 0, 0, None) if self.glx_window: glx.glXDestroyWindow(self.x_display, self.glx_window) self.glx_window = None def destroy(self): super(XlibContext13, self).destroy() if self.glx_window: glx.glXDestroyWindow(self.config.display._display, self.glx_window) self.glx_window = None if self.glx_context: glx.glXDestroyContext(self.x_display, self.glx_context) self.glx_context = None def flip(self): if not self.glx_window: return if self._vsync: self._wait_vsync() glx.glXSwapBuffers(self.x_display, self.glx_window) class XlibContextARB(XlibContext13): def _create_glx_context(self, share): if share: share_context = share.glx_context else: share_context = None attribs = [] if self.config.major_version is not None: attribs.extend([glxext_arb.GLX_CONTEXT_MAJOR_VERSION_ARB, self.config.major_version]) if self.config.minor_version is not None: attribs.extend([glxext_arb.GLX_CONTEXT_MINOR_VERSION_ARB, self.config.minor_version]) flags = 0 if self.config.forward_compatible: flags |= glxext_arb.GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB if self.config.debug: flags |= glxext_arb.GLX_CONTEXT_DEBUG_BIT_ARB if flags: attribs.extend([glxext_arb.GLX_CONTEXT_FLAGS_ARB, flags]) attribs.append(0) attribs = (c_int * len(attribs))(*attribs) return glxext_arb.glXCreateContextAttribsARB( self.config.canvas.display._display, self.config._fbconfig, share_context, True, attribs)