godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
89.92k stars 21.06k forks source link

Textures Will Not Render on Older Versions of iOS (GLES 2 - HTML5 Export) #60914

Open MossFrog opened 2 years ago

MossFrog commented 2 years ago

Godot version

3.4.4

System information

iPhone 7 iOS 13.6.1 GLES2

Issue description

Textures are rendered as plain white on older versions of iOS, Anything 15.0 or above will render just fine on Safari and Chrome when using the HTML5 exports. Pvrtc / Etc 2 / Etc / S 3 Tc Texture compression methods are all enabled and the import files have been refreshed multiple times. Png lossless Compression is also enabled. The issue seems to stem from the non-backwards compatability of the current Pvrtc texture compression algorithm.

More recent models of iPhones do not seem to have this issue and an iOS update does seem to relieve the issue on older devices, yet this is still an issue since most devices still run older versions of iOS and the project currently being developed targets the web on a wide range of platforms and devices.

Tested on two separate iPhone 7's one Running iOS 13.6.1 the latter Running iOS 15.4.1

Export is from a GLES2 Project for wider compatability.

Steps to reproduce

Simply add a texture to a material and stick it on any mesh or import a GLTF model with any relevant texturing. The issue persists with both transparent and non transparent textures then Export the game for HTML5 and run it on a local dev server using your HTTP server of choice (python3 -m http.server 8080 is an easy way to get the relevant directory hosted).

I have added an example project with 3 cubes with either a textured node parent or a textured mesh.

Minimal reproduction project

Texture Test.zip

akien-mga commented 2 years ago

The issue seems to stem from the non-backwards compatability of the current Pvrtc texture compression algorithm.

I'm not sure PVRTC would be used in the Web export in the first place. It likely uses ETC on mobile and S3TC on desktop. Can you try with PVRTC disabled to force using ETC?

MossFrog commented 2 years ago

Disabling ETC 2 and PVRTC Does not fix the issue still all textured meshes appear white on older iOS devices, plus now on modern android devices the leftmost cube in the Minimal Reproduction Project appears black. (I Refreshed the Imports and re exported the project after the updates)

MossFrog commented 2 years ago

I just want to ensure maximum compatibility with devices when serving the game over the web. This includes desktop PC's using a browser or mobile devices.

Calinou commented 2 years ago

I just want to ensure maximum compatibility with devices when serving the game over the web. This includes desktop PC's using a browser or mobile devices.

In this case, using ETC1 should be all you need. GLES2 only mandates support for ETC1, not anything else. On desktop platforms, S3TC is a better-looking compression algorithm, but it's not supported on mobile platforms.

We don't have access to iOS devices running older versions, so it's unlikely we can find a solution to this problem.

MossFrog commented 2 years ago

I see, thank you for the feedback. It's a shame because the engine holds a lot of potential for web based 3D applications but is hindered by only a couple of small issues such as slow load times, badly optimized skinned/animated mesh performance on GLES 2 and backwards compatibility issues with older and/or weaker mobile devices.

MossFrog commented 2 years ago

I have found a temporary fix to this issue on devices running iOS 13-13.9 Deleting ALL the import files for textures in PNG and JPG formats and then switching to the editor on a Non-3D scene will cause the textures to re-import properly, then exporting the game for the web will allow the game to run on said devices. But switching to any 3D texture utilizing scene within the editor will cause some of the textures to auto-reimport causing the following export to not work properly again.

On even older devices (iPhone 6 and below) running their respective latest iOS versions 12.5.5 (The latest release intended to keep these devices compatible with most applications) The game will not run properly and then crash later on during gameplay causing the page to attempt to reload the game, attached is the debug log from these devices.

Editor Version 3.4.4 Stable Arch Linux GLES2 Export to HTML5 Texture import Settings: S3TC, ETC enabled Force PNG Disabled Webp Compression Level 0 (I believe Webp is not correctly implemented on iOS)

Note that this project is targeting the Facebook instant games market where a large majority of the users are still using iOS 12.5.5 and older devices unlike the modern app store.

Note the load times are still abysmally slow on older devices, is there any way to optimize the load time of the application? The initial WASM load takes a large chunk of the time, the assets loading after the engine has revved up takes a shorter amount of time.

As for performance on low end devices I have realized that performing any sort of calculations within the physics fixed update loop instead of the regular frame based update loop is ill advised and will cause a large noticeable framerate loss on low end android devices (Even modern ones such as the Samsung Galaxy A10)

image

Calinou commented 2 years ago

Note the load times are still abysmally slow on older devices, is there any way to optimize the load time of the application? The initial WASM load takes a large chunk of the time, the assets loading after the engine has revved up takes a shorter amount of time.

You can compile an HTML5 export template with unused features disabled, but there isn't much else that can be done to optimize WASM startup speeds. Streamed instantiation is already in place since Godot 3.3.

