Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PyOpenGL on a Macbook retina display

I have some code that displays graphics using PyOpenGL in a glut window. On a retina Macbook Pro this window appears in a low-resolution mode, with one OpenGL pixel being represented by four physical pixels. It would be much nicer if it would display in the full native resolution.

My Question
Is there any way to obtain a native resolution OpenGL context in Python on Retina displays, using glut or otherwise?

Example of the issue
Below is a minimal working example of a PyOpenGL program. There is nothing special about it - this issue will be exhibited by any working PyOpenGL code. Here is a zoomed-in detail of a screenshot. Notice that the pixels making up the white triangle are four times the size of the pixels of the OS X widgets. This is the default for programs that aren't specifically designed for Retina devices. I want to know how to turn it off for PyOpenGL programs.

enter image description here

Here is the code:

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()
like image 317
N. Virgo Avatar asked Feb 09 '13 02:02

N. Virgo


3 Answers

To properly achieve the resolution you are after, you will need to use a NSView and then indicate that the view should use the best resolution (this also means you are no longer supposed to use GLUT). Since this is outside of OpenGL's scope, and you want to use Python, you will need to use some package that access these kind of objects together with py2app (but the full py2app build is not required).

In order to access an NSView, or more specifically a NSOpenGLView for your needs, pyobjc is required. The following code imports Cocoa (provided by pyobjc) just for clarity, the same objects can be accessed through AppKit. The code creates a NSWindow, then a view which defines the drawRect to be called whenever required. The single line that solves the resolution issue is: 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()

If you try running this code, it will run just fine but you won't notice any difference regarding the resolution problem. To solve that, you need to construct a simple setup.py assuming the code above was saved in a file called test1.py:

from distutils.core import setup
import py2app
setup(app=["test1.py"])

Then run mypythonexecutable setup.py py2app -A (where, of course, mypythonexecutable is replaced by something that makes sense like python or python2.7, for example). Now you do open dist/test1.app and the issue is solved (dist/test1.app will be created after the earlier command is executed).

like image 150
mmgp Avatar answered Oct 18 '22 13:10

mmgp


You can get a patched version of GLUT that optionally supports HiDPI from here: http://iihm.imag.fr/blanch/software/glut-macosx/

If you use this GLUT framework, your applications may opt-in to get an OpenGL context at real screen pixel resolution. You will just have to change the display initialization in your code (be it C, Python/PyOpenGL or any thing that links with the GLUT framework) from:

glutInitDisplayMode(GLUT_RGBA|GLUT_DOUBLE)

to:

glutInitDisplayString("rgba double hidpi")

The patched version gracefully handles multiple screens with different backing store scale factors, and provides a consistent behaviour for various functions that expect sizes or dimensions specified in pixels (i.e., glutInitWindowSize, glutReshapeWindow, glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT) and the position of the cursor passed to the event callbacks).

This version of GLUT is also fully backward compatible for other applications that decides not to opt-in for HiDPI support. The patches, and the instructions to build the framework by yourself are also provided if you prefer to build your own version.

The patched version also adds mouse wheel support, should you need it.

like image 40
rndblnch Avatar answered Oct 18 '22 13:10

rndblnch


I guess you'll have to use some PyObjC bindings to get the NSWindow that Glut creates and then fiddle with the scale factor. Here's some info on the Objective C parts: http://supermegaultragroovy.com/2012/10/24/coding-for-high-resolution-on-os-x-read-this/

like image 37
boxed Avatar answered Oct 18 '22 13:10

boxed