Sprite and multiple visits inside render texture

Hi,

With the new renderer, every time you call visit on, lets say, a sprite, even when inside a RenderTexture begin() / end() pair, what you are actually doing is changing the _quadCommand inside the sprite, which in turn is changing the quadCommand that is in renderer’s the render group, since it is storing a pointer to that quad command.
(See Using RenderTexture to render one sprite multiple times and RenderTexture visit problem for two examples of users having problems with their code in v3.x that worked in v2.x)

This makes it so that running the following example

_renderTarget->begin();
_sprite->setScale(4);
_sprite->setPosition(Point(10, 10));
_sprite->visit();
_sprite->setPosition(Point(50, 50));
_sprite->visit();
_sprite->setPosition(Point(250, 250));
_sprite->visit();
_renderTarget->end();

is pointless since you are just changing the underlying QuadCommand three times, making its last change the one that will actually count and be used in the rendering itself.

This only happens because a sprite uses only one quad command for everything.

So if you want to emulate the behavior in the code above in v3.x, you would have to create 3 Sprites, wasting both time and space to the developer and to the running program.

I am thinking of adding a new “visit” method inside Sprite, that submits a new QuadCommand to the renderer instead of changing the only one that it currently has. This way the code above works correctly (I have implemented it and tested it) and it just seems more natural and intuitive.

I was wondering what do you guys think of this approach, and what was the rationale behind the decision of just having one quad command inside the Sprite class.

Wouldn’t it just make more sense to have a vector of quad commands inside Sprite, that would be cleared after each render for example? This way, both the normal visit cycle case and the render texture case would work just fine

Something along these lines

In the header:

private
    std::vector<QuadCommand*> _quadCommands;

Inside draw

QuadCommand* quadCmd = new QuadCommand;
    quadCmd->init(_globalZOrder, _texture->getName(), getGLProgramState(), _blendFunc, &_quad, 1, transform);
    _quadCommands.push_back(quadCmd);
    renderer->addCommand(quadCmd);

with proper memory management etc of course

2 Likes

@miguel12345 Thanks for writing. Let me pass this along to some devs and get thoughts on this.

@miguel12345
Thanks for your suggestion.

The reason of current design is that, use reference for quad command can save memory copy and speed up performance, because drawing operation is done every frame.

The method you mentioned seems good for Sprite. But i think a common way to resolve this issue is to implement command pool. Command pool can be used in this situation, and can be used for any classes have the problem.

@zhangxm Yes you are right, this is not the implementation I would go for, I am just considering this at a higher level.

After some thinking I think I am able to explain better what the current problem is, what are the use scenarios and what we can do to improve.

First of all and just to make sure we are all in the same page this is the current code (cocos2d-x v3.2) for *Sprite::draw(Renderer renderer, const Mat4 &transform, uint32_t flags)

void Sprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
{
    // Don't do calculate the culling if the transform was not updated
    _insideBounds = (flags & FLAGS_TRANSFORM_DIRTY) ? renderer->checkVisibility(transform, _contentSize) : _insideBounds;

    if(_insideBounds)
    {
        _quadCommand.init(_globalZOrder, _texture->getName(), getGLProgramState(), _blendFunc, &_quad, 1, transform);
        renderer->addCommand(&_quadCommand);
    }
} 

I ignored the CC_SPRITE_DEBUG_DRAW because it is not useful for showing my point.

There are two scenarios where Sprite::visit (and therefore Sprite::draw ) will be called. The first one is inside the main loop and I will refer to it as Main Loop Visit from now on. On the Main Loop Visit scenario a Sprite’s visit method is called once per frame before the Renderer’s render method is called. If we were to represent the relation between the Sprite::draw and Renderer::render we would have the following

Sprite::draw(...)
Renderer::render(...)
Sprite::draw(...)
Renderer::render(...)
Sprite::draw(...)
Renderer::render(...)
...

As we can see, for this scenario we can be sure that at each Sprite::draw, our current quad command has already been rendered by the last Renderer::render so we can safely change it and submit again to the Renderer.

This scenario accounts for the majority of cases and the current Sprite::draw works perfectly fine here.

Our second scenario happens when we call multiple Sprite::visit (therefore Sprite::draw) between Renderer::renders. I will refer to it as Render Texture visit from now on because it happens more frequently when using RenderTextures.

In Render Texture visit scenario the relation between the Sprite::draw and Renderer::render could be represented as

Sprite::draw(...)
Sprite::draw(...) //n times
Renderer::render(...)
Sprite::draw(...)
Sprite::draw(...)  //y times
Renderer::render(...)
Sprite::draw(...)
Sprite::draw(...) //z times
Renderer::render(...)
...

As we can see, between each Renderer::render we could have more than one Sprite::draw. The problem here is that, since Sprite::draw changes it’s underlying command, it is submitting something to the Renderer’s queue that has already been submitted before. Not only this is not what the developer expects, it also incurs on unnecessary cpu cycles since we are adding the same command more than once to the same Vector which will make future sorts and traversals slower.

What can we do to solve this?

There is something we could potentially check in Sprite::Draw before changing the underlying quad command. We could check whether or not the command had already been executed in the current frame. As it currently stands this is not possible because there is no information inside RenderCommand (therefore QuadCommand) that tells us if that particular command has been executed by the Renderer in the current frame. If we had, let’s say, a variable name _executed inside RenderCommand that was initialized to false (and reset to false also on future init’s) and set to true after each Renderer::render we had the necessary information to make this decision.

We could have something like this inside Sprite::draw (pseudo-code)

void Sprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
{
    // Don't do calculate the culling if the transform was not updated
    _insideBounds = (flags & FLAGS_TRANSFORM_DIRTY) ? renderer->checkVisibility(transform, _contentSize) : _insideBounds;

    if(_insideBounds)
    {
     
        QuadCommand* commandToExecute = nullptr;
        if (_quadCommand.wasExecuted()) {
            commandToExecute = &_quadCommand;
        }
        else {
            commandToExecute = //get command from somewhere, pool whatever, or create it on the spot
        }
        
        commandToExecute->init(_globalZOrder, _texture->getName(), getGLProgramState(), _blendFunc, &_quad, 1, transform); //this would set _executed to false
        renderer->addCommand(commandToExecute);
    }
}

This behaviour would account for both scenarios and would keep the performance intact for the common and default Main Loop Visit scenario.

@miguel12345
It sounds good. We will consider it.

Is there any news about this? I really need this functionality (re-using a sprite to draw it at multiple positions/sizes) and can’t use other ways to do it without rewriting the whole app. Thanks

@zhangxm, i’m facing the same problem (re-using a sprite to draw it at multiple positions), is there any news about that ?

Any updates on this? We really need this !!!

Needing this. CUrrent implementation is not helpful for my case. I am trying to stretch a sprite (only a few pixels wide) to the full width of any screen. Scaling did not give good results (some weird alpha-ish results wiht halftransparent sprite!) so I am trying to tile the sprite as many times is needed to fit the full width, into a RenderTexture, but I ran into the exact same problem as stated here; it does not work because I guess only the last position from the last visit is used.

Why you don’t call

Director::getInstance()->getRenderer()->render();

after each visit? Using RenderTexture to render one sprite multiple times I have seen you wrote that

… begin(), end(), render() loop is super slow on Android, …

But I don’t understand how adding vector of quad commands fixes the performance. Is it all about stalling: http://blogs.msdn.com/b/shawnhar/archive/2008/04/14/stalling-the-pipeline.aspx ?

1 Like

This was the solution for me. I had a ui::Button nested in a listview and I was trying to draw it to a RenderTexture and the only way this would draw to the texture (if the button was already being drawn) was if I called render before and after.

Thanks @naghekyan

Here’s the code to do it, for everyone else, I’d love to hear how it could be improved:

cocos2d::Sprite* draw_node_to_texture(cocos2d::Node* node)
{
    cocos2d::ui::Button* button = dynamic_cast<cocos2d::ui::Button*>(node);

    float width = 0, height = 0;
    width = node->getContentSize().width*node->getScaleX();
    height = node->getContentSize().height*node->getScaleY();

    cocos2d::Vec2 original_anchor = node->getAnchorPoint();
    cocos2d::Vec2 original_pos = node->getPosition();
    node->setAnchorPoint({0, 0});
    node->setPosition({0, 0});


    cocos2d::Director::getInstance()->getRenderer()->render();
    cocos2d::RenderTexture* rt = cocos2d::RenderTexture::create((int)width, (int)height);
    rt->begin();
    node->visit();
    rt->end();
    cocos2d::Director::getInstance()->getRenderer()->render();

    node->setAnchorPoint(original_anchor);
    node->setPosition(original_pos);

    cocos2d::Sprite* tex_sprite = dynamic_cast<cocos2d::Sprite*>(rt->getSprite());
    tex_sprite->removeFromParent();
    return tex_sprite;
};

@TankorSmash Thank you a lot for sharing this, I think you saved me a headache.

I think you can safely delete the line cocos2d::ui::Button* button = dynamic_cast<cocos2d::ui::Button*>(node); since button variable is never used.
This code can be used for Label objects too which is what I needed.

1 Like