import platform from ctypes import c_uint32, c_int, byref import pyglet from pyglet.gl.base import Config, CanvasConfig, Context from pyglet.gl import ContextException from pyglet.canvas.cocoa import CocoaCanvas from pyglet.libs.darwin import cocoapy, quartz NSOpenGLPixelFormat = cocoapy.ObjCClass('NSOpenGLPixelFormat') NSOpenGLContext = cocoapy.ObjCClass('NSOpenGLContext') # Version info, needed as OpenGL different Lion and onward """Version is based on Darwin kernel, not OS-X version. OS-X / Darwin version history http://en.wikipedia.org/wiki/Darwin_(operating_system)#Release_history pre-release: 0.1, 0.2, 1.0, 1.1, kodiak: 1.2.1, cheetah: 1.3.1, puma: 1.4.1, 5.1 -> 5.5 jaguar: 6.0.1 -> 6.8 panther: 7.0 -> 7.9 tiger: 8.0 -> 8.11 leopard: 9.0 -> 9.8 snow_leopard: 10.0 -> 10.8 lion: 11.0 -> 11.4 mountain_lion: 12.0 -> 12.5 mavericks: 13.0 -> 13.4 yosemite: 14.0 -> 14.5 el_capitan: 15.0 -> 15.6 sierra: 16.0 -> 16.6 high_sierra: 17.0 -> 17.7 mojave: 18.0 -> 18.2 catalina: 19.0 -> 19.6 big_sur: 20.0 -> """ os_x_release = { 'pre-release': (0,1), 'kodiak': (1,2,1), 'cheetah': (1,3,1), 'puma': (1,4.1), 'jaguar': (6,0,1), 'panther': (7,), 'tiger': (8,), 'leopard': (9,), 'snow_leopard': (10,), 'lion': (11,), 'mountain_lion': (12,), 'mavericks': (13,), 'yosemite': (14,), 'el_capitan': (15,), 'sierra': (16,), 'high_sierra': (17,), 'mojave': (18,), 'catalina': (19,), 'big_sur': (20,) } def os_x_version(): version = tuple([int(v) for v in platform.release().split('.')]) # ensure we return a tuple if len(version) > 0: return version return (version,) _os_x_version = os_x_version() # Valid names for GL attributes and their corresponding NSOpenGL constant. _gl_attributes = { 'double_buffer': cocoapy.NSOpenGLPFADoubleBuffer, 'stereo': cocoapy.NSOpenGLPFAStereo, 'buffer_size': cocoapy.NSOpenGLPFAColorSize, 'sample_buffers': cocoapy.NSOpenGLPFASampleBuffers, 'samples': cocoapy.NSOpenGLPFASamples, 'aux_buffers': cocoapy.NSOpenGLPFAAuxBuffers, 'alpha_size': cocoapy.NSOpenGLPFAAlphaSize, 'depth_size': cocoapy.NSOpenGLPFADepthSize, 'stencil_size': cocoapy.NSOpenGLPFAStencilSize, # Not exposed by pyglet API (set internally) 'all_renderers': cocoapy.NSOpenGLPFAAllRenderers, 'fullscreen': cocoapy.NSOpenGLPFAFullScreen, 'minimum_policy': cocoapy.NSOpenGLPFAMinimumPolicy, 'maximum_policy': cocoapy.NSOpenGLPFAMaximumPolicy, 'screen_mask' : cocoapy.NSOpenGLPFAScreenMask, # Not supported in current pyglet API 'color_float': cocoapy.NSOpenGLPFAColorFloat, 'offscreen': cocoapy.NSOpenGLPFAOffScreen, 'sample_alpha': cocoapy.NSOpenGLPFASampleAlpha, 'multisample': cocoapy.NSOpenGLPFAMultisample, 'supersample': cocoapy.NSOpenGLPFASupersample, } # NSOpenGL constants which do not require a value. _boolean_gl_attributes = frozenset([ cocoapy.NSOpenGLPFAAllRenderers, cocoapy.NSOpenGLPFADoubleBuffer, cocoapy.NSOpenGLPFAStereo, cocoapy.NSOpenGLPFAMinimumPolicy, cocoapy.NSOpenGLPFAMaximumPolicy, cocoapy.NSOpenGLPFAOffScreen, cocoapy.NSOpenGLPFAFullScreen, cocoapy.NSOpenGLPFAColorFloat, cocoapy.NSOpenGLPFAMultisample, cocoapy.NSOpenGLPFASupersample, cocoapy.NSOpenGLPFASampleAlpha, ]) # Attributes for which no NSOpenGLPixelFormatAttribute name exists. # We could probably compute actual values for these using # NSOpenGLPFAColorSize / 4 and NSOpenGLFAAccumSize / 4, but I'm not that # confident I know what I'm doing. _fake_gl_attributes = { 'red_size': 0, 'green_size': 0, 'blue_size': 0, 'accum_red_size': 0, 'accum_green_size': 0, 'accum_blue_size': 0, 'accum_alpha_size': 0 } class CocoaConfig(Config): def match(self, canvas): # Construct array of attributes for NSOpenGLPixelFormat attrs = [] for name, value in self.get_gl_attributes(): attr = _gl_attributes.get(name) if not attr or not value: continue attrs.append(attr) if attr not in _boolean_gl_attributes: attrs.append(int(value)) # Support for RAGE-II, which is not compliant. attrs.append(cocoapy.NSOpenGLPFAAllRenderers) # Force selection policy. attrs.append(cocoapy.NSOpenGLPFAMaximumPolicy) # NSOpenGLPFAFullScreen is always supplied so we can switch to and # from fullscreen without losing the context. Also must supply the # NSOpenGLPFAScreenMask attribute with appropriate display ID. # Note that these attributes aren't necessary to render in fullscreen # on Mac OS X 10.6, because there we are simply rendering into a # screen sized window. See: # http://developer.apple.com/library/mac/#documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_fullscreen/opengl_cgl.html%23//apple_ref/doc/uid/TP40001987-CH210-SW6 # Otherwise, make sure we refer to the correct Profile for OpenGL (Core or # Legacy) on Lion and afterwards if _os_x_version < os_x_release['snow_leopard']: attrs.append(cocoapy.NSOpenGLPFAFullScreen) attrs.append(cocoapy.NSOpenGLPFAScreenMask) attrs.append(quartz.CGDisplayIDToOpenGLDisplayMask(quartz.CGMainDisplayID())) elif _os_x_version >= os_x_release['lion']: # check for opengl profile # This requires OS-X Lion (Darwin 11) or higher version = (getattr(self, 'major_version', None) or 3, getattr(self, 'minor_version', None) or 3) # tell os-x we want to request a profile attrs.append(cocoapy.NSOpenGLPFAOpenGLProfile) # check if we're wanting core or legacy # Mavericks (Darwin 13) and up are capable of the Core 4.1 profile, # while Lion and up are only capable of Core 3.2 if version[0] >= 4 and _os_x_version >= os_x_release['mavericks']: attrs.append(int(cocoapy.NSOpenGLProfileVersion4_1Core)) elif version[0] >= 3: attrs.append(int(cocoapy.NSOpenGLProfileVersion3_2Core)) else: attrs.append(int(cocoapy.NSOpenGLProfileVersionLegacy)) # Terminate the list. attrs.append(0) # Create the pixel format. attrsArrayType = c_uint32 * len(attrs) attrsArray = attrsArrayType(*attrs) pixel_format = NSOpenGLPixelFormat.alloc().initWithAttributes_(attrsArray) # Return the match list. if pixel_format is None: return [] else: return [CocoaCanvasConfig(canvas, self, pixel_format)] class CocoaCanvasConfig(CanvasConfig): def __init__(self, canvas, config, pixel_format): super(CocoaCanvasConfig, self).__init__(canvas, config) self._pixel_format = pixel_format # Query values for the attributes of the pixel format, and then set the # corresponding attributes of the canvas config. for name, attr in _gl_attributes.items(): vals = c_int() self._pixel_format.getValues_forAttribute_forVirtualScreen_(byref(vals), attr, 0) setattr(self, name, vals.value) # Set these attributes so that we can run pyglet.info. for name, value in _fake_gl_attributes.items(): setattr(self, name, value) # Update the minor/major version from profile if (Mountain)Lion if _os_x_version >= os_x_release['lion']: vals = c_int() profile = self._pixel_format.getValues_forAttribute_forVirtualScreen_( byref(vals), cocoapy.NSOpenGLPFAOpenGLProfile, 0 ) if vals.value == cocoapy.NSOpenGLProfileVersion4_1Core: setattr(self, "major_version", 4) setattr(self, "minor_version", 1) elif vals.value == cocoapy.NSOpenGLProfileVersion3_2Core: setattr(self, "major_version", 3) setattr(self, "minor_version", 2) else: setattr(self, "major_version", 2) setattr(self, "minor_version", 1) def create_context(self, share): # Determine the shared NSOpenGLContext. if share: share_context = share._nscontext else: share_context = None # Create a new NSOpenGLContext. nscontext = NSOpenGLContext.alloc().initWithFormat_shareContext_( self._pixel_format, share_context) return CocoaContext(self, nscontext, share) def compatible(self, canvas): return isinstance(canvas, CocoaCanvas) class CocoaContext(Context): def __init__(self, config, nscontext, share): super(CocoaContext, self).__init__(config, share) self.config = config self._nscontext = nscontext def attach(self, canvas): # See if we want OpenGL 3 in a non-Lion OS if _os_x_version < os_x_release['lion']: raise ContextException('OpenGL 3 not supported') super(CocoaContext, self).attach(canvas) # The NSView instance should be attached to a nondeferred window before calling # setView, otherwise you get an "invalid drawable" message. self._nscontext.setView_(canvas.nsview) self.set_current() def detach(self): super(CocoaContext, self).detach() self._nscontext.clearDrawable() def set_current(self): self._nscontext.makeCurrentContext() super(CocoaContext, self).set_current() def update_geometry(self): # Need to call this method whenever the context drawable (an NSView) # changes size or location. self._nscontext.update() def set_full_screen(self): self._nscontext.makeCurrentContext() self._nscontext.setFullScreen() def destroy(self): super(CocoaContext, self).destroy() self._nscontext.release() self._nscontext = None def set_vsync(self, vsync=True): vals = c_int(vsync) self._nscontext.setValues_forParameter_(byref(vals), cocoapy.NSOpenGLCPSwapInterval) def get_vsync(self): vals = c_int() self._nscontext.getValues_forParameter_(byref(vals), cocoapy.NSOpenGLCPSwapInterval) return vals.value def flip(self): self._nscontext.flushBuffer()