In general, if having the smallest bundle size for an HTML5 game is your goal, I recommend going for Phaser (2D) or Babylon.js (3D). A full-featured C++ game engine compiled to WebAssembly can't compete with the file sizes of pure JavaScript engines.

MossFrog commented 2 years ago

The compressed project file sizes are still manageable hovering around 3-8 Megabytes for a fully fledged project with multiple 3D assets / Textures / Sound effects. We already utilize tools such as Phaser and Three JS for most games but their rendering qualities are much lower and feature sets are lacking compared to Godot, not to mention the lack of an editor and a Node based development system. I will benchmark the load times today after stripping the binaries using a custom build template and compressing the output files with Gzip and other algorithms to see if there is a noticeable difference.

The iOS 12.5.5 bug still persists by the way, if anyone has an old iPhone 6 or lower device or maybe an earlier iPad family device sitting around.

clayjohn commented 2 years ago

It sounds like the texture issue is an issue with importing textures with sRGB formats. To test that, can you try importing the textures with the "sRGB" property set to "disabled" (instead of detect) and the "detect 3D" property unchecked (by default it is checked).

With respect to the skinning/animation, do you have software skinning enabled in the project settings? We have found that such low end devices really benefit from handling skinning on the CPU.

MossFrog commented 2 years ago

Yes I have Software skinning enabled, no worries on that end, I ran a profile of the software and found that a majority of the performance loss on lower end android devices was caused mostly by the calculations being done in the physics_process() update loop instead of the regular frame/delta based one. Since each animated model had its own respective script running this was causing a majority of the loss. Skinned meshes still do not perform perfectly but they are adequate enough for 40-60fps gameplay on low end devices.

The only remaning things to solve are the slow load times and the compatibility issues on older hardware. I will try your sRGB tip @clayjohn cheers for the pointer, but i doubt this will fix the 12.5.5 issue as the game just outright bugs out on those devices failing to run the code properly I posted the debug log and just seem to get the "bufferData size == 0" error. Maybe its due to it not being able to load a primitive mesh into memory? All models are in GLTF format by the way.

I'll check the textures now! Cheers!

MossFrog commented 2 years ago

Superb @clayjohn the textures work even on 12.5.5! Now I just have to figure out why the game crashes or does not function properly on those devices! Any ideas?

[Error] WebGL: INVALID_VALUE: bufferData: size == 0 bufferData _glBufferData (Crazy Kick - HT5.js:9:198483) wasm-stub <?>.wasm-function[19351] <?>.wasm-function[9671] <?>.wasm-function[19354] <?>.wasm-function[37868] <?>.wasm-function[7895] <?>.wasm-function[27013] <?>.wasm-function[14950] wasm-stub (anonymous function) callUserCallback (Crazy Kick - HT5.js:9:101160) runIter (Crazy Kick - HT5.js:9:102646) Browser_mainLoop_runner (Crazy Kick - HT5.js:9:100698)

MossFrog commented 2 years ago

Disabling Webp Compression was the final nail in the coffin for iOS Version 12.5.5! Thank you all so much for the help.

Also creating a custom export template with a majority of the unused modules removed improved load times and reduces the final project compressed size by about 800 Kilobytes! Hopefully with a couple of more tweaks the engine will work flawlessly on most devices!

Might be worth adding hints for web exports especially for those targeting mobile devices!

The total game currently sits at 10 levels (All 3D) and only takes up about 3.9 Megabytes Compressed! Cheers All!

lawnjelly commented 2 years ago
  • Don't perform too many calculations in the physics process loop of nodes (For performance on lower end devices)

