Tampilkan postingan dengan label Cocos2D. Tampilkan semua postingan
Tampilkan postingan dengan label Cocos2D. Tampilkan semua postingan

Comparison of memory footprints of different textures available in Cocos2d

So I have been fiddling with textures in Cocos2d to find out what image format produces the least memory foot print while not degrading quality in a very visible manner.

So I created an original PNG image, and then converted it to different image formats using Texture Packer. I then created an XCode project to load those images and print out the memory footprint. The XCode project can be fetched from: https://github.com/rumahfirman/texture-format

First, I will list image format from biggest memory foot print to smallest memory foot print (the original image is original.png which is RGBA 8888):


"original.png" file size=5 KB rc=2 id=2 1024 x 1024 @ 32 bpp = 4096 KB 
"rgba4444.png" file size=4 KB rc=2 id=3 1024 x 1024 @ 32 bpp = 4096 KB
"rgba4444fsa.png" file size=131 KB rc=2 id=4 1024 x 1024 @ 32 bpp = 4096 KB
"rgb888.pvr.ccz" file size=2 KB rc=2 id=8 640 x 960 @ 32 bpp = 2400 KB
"rgba4444fsa.pvr.ccz" file size=90 KB rc=2 id=9 1024 x 1024 @ 16 bpp = 2048 KB
"rgba4444fs.pvr.ccz" file size=16 KB rc=2 id=10 1024 x 1024 @ 16 bpp = 2048 KB
"rgba5551fs.pvr.ccz" file size=50 KB rc=2 id=11 1024 x 1024 @ 16 bpp = 2048 KB
"rgba5551fsa.pvr.ccz" file size=120 KB rc=2 id=12 1024 x 1024 @ 16 bpp = 2048 KB
"rgb565.pvr.ccz" file size=140 KB rc=2 id=7 640 x 960 @ 16 bpp = 1200 KB
"pvrtc4.pvr.ccz" file size=20 KB rc=2 id=6 1024 x 1024 @ 4 bpp = 512 KB
"pvrtc2.pvr.ccz" file size=16 KB rc=2 id=5 1024 x 1024 @ 2 bpp = 256 KB

From the above data, it can be seen that file size does not correlate to memory footprint. The smallest file size (which is 4 KB) produces the biggest memory foot print (4096 KB).

It is also obvious that the memory footprint of RGBA 4444 uncompressed PNG is the same as the original image which is RGBA 8888. So RGBA 4444 does not produce memory saving, yet result in quality degradation.

In the above file name, the suffix fs stands for Floyd Steinberg dithering. FSA stands for Floyd Steinberg Alpha dithering.

Dithering basically means to smoothen your image. If your original image contains color gradation, dithering is especially important. Reducing image quality often results in abrupt color changes while in the original image, the color gradually changes. This can be rectified by dithering and FS works well.

If your original image contains alpha channel, FSA also dithers the alpha channel, creating "dotted artifacts", which is especially visible if your image z-order is higher than some other image, i.e there is an image located behind the dithered image. To rectify this, choose FS without A, i.e dither all channels in the image except the alpha channel.

Now let's talk about quality. From best to worse:
Original
RGB 888
RGBA 5551
RGBA 4444
RGB 565

Rule of thumb: if possible, always use RGB 565 for background images.

PVR Texture Format, Texture Cache

I have been looking around for tools to make my sprite sheet smaller. People say that PVR textures are the way to go.

So the graphics processor on iPhone is a design licensed from Power VR  (http://en.wikipedia.org/wiki/PowerVR). PVR textures are in such a format that Power VR graphics chips can easily digest, resulting in much faster processing. And smaller file size.

Sometime ago I bought Vladu Bogdan's SpriteHelper and LevelHelper toolsuite. In SpriteHelper, there is an option to save the resulting sprite sheet into a PVR format. But the quality is so low (it's lossily compressed). I'm looking for a tool that enables me to reduce the size and memory footprint of my sprite sheets without the quality degradation (lossless compression or uncompressed).

I used Level Helper in just one project that needed it. It's ok. I use Sprite Helper quite a lot but recently with the introduction of the Gatekeeper for Mac, it has become increasingly a pain to use. Really. It just frustrates me how Sprite Helper gets stuck at the beach ball of death phase when starting up. How it fails to save the plist, how it won't add  multiple images at once, etc. I have just grown sick of it now.

Anyway, back to PVR.

XCode comes with a tool to convert sprite sheet into PVR texture, but just as SpriteHelper, it uses lossy compression. The tool can be found in dir: /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/texturetool

It's called texturetool.

Another tool: TexturePacker. I read people praising it, but it's paid app. Haven't tried.

