5
votes

I'm using OpenGL to draw in Mac OS. When my code runs on Retina display everything works fine except text drawing. Under Retinal display the text is twice as big as it should be. It happens because the font size is in points and each point is 2 pixels under Retina, but OpenGL is pixel based.

Here is the correct text drawing under standard display:
Correct text drawing under standard display

Here is the incorrect text drawing under Retina display:
Incorrect text drawing under Retina display

Here is how I normaly draw strings. Since OpenGL does not have text drawing functions, in order to draw text I do the following:

  1. Get the font:

    NSFontManager fontManager = [NSFontManager sharedFontManager];
    NSString
    font_name = [NSString stringWithCString: "Helvetica" encoding: NSMacOSRomanStringEncoding];
    font = [fontManager fontWithFamily: font_name traits:fontStyle weight:5 size:9];
    attribs = [[NSMutableDictionary dictionaryWithCapacity: 3] retain];
    [attribs setObject:font forKey:NSFontAttributeName];

  2. Create and measure the string:

    NSString* aString = [NSString stringWithCString: "blah blah" encoding: NSMacOSRomanStringEncoding];
    NSSize frameSize = [aString sizeWithAttributes: m_attribs];

  3. Allocate NSImage with the size:

    NSImage* image = [[NSImage alloc] initWithSize:frameSize];
    [image lockFocus];

  4. Draw the string into the image:

    [aString drawAtPoint:NSMakePoint (0, 0) withAttributes:m_attribs];

  5. Get the bits:

    NSBitmapImageRep* bitmap = [[NSBitmapImageRep alloc] initWithFocusedViewRect:NSMakeRect (0.0f, 0.0f, frameSize.width, frameSize.height)];
    [image unlockFocus];

  6. Create OpenGL texture:

    GLuint texture = 0;
    glGenTextures(1, &texture);
    glTexImage2D(GL_TEXTURE_RECTANGLE_EXT, 0, GL_RGBA, GLsizei(frameSize.width), GLsizei(frameSize.height), 0, GL_RGBA, [bitmap bitmapData]);

  7. Draw the texture:

    glBindTexture ….
    other OpenGL drawing code

My question is how to get NSString to draw in pixel resolution not in points.

I tried the following:

  1. Draw at half the point size: 4.5 instead of 9. This gives me the correct size but the text is drawn blurry.
  2. Draw at point size and shrink the texture to half the size in OpenGL, again this does not give good looking results:
    Texture shrink
3

3 Answers

1
votes

OpenGLs coordinate system is in fact point based not pixel based, but you are the one who decides what those points are. A context defines the coordinate system in 2D by the glOrtho function (or you could construct an ortho matrix by hand) which sets up the min and max x,y coordinates on the screen. For example an orthographic projection could be setup so that 0 was on the left of the screen and 100 was on the right regardless of the size of the framebuffers you are rendering into.

The font texture appears to be created just fine. The problem is that you are rendering it on geometry twice as large as it needs to be. In OpenGL, texture size does not effect the size of the object rendered on your screen. The size on screen is defined by the geometry passed to glDrawArrays or glBegin etc not the texture.

I think the problem is that you are using the pixel size of the font texture to define the quad size used to render on screen. This would put your problem in the "other OpenGL Drawing code" section. To fix that you could apply some sort of scale factor to the drawing. In retina mode the scale factor to 0.5 and for normal screens it would be 1.0 (UIKit uses a similar idea to render UIView content)

The quad calculation could look something like this:

quad.width = texture.width * scaleFactor 
quad.height = texture.height * scaleFactor

Another option would be to separate the quad rendering size completely from the texture. If you had a function or a class for drawing text it could have a font size parameter which it would use as the actual quad size instead of using the texture size.

0
votes

For my retina display, I had the problem of my framebuffer not fitting the actual window (the full buffer is rendered to a quarter of the window). In that case, using a doubled viewport solves the problem.

# Instead of actual window size width*height,
# double the dimensions for retina display 

glViewport(0, 0, width*2, height*2)

In your case (this part is only an assumption, since I cannot run your code), changing the frame sizes that you are passing to gl for texture creation can do the trick.

GLsizei(frameSize.width*2), GLsizei(frameSize.height*2)
0
votes

Given that you have an NSBitmapImageRep and get pixel raster data from that, you should be using bitmap.pixelsWide and bitmap.pixelsHigh, not frameSize when creating the texture from the bitmap.