Difficult-Rocket/pyglet/gl/cocoa.py

336 lines
12 KiB
Python
Raw Normal View History

2021-03-24 23:37:10 +08:00
# ----------------------------------------------------------------------------
# pyglet
# Copyright (c) 2006-2008 Alex Holkner
# Copyright (c) 2008-2020 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 platform
from ctypes import c_uint32, c_int, byref
from pyglet.gl.base import Config, CanvasConfig, Context
from pyglet.gl import ContextException
from pyglet.gl import gl
from pyglet.gl import agl
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 2,
getattr(self, 'minor_version', None)
)
# 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'] and self.config.requires_gl_3():
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._nscontext.view().setWantsBestResolutionOpenGLSurface_(1)
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()