Then there is this tool from Power VR themselves: PVRTexTool. It comes with both GU and CL interfaces. But I have not managed to get it working. I mean, I can't get it to convert my PNG files (generated by Sprite Helper) into pvr format.

So I fall back to texturetool. I created a small bash script to convert all PNG files in a directory into PVR format. Here it is:


#!/bin/bash

for file in `find ./ -name "*png"`
do
texturetool  -e PVRTC --bits-per-pixel-4 -f PVR -m  -o $file.pvr $file
done


The image quality is lower than the original PNG and in some cases the difference stands out. But I have not got much of a choice right now. Will tinker with PVRTexTool later when I got time.

I also learn about how sprite frame cache and texture cache work in Cocos2D. So basically texture cache is a dictionary with keys being the file/texture names and values the textures. One texture can (and should) contain a whole slew of images. Not just one. And you access each image by sprite frame cache.

So for instance you have image1.png, image2.png.... image50.png. You pack them into one big image using Sprite Helper or other tools. And then you have another file describing the position/coordinate of each image. This file is called the plist file. It's a dictionary with key being the individual image name (image1, etc) and value the coordinate of it in the big, packed image.

You create sprite frame cache from the plist (i.e you pass the plist name as the argument into sprite frame cache static methods). Then sprite frame cache will try to load the image with the same name as the plist into the texture cache. Sprite frame cache will expect the image to end with the png extension. If you have different image extension, such as pvr, you need to load the image manually into the texture  cache. Code snippet:


    CCSpriteFrameCache *frameCache = [CCSpriteFrameCache sharedSpriteFrameCache];
    NSString *spriteFrameName = @"myplistfile.plist";
    NSString *textureName = @"myspritesheet.pvr";
    CCTexture2D *texture = [[CCTextureCache sharedTextureCache] textureForKey:textureName];
    [frameCache addSpriteFramesWithFile:spriteFrameName texture:texture];




Objective C memory management

After a while finally (I think) I grasp how this memory management thing works in Objective C.

First about assign and retain.

When you declare properties using (retain) then the retain count will be incremented by one when you assign value to it.

@interface LayerA : CCLayer{

   CCSprite *sprite;
}

@property (retain) CCSprite *sprite;
@end

Now if in other classes you call:

LayerA.sprite = [CCSprite node];

The retain count of sprite in LayerA will be incremented by one and as such, it won't be released until you call:

LayerA.sprite = nil;

which will decrease the retain count by one. So everytime you assign a value to a retain property, you must have a matching nil assignment to it.

This can complicate manual memory management and I usually prefer to have assign instead of property.


@interface LayerA : CCLayer{

   CCSprite *sprite;
}

@property (assign) CCSprite *sprite;
@end

Now when you assign a value to the sprite property, the retain count will not be incremented.

Another case about memory management that I learnt the hard way was when passing argument to the class constructor.

When class A calls the constructor of class B and passes parameters, the parameters will be released when class A is released. So if class B caches those parameters, it is good only as long as class A lives. The constructor in class B needs to make deep copy of the parameter if class B wants to use the parameters in methods other than constructor.

Playing video with cocos2d


  1. Download cocos2d extension from: https://github.com/cocos2d/cocos2d-iphone-extensions
  2. Unzip, navigate into the directory: extensions -> CCVideoPlayer.
  3. Copy file CCVideoPlayer.h and CCVideoPlayer.m and the folder iOS. 
  4. Insert them anywhere in the project.
  5. In CCVideoPlayer.m, comment out the line #import "CCVideoPlayerImplMac.h"
  6. Done! To play video, simply do: [CCVideoPlayer playMovieWithFile:@"filename.mp4"]
  7. You can set a delegate to the CCVideoPlayer by implementing protocol: <CCVideoPlayerDelegate>
  8. The protocol will call method: 
    -(void) movieStartsPlaying
    when the movie starts
    and
    -(void) moviePlaybackFinished
    when the movie finishes.

Remove CCNode from parent after a sequence of actions

P.S:
Well, P here stands for Pre instead of Post, I guess. The following cocos2d discussion was started 3 years ago. Now I realize that there is a method: [CCNode removeFromParent]

So a useful post from cocos2d forum: http://www.cocos2d-iphone.org/forum/topic/981



I have some sprites and labels on the Layer. Before removing them they must do some actions (fade out for example). But after that I need to remove them from the layer with using [self removeChild:child cleanup:YES]. For that I create classes AutoCleaningSprite and AutoCleaningLabel which inherit Sprite and Label class and have new method:
- (void) removeFromParent{
CocosNode *parent=self.parent;
[parent removeChild:self cleanup:YES];
}
So for removing this objects after animation I used next actions:
[someAutoCleaningSprite runAction:
[Sequence actions:[FadeOut actionWithDuration:0.5],
[CallFunc actionWithTarget:someAutoCleaningSprite selector:@selector(removeFromParent)],nil]];
Is it correct method? Or may be there is something more simple way for removing CocosNode objects from the parent Object?
And I have another question - is it need to invoke [sprite release]; after [self removeChild:sprite cleanup:YES];?
Thanks..
Answer:


