Macbook视网膜显示器上的PyOpenGL
我有一些代码,用PyOpenGL在一个glut窗口中显示图形。在一台视网膜屏幕的Macbook Pro上,这个窗口的显示效果很低,OpenGL的一个像素被四个物理像素表示。如果能以原生分辨率显示就好了。
我的问题
有没有办法在视网膜显示器上,用Python获取原生分辨率的OpenGL上下文,不管是用glut还是其他方式?
问题示例
下面是一个简单的PyOpenGL程序的示例。这个程序没有什么特别之处——任何正常工作的PyOpenGL代码都会出现这个问题。这里有一张截图的放大细节。注意,构成白色三角形的像素是OS X小部件像素的四倍大。这是那些没有专门为视网膜设备设计的程序的默认情况。我想知道如何在PyOpenGL程序中关闭这个设置。
这里是代码:
from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *
import os
def initFunc():
glDisable(GL_DEPTH_TEST)
glClearColor(0.0, 0.0, 0.0, 0.0)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluOrtho2D(0.0, 400.0, 0.0, 400.0)
def displayFunc():
glClear(GL_COLOR_BUFFER_BIT)
glColor3f(1.0, 1.0, 1.0)
glBegin(GL_TRIANGLES)
glVertex2f(10.0, 10.0)
glVertex2f(10.0, 100.0)
glVertex2f(100.0, 100.0)
glEnd()
glFlush()
if __name__ == '__main__':
glutInit()
glutInitWindowSize(400,400)
glutCreateWindow("GL test")
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB)
glutDisplayFunc(displayFunc)
initFunc()
os.system('''/usr/bin/osascript -e 'tell app "Finder" to set frontmost of process "Python" to true' ''')
# prevent GL window from appearing behind other applications
glutMainLoop()
3 个回答
我想你需要用一些PyObjC的绑定来获取Glut创建的NSWindow,然后调整一下缩放比例。这里有一些关于Objective C部分的信息:http://supermegaultragroovy.com/2012/10/24/coding-for-high-resolution-on-os-x-read-this/
你可以从这里获取一个修补过的GLUT版本,它可以选择性地支持高分辨率显示(HiDPI):http://iihm.imag.fr/blanch/software/glut-macosx/
如果你使用这个GLUT框架,你的应用程序可以选择以真实的屏幕像素分辨率获取OpenGL上下文。你只需要在代码中(无论是C语言、Python/PyOpenGL还是其他与GLUT框架链接的语言)修改显示初始化部分,从:
glutInitDisplayMode(GLUT_RGBA|GLUT_DOUBLE)
改成:
glutInitDisplayString("rgba double hidpi")
这个修补版本能够很好地处理不同屏幕的不同缩放比例,并且为一些需要像素大小或维度的函数提供了一致的表现,比如 glutInitWindowSize, glutReshapeWindow, glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT)
以及传递给事件回调的光标位置。
这个版本的GLUT也完全向后兼容,适合那些不想使用高分辨率支持的其他应用程序。如果你想自己构建一个版本,修补程序和构建框架的说明也提供了。
此外,这个修补版本还增加了鼠标滚轮的支持,如果你需要的话。
要实现你想要的分辨率,首先需要使用一个叫做 NSView
的东西,并且要告诉这个视图使用最佳分辨率(这也意味着你不再使用 GLUT 了)。因为这超出了 OpenGL 的范围,而你又想用 Python,所以你需要用一些可以和 py2app 一起使用的包来访问这些对象(不过不需要完整的 py2app build
)。
为了访问 NSView
,更具体地说是 NSOpenGLView
,你需要用到 pyobjc
。下面的代码是为了清晰起见导入了 Cocoa
(这是 pyobjc
提供的),其实同样的对象也可以通过 AppKit
访问。代码创建了一个 NSWindow
,然后创建了一个视图,并定义了 drawRect
,这个方法会在需要的时候被调用。解决分辨率问题的关键代码是:view.setWantsBestResolutionOpenGLSurface_(True)
。
import Cocoa
from OpenGL.GL import *
from OpenGL.GLU import *
class GLView(Cocoa.NSOpenGLView):
def initWithFrame_(self, frame):
format = Cocoa.NSOpenGLPixelFormat.alloc().initWithAttributes_((0, ))
view = super(GLView, self).initWithFrame_pixelFormat_(frame, format)
view.setWantsBestResolutionOpenGLSurface_(True)
view.openGLContext().makeCurrentContext()
glDisable(GL_DEPTH_TEST)
glClearColor(0.0, 0.0, 0.0, 0.0)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluOrtho2D(0.0, 1.0, 0.0, 1.0)
return view
def drawRect_(self, bounds):
glClear(GL_COLOR_BUFFER_BIT)
glColor3f(1.0, 1.0, 1.0)
glBegin(GL_TRIANGLES)
glVertex2f(0.1, 0.1)
glVertex2f(0.1, 0.9)
glVertex2f(0.9, 0.9)
glEnd()
glFlush()
class AppDelegate(Cocoa.NSObject):
def windowWillClose_(self, notification):
app.terminate_(self)
app = Cocoa.NSApplication.sharedApplication()
rect = Cocoa.NSMakeRect(0, 0, 300, 300)
winflags = Cocoa.NSTitledWindowMask | Cocoa.NSClosableWindowMask
win = Cocoa.NSWindow.alloc().initWithContentRect_styleMask_backing_defer_(
rect, winflags, Cocoa.NSBackingStoreBuffered, False)
delegate = AppDelegate.alloc().init()
win.setDelegate_(delegate)
view = GLView.alloc().initWithFrame_(rect)
win.setContentView_(view)
win.makeKeyAndOrderFront_(None)
app.run()
如果你尝试运行这段代码,它会正常运行,但你不会发现分辨率方面有什么变化。要解决这个问题,你需要构建一个简单的 setup.py
,假设上面的代码保存在一个叫做 test1.py
的文件中:
from distutils.core import setup
import py2app
setup(app=["test1.py"])
然后运行 mypythonexecutable setup.py py2app -A
(当然,mypythonexecutable
要替换成像 python
或 python2.7
这样的有意义的东西)。现在你可以执行 open dist/test1.app
,这样问题就解决了(dist/test1.app
会在之前的命令执行后创建)。