Using RenderTexture to render one sprite multiple times

But in fact, a video is nothing more than rendering a texture each frame. If you are reusing that frame texture like an atlas, it is basically the same.

You can do instanced rendering, but you have to code your own solution. There is no support for that in the engine.

This seems like the easiest way to modify the existing code for a quick test, but the only sprite I see is the last one. Is this in your ::draw() method for your layer?

Because it really isn’t a tile based world. I used that as an example to make it clear what I was trying to do. Then as each person kept providing answers that didn’t work for my particular situation (3rd party video codec playback into a sprite) I had to keep providing more details than I thought I’d need for a response.

I’ve been writing games since the days of the C64 and Vic20, but what we are doing is NOT like how they handled it at all. Still, nice to see another long-time programmer on this site :slight_smile:

1 Like

@RogueProgrammer

It’s in init of scene in my code. Here’s the full init function, if you want to give it a shot:

 // on "init" you need to initialize your instance
 bool newproject::init()
 {
     if ( !Layer::init() )
     {
         return false;
     }
     Size visibleSize = Director::getInstance()->getVisibleSize();
     Vec2 origin = Director::getInstance()->getVisibleOrigin();
 	RenderTexture* renderTexture = RenderTexture::create(512, 512);
     auto sprite = Sprite::create("newproject.png");
     // position the sprite on the center of the screen
 	renderTexture->setPosition(Vec2(visibleSize.width * 0.5f, visibleSize.height / 2 + origin.y));
 	addChild(renderTexture, 100);
 	renderTexture->setAutoDraw(false);
 	renderTexture->beginWithClear(1.0f, 0.0f, 0.0f, 0.0f);
 	sprite->setPosition(Vec2(visibleSize.width * 0.5f, visibleSize.height / 2 + origin.y));
 	sprite->visit();
 	renderTexture->end();
 	Director::getInstance()->getRenderer()->render();
 	renderTexture->begin();
 	sprite->setPosition(Vec2(visibleSize.width * 0.25f, visibleSize.height * 0.25f + origin.y));
 	sprite->visit();
 	renderTexture->end();
 	Director::getInstance()->getRenderer()->render();
     return true;
 }
1 Like

Unfortunately that doesn’t help since the video is updating the sprites each frame. So I tried doing something similar to what you are doing, but in the update() function. I still only see one sprite being drawn instead of the 5x4 array that should be shown.

It was worth a shot. We can switch to sprite sheets instead of the videos, but then I will have to scale them down to fit them into reasonable sized texture sheets. This means poorer quality animations :frowning:

I figured out a solution that doesn’t require a major rewrite of my code. I don’t know what the performance will be like on mobile devices, but the Windows build runs at 60 fps without any issues. Thanks to everyone that helped get me in the right direction.

Thank you very much, I will learn about TextureAtlas.

And thank you all for your food for thought. It is thanks to such topics, I love cocos` community.

Raster line interrupt for the win :wink:

I assume your assets are raster graphics and not vector graphics?

IQD
What games did you write?

I should say
RogueProgrammer
What is your C64 provenance?

I ported Crisis Mountain to the Vic20, and worked on “Save New York” (just some sound stuff) and “Spitball” for the C64. At Epyx I worked on “Rescue on Fractalus” and “Koronis Rift” for the Tandy Coco, and California Games on the PC, World Games and Winter Games for the Apple 2e.

You can see my more recent credits on Moby Games:

None. Just a few in development.

This solution works. I am wondering what the cost will be when used inside a for loop for example.

@iQD

You said we could use a TextureAtlas to basically use several instances of a node with different properties (position, etc) but with the same texture that would not incur in unnecessary openGL calls. Can you provide any simple example please?

I am currently using the solution suggested by @KJS but I am considering trying multiple Nodes if the performance is significantly better.

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