re:
(void) removeFromParent{
CocosNode *parent=self.parent;
[parent removeChild:self cleanup:YES];
}
This strategy will work and I've found it very useful on my projects but one thing anyone who tries it needs to be aware of is a really really really evil memory bug that gets caused unless your careful. First off, the solution:
CocosNode *parent=self.parent;
[self retain];
[parent removeChild:self cleanup:YES];
[self autorelease];
Huge hack I know, and there are cleaner ways to do this but what happens if you don't is total heisenbuggery. Background:
- The NSInvocation object associated with the CallFunc action wants to write a 'return value' to an address in the NSInvocation object ... even if your function doesn't return anything.
- When you call removeChild:self cleanup:YES your sprites ref count goes down by one
- If your sprites ref count just hit zero (as it probably will), instead of being released 'at some time in the future' its going to get deallocated NOW. Thank apple for that little bit of magic they do when removing objects from NSArrays
- That deallocation cascades to the Action and then the NSInvocation all before your function returns.
If you've got anything else going on (other threads for instance that may be allocating something ... I'm looking at you OpenFeint) the NSInvocation return is about to write on some memory that no longer belongs to it ... and you will likely not find out about it until way past too late ;)

Manual OpenGL draw on CCNode

Every point must be multiplied by     CC_CONTENT_SCALE_FACTOR();

Otherwise, the drawing will not be correctly placed on the screen!

Problem with Sprite Helper, Level Helper, Interlaced PNG

So....
I have this project in which I use @vladubogdan's Level Helper and Sprite Helper. Then I get this extremely annoying bug that I can't figure out the root:
If I run it on device the first time, the pictures are showing. Second time, not showing, third time showing, etc. WHAT HAS GONE WRONG?
Then I tried to clear the project and re build and I got this:


CopyPNGFile /Users/firman/Library/Developer/Xcode/DerivedData/Dotugov2-fhgqwfonumwjtachcdehbmsgrfdu/Build/Products/Debug-iphoneos/Dotugov2.app/timeselect-hd.png Dotugov2/Resources/Images/timeselect-hd.png
    cd /Users/firman/Documents/ios-project/Dotugov2
    setenv PATH "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin:/Applications/Xcode.app/Contents/Developer/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin"
    "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/PrivatePlugIns/iPhoneOS Build System Support.xcplugin/Contents/Resources/copypng" -compress "" /Users/firman/Documents/ios-project/Dotugov2/Dotugov2/Resources/Images/timeselect-hd.png /Users/firman/Library/Developer/Xcode/DerivedData/Dotugov2-fhgqwfonumwjtachcdehbmsgrfdu/Build/Products/Debug-iphoneos/Dotugov2.app/timeselect-hd.png


While reading /Users/firman/Documents/ios-project/Dotugov2/Dotugov2/Resources/Images/timeselect-hd.png pngcrush caught libpng error:
   \341\217


While reading /Users/firman/Documents/ios-project/Dotugov2/Dotugov2/Resources/Images/timeselect-hd.png pngcrush caught libpng error:


I googled and arrived here: 


It says that the such error is caused by interlaced PNG images. I tried to resave the PNG images to non interlaced but to no avail. I don't know what to do next...

Small notes

Some lessons I learnt the past few days:

Cocos2D: Don't load heavy resources in the init method. This will crash the app. Instead, load them in a scheduled method.
Box2D: Don't change scene while world is locked (when detecting and responding to touch). This will cause a memory leak. Instead, change the scene in an scheduled method.
Android: The method insert in SQLiteDatabase doesn't throw exception. It does throw exception and print the stack trace but it's internally handled by that method and the exception won't by caught by the try catch block. If you need to catch exception, use insertOrThrow instead.

Code snippet to pad numbers with zero:

        NSString *paddingFormat = [[NSString stringWithFormat:@"time\n%%0%dd:%%0%dd", 2, 2] retain];
        NSString *paddedNumber = [NSString stringWithFormat:paddingFormat, 0, 0];

Result:

time
00:00

Correcting gravity of Box2D for portrait mode


In the accelerometer function of HelloWorldLayer.mm the gravity is set with this line
b2Vec2 gravity( -accelY * 10, accelX * 10);
In order to simulate the desired effect in portrait mode, the line must be re-ordered to:
b2Vec2 gravity( accelX * 10, accelY * 10);