I'm sure it could be faster...

Member
Posts: 65
Joined: 2009.03
Post: #1
Hi all

I've been learning Obj-C and OpenGL for the last month or so. To get to grips with things I've been creating my own classes to handle rendering images (Texture2D for the initial texture loading), scaling, rotating, Sprite Sheets, Bitmap Fonts, Animation, Tilemap etc.

I've got all this stuff working ok but to me I just don't think it is as fast as it could be as I see things slow down when for instance the screen is full of tile map tiles but then is nice and smooth when there is maybe only half the screen filled. My background is Java and so I've tackled my game code using straight Obj-C with classes, class instances and methods etc.

I've been reading through the excellent info on this forum for a while and it would seem that using Obj-C for all this adds an overhead with the creation of objects and message despatch etc which is bad on the iPhone with limited resources. I want to make my game code as fast as possible, doesn't everyone Smile but I'm not sure from my current code where the best place to start is and where I should focus.

I've used info from the forums to try and optimize my gameloop and instruments is showing that most time is being spent in the game loop and render code, so things like should I draw all quads in one hit rather than separate as they are now etc, although I'm not sure how to tackle that etc.

I'd be happy to email my entire test project to someone if they would be willing to take a quick look and tell me where I should spend my next dev hours as I don't want to focus on an area, change it so using inline structs and functions only to find I get a 0.1% improvement Smile that would be a real bummer.

If anyone is up for checking out my project or just has advice it would all be very much appreciated. On the advice side, I don't have any C or C++ experience so I'm not sure if I move from classes etc how to structure my code and project structure so again, any advice for this noob would be great.

Cheers

MikeD
Quote this message in a reply
Member
Posts: 86
Joined: 2008.04
Post: #2
In my experience, Objective-C performs just as well as c and c++for the most part.

More important than the language is the general design of the application.
Remember to follow best practices and optimization.

I find programming in objective-c to be more productive than c++. Sifting through miles of c++ STL debug stack traces is not any fun.

The only downside to objective-c is that you are platform specific. If this is a concern, than I would switch to c++.

Otherwise, my advice is to focus on learning good design - and continue to learn cocoa and OpenGL.
Quote this message in a reply
Member
Posts: 65
Joined: 2009.03
Post: #3
Thanks OptimisticMonkey that is great feedback.

I've actually just seen another post on the forum where a link to a Tech Note from Apple about Optimising OpenGL ES has been posted. This talks about all the things I had read about but was not sure would add much value, so that will help me focus on what to optimize.

I'm really pleased to hear that there is not much in C++ and Obj-C. Some of the posts in this forum made it sound like the overhead was significant, which I suppose it can be if your code is not using the most efficient patterns. I'm enjoying Cocoa/Obj-C at the moment and did not fancy having to learn C++.

Cheers

Mike
Quote this message in a reply
Member
Posts: 22
Joined: 2006.04
Post: #4
OptimisticMonkey is right: it's very unlikely that using Obj-C as opposed to C/C++ is causing a major slow-down of your code.

If the rendering code seems to be where your application is spending it's time then it sounds to me like it may be worth checking if your application is actually GPU limited or not. Optimizing CPU calls is unlikely help you if it's the GPU that's actually the bottleneck for your application (it may be that you have a GPU driver bottleneck in which case you will still benefit from CPU optimizations and will benefit from optimizing GPU calls too).

Try looking at the OpenGL ES performance in Instruments and see if this is close to 100%. Also try running your app without actual GL draw calls, but with the game logic intact and see if your performance improves suddenly. If it doesn't then you should consider optimizing your CPU routines instead.

Flash!
Quote this message in a reply
Member
Posts: 65
Joined: 2009.03
Post: #5
Thanks flash

I've run it through OpenGL ES in Instruments and its using 50% of the renderer, which to me seems to point to it being CPU bound. The CPU Sampler seems spikey and is showing that my draw method is running at around 30-40% and the loop which draws the tile map is running at around 15-20%, which seems high. The loop is going through the tiles which should be displayed for the current screen location and does not seem that heavy to me. I've posted it below:

