How to "merge" sprites in defold

hm…
I use box-nodes with different colors. I thought this would be the most “hardware-friendly” way (and I like simple looks).
All the dynamic (cloned) buttons have a coloured box as background. its about 15 buttons containing a background box-node, a box-node with a sprite and 2 text-nodes with 2 different fonts (sizes).

The content of the “ingameGUI” which will be activated after clicking on a unit or structure which takes about these 70 are in the last screenshot. The button left top is a template. it will be cloned depending on a table content which is actually 8 entries.
there is also a nearly same GUI (for structures)- dont have these small buttons in the middle - rest the same. also 7 dynamic buttons.

These 2 GUIs activated (happens if you click on unit OR structure) takes all the performance.

I could give you screens, source or access if you like.

I’ve saw this kind of solving and handling in some examples and thought it would be a good way.
Maybe I should handle it all in one GUI (unit and structure).
I have a couple of GUIs actually.

I was wrong. Box nodes with different colors does not break the batch. Sorry about that.

You’re welcome to invite me (bjorn.ritzl@king.com) to the project and I’ll take a look.

Uploaded and added you to the dev-team :slight_smile: Thanks so far.

Some things I’ve noticed when looking at the unit.gui:

  • You’re using two layers; img and txt. The thing is that you’re using both the system font and your own font and you assign all text nodes to the layer txt regardless of font. This will break batching every time the font changes. Solution: Use one font or use more layers (one per font).
  • The node structure_underconstruction is assigned to the img layer but it is set as semi transparent (alpha = 0.3). I believe this will break a batch.
  • The node move_selector doesn’t have a texture but it is assigned to the img layer (and it’s semi transparent). This will break the batch. Create an image, add it to the atlas and use the image instead.
  • The node buildmenu/button_buildstructure_base doesn’t have a texture assigned either.

I believe the above couple of issues are causing all of the additional draw calls in that gui scene.

1 Like

Thanks so far @britzl.

hm… that means every box-node needs to have a texture?

if there is any transparency there has to be a separate layer?

for each font a layer?

OMG. Nearly everything designed is a “batch-breaker”.

There should be a separate thread which explains the batches :wink:

Like I’ve said, I’ve coded this game in another language which is no engine. There, I have to code a couple of procedures (was a basic language). One for unit-tracks, one for kind of particles, one for units, one for … etc. There I had to take priority who many such procedures will become called and what content. I needed 1 month for optimizations. So I compare your batches like classes our routines which handels specific stuff. one for box-nodes, one for textured-box-nodes, one for text-nodes etc. If you mix up too much some classes/routines has to be called twice - thats what its meant to “break a batch”, right? layers seems a way to handle that if I understand the docs and you right.

Is there any “batch” list, priority or anything to read for a deeper understanding or understanding at what “situation” a batch breaks?

What we are talking about really is the work of the GPU and OpenGL. Two things are involved here:

  1. Giving OpenGL instructions to set up the render state. Which shader should be used? Which texture? Which blend mode? And so on.
  2. Instructions sent to the GPU to draw a mesh. These are the draw calls.

The initial setup of the render state is expensive. Telling the GPU to draw a mesh isn’t.

Defold will try to batch draw calls that have the same render state (ie the same texture, shader, blend mode etc).

When I write “break a batch” what I really mean is that if we have a long list of things in the game (for instance sprites) that have the same properties they will all be drawn in a single draw call. This is good. BUT if I have one sprite with another material or with another blend mode and stick that into the middle of my long list of otherwise identical sprites then I’m breaking the batch of draw calls. What would otherwise have resulted in 1 draw call now results in 3 draw calls. This is bad.

The documented that has already been linked previously covers this: Draw calls and Defold

To clarify:

Rendering of sprites, spine models, particle fx etc

  • Rendering is based on z-order, back to front.
  • Components on different depths will be batched unless one of the following is different from the previous component:
  • Component type (sprite, spine, particle, label, model)
  • Texture
  • Material
  • Blend mode, tint etc
  • Collection proxies
  • Note: Each particle emitter will result in a draw call

