Using RenderTexture to render one sprite multiple times

I have an app that has a RenderTexture used for rendering some tiles multiple times at different positions. This worked fine in Cocos2d-x 2.x, but with the new renderer in 3.x it no longer works. Basically I was loading 1 sprite, creating a render texture, then doing something like:

renderTexture->beginWithClear( 0,0,0,0 );
sprite1->setPosition( 100, 100 );
sprite1->visit();
sprite1->setPosition( 200, 100 );
sprite1->visit();
// etc
renderTexture->end();

Think of this as a very large tiled world where any given tile may be placed at any x,y in the world. I realize I could just create a sprite for every tile in the level, but since 90% of them are off screen at any given moment I was trying to be more efficient than adding a lot of items to the Node list.

I’ve modified RenderTextureTest to add two new tests. RenderTextureTestNode2 just creates 20 of the same sprite, and sets their positions in the update() func, letting the render texture use the “autoDraw” feature. The second test I added is RenderTextureTestNode3. This creates a single sprite instance and tries to repeatedly visit() it at each position it should be shown in. This approach worked fine in Cocos2d-x 2.x, but no longer works. I am guessing there is a render state I need to set/save for this approach to work with Cocos2d-x 3.x.

Can anyone enlighten me on how to solve this?

ThanksRenderTextureTest.zip (6.7 KB)

2 Likes

Hi
Unfortunately, I can not help you, since I’m not quite good at the cocos. But I had a similar task, and search the forum showed that some other people also want to do the same.
While I know that openGL allow displaying the texture on the same object multiple times (as on a chessboard).
So unfortunately I have not found an answer on the forum. It would be great to add support for such textures in the cocos in the future.

I had exactly the same problem, and choosed the easy path: just create a different Sprite for each visit() call.

I believe it’s due to how the new Renderer works: actual draw calls are done later, so it will keep the properties of the Sprite and do all the actual visit() later on, and that’s why your Sprite is always drawn at the same place.

On a performance note, the overhead is marginal: the Texture used is going to be the same, and I would bet that case is optimized so that there is only one OpenGL draw call (but if you are worried about it, check the source code, I’m not 100% sure).

Hi,
Try this way, works for me

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();
3 Likes

If your world is tiled based, why are you not using the tile map feature? It implements the approach I described below. If you want to know which one(naive or optimized), you have to look into the source.

Why?
You don’t need to create a sprite for every tile in your level, just for the tiles presented on-screen. As you are scrolling through the scene, you are moving the on-screen tiles around and streaming in/updating only the border (off-screen tiles) around the screen.

Imagine your world consists of 160x90 tiles, which is 14400 tiles aka sprites, 10x10 pixels each . Those are always presented on the screen.

The naive approach has some border around the screen, with the tiles updating at scrolling the screen.
So the overhead in the invisibility check is 160+90+160+90, which is 500 tiles or 3.48%.

A more optimized approach would be swapping over the long and the short boarder. The overhead here would be 160+90 , which is 250 tiles or 1.74%.

Those numbers are all negligible, and this approach is done since the times of the C64 ;).

Just use an atlas, so you are only relying on one texture. OpenGL will bind it once, and doing an UV offset read into the texture. Only the data structures to hold the sub-texture(Node) will be duplicated.

Only if you are using an atlas. If you are using the same texture, OpenGL will bind it again…

If you are interested in how to achieve draw calls up to a million, here is an interesting read:

1 Like

Unfortunately, our situation is a bit more complicated than the trivial example I gave. These aren’t single frame sprites, nor even animations using texture sheets. These are actually video playback sprites. So loading multiple instances of each video would be too expensive in memory and performance.

So it seems we cannot use the video animations with Cocos2d-x 3.x :frowning:

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.