Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sprite3D and AnimatedSprite3D is rendering very slow #20855

Closed
i-void opened this issue Aug 9, 2018 · 22 comments
Closed

Sprite3D and AnimatedSprite3D is rendering very slow #20855

i-void opened this issue Aug 9, 2018 · 22 comments

Comments

@i-void
Copy link

i-void commented Aug 9, 2018

I tried so much variations with/without shading with gdScript, nativeScript (Nim) and different import settings without luck. One archer walk animation, with 8 frame costs 14 fps (reduced to 46fps) . It slows to 19-20fps when I exported it to android also. I'm adding screen shots from my computer with GeForce 940MX screen card (I can play Anno 1404 without any lag with this screen card).

I have only one archer on the screen, and I scripted with nim (very fast programming language). I supposed in this example I made the fastest things I can do. If not please share my mistake.

Version: Godot 3.0
System: Linux Manjaro (ArchLinux derive)
Frame rate drops too much (14fps for only one node). I expect it to work with 60fps.

Screen shots:
animation
code
import-settings
screen1

I have only one scene as Archer and I'm using a sprite image only. Nothing more.

@semirix
Copy link

semirix commented Sep 4, 2018

I've experienced this as well. After removing them all and replacing with MeshInstance planes, FPS returned to normal.

@usbhell
Copy link

usbhell commented Apr 25, 2019

I've noticed this also on 3.1, quite bad fps on 2 android devices I tried (GLES2) with just a couple of sprite3d's. If I use the builtin quadmesh instead to display my texture the fps is perfectly fine.

@Toshiwoz
Copy link
Contributor

Toshiwoz commented Sep 27, 2019

Same here, I had to get rid of them, and now I know what to use in replacement.
I've taken a bit of my time to make a sample project, that, if you run on pc, it doesn't make a huge difference, but as you export to a mobile you can see the incredible difference.
On my MYA-L11 I can run at 50+fps more than a hundred plane meshes (that is not that much, but in comparison), while it can't stay above 10fps with barely 10 Sprite3D.
Sprite3D_Mobile_Test.zip

I think this is indeed a quite old issue: #2096
And afaik also ImmediateGeometry displays similar, hum... symptoms.

I'm not sure, but, if the class has the _draw method, that means it overrides it right?
Might be there the problem?
As I can't debug (and I know very little about C++) I can't tell for sure, but can it be that it's doing too many things at every frame?
Like checking properties, calculating frame offset, and more:

void Sprite3D::_draw() {
RID immediate = get_immediate();
VS::get_singleton()->immediate_clear(immediate);
if (!texture.is_valid())
return;
Vector2 tsize = texture->get_size();
if (tsize.x == 0 || tsize.y == 0)
return;
Rect2 base_rect;
if (region)
base_rect = region_rect;
else
base_rect = Rect2(0, 0, texture->get_width(), texture->get_height());
Size2 frame_size = base_rect.size / Size2(hframes, vframes);
Point2 frame_offset = Point2(frame % hframes, frame / hframes);
frame_offset *= frame_size;
Point2 dest_offset = get_offset();
if (is_centered())
dest_offset -= frame_size / 2;
Rect2 src_rect(base_rect.position + frame_offset, frame_size);
Rect2 final_dst_rect(dest_offset, frame_size);
Rect2 final_rect;
Rect2 final_src_rect;
if (!texture->get_rect_region(final_dst_rect, src_rect, final_rect, final_src_rect))
return;
if (final_rect.size.x == 0 || final_rect.size.y == 0)
return;
Color color = _get_color_accum();
color.a *= get_opacity();
float pixel_size = get_pixel_size();
Vector2 vertices[4] = {
(final_rect.position + Vector2(0, final_rect.size.y)) * pixel_size,
(final_rect.position + final_rect.size) * pixel_size,
(final_rect.position + Vector2(final_rect.size.x, 0)) * pixel_size,
final_rect.position * pixel_size,
};
Vector2 src_tsize = tsize;
// Properly setup UVs for impostor textures (AtlasTexture).
Ref<AtlasTexture> atlas_tex = texture;
if (atlas_tex != NULL) {
src_tsize[0] = atlas_tex->get_atlas()->get_width();
src_tsize[1] = atlas_tex->get_atlas()->get_height();
}
Vector2 uvs[4] = {
final_src_rect.position / src_tsize,
(final_src_rect.position + Vector2(final_src_rect.size.x, 0)) / src_tsize,
(final_src_rect.position + final_src_rect.size) / src_tsize,
(final_src_rect.position + Vector2(0, final_src_rect.size.y)) / src_tsize,
};
if (is_flipped_h()) {
SWAP(uvs[0], uvs[1]);
SWAP(uvs[2], uvs[3]);
}
if (is_flipped_v()) {
SWAP(uvs[0], uvs[3]);
SWAP(uvs[1], uvs[2]);
}
Vector3 normal;
int axis = get_axis();
normal[axis] = 1.0;
Plane tangent;
if (axis == Vector3::AXIS_X) {
tangent = Plane(0, 0, -1, 1);
} else {
tangent = Plane(1, 0, 0, 1);
}
RID mat = SpatialMaterial::get_material_rid_for_2d(get_draw_flag(FLAG_SHADED), get_draw_flag(FLAG_TRANSPARENT), get_draw_flag(FLAG_DOUBLE_SIDED), get_alpha_cut_mode() == ALPHA_CUT_DISCARD, get_alpha_cut_mode() == ALPHA_CUT_OPAQUE_PREPASS, get_billboard_mode() == SpatialMaterial::BILLBOARD_ENABLED, get_billboard_mode() == SpatialMaterial::BILLBOARD_FIXED_Y);
VS::get_singleton()->immediate_set_material(immediate, mat);
VS::get_singleton()->immediate_begin(immediate, VS::PRIMITIVE_TRIANGLE_FAN, texture->get_rid());
int x_axis = ((axis + 1) % 3);
int y_axis = ((axis + 2) % 3);
if (axis != Vector3::AXIS_Z) {
SWAP(x_axis, y_axis);
for (int i = 0; i < 4; i++) {
//uvs[i] = Vector2(1.0,1.0)-uvs[i];
//SWAP(vertices[i].x,vertices[i].y);
if (axis == Vector3::AXIS_Y) {
vertices[i].y = -vertices[i].y;
} else if (axis == Vector3::AXIS_X) {
vertices[i].x = -vertices[i].x;
}
}
}
AABB aabb;
for (int i = 0; i < 4; i++) {
VS::get_singleton()->immediate_normal(immediate, normal);
VS::get_singleton()->immediate_tangent(immediate, tangent);
VS::get_singleton()->immediate_color(immediate, color);
VS::get_singleton()->immediate_uv(immediate, uvs[i]);
Vector3 vtx;
vtx[x_axis] = vertices[i][0];
vtx[y_axis] = vertices[i][1];
VS::get_singleton()->immediate_vertex(immediate, vtx);
if (i == 0) {
aabb.position = vtx;
aabb.size = Vector3();
} else {
aabb.expand_to(vtx);
}
}
set_aabb(aabb);
VS::get_singleton()->immediate_end(immediate);
}