Rendering of gui scenes

  • Rendering is based on the order of the nodes in the outline, depth first
  • Nodes will be batched unless one of the following is different from the previous node:
  • Node type (box, text, pie)
  • Texture
  • Blend mode
  • Font
  • Stencil settings

Layers are used to group different nodes to reduce the number of draw calls. Some images:



5 Likes

Big thanks @britzl!

Very detailed answer. Its principially what I thought and what I meant with my posting. Without an engine (only some sprite commands) you have to write your own “Batches” etc.

But now it is a clear thing what it is about.

As I asked for a “list of breaking batches” its exactly that: “sprite with different texture” etc.
Does your posting includes all states of breaking/changing a batch? Or are there much more “mistakes” to make ?

1 Like

I believe my list should be complete, but I’ll gladly let @sven, @Mathias_Westerdahl or @sicher correct me!

1 Like

in my case, I already use layers to reduce the drawcalls. but thats not enough. there are different sprites for each box and in every background box are 2 different fonts o.O

So does it make sense to “group” the boxes without textures to one layer, the all-different-texture-boxes to another layer and for each text-box-font a layer (2 diff. fonts = 2 layers)?

oh by the way… texture in a box… I’ve “played” a bit with the settings, is there no setting to “repeat” a texture?
Also if I try to “stretch” a texture, f.e. a 1x1 pixel or 10x10 (one color) to fit to the whole box-node?

//EDIT:
I’ve did it like said above and decreased the drawcalls from 92 to 31 o.O. Now this GUI takes about 10 drawcalls.

Reduced round about 100 drawcalls now! wow. Knowledge is power! :wink:

thanks again :slight_smile: @britzl you’re doing a great job to help devs here! applause

1 Like

Hey there.

Just asking again, maybe there is something I’ve missed to the main theme…

Is there a way to have 2 or more sprites on a gameobject and rotate them diffrently?

In the example I am trying to have a tank (tracks) and a gun. the gun Needs to be rotated , tracks stay as they are.

Is it possible?

You probably want sub GOs for each of the sprite elements which are parented to the main tank GO so that each sub element can have its own transform while still being transformed by main tank position.

thats my thoughts. But as far as I know not possible in defold…or?
In editor I cant see a way to add a go to a go .

It has to be in a collection to do that. Just drag and drop.

ah ok. for sure.

but then I have to use the collection-factory as well, right? hm… means changing lots of coding I quess…

Right, that’s the drawback. Depending on your code you hopefully only have to change spawning and destroying though.

I guess I remember I already was at that point and dropped it… have to take a closer look (again).
Thanks!

You can use plain GOs too but requires you to build/destroy all GOs of an object and set the correct parenting and relative positioning when you create the tank with the main tank script having reference of all of the tank part GOs.

hm…

using a normal factory I can send to the new go’s script a table like this:

factory.create("main:/gamecontrol" .. self.buildingDefence[i], mapPos, nil, {building = hash(self.buildingDefence[i]), ownerID = GLOBAL.PlayerID})

with a collectionfactory this returns an error. Without the table it dont dropps an error but of cause dont work for me :slight_smile:

collectionfactory.create("main:/gamecontrol" .. self.buildingDefence[i], mapPos, nil, {building = hash(self.buildingDefence[i]), ownerID = GLOBAL.PlayerID})

Error: bad argument #7 to 'create' (hash expected, got string)
there is no way to send a table while spawning?

oh, i guess I found something in the docs…

-- planet.script
--
local props = {}
props[hash("/astronaut")] = { size = 10.0 }
props[hash("/probe1")] = { color = hash("red") }
props[hash("/probe2")] = { color = hash("green") }
local astro = collectionfactory.create("#factory", nil, nil, props, nil)
...

But why this wont work?

local props = {}
props["turret_gun"] = {building = hash("structure_turretgun"), ownerID = GLOBAL.PlayerID}
props["wall"] = {building = hash("structure_wall"), ownerID = GLOBAL.PlayerID}
local result = collectionfactory.create("main:/gamecontrol" .. self.buildingDefence[i], mapPos, nil, props, nil)
pprint(result)

Error
bad argument #8 to 'create' (hash expected, got string)