Code:
- (void)renderAtPoint:(CGPoint)point mapX:(int)mapX mapY:(int)mapY width:(int)width height:(int)height layer:(int)lid {
    
    Layer *layer = [layers objectAtIndex:lid];
    TileSet *tileSet = nil;
    int x = point.x;
    int y = point.y;
    
    // Loop through all the tile sets we have.  Moving through the map data and rendering all those
    // tiles which are from the current tile map will stop us switching between textures all the time
    for(int tileSetIndex=0; tileSetIndex < [tileSets count]; tileSetIndex++) {
        
        // Clear the tileSet ready for a new one
        tileSet = nil;
        
        // Loop through the rows of tiles to be rendered
        for(int mapTileY=mapY; mapTileY < (mapY + height); mapTileY++) {
            
            // Loop through tiles in each row
            for(int mapTileX=mapX; mapTileX < (mapX + width); mapTileX++) {
                
                int currentTileSetIndex = [layer getTileSetIDAtX:mapTileX y:mapTileY];

                if(tileSetIndex == currentTileSetIndex) {
                    // If the tileset has not already been defined then define it
                    if(!tileSet) {
                        tileSet = [tileSets objectAtIndex:tileSetIndex];
                    }
                    
                    // Grab the tile ID for the current tile
                    int tileID = [layer getTileIDAtX:mapTileX y:mapTileY];
                    
                    // Using the current tileset, render the correct sprite in the spritesheet for the current tile
                    [[tileSet tiles] renderSpriteAtX:[tileSet getTileX:tileID] y:[tileSet getTileY:tileID] point:CGPointMake(x, y)];
                }
                
                // Move the x coord for the next tile to be drawn
                x += tileWidth;
            }
            // Move the y coord for the next row and reset x
            y -= tileHeight;
            x = point.x;
        }
        // Get ready to loop through any other tilesets we have on this layer
        x = point.x;
        y = point.y;
    }
}

renderSpriteAtX does...

Code:
- (void)renderSpriteAtX:(GLuint)x y:(GLuint)y point:(CGPoint)point {
    //Calculate the point from which the sprite should be taken within the spritesheet
    CGPoint spritePoint = CGPointMake(x * (spriteWidth + spacing), y * (spriteHeight + spacing));
    
    // Rather than return a new image for this sprite we are going to just render the specified
    // sprite at the specified location
    [image renderSubImageAtPoint:point offset:spritePoint subImageWidth:spriteWidth subImageHeight:spriteHeight centerOfImage:NO];
}

and renderSubImageAtPoint does...

Code:
- (void)renderSubImageAtPoint:(CGPoint)point offset:(CGPoint)offsetPoint subImageWidth:(GLfloat)subImageWidth subImageHeight:(GLfloat)subImageHeight centerOfImage:(BOOL)center {
    
    // Calculate the texture coordinates using the offset point from which to start the image and then using the width and height
    // passed in
    GLfloat    textureCoordinates[] = {
        texWidthRatio * subImageWidth + (texWidthRatio * offsetPoint.x),    texHeightRatio * offsetPoint.y,
        texWidthRatio * subImageWidth + (texWidthRatio * offsetPoint.x),    texHeightRatio * subImageHeight + (texHeightRatio * offsetPoint.y),
        texWidthRatio * offsetPoint.x,                                        texHeightRatio * offsetPoint.y,
        texWidthRatio * offsetPoint.x,                                        texHeightRatio * subImageHeight + (texHeightRatio * offsetPoint.y)
    };
    
    // Calculate the width and the height of the quad using the current image scale and the width and height
    // of the image we are going to render
    GLfloat quadWidth = subImageWidth * scale;
    GLfloat quadHeight = subImageHeight * scale;
    
    // Define the vertices for each corner of the quad which is going to contain our image.
    // We calculate the size of the quad to match the size of the subimage which has been defined.
    // If center is true, then make sure the point provided is in the center of the image else it will be
    // the bottom left hand corner of the image
    GLfloat quadVertices[8];
    if(center) {
        quadVertices[0] = quadWidth / 2;
        quadVertices[1] = quadHeight / 2;
        
        quadVertices[2] = quadWidth / 2;
        quadVertices[3] = -quadHeight / 2;
        
        quadVertices[4] = -quadWidth / 2;
        quadVertices[5] = quadHeight / 2;
        
        quadVertices[6] = -quadWidth / 2;
        quadVertices[7] = -quadHeight / 2;
    } else {
        quadVertices[0] = quadWidth;
        quadVertices[1] = quadHeight;
        
        quadVertices[2] = quadWidth;
        quadVertices[3] = 0;
        
        quadVertices[4] = 0;
        quadVertices[5] = quadHeight;
        
        quadVertices[6] = 0;
        quadVertices[7] = 0;
    }        
    
    // Now that we have defined the texture coordinates and the quad vertices we can render to the screen
    // using them
    [self renderAt:point texCoords:textureCoordinates quadVertices:quadVertices];
}


