Hello,
I am currently having the following issue: I created an interface myself with several buttons (each button is derived from CCNode
).
Each button consists of different images (CCSprite) and Texts (CCLabel).
Unfortunately, each sprite and each label causes an increase in the draw calls.
I am loading and caching the images the following way:
.....
var buttonsFile = "buttons.plist";
var buttons = new CCSpriteSheet(buttonsFile);
CCSpriteFrameCache.SharedSpriteFrameCache.AddSpriteFrames(buttonsFile);
CCSpriteSheetCache.Instance.AddSpriteSheet(buttonsFile);
foreach(var button in buttons.Frames){
CCTextureCache.SharedTextureCache.AddImage(button.TextureFileName);
}
And then later when creating a button
...
var background = new CCSprite(buttons.Frames.Find(frame => frame.TextureFilename.StartsWith("button_background")));
this.AddChild(background);
.... adding further sprites ...
var label = new CCLabel("Button","arial",30,CCLabelFormat.SpriteFont);
this.AddChild(label);
Unforutnately, this increases the draw calls for each label and sprite added to that button.
I discovered that when I was inheriting my buttons from CCMenuItemImage
and setting NormalImage
to the sprite and additionally adding the sprite with this.AddChild(button)
, the draw calls where not increased and I needed only one draw call for that sprite in all buttons.
Can anybody tell me how I should cache and create my sprites in order to keep the draw calls low? I thought about CCSpriteBatchNode
, but that's marked as obsolete/deprecated.
Heikofant
Simplify the moving parts as much as possible. Try using sprite sheets where possible and/or merge multiple sprite sheets into one so that the textures are not moved from memory -> shader -> gpu memory.
You also try rendering your dynamic pieces to static. For example you say you have a lot of buttons that use a CCSprite and CCLabel. Let's try something that will make them static as follows:
Create a render texture, layout your button sprite and then draw your text on top of that. For example:
class Button : CCNode
{
CCSprite buttonSprite;
public event TriggeredHandler Triggered;
// A delegate type for hooking up button triggered events
public delegate void TriggeredHandler(object sender, EventArgs e);
private Button()
{
AttachListener();
}
public Button(CCSprite sprite, CCLabel label)
: this()
{
this.ContentSize = sprite.ScaledContentSize;
sprite.AnchorPoint = CCPoint.AnchorLowerLeft;
label.Position = sprite.ContentSize.Center;
// Create the render texture to draw to. It will be the size of the button background sprite
var render = new CCRenderTexture(sprite.ContentSize, sprite.ContentSize);
// Clear it to any background color you want
render.BeginWithClear(CCColor4B.Transparent);
// Render the background sprite to the render texture
sprite.Visit();
// Render the label to the render texture
label.Visit();
// End the rendering
render.End();
// Add the button sprite to this node so it can be rendered
buttonSprite = render.Sprite;
buttonSprite.AnchorPoint = CCPoint.AnchorMiddle;
AddChild(this.buttonSprite);
}
void AttachListener()
{
// Register Touch Event
var listener = new CCEventListenerTouchOneByOne();
listener.IsSwallowTouches = true;
listener.OnTouchBegan = OnTouchBegan;
listener.OnTouchEnded = OnTouchEnded;
listener.OnTouchCancelled = OnTouchCancelled;
AddEventListener(listener, this);
}
bool touchHits(CCTouch touch)
{
var location = touch.Location;
var area = buttonSprite.BoundingBox;
return area.ContainsPoint(buttonSprite.WorldToParentspace(location));
}
bool OnTouchBegan(CCTouch touch, CCEvent touchEvent)
{
bool hits = touchHits(touch);
if (hits)
{
// undo the rotation that was applied by the action attached.
Rotation = 0;
scaleButtonTo(0.9f);
}
return hits;
}
void OnTouchEnded(CCTouch touch, CCEvent touchEvent)
{
bool hits = touchHits(touch);
if (hits && Triggered != null)
Triggered(this, EventArgs.Empty);
scaleButtonTo(1);
}
void OnTouchCancelled(CCTouch touch, CCEvent touchEvent)
{
scaleButtonTo(1);
}
void scaleButtonTo(float scale)
{
var action = new CCScaleTo(0.1f, scale);
action.Tag = 900;
StopAction(900);
RunAction(action);
}
}
The pertinent part of the code is the following:
// Create the render texture to draw to. It will be the size of the button background sprite
var render = new CCRenderTexture(sprite.ContentSize, sprite.ContentSize);
// Clear it to any background color you want
render.BeginWithClear(CCColor4B.Transparent);
// Render the background sprite to the render texture
sprite.Visit();
// Render the label to the render texture
label.Visit();
// End the rendering
render.End();
// Add the button sprite to this node so it can be rendered
buttonSprite = render.Sprite;
buttonSprite.AnchorPoint = CCPoint.AnchorMiddle;
AddChild(this.buttonSprite);
What we have done here is create a static image to be drawn for your button. The number of vertices that will be drawn are 4 and only 1 change of texture for the Renderer. With adding the sprite and label as children you would have had 4 vertices for the background image with a swap of Texture as well as 4 vertices for each character of the label with a swap of texture. So with just this one modification with a button background image and label with text of "Push Me" the number of vertices pushed to the graphics card is a very small percentage compared to sprite and label as children. The draw calls are also cut by 50% with the vertices draw cut by ~80%.
This is just one idea as there could be many others in your interface. You can see the full source here with actions attached as well as the draw calls.
Answers
Heiko
You should not need to use CCSpriteBatchNode as can be seen the CCMenuItemImage is supposedly doing things correctly it seems so there must be something that is not being cached correctly. Also, we could be missing something on our side as well. You could look at the CCMenuItemImage code and do something like what it is doing if you would like. If not could you please send a small solution file that demonstrates this inconsistency. Your last example was really precise and thank you for that.
Okay, I found out that the problem was that I have added a
CCLayer
to my nodes and added the sprite to that layer. That results in 1 draw call for each sprite.Attached is my short project that shows that behavior.
If you uncomment the line in
DummyMenuItem.cs
, then the draw calls are reduced to 1. Otherwise, they stick at 10.By the way, DisplayStats is currently not working for iOS.
I have another question: I want to use many
CCLabel
for my interface (I currently have around 18 buttons, each button has two labels and several sprites). Is there any way I can reduce the draw calls that are caused by the labels? Each label increases the draw calls by 1, however most of the labels are similar (e.g. Exercise 1 and Exercise 2).Heifkofant
Ok I am finally getting to the heart of the problems you are having.
First, again, want to thank you for the very precise examples. All of them are showing the same thing that you are working a lot with CCLayer. There have been a few changes and CCLayer does not work the way I think you are trying to use it. Even the issues you reported have the same theme. You are using CCLayer for everything. Take a quick look at the following on the Hierarchy.
I noticed this in most of your samples as well but never thought to mention it because it was not the problem you were having.
In your code attached in each new CCNode that will hold your button sprite you are adding a CCLayer to that node and then adding all of your graphics to that CCLayer. In the renderer each CCLayer would bump the draw calls up because it is not on the same layer.
If in your code you do the following:
The draw calls go to 1 even with 10 buttons drawn. It would be the same for 100 or more as well.
Maybe this might help:
To use the above:
This would work for either a textured button or a Text button.
For you CCLabel problem. If you are using SystemFont labels there is no way to reduce the draw calls. Each label is it's own entity and do not share a common texture. The only labels that share a common font texture are sprite fonts and bitmap fonts. This is what they were created for.
There is a backing texture atlas for each font definition that all characters use with bitmap fonts and sprite fonts. They even look the same across platforms whereas each platform using the underlying system calls will draw fonts with a noticeable difference. Even the font sizes will not be the same because of the differences between points, pixels DPI and the such.
Thanks for your answers @kjpou1 !
I switched over using CCNode insteaf of CCLayer if I want to group several sprites.
For the CCLabel issue: I am using SpriteFonts, but the draw calls increase by 1 for each label I create.
Is there any way to manually cache the fonts? The
CCSpriteFontCache.SharedInstance
has no method at all at the current PCL.Heikofant
That should be automatic.
Here is a test I just did with WindowsPhone 8.1 using a spritefont.
This will create 100 labels. Notice the number of draws is 2 even for all 100. 1 for the labels and 1 for the stats.

