Is tinting without increasing the number of draw calls possible?

I was working this weekend on this prototype

and got two warnings from the engine:

First one, max number of sprites was reached, which ok, makes sense because I’m creating a matrix of 40x40 sprites, so I need at least 1600.

That matrix is all the same sprite, tinted to represent some kind of pheromone trail. And that raises the second warning: “RENDER: Max number of draw calls reached (1024), some objects will not be rendered. Increase the capacity with graphics.max_draw_calls”

Digging up a bit, I found that tinting does indeed break batching, although it seems that “Tintin breaks batching but components have the exact same tinting will batch.” as stated here: Draw calls and Defold

So if that is true, instead of using floating numbers I could do some value quantization and reduce grayscale values to, say, 32, 64 or 128. That would reduce draw calls by a few thousands already. Indeed, my values range from 0 to 1, so if I do something like tint being math.floor(cell_value * 128) / 10 I get 128 values, which looks like this and doesn’t go beyond 128 drawcalls (tested and works)

But I am curious: Is there another way of reducing drawcalls when tinting? Because a) this trick won’t always work and b) it doesn’t look as good as the original version, which has many more gray values! (although I could improve the way I quantize, spreading values a bit better)

(Also, what is a good value for draw calls? I’ve always understood that the lower the better)

1 Like

I think the easiest would be to use a tilesource or atlas with 256 or so greyscale values and use sprite.play_flipbook() to select the right image based on the color value. This way you’ll get a single drawcall.

1 Like

There is the capability to use vertex color for sprites with a custom vertex format, but currently, this can only be done from the editor. It will also be processed as a single DrawCall.

Well, for this specific case it would work. But imagine a game where every single character can be tinted depending on altered states (red for damage, purple for poison, green for sickness and so on…). You would need every single frame of each character in all possible colors?

Ah, that sounds reasonable! Will it ever be a runtime feature? Tinting is not critical, but it’s certainly handy in lots of situations : )

Yes, it will, I think @jhonny.goransson has it in his backlog. But no ETA for now

1 Like

You can also use a texture. You can create a texture resource in runtime and upload colors to it every frame if you’d like. You would need to figure out a way to map a specific sprite to its corresponding texture UV in the color / tint texture. One way of doing so would be to use a custom vertex attribute and assign your sprites with a unique index into the attribute that you could use to calculate the UV.

(Another way would this could be solved would be to use instancing and utilize the gl_InstanceId to calculate the UV, but that’s also on my list and not available yet)

2 Likes

Isn’t it quite convoluted for such a relatively simple thing? Fist, painting a texture might take time unless there’s a way of setting all pixels at the same time (certainly not looping through all of them). But then the UV mapping complicates it all, because as stated in this post, if the sprite is part of an atlas you can’t get it’s UV easily.

It might also by that I didn’t understand the custom vertex attribute part :innocent:

But a texture can also be used as a generic data buffer (and it’s very common to do so). You can have a buffer representation with all your dynamic colors that you write colors to when needed, and when you do changes to it you can upload that buffer to a texture. Calculating the UV isn’t that tricky either, especially if you assign an index to each sprite via the vertex formats

Here’s another wild idea: Create a 40x40 pixel texture. Assign it to a mesh. Scale it up to the size you need. Draw to it with your pixels. It’s basically a pixel canvas. One draw call. Simple to update. Basically what this extension does: Draw Pixels (Native Extension)

“Is tinting without increasing the number of draw calls possible?”

I’d just like to answer the OP more specifically.
The “tint” property is a uniform in the shader, and these can only be set once, before the draw call.
Since that is the case, these uniforms are also considered when we create the batches. So if two sprites have different values on their uniforms, they will be part of two different batches, and thus become multiple draw calls.

1 Like

@jhonny.goransson Then I’m not sure I understand those vertex formats you mention. Is there somewhere I can read about them?

@britzl but that drawing sounds slower than many draw calls, unless it’s done by a shader, isn’t it?

@Mathias_Westerdahl Yes, I mentioned and even tested that by quantizing colors, right? It indeed reduced draw calls by an order of magnitude.

1 Like

Instancing is the way to go. You can hack it by calling pure OpenGL calls from inside an extension, but that requires a high level of expertise.

Alternatively making a 40x40 texture at runtime each frame is also not a bad solution given these numbers.

1 Like

Exactly. If it really is 40x40 pixels then that’s not a problem at all. It reminds me of the Pico-8 Tweetcart thing I did in Defold a few years ago. It could take simple tweetcarts (Pico-8 carts with visual effects which fit in a tweet) and run those in Defold and render to a 128x128 pixel canvas.

2 Likes