• Runtimes
  • My solution for importing pvr.ccz with PVRTC2 in cocos2d-x

Just try to share my experience here in case anyone is interested.

As we know, .pvr or .pvr.ccz file is much smaller than png, and Texture Packer actually supports exporting for Spine.

However, even though the pvr file is smaller, the textures take the same size as PNG after being applied in your game. They are just smaller on disk anyway.

TexturePacker has an option to set pixel format to PVRTC2/PVRTC4, which provides kind of a lossy compressed texture and takes much less memory in runtime. Unfortunately, if you set the pixel format to PVRTC2/4 and try to export, it's going to say the format is not supported for Spine (not supported for libgdx actually).

Ok, I'm not doing anything fancy here, just need to modify a few lines in _spAtlasPage_createTexture to load PVRTC2/4 texture from SpriteFrameCache install of just addImage() :

Texture2D* texture = NULL;

SpriteFrame* pFrame = SpriteFrameCache::getInstance()->getSpriteFrameByName(path);
if (pFrame)
{
    texture = pFrame->getTexture();
}
else
{
    texture = Director::getInstance()->getTextureCache()->addImage(path);
    
}

texture->retain();
self->rendererObject = texture;
self->width = texture->getPixelsWide();
self->height = texture->getPixelsHigh();

In order for this to work, we need to get the sprite frames ready before creating skeleton, and those frames should be from the .pvr.ccz files created by TexturePacker. Here are my steps to create the "identical" pvr.ccz files from the .png files form Spine.

  1. Just export .json, .atlas, and .png files as usual from spine. MAKE SURE SETTING PAGE WITH/HEIGHT to 512/512 (both max and min), and check "Square" "Premultiply alpha". So we can make sure the exported PNG files are 512x512

  2. In TexturePacker, add the path for your .png files. MAKE SURE the relative path is the same as your .json/.atlas files.

  3. In TexturePacker, set the following:
    Data format: cocos2d
    Texture Format: PVR+zlib
    Premultiply Alpha: yes
    Pixel format: PVRTC2 or PVRTC4
    Fixed Size W/H: 1024 (so it will contain exactly 1 atlas page)
    Forced Squared: yes
    Multipack: yes
    Allow rotation: NO
    Trim Mode: NO

ok, then export

  1. in the genrated .plist file, make sure the path for the sprite frames are the same path passed to _spAtlasPage_createTexture (spAtlasPage* self, const char* path). If not, change the path in your texturepacker and repeat.
Related Discussions
...

I'm not sure exactly what SpriteFrameCache is? Isn't there a way to get the PVRTC2/4 textures into the texture cache?

I'll ask Andreas to allow PVRTC for Texture Packer Pro Spine export.

The problem why TexturePacker does not write PVRTC for spine / libgdx is that I don't know what to write in the "format" field of the .atlas file....
I did not see any definition for PVRTC in the Java definitions. Maybe there are some newer values I did not know about when we created the exporter?

Ah, right. The format field is used to tell whatever loads the texture which format to use for the image file once loaded into GPU memory. The format field is not actually read by most Spine runtimes (only libgdx AFAIK), so you can put whatever you like there. 😃

Great, so I'm supposing the future TexturePacker can export PVRTC format for Spine and I don't need to modify the spine runtime code? Thanks guys :yes: :yes:

likexx, before I make your SpriteFrameCache changes can you answer my questions above?

@Nate, SpriteFrameCache is very similar to atlas to save texture information in .plist (which includes the texture position, offset, etc.). Usually we put image frames (for frame animation in most cases, but we can use it for whatever purpose anyway) into a big texture file, and generate a plist to mark the frames locations (exactly the same as in TexturePacker, or the atlas in Spine).

After that, we can call SpriteFrameCache::addSpriteFramesWithFile(const std::string& plist, const std::string& textureFileName) to preload the image frames. The frames are identified by the path .plist, some like "path/subpath/image1.png" "path/subpath/image2.png"... Then we can retrieve the frames later use

SpriteFrameCache::getSpriteFrame("path/subpath/image1.png"); // the filename here doesn't mean the file really exists, it's just the key for the frame and it's defined in the .plist when the frame texture file is generated.

loading PVRTC2/4 is not an issue since cocos2d-x supports it natively. When we call:
SpriteFrameCache::addSpriteFramesWithFile("test.plist", "test.pvr.ccz"); // the test.pvr.ccz could be in PVRTC format and cocos2d-x is fine with it).

Thanks. It seems you aren't using the frames (regions defined in the plist) feature of the SpriteFrameCache and are only using it to get a texture. This leads me to wonder why you don't load the texture thru the texture cache?

@Nate, I followed your suggestion and it works great 😃

Just tried loading pvr.ccz directly through TextureCache and it works 🙂

Spine side doesn't need to do anything. What I did is just loading the TexturePacker generated pvr texture files in sequence and treat them as .png files and set the key as what spine atlas defines. The conversion is kind like:

  1. Spine generated PNGs
  2. Convert to 1:1 mapping pvr files using TexturePacker, make sure each PNG has a corresponding PVR file
  3. loading the pvr files in TextureCache of cocos2d, and set their keys back to the same path of the PNG files. so when spine use addImage to load the textures form atlas, it will look for the PNG filenames in TextureCache, which are the keys we set.

Talk is cheap, let me show the code:

    for(int i=1;;i++)
    {
       char pvrPath[256] = {0};
        sprintf(pvrPath, "images/skin/%s/skeleton%d.pvr.ccz", pName, i);   // this is where the pvr files exist, and replaced all the original PNG files
        if (!FileUtils::getInstance()->isFileExist(pvrPath))
        {
            break;
        }
        
        Image* image = new Image();
        bool isOK = image->initWithImageFile(pvrPath);  // TextureCache cannot directly load a file and set the key. We must create an Image object first.
        if (! isOK) {
            log("cannot load pvr texture");
            return nullptr;
        }
        
        char key[256] = {0};
        if (i == 1)
        {
            sprintf(key, "images/skin/%s/skeleton.png", pName);   // set the texture key back to the PNG filename. Note the 1st texture file created in Spine doesn't have the sequence number "1", while TexturePacker starts from "1"
            
        }
        else
        {
            sprintf(key, "images/skin/%s/skeleton%d.png", pName, i);
            
        }
        
        Director::getInstance()->getTextureCache()->addImage(image, key); 
        CC_SAFE_RELEASE(image);
        
    }

Thanks for the reminder. It's a more elegant solution 🙂