@Calinou
Copy link
Member

Calinou commented Sep 27, 2019

ImmediateGeometry isn't intended to manage large amounts of geometry, so it should probably be replaced to use some other kind of mesh generation (like ArrayMesh?).

@Toshiwoz
Copy link
Contributor

Toshiwoz commented Sep 27, 2019

ImmediateGeometry isn't intended to manage large amounts of geometry, so it should probably be replaced to use some other kind of mesh generation (like ArrayMesh?).

Yeah I am referring to a few lines drawn (I was reusing part of the navmesh code for my game, and let the path as it was, found out it was slowing down a lot on mobile).
But, never mind, I'll open a new issue or add to existing ones as I have time to make another test/benchmark project.

@BenMcLean
Copy link

I am also getting hit by this / #31023 in my WOLF3D project. As a workaround, I am looking into replacing all my Sprite3Ds with MeshInstances of a QuadMesh. Sure would be nice if Sprite3D was actually fixed though

@sp00mm
Copy link

sp00mm commented Feb 16, 2020

I'm also having this issue. They almost should put a disclaimer on the sprit3d node. Now I'm going through ~20+ nodes and changing them to meshes. It would be REALLY nice to have a feature like the 2d sprite where you can convert it to a mesh2D with just a click.

@semirix
Copy link

semirix commented Feb 17, 2020

Does Sprite3D need a refactor? Surely an object that simple should not be so slow to render.

@clayjohn
Copy link
Member

clayjohn commented Feb 17, 2020

@semirix it should probably just be removed and made into a thin shell for a billboarded quad.

@Calinou
Copy link
Member

Calinou commented Feb 17, 2020

@clayjohn What about AnimatedSprite3D? It's surely possible to emulate its functionality using a MeshInstance + QuadMesh, but it sounds tedious to set up.

@Seel
Copy link

Seel commented Feb 21, 2020

Yeah... as someone working on a mixed 2D and 3D game I really want that sprite functionality.

Calinou added a commit to Calinou/godot that referenced this issue Mar 9, 2020
@fossegutten
Copy link
Contributor

I can only draw about 100 Sprite3D before i drop below 100fps, but i can draw almost 1000 QuadMesh.

@t-mw
Copy link
Contributor

t-mw commented Apr 28, 2020

For anyone trying to work around this by using an AtlasTexture on a MeshInstance material, here's a shader and script that might help: https://gist.github.com/t-mw/0b78167372ed97e7c78e3f3844f3ae75.

@Calinou Calinou added this to the 4.0 milestone Jun 5, 2020
@tommiemartin
Copy link

I started looking at this as well but my knowledge of rendering isn't to the point where I can be much help.
Is this currently using immediate geometry to create quads on which the frames are drawn? Seems like the goal would be to put a SpriteFrames resource in 3d space which looks like would somehow need to inherit from a visual instance.