Thanks @kjpou1 for further investigation.
I now have found out, that indeed the draw calls are not increased but ONLY IF no
CCSprite
andCCLabel
is simultaneously added to aCCNode
.My given example produces either 400 draw calls or 1:
Project can be downloaded at: http://mmorats.com/Heiko/TIL/DrawCallsBug.zip
This might be relevant. It also appears to happen if you just add a
CCSprite
to anotherCCSprite
instead of aCCNode
. The following will also increase the draw calls by 400 if the value of lag is set to ture:Hey guys
Draw calls are only batched on the Texture. The renderer tries to keep the texture in memory as long as possible.
In the CCLabel example above, using a SpriteFont, the same texture is used so under the hood the Renderer can batch all the rendering calls.
The only way for this to work is that the texture being used is the same for each draw call. Once the texture changes then a new draw call is issued so that the new texture can be moved to memory.
In Henkofant's example
--> CCSprite texture to gpu memory
--> Draw sprite using texture
--> CCLabel texture to gpu memory
--> Draw all label sprites using this same texture atlas associated with the label
--> Next CCSprite used for grouping is the same? No because the last texture was for the sprite font so go to the beginning above and start again.
So there really is no way for the renderer to keep the same texture in memory because they are different.
Peter's is kind of a corner case because even though the CCSprite does not have a texture the Renderer still thinks it does because normally a CCSprite is associated with one. In fact the Texture Id works out to zero in this case which would be a different Id than the one used for the render target texture. Could this be considered a bug? Maybe, depending on how you look at it. The definition of a CCSprite is that it has a Texture associated with it even though in this case there is no texture. If someone created a CCSprite without a texture to group other nodes then they should probably rethink the CCSprite and change to a CCNode instead. That would be the correct way.
That is all CCSpriteBatchNode was, just a way to keep contiguous associated sprites together to use the same texture. Even though it kept them together each CCSpriteBatchNode still created a draw call. Even though there might have been multiple sprites associated with each batch node it still created a draw call for each CCSpriteBatch. Besides doing this it really was not very easy to use with the code becoming quite complicated at times if you wanted to use it as part of a custom component.
The Renderer tries to work this out dynamically for itself but sometimes needs a little help.
For example the particle system used a sprite batch to group the particle textures. If you had an explosion particle system and ran it three times it would show three draw calls. With the Renderer all those calls would be batched automatically thus show only one draw call even though there may be 1000 different particles associated. Also assuming that the particle system was placed to be drawn one right after the other in the Scene Graph hierarchy. If for instance you added a particle system to the Scene Graph then a CCSprite and then another particle system using the same texture as the first, you may get 2 draw calls or 3 depending on the order to be drawn as well as the z-order in the scene. This also may change as you add and remove children from the Scene Graph and the Renderer dynamically sorting those to work out if they can be grouped or not.
Hope this helps.
Thank you @kjpou1 for your explanation.
So what am I supposed to do for my interface? My fps is dropping too low, the user experience is quite bad. As I said, I have plenty of interface elements with both, sprites and fonts.
Heikofant
Simplify the moving parts as much as possible. Try using sprite sheets where possible and/or merge multiple sprite sheets into one so that the textures are not moved from memory -> shader -> gpu memory.
You also try rendering your dynamic pieces to static. For example you say you have a lot of buttons that use a CCSprite and CCLabel. Let's try something that will make them static as follows:
Create a render texture, layout your button sprite and then draw your text on top of that. For example:
The pertinent part of the code is the following:
What we have done here is create a static image to be drawn for your button. The number of vertices that will be drawn are 4 and only 1 change of texture for the Renderer. With adding the sprite and label as children you would have had 4 vertices for the background image with a swap of Texture as well as 4 vertices for each character of the label with a swap of texture. So with just this one modification with a button background image and label with text of "Push Me" the number of vertices pushed to the graphics card is a very small percentage compared to sprite and label as children. The draw calls are also cut by 50% with the vertices draw cut by ~80%.
This is just one idea as there could be many others in your interface. You can see the full source here with actions attached as well as the draw calls.
Thanks @kjpou1 .
I could now lower the draw calls to get proper FPS as every of my buttons now increase the draw calls by only 1.
Do you / does anyone has any further tips in respect to rendering etc? I am quite unfamiliar with graphic performance enhancement.
I have now the issue that the
RenderTexture.Sprite
looks pixelated / shows artifacts.If I set
RenderTexture.Sprite.IsAntialiased = true
then the sprite looks better but is blurry.I've read that I have to set the
CCCameraProjection
toCCCameraProjection.Projection2D
, but where do I exactly have to set that?Changing the default
CCLayer.DefaultCameraProjection= CCCameraProjection.Projection2D
had no effect.@Heikofant
What do you mean by artifacts? Can you post a screen shot?
Here are two screenshots.
As you can see, the font and the star are pixelated (you can compare the star in a blue menu item with the stars on the right).
Are those scaled in any way?
The blue background of the button is scaled, everything else is not.
@kjpou1 Do you have a solution yet?
Heikofant
Sorry to say I do not. Have you tried using different blends for that. Try setting the BlendFunc when drawing. The default is AlphaBlend but maybe what is outline here. This uses a NonPremultiplied blend when drawing.
Here is some info on PreMultiplied
http://blogs.msdn.com/b/shawnhar/archive/2009/11/07/premultiplied-alpha-and-image-composition.aspx
ugly fringes
These explain the different ways that Pre-Multiplied and NonPremultiplied work with image composition using render targets. Especially with the fringe bleeding.
Hey, I already tried the blendfuncs.
The result is still pretty crappy (exact the same as on the screenshots I posted above).
Anyone found a solution how to use sprites + labels with CCRenderTexture and obtain good results?
Heikofant
Can you put together a small project with your assets that is demonstrating that please.