- (void)renderAt:(CGPoint)point texCoords:(GLfloat[])texCoords quadVertices:(GLfloat[])quadVertices {
    
    // Save the current matrix to the stack
    glPushMatrix();
    
    // Move to where we want to draw the image
    glTranslatef(point.x, point.y, 0.0f);
    
    // Rotate around the Z axis by the angle define for this image
    glRotatef(-rotation, 0.0f, 0.0f, 1.0f);
    
    // Set the glColor to apply alpha to the image
    glColor4f(colourFilter[0], colourFilter[1], colourFilter[2], colourFilter[3]);
    
    // Set client states so that the Texture Coordinate Array will be used during rendering
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    
    // Enable Texture_2D
    //glEnable(GL_TEXTURE_2D);
    
    // Bind to the texture that is associated with this image
    glBindTexture(GL_TEXTURE_2D, [texture name]);
    
    // Set up the VertexPointer to point to the vertices we have defined
    glVertexPointer(2, GL_FLOAT, 0, quadVertices);
    
    // Set up the TexCoordPointer to point to the texture coordinates we want to use
    glTexCoordPointer(2, GL_FLOAT, 0, texCoords);
    
    // Enable blending as we want the transparent parts of the image to be transparent
    glEnable(GL_BLEND);
    
    // Draw the vertices to the screen
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    
    // Now we are done drawing disable blending
    glDisable(GL_BLEND);
    
    // Disable as necessary
    //glDisable(GL_TEXTURE_2D);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    
    // Restore the saved matrix from the stack
    glPopMatrix();
}

The first piece of code lives in my TileMap Class, the second in my SpriteSheet class and the third in my Image Class. This last piece of code is how all images are rendered. I have an image class which can either generate a new texture based on an image passed in, using Texture2D, or it can reference a texture which has already be generated. Each image is them rendered using the code above.

I'm not sure how to start to optimize this. I am assuming that putting all vertices for an entire scene into a single array and the same for the tex coords and doing a single draw would speed things up, as maybe the API overhead of calling glDrawArrays lots of times is draining the CPU.

Make sense or should I take up knitting Blink

MikeD
Quote this message in a reply
Member
Posts: 45
Joined: 2008.04
Post: #6
Ouch... at the top level you you loop through the tile set to "avoid switching textures", but at the bottom level you are still binding the texture every time (along with enabling and disabling a whole lot of other opengl state).

By moving to vertex arrays and drawing multiple tiles at once, you should be able to significantly improve the performance.

The other possible performance issue is the additional looping through the map based on the size of your tileset... There may be ways to avoid this, ie. by using a texture atlas, or batching the work with a vertex array per texture.

Study the information you get from Instruments carefully.
Quote this message in a reply
Member
Posts: 65
Joined: 2009.03
Post: #7
Thanks aBabyRabbit. I've changed my tilemap code so that it uses Vertex arrays and the performance improvement is amazing. CPU usage in instruments has gone from 30-40% for my tilemap draw method to 5-11% and its running REALLY smoothly.

Thanks for the tips guys, I now need to tidy up the code having hacked in this change Grin

MikeD
Quote this message in a reply
Member
Posts: 65
Joined: 2009.03
Post: #8
Just in case anyone wants to see how I did it in the end, I've pasted the render method of my tile map class below.

Code:
- (void)renderAtPoint:(CGPoint)point mapX:(int)mapX mapY:(int)mapY width:(int)width height:(int)height layer:(int)lid {
    
    // If the map coords are out of bounds then return doing nothing
    if(mapY < 0)
        mapY = 0;
    
    Layer *layer = [layers objectAtIndex:lid];
    TileSet *tileSet = nil;
    int x = point.x;
    int y = point.y;
    int currentQuad = 0;
    int textureName = -1;
    int currentTexture = -1;
    
    glEnable(GL_TEXTURE_2D);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    
    // Loop through all the tile sets we have.  Moving through the map data and rendering all those
    // tiles which are from the current tile map will stop us switching between textures all the time
    for(int tileSetIndex=0; tileSetIndex < [tileSets count]; tileSetIndex++) {
        
        // Clear the tileSet ready for a new one
        tileSet = nil;
        
        // Loop through the rows of tiles to be rendered
        for(int mapTileY=mapY; mapTileY < (mapY + height); mapTileY++) {
            
            // Loop through tiles in each row
            for(int mapTileX=mapX; mapTileX < (mapX + width); mapTileX++) {
                
                int currentTileSetIndex = [layer getTileSetIDAtX:mapTileX y:mapTileY];

                if(tileSetIndex == currentTileSetIndex) {
                    // If the tileset has not already been defined then define it and get the tileset name
                    if(!tileSet) {
                        tileSet = [tileSets objectAtIndex:tileSetIndex];
                        textureName = (int)[[[[tileSet tiles] image] texture] name];
                    }
                    
                    // Only bind the texture if its changed since last time
                    if(textureName != currentTexture) {
                        currentTexture = textureName;
                        glBindTexture(GL_TEXTURE_2D, textureName);
                    }
                    
                    // Grab the necessary properties from the tileset
                    int tileID = [layer getTileIDAtX:mapTileX y:mapTileY];
                    int w = [[tileSet tiles] spriteWidth];
                    int h = [[tileSet tiles] spriteHeight];
                    int s = [[tileSet tiles] spacing];
                    float wr = [[[tileSet tiles] image] texWidthRatio];
                    float hr = [[[tileSet tiles] image] texHeightRatio];
                    int sc = [[[tileSet tiles] image] scale];
                    
                    // Calculate the texture offset within the tileset image.
                                        // Need to work on getting the SpriteSheet class to do this for me.
                    CGPoint offsetPoint = CGPointMake([tileSet getTileX:tileID] * (w + s),
                                                      [tileSet getTileY:tileID] * (h + s));

                    // Calculate the texture coordinates using the offset point from which to start the image and then using the width and height
                    // passed in
                    texCoords[currentQuad].br_x = wr * w + (wr * offsetPoint.x);
                    texCoords[currentQuad].br_y = hr * offsetPoint.y;
                    
                    texCoords[currentQuad].tr_x = wr * w + (wr * offsetPoint.x);
                    texCoords[currentQuad].tr_y = hr * h + (hr * offsetPoint.y);
                    
                    texCoords[currentQuad].bl_x = wr * offsetPoint.x;
                    texCoords[currentQuad].bl_y = hr * offsetPoint.y;
                    
                    texCoords[currentQuad].tl_x = wr * offsetPoint.x;
                    texCoords[currentQuad].tl_y = hr * h + (hr * offsetPoint.y);
                    
                    // Calculate the width and the height of the quad using the current image scale and the width and height
                    // of the texture we are going to render
                    GLfloat quadWidth = w * sc;
                    GLfloat quadHeight = h * sc;
                    
                    // Define the vertices for each corner of the quad which is going to contain the image.
                    // Calculate the size of the quad to match the size of the subimage which has been defined.
                    // If center is true, then make sure the point provided is in the center of the image else it will be
                    // the bottom left hand corner of the image
                    vertices[currentQuad].br_x = x + quadWidth;
                    vertices[currentQuad].br_y = y + quadHeight;
                    
                    vertices[currentQuad].tr_x = x + quadWidth;
                    vertices[currentQuad].tr_y = y;
                    
                    vertices[currentQuad].bl_x = x;
                    vertices[currentQuad].bl_y = y + quadHeight;
                    
                    vertices[currentQuad].tl_x = x;
                    vertices[currentQuad].tl_y = y;
                    currentQuad++;
                }
                
                // Move the x coord for the next tile to be drawn
                x += tileWidth;            
            }
            // Move the y coord for the next row and reset x
            y -= tileHeight;
            x = point.x;
        }
        
        glVertexPointer(2, GL_FLOAT, 0, vertices);
        glTexCoordPointer(2, GL_FLOAT, 0, texCoords);
        glDrawElements(GL_TRIANGLES, currentQuad*6, GL_UNSIGNED_SHORT, indices);
        currentQuad = 0;
        
        // Get ready to loop through any other tilesets we have on this layer
        x = point.x;
        y = point.y;
    }
    glDisable(GL_TEXTURE_2D);
}

If anyone has any comments they are ALWAYS welcome.

Cheers

MikeD
Quote this message in a reply
Nibbie
Posts: 4
Joined: 2009.03
Post: #9
@MikeD

What kind of structure are you using for "vertices"?
I was under the assumption that the vertex pointer passed into "glVertexPointer" had to be a simple array of vertices; yet, you seem to be using some structure that has member variables addressing each of the corners (e.g. "bl" "tl" etc.).
Please enlighten this confused programmer.. thank you.
Quote this message in a reply
Apprentice
Posts: 11
Joined: 2008.09
Post: #10
A question.. Do you have tiles with different sizes ?
If not, couldn't the vertices be static ? Only thing that would change then are the texcoords (+ a translate (mapPos AND tileSize) for smooth scrolling).
Then you could calculate all the vertices just once.
Quote this message in a reply
Member
Posts: 65
Joined: 2009.03
Post: #11
@devnote, the structure I am using is:

Code:
typedef struct _Quad2 {
    float tl_x, tl_y;
    float tr_x, tr_y;
    float bl_x, bl_y;
    float br_x, br_y;
} Quad2;

So its just a structure with 8 floats which hold the vertices for the quad to be drawn. I use the same structure to hold the texture coords as well.

@probchild, that is a good question. At the moment I am moving the tiles by changing their vertices positions when as you say, I could work out the vertices once to fill the screen, plus a little to handle tiles scrolling onto the screen, and then just move the tiles using a gltranslatef. Once a new set of tiles is needed I just change the texture coords being used. At least I think that is what you mean Smile

What I have done is change my Image class so that I can ask it just calculate the vertices and texture coords which can then be used either by the Image class when it renders, or I can grab those coordinates to load into arrays to then use inside glDrawnElements. My SpriteSheet class also makes use of those methods as well as it has an underlying image which holds the sprite sheet. I wonder how much would be saved by only calculating the vertices once?

The current tilemap tender method looks like this:
Code:
- (void)renderAtPoint:(CGPoint)point mapX:(int)mapX mapY:(int)mapY width:(int)width height:(int)height layer:(int)lid {
    
    Layer *layer = [layers objectAtIndex:lid];
    TileSet *tileSet = nil;
    int x = point.x;
    int y = point.y;
    int tileID;
    int currentQuad = 0;
    int textureName = -1;
    int currentTexture = -1;
    int currentTileSetIndex;
    
    glEnable(GL_TEXTURE_2D);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    
    // Loop through all the tile sets we have.  Moving through the map data and rendering all those
    // tiles which are from the current tile map will stop us switching between textures all the time
    for(int tileSetIndex=0; tileSetIndex < [tileSets count]; tileSetIndex++) {
        
        // Clear the tileSet ready for a new one
        tileSet = nil;
        
        // Loop through the rows of tiles to be rendered
        for(int mapTileY=mapY; mapTileY < (mapY + height); mapTileY++) {
            
            // Loop through tiles in each row
            for(int mapTileX=mapX; mapTileX < (mapX + width); mapTileX++) {
                                
                // If the requested X or Y coordinates are out of bounds then don't bother trying
                // to get a tile for that location or render anything, but still increment x and y
                // so that the drawing position is moved on screen.
                if(mapTileX < 0 || mapTileY < 0 || mapTileX > mapWidth || mapTileY > mapHeight) {
                    // If the currentTileSetIndex is -1 then no drawing is done for this tile
                    currentTileSetIndex = -1;
                } else {
                    // We are within bounds so get the tile info
                    currentTileSetIndex = [layer getTileSetIDAtX:mapTileX y:mapTileY];
                }

                // If the tileSetIndex is not the same as the current one then either the tile comes from a different
                // tile set or we have set it to -1 above as we are trying to get a tile outside the bounds of the
                // tilemap
                if(tileSetIndex == currentTileSetIndex) {
                    // If the tileset has not already been defined then define it and get the tileset name
                    if(!tileSet) {
                        tileSet = [tileSets objectAtIndex:tileSetIndex];
                        textureName = (int)[[[[tileSet tiles] image] texture] name];
                    }
                    
                    // Only bind the texture if its changed since last time.  This will cause the texture to only be bound
                    // once for each new tileset used as we are looping through all tiles per tileset
                    if(textureName != currentTexture) {
                        currentTexture = textureName;
                        glBindTexture(GL_TEXTURE_2D, textureName);
                    }
                    
                    // Get the texture coordinates and quad coordinates for the current tile and add them the to vertex arrays
                    // we are going to use to render the tile map to the screen in one glDrawElements call
                    tileID = [layer getTileIDAtX:mapTileX y:mapTileY];
                    texCoords[currentQuad] = *[[tileSet tiles] getTextureCoordsForSpriteAtX:[tileSet getTileX:tileID] y:[tileSet getTileY:tileID]];
                    vertices[currentQuad] = *[[tileSet tiles] getVerticesForSpriteAtX:mapTileX y:mapTileY point:CGPointMake(x, y) centerOfImage:NO];
                    currentQuad++;
                }
                
                // Move the x coord for the next tile to be drawn
                x += tileWidth;            
            }
            // Move the y coord for the next row and reset x
            y -= tileHeight;
            x = point.x;
        }
        
        glVertexPointer(2, GL_FLOAT, 0, vertices);
        glTexCoordPointer(2, GL_FLOAT, 0, texCoords);
        glDrawElements(GL_TRIANGLES, currentQuad*6, GL_UNSIGNED_SHORT, indices);
        currentQuad = 0;
        
        // Get ready to loop through any other tilesets we have on this layer
        x = point.x;
        y = point.y;
    }
    glDisable(GL_TEXTURE_2D);
    glDisable(GL_BLEND);
}

The methods in my Image class look like:
Code:
- (void)calculateTexCoordsAtOffset:(CGPoint)offsetPoint subImageWidth:(GLuint)subImageWidth subImageHeight:(GLuint)subImageHeight {
    // Calculate the texture coordinates using the offset point from which to start the image and then using the width and height
    // passed in

    if(!flipHorizontally && !flipVertically) {
        texCoords[0].br_x = texWidthRatio * subImageWidth + (texWidthRatio * offsetPoint.x);
        texCoords[0].br_y = texHeightRatio * offsetPoint.y;
        
        texCoords[0].tr_x = texWidthRatio * subImageWidth + (texWidthRatio * offsetPoint.x);
        texCoords[0].tr_y = texHeightRatio * subImageHeight + (texHeightRatio * offsetPoint.y);
        
        texCoords[0].bl_x = texWidthRatio * offsetPoint.x;
        texCoords[0].bl_y = texHeightRatio * offsetPoint.y;
        
        texCoords[0].tl_x = texWidthRatio * offsetPoint.x;
        texCoords[0].tl_y = texHeightRatio * subImageHeight + (texHeightRatio * offsetPoint.y);
        return;
    }
    
    if(flipVertically && flipHorizontally) {
        texCoords[0].tl_x = texWidthRatio * subImageWidth + (texWidthRatio * offsetPoint.x);
        texCoords[0].tl_y = texHeightRatio * offsetPoint.y;
        
        texCoords[0].bl_x = texWidthRatio * subImageWidth + (texWidthRatio * offsetPoint.x);
        texCoords[0].bl_y = texHeightRatio * subImageHeight + (texHeightRatio * offsetPoint.y);
        
        texCoords[0].tr_x = texWidthRatio * offsetPoint.x;
        texCoords[0].tr_y = texHeightRatio * offsetPoint.y;
        
        texCoords[0].br_x = texWidthRatio * offsetPoint.x;
        texCoords[0].br_y = texHeightRatio * subImageHeight + (texHeightRatio * offsetPoint.y);
        return;
    }
    
    if(flipHorizontally) {
        texCoords[0].bl_x = texWidthRatio * subImageWidth + (texWidthRatio * offsetPoint.x);
        texCoords[0].bl_y = texHeightRatio * offsetPoint.y;
    
        texCoords[0].tl_x = texWidthRatio * subImageWidth + (texWidthRatio * offsetPoint.x);
        texCoords[0].tl_y = texHeightRatio * subImageHeight + (texHeightRatio * offsetPoint.y);
    
        texCoords[0].br_x = texWidthRatio * offsetPoint.x;
        texCoords[0].br_y = texHeightRatio * offsetPoint.y;
    
        texCoords[0].tr_x = texWidthRatio * offsetPoint.x;
        texCoords[0].tr_y = texHeightRatio * subImageHeight + (texHeightRatio * offsetPoint.y);
        return;
    }
    
    if(flipVertically) {
        texCoords[0].tr_x = texWidthRatio * subImageWidth + (texWidthRatio * offsetPoint.x);
        texCoords[0].tr_y = texHeightRatio * offsetPoint.y;
        
        texCoords[0].br_x = texWidthRatio * subImageWidth + (texWidthRatio * offsetPoint.x);
        texCoords[0].br_y = texHeightRatio * subImageHeight + (texHeightRatio * offsetPoint.y);
        
        texCoords[0].tl_x = texWidthRatio * offsetPoint.x;
        texCoords[0].tl_y = texHeightRatio * offsetPoint.y;
        
        texCoords[0].bl_x = texWidthRatio * offsetPoint.x;
        texCoords[0].bl_y = texHeightRatio * subImageHeight + (texHeightRatio * offsetPoint.y);
        return;
    }
    
    if(flipVertically && flipHorizontally) {
        texCoords[0].tl_x = texWidthRatio * subImageWidth + (texWidthRatio * offsetPoint.x);
        texCoords[0].tl_y = texHeightRatio * offsetPoint.y;
        
        texCoords[0].bl_x = texWidthRatio * subImageWidth + (texWidthRatio * offsetPoint.x);
        texCoords[0].bl_y = texHeightRatio * subImageHeight + (texHeightRatio * offsetPoint.y);
        
        texCoords[0].tr_x = texWidthRatio * offsetPoint.x;
        texCoords[0].tr_y = texHeightRatio * offsetPoint.y;
        
        texCoords[0].br_x = texWidthRatio * offsetPoint.x;
        texCoords[0].br_y = texHeightRatio * subImageHeight + (texHeightRatio * offsetPoint.y);
        return;
    }
}


- (void)calculateVerticesAtPoint:(CGPoint)point subImageWidth:(GLuint)subImageWidth subImageHeight:(GLuint)subImageHeight centerOfImage:(BOOL)center {
    
    // Calculate the width and the height of the quad using the current image scale and the width and height
    // of the image we are going to render
    GLfloat quadWidth = subImageWidth * scale;
    GLfloat quadHeight = subImageHeight * scale;
    
    // Define the vertices for each corner of the quad which is going to contain our image.
    // We calculate the size of the quad to match the size of the subimage which has been defined.
    // If center is true, then make sure the point provided is in the center of the image else it will be
    // the bottom left hand corner of the image
    if(center) {
        vertices[0].br_x = point.x + quadWidth / 2;
        vertices[0].br_y = point.y + quadHeight / 2;
        
        vertices[0].tr_x = point.x + quadWidth / 2;
        vertices[0].tr_y = point.y + -quadHeight / 2;
        
        vertices[0].bl_x = point.x + -quadWidth / 2;
        vertices[0].bl_y = point.y + quadHeight / 2;
        
        vertices[0].tl_x = point.x + -quadWidth / 2;
        vertices[0].tl_y = point.y + -quadHeight / 2;
    } else {
        vertices[0].br_x = point.x + quadWidth;
        vertices[0].br_y = point.y + quadHeight;
        
        vertices[0].tr_x = point.x + quadWidth;
        vertices[0].tr_y = point.y;
        
        vertices[0].bl_x = point.x;
        vertices[0].bl_y = point.y + quadHeight;
        
        vertices[0].tl_x = point.x;
        vertices[0].tl_y = point.y;
    }                
}

I've got a dirty hack in there at the moment to handle flipping the textures which I'll tidy up later Smile

vertices and texCoords are arrays or Quad2 structures.


Thanks for the comments Grin

MikeD
Quote this message in a reply
Member
Posts: 45
Joined: 2008.04
Post: #12
If your [tileSets count] is large then this additional outer loop using tileSetIndex may begin to hurt. You can eliminate this loop by maintaining a renderer (indicies) for each texture and defering all the drawing to the end. Try the following psuedo-code:
Code:
const int maxTiles = [tileSets count];
struct {
    GLushort indicies[maxIndices];
    int nIndices;
} renderer[maxTiles]

// reset renderers
for(int i = 0; i < maxTiles; i++) renderer[i].nIndices = 0;

// generate verticies and texcoords
for(int mapTileY=mapY; mapTileY < (mapY + height); mapTileY++) {
    for(int mapTileX=mapX; mapTileX < (mapX + width); mapTileX++) {
        currentTileSetIndex = [layer getTileSetIDAtX:mapTileX y:mapTileY];

        ... // generate verticies & texcoords
        
        renderer[currentTileSetIndex].indices = ... // append the two triangle indicies based on your vertices
        renderer[currentTileSetIndex].nIndices += 6;
    }
}

// all rendering happens at the end
glVertexPointer(2, GL_FLOAT, 0, vertices);
glTexCoordPointer(2, GL_FLOAT, 0, texCoords);
for(int i = 0; i < maxTiles; i++) if(renderer[i].nIndices != 0) {
    tileSet = [tileSets objectAtIndex:i];
    textureName = (int)[[[[tileSet tiles] image] texture] name];
    glBindTexture(GL_TEXTURE_2D, textureName);
    glDrawElements(GL_TRIANGLES, renderer[i].nIndices, GL_UNSIGNED_SHORT, renderer[i].indicies);
}

I also find this style makes the code look 'cleaner'.
Quote this message in a reply
Member
Posts: 65
Joined: 2009.03
Post: #13
Thanks aBabyRabbit

That approach looks really interesting and a lot cleaner as you say. I like the idea of being able to loop around for the rendering taking the vertices etc that I need for each tileset I'm processing, that is a great idea.

I'm going to go through your pseudo code and work it into mine to see how things run. I will also add a little check in the render loop to see if the texture has changed and only perform a bind if it has.

Thanks for the great feedback.

MikeD
Quote this message in a reply
Member
Posts: 45
Joined: 2008.04
Post: #14
MikeD Wrote:I'm going to go through your pseudo code and work it into mine to see how things run. I will also add a little check in the render loop to see if the texture has changed and only perform a bind if it has.
MikeD

Ah - yes, my pseudo-code should be changed to have one renderer per texture, rather than one per tileset...
Quote this message in a reply
Member
Posts: 166
Joined: 2009.04
Post: #15
MikeD Wrote:Hi all

I've been learning Obj-C and OpenGL for the last month or so. To get to grips with things I've been creating my own classes to handle rendering images (Texture2D for the initial texture loading), scaling, rotating, Sprite Sheets, Bitmap Fonts, Animation, Tilemap etc.

I've got all this stuff working ok but to me I just don't think it is as fast as it could be as I see things slow down when for instance the screen is full of tile map tiles but then is nice and smooth when there is maybe only half the screen filled. My background is Java and so I've tackled my game code using straight Obj-C with classes, class instances and methods etc.

I've been reading through the excellent info on this forum for a while and it would seem that using Obj-C for all this adds an overhead with the creation of objects and message despatch etc which is bad on the iPhone with limited resources. I want to make my game code as fast as possible, doesn't everyone Smile but I'm not sure from my current code where the best place to start is and where I should focus.

I've used info from the forums to try and optimize my gameloop and instruments is showing that most time is being spent in the game loop and render code, so things like should I draw all quads in one hit rather than separate as they are now etc, although I'm not sure how to tackle that etc.

I'd be happy to email my entire test project to someone if they would be willing to take a quick look and tell me where I should spend my next dev hours as I don't want to focus on an area, change it so using inline structs and functions only to find I get a 0.1% improvement Smile that would be a real bummer.

If anyone is up for checking out my project or just has advice it would all be very much appreciated. On the advice side, I don't have any C or C++ experience so I'm not sure if I move from classes etc how to structure my code and project structure so again, any advice for this noob would be great.

Cheers

MikeD

I have everything written in C++ running on both Windows ( using Img Tech SDK emulator) and iPhone.
There is a small system specific layer ( mostly dealing with setup and event handling code ) which is written in Objective C++ , beyond that I don't even bother with Objective C.

You gain nothing from using it, there is no reason having your generic engine classes ( scene , geometry management) using Objective C - loop for loop or statement for statement , Objective C won't be any slower than your typical C++ code, but as soon as you move into frameworks, containers and other stuff, std or boost or your own well designed C++ code will just about always outperform Apple framework containers ( sometimes by a big margin).

There is a wealth of third party code written in C++ , a large community with tons of useful examples etc .. different implementation of std libraries often tailored for specific environment (small memory footprint etc), compared to all that the Objective C community is a relative fringe with not much in terms of options . Of course you can always interface with C++ classes but this approach is rather cumbersome and introduces artificial firewalls where there shouldn’t be any … so why bother.

Having said that, in the end you will be best served using language you are most comfortable with (within reason of course, you don’t want to write your code using javascript )
Quote this message in a reply
Post Reply 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  any tips for making CG render faster? aerospaceman 7 4,472 May 3, 2010 07:00 AM
Last Post: Rasterman
  2nd Generation iPod Touch Faster than iPhone PowerMacX 0 2,106 Nov 23, 2008 10:08 AM
Last Post: PowerMacX