There is nothing "special" in the normal course of events (that I'm aware of off the top of my head) which will obviously make operations during physics_process any slower than during process. This could be a bug that needs investigation, or something specific you are doing in your project which is causing this effect (perhaps accessing something in the Rasterizer which is causing a stall or something). An issue with an MRP is the way to go on this.

This could alternatively be just how physics ticks / frames work. If the frame rate drops to say, 20fps on a low end device, process will run 20 times a second, and physics will still run at 60 ticks per second (if you have it set at default). So the code in the physics tick could appear "slower" but it is just running 3x as often.

Also, if this is a 3D game, make sure to use a VisibilityEnabler on each animated / skinned object, so that when off screen it does as little processing as possible. I have an inkling feeling that it does only shut off the skinning when you use an enabler, the software skinning doesn't have a good way of determining ahead of time when off screen.

MossFrog commented 2 years ago

No its not just the physics running 3x faster, it is a noticeable framerate drop since the camera movement, animation updates are not bound by the physics update rate. You nailed the reason though, running 60 updates per second on a CPU bottle-necked device (Such as the Samsung Galaxy A10) utilizing simple transform/basis mathematics and trigonometry (I'm not accessing any materials/shaders/nodes) causes the issue. If you leave said operations within the regular update loop the game will balance this issue out with the framerate, so lower framerate = less calculations in the latter lower framerate => high amount of calculations. When this is done over multiple nodes in a single threaded browser environment you will lose a large chunk of your CPU time to physics calculations rather than updating/rendering the next frame.

I tested disabling animations/skeletons/skinned meshes and reducing the rendered polygon count in the scene these only improved the FPS by about 3-5. The software skinning isn't even the main culprit here because you can disable it and still get decent results.

If you are curious this is the code that is running on a kinematic body causing most of the loss, the body has an animated mesh as a child. If placed within the physics update cycle the code will cause a huge performance hit on lower end devices.

velocity.y += gravity * delta if playerDetected && !hasKicked && jumpDir == 0 && detectedPlayerRef.physicsEnabled: animationPlayerRef.play("Running", 0.2) look_at(detectedPlayerRef.get_translation(), Vector3.UP) velocity = -transform.basis.z * speed if velocity.y > 0: velocity.y = 0 velocity = move_and_slide(velocity, Vector3.UP)

velocity is a variable under the kinematic body the detectedPlayerRef is a reference kept of the player detected by the body.

lawnjelly commented 2 years ago

If this is a 3D game, you might also want to try using 3.5 and physics interpolation. Depending on the game, you may be able to lower the physics tick rate to e.g. 20tps, and the interpolation should be super cheap. This can really be beneficial if you are CPU limited for some reason in the game logic.

But it is hard to say without seeing the game / what you are doing in process etc.

EDIT: I'd try commenting out parts of that physics update you posted to see the effect, and find which bit is slowing it. I don't know how often that if section is running, whether it is rare or continuous.

It could be e.g.

Also calling lookat on a physics node might be screwing it up. You generally don't want to be setting the transform on a physics node every tick (this may be happening indirectly via lookat). Setting the transform of a physics node is bad except when done rarely, because it throws the physics out of sync and causes all kinds of processing I expect. What you could do e.g. is run the physics, then have a mesh as a child of the physics node that then has lookat called on it.

If you want to do a lookat in general on a physics object, there are two good ways of doing it: 1) Apply rotation forces to the physics object manually 2) Apply the lookat to a child object (e.g. MeshInstance) and do not affect the rotation of the physics

MossFrog commented 2 years ago

True this would help alleviate the issue and still be able to used a fixed update rate on calculations, Cheers!

But I don't want to risk causing further compatibility and stability issues since the game is due to release within a week or so after a couple of dozen more levels. I would love to give that a go after the 3.5.x Stable release though!

I posted the script, it's running on 5 nodes (Kinematic Bodies) in a scene. The rest of the logic is event based so it's only called once.

image

If you move all the logic to the physics process this will cause a major performance hit.

clayjohn commented 2 years ago

I am reopening this to track needed documentation changes. While it appears the bulk of the issues come from driver bugs in the target devices, most importantly we should document:

  1. That sRGB import may not be supported on older devices (in fact in Godot 4.0, the OpenGL renderer won't even try to use them)
  2. That WebP may cause problems on older devices that don't properly implement WebP

The other tips are also useful, and are documented already, but may be worth documenting them all in one place. It would definitely be helpful to have all our low-end advice gathered together in one place.

Calinou commented 2 years ago

That WebP may cause problems on older devices that don't properly implement WebP

The WebP decoder is compiled to WebAssembly, so it does not require the device to have hardware acceleration for WebP decoding (which is very uncommon anyway). However, there's a bug in libwebp that causes this error on old iOS devices on WebAssembly. This is very difficult to track down, so I think we should just document the workaround.

For reference, libwebp required custom patches back in 2014 to build successfully for asm.js (WebAssembly's predecessor). @punto- wrote those patches, maybe they know more about the current situation.

MossFrog commented 2 years ago

Could I suggest adding a sub-category to the documentation of the engine concerning exporting for the web targeting mobile devices? Would be much less of a pain for those targeting these platforms, a good example is GOTM.io the games mostly run on mobile devices but on some either the site does not even load or the games do not run.

The main goal should be to get the GLES 2 Web exports to the level of web based renderers/engines. An example list of these are a provided by facebook for their instant games and a lot of other companies are heading this way too with their own ecosystems popping up for social media platforms. https://developers.facebook.com/docs/games/build/instant-games/compatible-engines

And web-gaming platforms such as https://poki.com/ are on the rise again where we can see games made in just about any other engine except Godot.

Calinou commented 2 years ago

@MossFrog If you keep Force PNG enabled but enable sRGB texture conversion again, does it still work on iOS? I'm not sure why sRGB texture conversion would affect this issue – it's an operation that happens purely at import-time.