iPad texture loading differences (32-bit vs. 64-bit)

I am working on a drawing application and I am noticing significant differences in textures loaded on a 32-bit iPad vs. a 64-bit iPad.

I create a default brush texture with this code:

UIGraphicsBeginImageContext(CGSizeMake(64, 64));
CGContextRef defBrushTextureContext = UIGraphicsGetCurrentContext();
UIGraphicsPushContext(defBrushTextureContext);

size_t num_locations = 3;
CGFloat locations[3] = { 0.0, 0.8, 1.0 };
CGFloat components[12] = { 1.0,1.0,1.0, 1.0,
    1.0,1.0,1.0, 1.0,
    1.0,1.0,1.0, 0.0 };
CGColorSpaceRef myColorspace = CGColorSpaceCreateDeviceRGB();
CGGradientRef myGradient = CGGradientCreateWithColorComponents (myColorspace, components, locations, num_locations);

CGPoint myCentrePoint = CGPointMake(32, 32);
float myRadius = 20;

CGGradientDrawingOptions options = kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation;
CGContextDrawRadialGradient (UIGraphicsGetCurrentContext(), myGradient, myCentrePoint,
                             0, myCentrePoint, myRadius,
                             options);

CFRelease(myGradient);
CFRelease(myColorspace);
UIGraphicsPopContext();

[self setBrushTexture:UIGraphicsGetImageFromCurrentImageContext()];

UIGraphicsEndImageContext();

And then actually set the brush texture like this:

-(void) setBrushTexture:(UIImage*)brushImage{
// save our current texture.
currentTexture = brushImage;

// first, delete the old texture if needed
if (brushTexture){
    glDeleteTextures(1, &brushTexture);
    brushTexture = 0;
}

// fetch the cgimage for us to draw into a texture
CGImageRef brushCGImage = brushImage.CGImage;

// Make sure the image exists
if(brushCGImage) {
    // Get the width and height of the image
    GLint width = CGImageGetWidth(brushCGImage);
    GLint height = CGImageGetHeight(brushCGImage);

    // Texture dimensions must be a power of 2. If you write an application that allows users to supply an image,
    // you'll want to add code that checks the dimensions and takes appropriate action if they are not a power of 2.

    // Allocate  memory needed for the bitmap context
    GLubyte* brushData = (GLubyte *) calloc(width * height * 4, sizeof(GLubyte));
    // Use  the bitmatp creation function provided by the Core Graphics framework.
    CGContextRef brushContext = CGBitmapContextCreate(brushData, width, height, 8, width * 4, CGImageGetColorSpace(brushCGImage), kCGImageAlphaPremultipliedLast);
    // After you create the context, you can draw the  image to the context.
    CGContextDrawImage(brushContext, CGRectMake(0.0, 0.0, (CGFloat)width, (CGFloat)height), brushCGImage);
    // You don't need the context at this point, so you need to release it to avoid memory leaks.
    CGContextRelease(brushContext);

    // Use OpenGL ES to generate a name for the texture.
    glGenTextures(1, &brushTexture);
    // Bind the texture name.
    glBindTexture(GL_TEXTURE_2D, brushTexture);
    // Set the texture parameters to use a minifying filter and a linear filer (weighted average)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    // Specify a 2D texture image, providing the a pointer to the image data in memory
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, brushData);
    // Release  the image data; it's no longer needed
    free(brushData);
}
}

I’ve updated CGFloats to be GLfloats throughout my project no success, since I thought maybe that was the issue. Maybe there is an issue with this rendering code?

if(frameBuffer){
    // draw the stroke element
    [self prepOpenGLStateForFBO:frameBuffer];
    [self prepOpenGLBlendModeForColor:element.color];
    CheckGLError();
}

// find our screen scale so that we can convert from
// points to pixels
GLfloat scale = self.contentScaleFactor;

// fetch the vertex data from the element
struct Vertex* vertexBuffer = [element generatedVertexArrayWithPreviousElement:previousElement forScale:scale];

glLineWidth(2);

// if the element has any data, then draw it
if(vertexBuffer){
    glVertexPointer(2, GL_FLOAT, sizeof(struct Vertex), &vertexBuffer[0].Position[0]);
    glColorPointer(4, GL_FLOAT, sizeof(struct Vertex), &vertexBuffer[0].Color[0]);
    glTexCoordPointer(2, GL_FLOAT, sizeof(struct Vertex), &vertexBuffer[0].Texture[0]);
    glDrawArrays(GL_TRIANGLES, 0, (GLint)[element numberOfSteps] * (GLint)[element numberOfVerticesPerStep]);
    CheckGLError();
}

if(frameBuffer){
    [self unprepOpenGLState];
}

The vertex struct is the following:

struct Vertex{
    GLfloat Position[2];    // x,y position
    GLfloat Color [4];      // rgba color
    GLfloat Texture[2];    // x,y texture coord
};

Are you comparing a 32-bit build and a 64-bit build on the same machine, or native builds on two different iPads?

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, brushData);

I’d start right here. View the contents of brushData as raw hex bytes on both 32- and 64-bit builds. If the contents are different, then your problem is with CG and has nothing to do with OpenGL.

I’ve updated CGFloats to be GLfloats throughout my project no success, since I thought maybe that was the issue.

A CGFloat is a double in a 64-bit build. A GLfloat is always a 32 bit float. Anywhere you use CGFloat or CGPoint (or NSSize, NSInteger, etc etc) you need to ensure you are handling type conversion.

Two different iPads.

[QUOTE=arekkusu;1257916]

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, brushData);

I’d start right here. View the contents of brushData as raw hex bytes on both 32- and 64-bit builds. If the contents are different, then your problem is with CG and has nothing to do with OpenGL.[/QUOTE]

A guy on the Apple developer forums pointed to the same line as you and recommended:

“You’ve likely created a texture with a variant of RGBA/UNSIGNED_BYTE pixel format, which has fairly limited precision. You can instead create a texture where each color component is a 16 bit half-float value, which will have much greater precision.”

I changed my code to this:

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_HALF_FLOAT_OES, brushData);

But it seemed to make things really crazy like I was drawing with five brushes.

I made sure I was handling type conversion in my entire project. Not the issue, unfortunately.

So, stop doing that. First narrow down the problem to figure out of it is due to the 64-bit CPU, or the completely different GPU and GL drivers in the two iPads.

Build your project 32-bit on the 64-bit iPad and see if the problem still happens. Debug the data you have on the CPU (your brushData from CG) to see if the problem occurs before or after you hand the data to OpenGL.

But it seemed to make things really crazy like I was drawing with five brushes.

Obviously, taking a pointer to CG’s unsigned bytes and asking GL to bit-cast it as half floats will produce crazy results. If you’re lucky it will crash because your malloc was too small.

If you are doing something complicated (like blending 10,000 layers each with 0.001 alpha) then there certainly could be GPU-specific differences, and changing the format of the framebuffer texture could make older iPads work more like the new iPad. But that’s a leap of logic. You need to determine if the problem is due to CG or GL before going any further.

[QUOTE=arekkusu;1257923]So, stop doing that. First narrow down the problem to figure out of it is due to the 64-bit CPU, or the completely different GPU and GL drivers in the two iPads.

Build your project 32-bit on the 64-bit iPad and see if the problem still happens. Debug the data you have on the CPU (your brushData from CG) to see if the problem occurs before or after you hand the data to OpenGL.[/QUOTE]

I guess the issue is not 32-bit/64-bit based. I ran the 32-bit and the 64-bit builds on the 64-bit iPad and the textures all look the same.

So that indicates there is something GPU-specific. Keep debugging: at this point you could delete all of the CG code from your project to eliminate it as a problem. Replace the texture with something simple like a 1x1 grey pixel or 2x2 gradient.

Then examine each part of your GL state and keep reducing it until you isolate where unexpected results are introduced. (i.e. in the code you posted, there are at least three more variables – blending, texturing, and the fbo.)

If you were using ES2, I would suggest looking at all lowp variables in your shaders, since the precision qualifiers are only hints, and are treated differently on the A7 GPU (lowp is mediump). But your code is using ES1, so all of the color, texture, and blending values are supposed to be clamped to [0,1] all of the time.

How much are you drawing with blending? If you are rendering something complicated (lots of overlapping blended triangles) the higher internal precision can produce noticeably different results from older GPUs. To eliminate this, you could try rendering only one triangle at a time, with a glFlush() (or a glReadPixels or other commands that will force rendering to occur) in-between each triangle. That should force the higher-precision tile to be written to the FBO, quantizing it to the real texture format. (Obviously this will kill performance-- just a debug tool.)

I tried this with no success. I’ve also tried loading in a texture from an image, it doesn’t seem to be the root of the issue.

If you were using ES2, I would suggest looking at all lowp variables in your shaders, since the precision qualifiers are only hints, and are treated differently on the A7 GPU (lowp is mediump). But your code is using ES1, so all of the color, texture, and blending values are supposed to be clamped to [0,1] all of the time.

I actually updated to ES2 in hope that maybe ES1 in conjunction with the A7 was causing some issues; that wasn’t the case.

How much are you drawing with blending? If you are rendering something complicated (lots of overlapping blended triangles) the higher internal precision can produce noticeably different results from older GPUs. To eliminate this, you could try rendering only one triangle at a time, with a glFlush() (or a glReadPixels or other commands that will force rendering to occur) in-between each triangle. That should force the higher-precision tile to be written to the FBO, quantizing it to the real texture format. (Obviously this will kill performance-- just a debug tool.)

I am rendering lots of overlapping triangle. I’m not exactly sure how to go about doing the glFlush thing, but I’ll give it a try.

In addition to the strange texture issue, if this will help may help you figure out what is going on, I am also noticing strange color changes on the 32-bit iPad when I create brushes with really low alpha values. Could be this be the same issue?