SpriteMesh icon indicating copy to clipboard operation
SpriteMesh copied to clipboard

Performance considerations of `SpriteMesh`

Open Xrayez opened this issue 3 years ago • 6 comments

Hi, I've recently noticed your work and it looks great, if you feel like GDScript is slow for procedural stuff with SpriteMesh perhaps some performance critical stuff could be implemented in https://github.com/goostengine/goost.

I don't necessarily say that you need to implement SpriteMesh in Goost (although voxel-based games are quite popular nowadays, the way I see it, if you feel like SpriteMesh could be a nice addition to Goost, feel free to contribute), but there are certain techniques which could be integrated into your plugin without requiring users to depend on Goost features at all, something like:

if ClassDB.class_exists("GoostGeometry3D"):
   Engine.get_singleton("GoostGeometry3D").do_stuff()
else:
   # GDScript code.

What I'm proposing is like optional performance boost. I know you've contributed to ImageTools/Goost before, therefore I presume this could be useful for you (and others!).

This is more like a question/discussion to see what kind of bottlenecks you stumble upon exactly, and whether these underlying algorithms could be seen useful for more than what your plugin accomplishes.

Xrayez avatar Sep 02 '21 14:09 Xrayez

Hi! I'm glad you liked it! I would start talking about the bottlenecks. Right now, if you use the editor, only the developer would execute the algorithm. So it would not affect the performance of the final game. The only case where performance may be an issue is if the game generates many meshes procedurally. In this case, implementing the algorithm in C++ would certainly be noticeable.

Implementing the algorithm in Goost is quite challenging because it takes many parameters. However, many of these properties' only purpose is to transform the mesh's vertices.

So what I would do first is to implement many methods to transform meshes, something like:

GoostGeometry3D.scale(mesh, scale) -> mesh

So there would be a method for position, rotation, scale, and transformation. Then, the function for SpriteMesh could receive a lot fewer parameters:

GoostGeometry3D.sprite_mesh(texture, double_sided, alpha_threshold, uv_correction) -> mesh

98teg avatar Sep 02 '21 21:09 98teg

I'm not really familiar with 3D transformations much, but note that you can transform a bunch of PoolVector3Array with Transform.xform() method, so perhaps you could already speed this up even via GDScript if we're speaking about things like transforming vertices.

Speaking of:

GoostGeometry3D.sprite_mesh(texture, double_sided, alpha_threshold, uv_correction) -> mesh

I'm afraid this kind of API would be too specific, I think geometry's singleton scope should mostly deal with built-in data structures, not objects. I'd rather break this down into functions that have single responsibility. But again, as I said, perhaps all you really need is to use Transform.xform()!

However, Godot's editor has some methods to convert Sprite to MeshInstance2D similarly. I don't see anything like that exposed in Godot either. So in theory both functionalities could be exposed/implemented, and it would no longer be that much specific I guess.

You can use Godot's profiler to first identify which parts take considerable amount of resources when doing this via code. It may just happen that there's not much which could be done to achieve considerable performance improvements... By "considerable" I mean performance gains that improve current speed by ~30-50%.

Xrayez avatar Sep 02 '21 22:09 Xrayez

I have been looking a bit into Transform.xform() and, as you said, it can be used to apply the transformations I was talking about. I could generate the mesh using ArrayMesh instead of SurfaceTool. This would improve performance slightly as the documentation says.

However, I think the major performance bust could be achieved by implementing the algorithm itself because it needs to iterate through the image multiple times. Right now, the transformations I was talking about are implemented in each step of the algorithm, so removing them would not reduce the number of iterations, just reduce the time per step. The algorithm has to generate both the vertices and the UV mapping at the same time, so I don't really know how to implement the API, as you already said than returning a mesh would be too specific. Maybe a method that returns a PoolVector3Array with the vertices and another that returns a PoolVector2Array with the UV mapping?

Lastly, I would not be able to do any benchmark for a while to check any of these ideas as I would not be at home, but I'm fine just discussing them!

98teg avatar Sep 03 '21 08:09 98teg

You've mentioned SurfaceTool which is used to procedurally generate meshes. I think the best approach to solve this would be to create MeshTool class that will be responsible for generating all kinds of mesh geometry, including sprite mesh:

MeshTool.create_from_texture(texture, double_sided, alpha_threshold, uv_correction) -> Mesh

But at the same time, it must be useful for generating 2D meshes from texture... (as done with Godot's SpriteMeshInstance2D conversion tool, see source code https://github.com/godotengine/godot/blob/140350d/editor/plugins/sprite_editor_plugin.cpp#L159-L297). Perhaps it would be necessary to make proper separation of 2D/3D here, such as MeshTool2D and MeshTool3D.

I've even noticed some other modules like https://github.com/EternalColor/Godot-Planet-Generator-Module, so doing it this way could make the class more useful for other stuff in the future. But instead MeshTool would generate meshes rather than actual mesh instances.

Xrayez avatar Sep 03 '21 12:09 Xrayez

That seems promising! Then the implementation could look something like this:

  • Create the basic mesh using MeshTool.create_from_texture.
  • Obtain the PoolVector3Array of vertices using the method surface_get_arrays from Mesh.
  • Use the Transform.xform method to apply the necessary transformations to the PoolVector3Array of vertices.
  • Use ArrayMesh to generate the transformed mesh.

I would need to consider how to handle flipping the mesh, as it can be challenging. As to whether or not to create separate classes to generate 3D or 2D meshes, I would name it MeshTool3D, even if there is no 2D counterpart at the moment, to avoid compatibilities issues in the future.

98teg avatar Sep 14 '21 12:09 98teg

Sounds good to me! Yes, creating MeshTool3D should be fine, also consistency with potential MeshTool2D API shouldn't matter much I guess.

Xrayez avatar Sep 14 '21 13:09 Xrayez