Seems like ios (ipad mini 2) handles lighting, normal maps and sprite3d objects much better in its pipeline where as even a couple sprite3d objects on galaxy s7 causes huge fps drops.

I feel this is pretty huge even for 2d games where you see games like hollow knight leveraging 3d for parallax effects. Also anything mixed mode like using meshes instead of sprites for character skins / items.

Another workaround I've used is to create an animated texture resource in a spreadsheet for parsing and import it into the engine. It has a max of 256 frames and very limited control but works for basic playback.

Really hoping this gets cherry picked for the 4.0 release.

@Calinou
Copy link
Member

Calinou commented Jun 26, 2020

Is this currently using immediate geometry to create quads on which the frames are drawn? Seems like the goal would be to put a SpriteFrames resource in 3d space which looks like would somehow need to inherit from a visual instance.

Yes. We should probably use ArrayMesh instead of relying on ImmediateGeometry. I don't know how difficult it would be; maybe it's actually really easy. I don't know if anyone actually attempted to rework it to use ArrayMesh 🙂

Seems like ios (ipad mini 2) handles lighting, normal maps and sprite3d objects much better in its pipeline where as even a couple sprite3d objects on galaxy s7 causes huge fps drops.

iOS devices have better performance and better GLES3 support across the board, so that makes sense.

I feel this is pretty huge even for 2d games where you see games like hollow knight leveraging 3d for parallax effects.

Godot supports pseudo "3D in 2D" since 3.2, without having to rely on the 3D engine for this.

Really hoping this gets cherry picked for the 4.0 release.

It's the other way around 😉 Pull requests in the master branch get cherry-picked to the 3.2 branch if they're backwards-compatible.

@clayjohn clayjohn self-assigned this Jun 26, 2020
@clayjohn
Copy link
Member

I'm going to just replace the backend of the Sprite3D with a proper mesh instead of using ImmediateGeometry.

@clayjohn
Copy link
Member

PR is up now! Please test it if you are having performance issues with Sprite3D!

#39867

@akien-mga
Copy link
Member

Fixed by #39867 in the 3.2 branch.

It's not fixed in master yet though, so keeping this issue open. A different fix will be used there.

@berarma
Copy link
Contributor

berarma commented Apr 28, 2021

This has already been fixed, hasn't it? Can we close it?

@akien-mga
Copy link
Member

See my comment above yours: #20855 (comment)

@berarma
Copy link
Contributor

berarma commented Apr 28, 2021

See my comment above yours: #20855 (comment)

Sorry, I thought it was a hanging comment. I didn't realize master is 4.0.

@Calinou Calinou changed the title Sprite3D and AnimatedSprite3D is rendering very slow Sprite3D and AnimatedSprite3D is rendering very slow (fixed in 3.x, not in master yet) May 18, 2021
@Calinou
Copy link
Member

Calinou commented Nov 3, 2021

This was resolved for master by #50014, closing.

To confirm this, I benchmarked 3.x against master to compare the current rendering performance of 900 Sprite3Ds in a blank scene (no lights, fallback environment).

Benchmark

OS: Fedora 34
CPU: Intel Core i7-6700K
GPU: GeForce GTX 1080
Resolution: 2560×1440
Godot versions: 3.x a05aefb, master fe6c65a

Testing projects:

master - Vulkan Clustered

Project FPS: 1198 (0.8 mspf)
Project FPS: 1195 (0.8 mspf)
Project FPS: 1198 (0.8 mspf)
Project FPS: 1198 (0.8 mspf)
Project FPS: 1199 (0.8 mspf)

master - Vulkan Mobile

Runs faster than Vulkan Clustered thanks to its lower base cost. However, its performance will fall behind compared to Vulkan Clustered in a more complex scene with many lights.

Project FPS: 1380 (0.7 mspf)
Project FPS: 1376 (0.7 mspf)
Project FPS: 1380 (0.7 mspf)
Project FPS: 1379 (0.7 mspf)
Project FPS: 1349 (0.7 mspf)

3.x - GLES3

Project FPS: 1691 (0.5 mspf)
Project FPS: 1704 (0.5 mspf)
Project FPS: 1667 (0.5 mspf)
Project FPS: 1690 (0.5 mspf)
Project FPS: 1689 (0.5 mspf)

3.x - GLES2

Despite the GLES2 renderer's lowest base cost (out of all the renderers tested here), this is by far the slowest result.

Project FPS: 546 (1.8 mspf)
Project FPS: 548 (1.8 mspf)
Project FPS: 550 (1.8 mspf)
Project FPS: 548 (1.8 mspf)
Project FPS: 550 (1.8 mspf)

@Calinou Calinou closed this as completed Nov 3, 2021
@Calinou Calinou changed the title Sprite3D and AnimatedSprite3D is rendering very slow (fixed in 3.x, not in master yet) Sprite3D and AnimatedSprite3D is rendering very slow Jan 26, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests