Using RenderTexture to render one sprite multiple times

In 2,x there was batch node, which you had to feed with nodes/sprites, to group draw calls together. With version 3.x, there was auto batching introduced, so you don’t need to use a batch node anymore.

Basically the steps are as following:

  • Step 1: Create the asset for SpriteA(spriteA.png) and
    SpriteB(spriteB.png).
  • Step 2: Pack those assets together into an atlas(there are free tools
    and commercial tools available). The atlas is just one big asset with a data file, which represents the offsets of the single assets into the texture.
  • Step 3: Load in your atlas. cocos2d-x automatically loads the
    atlas texture and the data file into the sprite cache and will
    bind it as one single OpenGL texture.
  • Step 4: Use the single texture assets by calling it by their name.
    cocos2d-x will read the assets as a sub-texture to the atlas by using
    the offsets defined in the data file as UV coordinates.

This way OpenGL only has to bind the atlas and can group together the draw calls, as the texture state for different sprites is never changed(same texture for all sprites, but with different UV offsets).
The different sprites are created by triangle strips/degenerated strips.

With that setup, it will bind the atlas and use vertex pointers and texture pointers for drawing all those sprites in one draw call.

Step 1 and Step 2 is up to you :smile: E.g. spriteA.png and spriteB.png. Pack them into an atlas called myAtlas(the available packers support the cocos2d-x format and will generate myAtlas.png and myAtlas.plist(of course you can also pack to other formats like pvr).
Step 3:

// Get the SpriteFrameCache singleton
cocos2d::SpriteFrameCache *cache = SpriteFrameCache::getInstance();

// add the atlas to the SpriteFrameCache
cache->addSpriteFramesWithFile("myAtlas.plist");

// create the sprites form the atlas
// the string you pass to SpriteFrameCache, is the string of your asset you added to the atlas:
// spriteA.png and spriteB.png -> you can of course strip the suffix; we assume that we did that
Sprite *mySpriteA = Sprite::createWithSpriteFrameName("spriteA");
Sprite *mySpriteB = Sprite::createWithSpriteFrameName("spriteB");

// add them as childs
addChild(mySpriteA);
addChild(mySpriteB);

Now the two sprites will be drawn in one draw call, as they share the same texture(atlas), but use different UV coordinates.
Of course the same applies, if you have multiple instances of the same node/sprite with different properties like position or scale.

You can only group together draw calls, it the render state is not changed. E.g. different shaders require separate draw calls.

Nice reads about atlases:
http://www.gamasutra.com/view/feature/2530/practical_texture_atlases.php?print=1

2 Likes

Awesome, that was a great explanation

Thanks. Hoped it’s easy understandable and comprehensible…

You can do director->setDisplayStats(true);. It will show you the draw calls in the left corner of your app screen.

May you could report back, which is more performant/useful for you: The render to texture approach of @KJS or just using an atlas. Would be nice to see some numbers :smiley:

@iQD @KJS

So, I have been thinking about this and I don’t really like neither of the solutions presented. Doing that begin(), end(), render() loop is super slow on Android, but having to create several dummy nodes just doesn’t feel like a good solution either.

I have created a new topic illustrating the cause of the problem for cocos weird renderer behaviour and how to improve (http://discuss.cocos2d-x.org/t/sprite-and-multiple-visits-inside-render-texture/16513). What do you guys think?

Edit:

After having implemented the solution I proposed, I am able to get steady 60 fps on Android for my canvas painting component.

What did you do to handle this? We have a single Cocos2dx Sprite that needs to be drawn in multiple locations on the screen. Everything worked fine in 2.x, but we only ever see the last instance drawn in 3.x due to the change in the render code.

@miguel12345 Hi Miguel, can you by any chance post some code or maybe explain with more details on how you solved the issue? We really need this for some time now (we are still using v2 because of this). Thanks

You just need remove RenderTextture caches, it works. Here is the code:

Director::getInstance()->getTextureCache()->removeAllTextures();

OR just remove a specific Texture using:
Director::getInstance()->getTextureCache()->removeXXXXX