Dynamic creation of the atlas in the script

Hello! I upload a lot of avatars of users from social networks into the game. I do this with the code

http.request(link, "GET", function(self, id, res)

	local img = image.load(res.response)
	
	if gui.new_texture(link, img.width, img.height, img.type, img.buffer) then
		gui.set_texture(avatar_node, link)
	end
end)

But I stumbled on the fact that the number of drawcalls!
Is it possible to collect dynamic user avatars in one atlas and use from

.......
local img = image.load(res.response)
	-- avatars is atlas name
	if gui.new_texture('avatars/'..link, img.width, img.height, img.type, img.buffer) then
		gui.set_texture(avatar_node, 'avatars')
		gui.play_flipbook(avatar_node, link)
	end

How to download a lot of avatars of users in the game and avoid a big drawcalls!

Very interesting!
Thank you!

3 Likes

Unfortunately it’s not possible easily dynamically create atlases at runtime… but it would be an awesome feature if it was added. I don’t think there is an issue yet for it so you should create one to request this feature https://github.com/defold/editor2-issues/issues

I thought maybe you could create a buffer which is then dynamically updated, but custom properties sent to the shader for offsets would cause extra drawcalls too so we would need to be able to create UVs at runtime too.

1 Like

It might be possible to calculate the UVs in GLSL based on a “image index” that could be stored in the Z-component of the GUI node (keeping the draw count down due to Z not breaking batching in GUIs). (I think, not tested myself. :slight_smile: )

2 Likes

Good idea! I’ll try to make a demo for that now.

3 Likes

What dimensions are your avatars? How many do you want to be able to be loaded at once? They are GUI nodes?

My current thinking is to use tilesource for textures because then you can use it as a texture with animations and the placements are consistent. Then you can setup animations for each avatar slot so it’s easy to set in the the right slot gui scripts. So you download the image, replace the image data in the buffer at the correct offsets, and then update the tilesource texture with resource.set_texture. The only issue with this is that there will probably be lag every time you update the texture.

@Mathias_Westerdahl this should be doable?

3 Likes

Sounds doable to me. You can mitigate the hitches by only updating parts of the texture per frame.

4 Likes

~30 avatars, gui node type, 100X100
Thanks!

1 Like

I have it working, cleaning it up and then will release it.

Test images

https://i.imgur.com/iMPi3lq.png
https://i.imgur.com/Y8JhMoR.png
4 Likes

It would be easier if the images are a power of 2 due to how the textures work. I’ll have to do a test for that size…

DynamicAtlasNoBreakBatching.zip (460.1 KB)

Here is the ugly proof of concept so you can see how it works. I still need to clean it up, add proper offsetting, and make it work with irregular texture sizes. Right now the tiles are starting from the bottom left because it’s easier.

I can’t spend more time on it right now though but can finish it tomorrow. If anyone else feels like making the final production ready version before then please do. :pray:

4 Likes

Fine work, thank you!
Unfortunately, I can not use it - all social networks give out avatars in jpg format :joy:

You should be able to adapt the solution to work with jpg as well. The solution could likely be adapted to using image.load() instead (since it supports both png and jpg).

1 Like

image.load() gives the raw image data that has not yet been decoded to rgb yet I think? Or is that data ready to use but in a different way?

Working on this again now. JPG format will be supported.

image.load() gives you the decoded pixel data.

1 Like

Have irregular sizes working, tiles now start at top left, have offsets working properly.

A feature which would be nice to add after everything else is working is automatic edge extrusion support so the images don’t bleed into each other when scaled as they share the same texture space. If you have a frame around the avatar it’s not noticeable, but otherwise you can see a tile bleed into another.

3 Likes

For JPG / image.load() the raw data is in the Lua table instead of a Defold buffer. Would it be better to transfer the raw data into a Defold buffer after loading and then use that, or use the raw data directly?

Yes, image.load() returns a table with the image data and some meta data about the image

I guess it depends on what you want to do. There’s a benefit of having it as a buffer if you intend to pass it back and forth to some C code.

I’ll work with the raw data in Lua then.

Maybe there can be made a sister module to the PNG one for JPG that does direct Defold buffer creation.

1 Like

Switched to image.load() so now it should support any format that ends up supporting.

I suppose I need to flip the pixels, or grab the bytes in opposite order. It’s black and white too because of the wrong order?

string.reverse() worked to flip it correctly, but still in black and white?

Ah, it’s not actually flipped correctly…

My mistake I was only setting the red channel to each rgb when I changed over the code. The images do appear to be incorrectly flipped horizontally still…

There is a flip flag to gui.set_texture_data, could you give it a try?