5

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()
N. Virgo
  • 7,970
  • 11
  • 44
  • 65
  • You should probably post some code. – djc Feb 11 '13 at 10:06
  • What you are after is documented in https://developer.apple.com/library/mac/#documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/Introduction/Introduction.html, and more directly at http://developer.apple.com/library/mac/#documentation/graphicsimaging/conceptual/opengl-macprogguide/EnablingOpenGLforHighResolution/EnablingOpenGLforHighResolution.html. Now you need to map it to Python. – mmgp Feb 14 '13 at 14:29
  • @Nathaniel so the question is a different one. It didn't seem like you were aware of what was required, now that you do it is only a matter of knowing how to use `NSOpenGLView` obtained by `from AppKit import NSOpenGLView`. – mmgp Feb 14 '13 at 14:47
  • @mmgp many apologies for my hasty comment. I had no idea that this AppKit stuff could be called from Python at all, so I thought that by "map it to Python" you just meant "do the same thing in Python somehow". I will look into the AppKit module soon. (If you can post a slightly more detailed version as an answer that would be awesome.) – N. Virgo Feb 14 '13 at 14:54
  • @Nathaniel I could include an answer for doing that, but I don't have a retina macbook readily available to test it. – mmgp Feb 14 '13 at 20:04
  • @mmgp well, even if your answer only gives a hint that sends me in the right direction it would still be helpful. (And there's a 50 point bounty available if you care about such things.) – N. Virgo Feb 15 '13 at 02:03

3 Answers3

5

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).

mmgp
  • 18,901
  • 3
  • 53
  • 80
  • This looks great. I've awarded you the bounty and will accept the answer once I have a chance to test it. – N. Virgo Feb 17 '13 at 06:18
  • So after getting distracted by actual work for a long period, I finally got a chance to try this. Unfortunately, although it runs fine (but low res) with `python test1.py`, when doing `open dist/test1.app` I get a dialogue with the oh-so-helpful error "test1 Error". There's an option to open Console, but there's nothing from python in the console log as far as I can see. – N. Virgo Jun 14 '13 at 04:31
3

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.

rndblnch
  • 41
  • 2
1

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/

boxed
  • 3,895
  • 2
  • 24
  • 26