1# ---------------------------------------------------------------------------- 2# pyglet 3# Copyright (c) 2006-2008 Alex Holkner 4# Copyright (c) 2008-2020 pyglet contributors 5# All rights reserved. 6# 7# Redistribution and use in source and binary forms, with or without 8# modification, are permitted provided that the following conditions 9# are met: 10# 11# * Redistributions of source code must retain the above copyright 12# notice, this list of conditions and the following disclaimer. 13# * Redistributions in binary form must reproduce the above copyright 14# notice, this list of conditions and the following disclaimer in 15# the documentation and/or other materials provided with the 16# distribution. 17# * Neither the name of pyglet nor the names of its 18# contributors may be used to endorse or promote products 19# derived from this software without specific prior written 20# permission. 21# 22# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33# POSSIBILITY OF SUCH DAMAGE. 34# ---------------------------------------------------------------------------- 35import platform 36from ctypes import c_uint32, c_int, byref 37 38from pyglet.gl.base import Config, CanvasConfig, Context 39 40from pyglet.gl import ContextException 41from pyglet.gl import gl 42from pyglet.gl import agl 43 44from pyglet.canvas.cocoa import CocoaCanvas 45 46from pyglet.libs.darwin import cocoapy, quartz 47 48 49NSOpenGLPixelFormat = cocoapy.ObjCClass('NSOpenGLPixelFormat') 50NSOpenGLContext = cocoapy.ObjCClass('NSOpenGLContext') 51 52# Version info, needed as OpenGL different Lion and onward 53"""Version is based on Darwin kernel, not OS-X version. 54OS-X / Darwin version history 55http://en.wikipedia.org/wiki/Darwin_(operating_system)#Release_history 56pre-release: 0.1, 0.2, 1.0, 1.1, 57kodiak: 1.2.1, 58cheetah: 1.3.1, 59puma: 1.4.1, 5.1 -> 5.5 60jaguar: 6.0.1 -> 6.8 61panther: 7.0 -> 7.9 62tiger: 8.0 -> 8.11 63leopard: 9.0 -> 9.8 64snow_leopard: 10.0 -> 10.8 65lion: 11.0 -> 11.4 66mountain_lion: 12.0 -> 12.5 67mavericks: 13.0 -> 13.4 68yosemite: 14.0 -> 14.5 69el_capitan: 15.0 -> 15.6 70sierra: 16.0 -> 16.6 71""" 72os_x_release = { 73 'pre-release': (0,1), 74 'kodiak': (1,2,1), 75 'cheetah': (1,3,1), 76 'puma': (1,4.1), 77 'jaguar': (6,0,1), 78 'panther': (7,), 79 'tiger': (8,), 80 'leopard': (9,), 81 'snow_leopard': (10,), 82 'lion': (11,), 83 'mountain_lion': (12,), 84 'mavericks': (13,), 85 'yosemite': (14,), 86 'el_capitan': (15,), 87 'sierra': (16,), 88 } 89 90def os_x_version(): 91 version = tuple([int(v) for v in platform.release().split('.')]) 92 93 # ensure we return a tuple 94 if len(version) > 0: 95 return version 96 return (version,) 97 98_os_x_version = os_x_version() 99 100# Valid names for GL attributes and their corresponding NSOpenGL constant. 101_gl_attributes = { 102 'double_buffer': cocoapy.NSOpenGLPFADoubleBuffer, 103 'stereo': cocoapy.NSOpenGLPFAStereo, 104 'buffer_size': cocoapy.NSOpenGLPFAColorSize, 105 'sample_buffers': cocoapy.NSOpenGLPFASampleBuffers, 106 'samples': cocoapy.NSOpenGLPFASamples, 107 'aux_buffers': cocoapy.NSOpenGLPFAAuxBuffers, 108 'alpha_size': cocoapy.NSOpenGLPFAAlphaSize, 109 'depth_size': cocoapy.NSOpenGLPFADepthSize, 110 'stencil_size': cocoapy.NSOpenGLPFAStencilSize, 111 112 # Not exposed by pyglet API (set internally) 113 'all_renderers': cocoapy.NSOpenGLPFAAllRenderers, 114 'fullscreen': cocoapy.NSOpenGLPFAFullScreen, 115 'minimum_policy': cocoapy.NSOpenGLPFAMinimumPolicy, 116 'maximum_policy': cocoapy.NSOpenGLPFAMaximumPolicy, 117 'screen_mask' : cocoapy.NSOpenGLPFAScreenMask, 118 119 # Not supported in current pyglet API 120 'color_float': cocoapy.NSOpenGLPFAColorFloat, 121 'offscreen': cocoapy.NSOpenGLPFAOffScreen, 122 'sample_alpha': cocoapy.NSOpenGLPFASampleAlpha, 123 'multisample': cocoapy.NSOpenGLPFAMultisample, 124 'supersample': cocoapy.NSOpenGLPFASupersample, 125} 126 127# NSOpenGL constants which do not require a value. 128_boolean_gl_attributes = frozenset([ 129 cocoapy.NSOpenGLPFAAllRenderers, 130 cocoapy.NSOpenGLPFADoubleBuffer, 131 cocoapy.NSOpenGLPFAStereo, 132 cocoapy.NSOpenGLPFAMinimumPolicy, 133 cocoapy.NSOpenGLPFAMaximumPolicy, 134 cocoapy.NSOpenGLPFAOffScreen, 135 cocoapy.NSOpenGLPFAFullScreen, 136 cocoapy.NSOpenGLPFAColorFloat, 137 cocoapy.NSOpenGLPFAMultisample, 138 cocoapy.NSOpenGLPFASupersample, 139 cocoapy.NSOpenGLPFASampleAlpha, 140]) 141 142# Attributes for which no NSOpenGLPixelFormatAttribute name exists. 143# We could probably compute actual values for these using 144# NSOpenGLPFAColorSize / 4 and NSOpenGLFAAccumSize / 4, but I'm not that 145# confident I know what I'm doing. 146_fake_gl_attributes = { 147 'red_size': 0, 148 'green_size': 0, 149 'blue_size': 0, 150 'accum_red_size': 0, 151 'accum_green_size': 0, 152 'accum_blue_size': 0, 153 'accum_alpha_size': 0 154} 155 156class CocoaConfig(Config): 157 158 def match(self, canvas): 159 # Construct array of attributes for NSOpenGLPixelFormat 160 attrs = [] 161 for name, value in self.get_gl_attributes(): 162 attr = _gl_attributes.get(name) 163 if not attr or not value: 164 continue 165 attrs.append(attr) 166 if attr not in _boolean_gl_attributes: 167 attrs.append(int(value)) 168 169 # Support for RAGE-II, which is not compliant. 170 attrs.append(cocoapy.NSOpenGLPFAAllRenderers) 171 172 # Force selection policy. 173 attrs.append(cocoapy.NSOpenGLPFAMaximumPolicy) 174 175 # NSOpenGLPFAFullScreen is always supplied so we can switch to and 176 # from fullscreen without losing the context. Also must supply the 177 # NSOpenGLPFAScreenMask attribute with appropriate display ID. 178 # Note that these attributes aren't necessary to render in fullscreen 179 # on Mac OS X 10.6, because there we are simply rendering into a 180 # screen sized window. See: 181 # http://developer.apple.com/library/mac/#documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_fullscreen/opengl_cgl.html%23//apple_ref/doc/uid/TP40001987-CH210-SW6 182 # Otherwise, make sure we refer to the correct Profile for OpenGL (Core or 183 # Legacy) on Lion and afterwards 184 if _os_x_version < os_x_release['snow_leopard']: 185 attrs.append(cocoapy.NSOpenGLPFAFullScreen) 186 attrs.append(cocoapy.NSOpenGLPFAScreenMask) 187 attrs.append(quartz.CGDisplayIDToOpenGLDisplayMask(quartz.CGMainDisplayID())) 188 elif _os_x_version >= os_x_release['lion']: 189 # check for opengl profile 190 # This requires OS-X Lion (Darwin 11) or higher 191 version = ( 192 getattr(self, 'major_version', None) or 2, 193 getattr(self, 'minor_version', None) 194 ) 195 # tell os-x we want to request a profile 196 attrs.append(cocoapy.NSOpenGLPFAOpenGLProfile) 197 198 # check if we're wanting core or legacy 199 # Mavericks (Darwin 13) and up are capable of the Core 4.1 profile, 200 # while Lion and up are only capable of Core 3.2 201 if version[0] >= 4 and _os_x_version >= os_x_release['mavericks']: 202 attrs.append(int(cocoapy.NSOpenGLProfileVersion4_1Core)) 203 elif version[0] >= 3: 204 attrs.append(int(cocoapy.NSOpenGLProfileVersion3_2Core)) 205 else: 206 attrs.append(int(cocoapy.NSOpenGLProfileVersionLegacy)) 207 # Terminate the list. 208 attrs.append(0) 209 210 # Create the pixel format. 211 attrsArrayType = c_uint32 * len(attrs) 212 attrsArray = attrsArrayType(*attrs) 213 pixel_format = NSOpenGLPixelFormat.alloc().initWithAttributes_(attrsArray) 214 215 # Return the match list. 216 if pixel_format is None: 217 return [] 218 else: 219 return [CocoaCanvasConfig(canvas, self, pixel_format)] 220 221 222class CocoaCanvasConfig(CanvasConfig): 223 224 def __init__(self, canvas, config, pixel_format): 225 super(CocoaCanvasConfig, self).__init__(canvas, config) 226 self._pixel_format = pixel_format 227 228 # Query values for the attributes of the pixel format, and then set the 229 # corresponding attributes of the canvas config. 230 for name, attr in _gl_attributes.items(): 231 vals = c_int() 232 self._pixel_format.getValues_forAttribute_forVirtualScreen_(byref(vals), attr, 0) 233 setattr(self, name, vals.value) 234 235 # Set these attributes so that we can run pyglet.info. 236 for name, value in _fake_gl_attributes.items(): 237 setattr(self, name, value) 238 239 # Update the minor/major version from profile if (Mountain)Lion 240 if _os_x_version >= os_x_release['lion']: 241 vals = c_int() 242 profile = self._pixel_format.getValues_forAttribute_forVirtualScreen_( 243 byref(vals), 244 cocoapy.NSOpenGLPFAOpenGLProfile, 245 0 246 ) 247 248 if vals.value == cocoapy.NSOpenGLProfileVersion4_1Core: 249 setattr(self, "major_version", 4) 250 setattr(self, "minor_version", 1) 251 elif vals.value == cocoapy.NSOpenGLProfileVersion3_2Core: 252 setattr(self, "major_version", 3) 253 setattr(self, "minor_version", 2) 254 else: 255 setattr(self, "major_version", 2) 256 setattr(self, "minor_version", 1) 257 258 def create_context(self, share): 259 # Determine the shared NSOpenGLContext. 260 if share: 261 share_context = share._nscontext 262 else: 263 share_context = None 264 265 # Create a new NSOpenGLContext. 266 nscontext = NSOpenGLContext.alloc().initWithFormat_shareContext_( 267 self._pixel_format, 268 share_context) 269 270 return CocoaContext(self, nscontext, share) 271 272 def compatible(self, canvas): 273 return isinstance(canvas, CocoaCanvas) 274 275 276class CocoaContext(Context): 277 278 def __init__(self, config, nscontext, share): 279 super(CocoaContext, self).__init__(config, share) 280 self.config = config 281 self._nscontext = nscontext 282 283 def attach(self, canvas): 284 # See if we want OpenGL 3 in a non-Lion OS 285 if _os_x_version < os_x_release['lion'] and self.config.requires_gl_3(): 286 raise ContextException('OpenGL 3 not supported') 287 288 super(CocoaContext, self).attach(canvas) 289 # The NSView instance should be attached to a nondeferred window before calling 290 # setView, otherwise you get an "invalid drawable" message. 291 self._nscontext.setView_(canvas.nsview) 292 self._nscontext.view().setWantsBestResolutionOpenGLSurface_(1) 293 self.set_current() 294 295 def detach(self): 296 super(CocoaContext, self).detach() 297 self._nscontext.clearDrawable() 298 299 def set_current(self): 300 self._nscontext.makeCurrentContext() 301 super(CocoaContext, self).set_current() 302 303 def update_geometry(self): 304 # Need to call this method whenever the context drawable (an NSView) 305 # changes size or location. 306 self._nscontext.update() 307 308 def set_full_screen(self): 309 self._nscontext.makeCurrentContext() 310 self._nscontext.setFullScreen() 311 312 def destroy(self): 313 super(CocoaContext, self).destroy() 314 self._nscontext.release() 315 self._nscontext = None 316 317 def set_vsync(self, vsync=True): 318 vals = c_int(vsync) 319 self._nscontext.setValues_forParameter_(byref(vals), cocoapy.NSOpenGLCPSwapInterval) 320 321 def get_vsync(self): 322 vals = c_int() 323 self._nscontext.getValues_forParameter_(byref(vals), cocoapy.NSOpenGLCPSwapInterval) 324 return vals.value 325 326 def flip(self): 327 self._nscontext.flushBuffer() 328