godot
godot copied to clipboard
Rework scene preview thumbnails
Supersedes #102263 Will need to disable code from #96544 because of conflict
Make scene files (.glb, gltf, .tscn, .scn) to always generate missing/outdated thumbnails, captures in a fixed angle, and fits all visual elements into the image, operation is done on a separate thread. Respects environment settings at rendering/environment/defaults/default_clear_color.
After this PR, will lose ability to generate custom thumbnails, open and save a scene still result in fixed view thumbnails, but as discussed in #102263, I think it would be better to implement custom thumbnails in another better approach.
The top one problem to solve for this PR is find a way to instantiate scenes without triggering tool scripts, still experimenting, it will be helpful if someone knows how to do this. (Edit 2024/2/3: done)
This PR also fixes preview image caching, when testing, file has to be changed to generate a new thumbnail. (md5 != last_md5)
Caveats
- Loses the ability to capture custom thumbnails with view.
- If scene has particle with large bounding box, preview camera will be set far away because of it. Meshes might be small in view. This is done intentionally, to accommodate scenes that are basically particle effects.
3D
Camera will always point at world center, dolly out at 3/4 view angle, until all contents fit into screen. For particles, fast-forwards them it to (lifetime * 0.5) in order to render something, with fixed seed.
Practical:
Production:
2D
Render in two passes, 2D and GUI, then combines 2D + GUI captures for the final thumbnail image.
Does NOT account the world center for 2D capture, this decision is made because the use case that will benefit most from scene thumbnails are prefabs, which normally don't care about world coordinates, thus dedicating more pixels to actual contents.
TODO:
- [x] Generate scene thumbnails on file create/rename/save/duplicate/import
- [x] Only apply preview lighting to scenes that have 0 light nodes
- [x] Prevent tool scripts from running when generating thumbnails
- [x] Support for 2d scenes (Scenes with 0 Node3D count)
- [x] Make thumbnail creation thread-safe
- [x] Do proper cache validation at
editor_resource_preview.cppto prevent outdated thumbnails
NOTES
-
Rip off scripts by using
SceneState::get_bundled_scene()andSceneState::set_bundled_scene()to change the data of PackedScene before instantiating it. -
In
editor_resource_preview.cpp, suspect cache validation is somewhat wrong within these lines, will return early with a outdated thumbnail image:- line 276-281
- line 305-307
- line 488-491
-
Need to disable the old
EditorNode::_save_scene_with_previewfor this PR -
Will result in outdated thumbnail if user quickly (save->edit->save) a scene within ~0.5 seconds, this should not happen, is there something wrong with how
EditorResourcePreview::queueis managed? -
Control anchors do not play well with Camera2D, thus 2d scene thumbnail need to be rendered twice (gui + node2d)
-
It might be possible to draw viewport texture outside of the tree using
RenderingServer::viewport_create()withEditorResourcePreviewGenerator::draw_requester.request_and_wait(), also being thread safe.
- Production edit: This partially addresses https://github.com/godotengine/godot-proposals/issues/7232 (3D only).
The top one problem to solve for this PR to work is to find a way to instantiate scenes without triggering tool scripts
I'm sure there is a valid reason for this, but I'm curious since I haven't given it much thought, but what is the issue with tool scripts?
The reason I ask is because I have a project that uses tools scripts to automatically and dynamically generate objects to build out a scene when it is first opened, so if the tool scripts were not ran it would be an empty or broken scene with nothing to show for a thumbnail.
I'm sure there is a valid reason for this, but I'm curious since I haven't given it much thought, but what is the issue with tool scripts?
Mainly about unexpected behavior, running scripts even when scenes are not opened might confuse many, like if someone decided to print() messages or access filesystem in their tool scripts. As for stability concerns, I think if a tool script will crash the editor, it might be written in a very wrong way at the first place.
I think in general if a user is using tool scripts it is at their own risk, and they should handle edge cases themselves, but new functionality should still accommodate their existence because they're vital for various workflows, users just need the tools to be able to minimize that risk.
This isn't to say that there is a solution to this problem, and we may just have to disable tool scripts while generating thumbnails and deal with it, but just food for thought.
Also in this PR, thumbnails are generated in a separate thread, and works by adding the preview scene under the current EditorNode to make it render (in a SubViewport), this means if your tool script is not thread-safe (by calling add_child() in _ready() ), it will cause errors.
I experimented if creating a separate SceneTree is possible, turns out it's not easy, and tool script can do more than manipulating the tree, raises risk. So I decided to focus on providing this feature in a safest way, with scripts disabled.
This is looking very good! I think that, if merged, it would be enough to close https://github.com/godotengine/godot-proposals/issues/7232. Additional tweaks and personalization settings can be proposed by users after real world usage.
I'm very glad someone got to implementing this!
I discovered there's no universal way to accurately determine the global Rect2 of every Node2D types (some can have textures, or meshes with various sizes, and it does not affect their transform2d, rect2), I decided to only address the most important types of Node2D into Rect2 calculation, where it will cover most use cases.
Fow now only Sprite2D is accounted into thumbnails, plan to implement for AnimatedSprite2D, MeshInstance2D, MultimeshInstance2D, TileMapLayer, Polygon2D, TouchScreenButton later. By then will consider 2D scene thumbnail as completed.
2D scene thumbnail is completed, Node2D types that participates scene Rect2 calculations are: Sprite2D, AnimatedSprite2D, MeshInstance2D, MultimeshInstance2D, TileMapLayer, Polygon2D, Line2D, TouchScreenButton
One limitation for 2d thumbnail is the max render viewport size is set to 2048x2048 for now (consider web editor could only handle 2K textures at most in rendering, correct me if wrong), if the scene is bigger than that, preview area will be cropped and centered.
All planned features completed. Squashed and green lighting this. Now for testing and clean up, should have plenty of time to bug fix this before 4.5 cycle
consider web editor could only handle 2K textures at most in rendering, correct me if wrong
The universal baseline across all platforms Godot 4 runs on is 4096x4096 nowadays.
The universal baseline across all platforms Godot 4 runs on is 4096x4096 nowadays.
Thanks to clarify that, anyway this should not be a problem now. I updated to only zoom out the preview Camera2D when capturing 2d thumbnails (previously is done by scaling the SubViewport size).
I get an issue in 2D. The 2D platformer demo freezes when opened in the editor after the "restoring scene layout" popup is done running. This seems to occur with every 2D project I've tried.
Issue caused by renderer being set to Compatibility, EditorResourcePreview will force all preview plugins to run on the main thread. I tweaked the code to have it run safely on single thread. I'm experimenting on a workaround to enable multi-thread if possible.
Edit 2025/02/28:
In rasterizer_gles3.h, this line was written:
_ALWAYS_INLINE_ bool can_create_resources_async() const { return false; }
I have no idea why this is set to false, so let's leave it as it is. We should only get back to this if anyone ever run into performance issues when generating previews using Compatibility renderer.
Changes made:
- Resolve editor freeze on generating scene preview when renderer is set to Compatibility
- Bring back progress bar when saving scenes
- Enable SSAA and MSAA(8x) when creating scene previews
Note that player.tscn is very small in the 3D platformer demo because the Decal node's AABB is taken into account.
Decal is not taken into account for AABB calculation in this PR, the actual problem is mesh with skeletal animations, since skinning is ignored when generating preview, what you see in the preview is the original mesh without any scaling done by skeleton or animation (The player mesh you mentioned is 3x smaller without skeleton). This could be resolved by applying skeleton to meshes when generating previews, but it's not straight forward, I will need some time for this.
This PR also gets rid of the progress dialog that appears when saving a scene I consider this to be a good change for scenes that save in less than 1 second – this should also help resolve https://github.com/godotengine/godot/issues/78753.
I brought back the save scene progress bar now, like you said, it will cause confusion when user waits for large scenes to be saved. The issue you mentioned is caused by Godot keeps saving scenes that are not modified when user presses play, this should not be relevant to this PR.
Additionally, you could do the same as https://github.com/godotengine/godot/pull/97468 to improve preview quality using MSAA and SSAA together.
Done. For 3D previews, SSAA (x2.0) + MSAA_3D(x8), scaling clamped to 4K at max. For 2D previews, MSAA_2D(x8)
For a future PR, we can also further improve the preview by adding a checkered floor below the object and enabling shadows on the DirectionalLight3D. This way, you get a sense of the object's scale.
In my opinion, this should be discussed carefully in a proposal. A 3D tscn file can be a collection of meshes, or particles, or even nothing visible. For a 3D scene that is mainly particles, meant to be used as VFX prefabs, this approach might not fit well. Not to mention if a scene has no geometry at all, drawing a checker floor for its preview image could confuse the users.
Changes made:
- Don't use 2D MSAA in scene previews when renderer is set to Compatibility
- Respects project settings
rendering/textures/canvas_textures/default_texture_filterandrendering/textures/canvas_textures/default_texture_repeatwhen generating 2d previews. - Clean up resources properly when generating preview for scene that contains GridMap.
- Remove
EditorNode::_save_scene_with_preview, routeEditorInterface.save_scene_as()to always callEditorNode::_save_scene, also updates comments and document.
damn, accidentally merged the whole change from master, let me revert back
Good again. Sorry for the disturbance.
Do you think it will be possible to merge this in time for 4.5?
Would need a rebase, first of all
Changes made:
- Rebase to master commit 215acd52e82f4c575abb715e25e54558deeef998
- Account for nested tool scripts (scene with tool scripts inside a scene) while generating thumbnails:
// editor/plugins/editor_preview_plugins.cpp:903
bool EditorPackedScenePreviewPlugin::_remove_scripts_from_packed_scene(Ref<PackedScene> pack) const {
...
if (Object::cast_to<PackedScene>(edited_variants[i])) {
_remove_scripts_from_packed_scene(edited_variants[i]); // Account for nested tool scripts
}
Do you have a test case project for reviewing?
Changes made:
- Fixed various bug since rebased to master at 215acd52e82f4c575abb715e25e54558deeef998
- Fixed bug in
EditorMaterialPreviewPluginwhere material resources will be assigned to a incorrect thumbnail while using Forward+ or Mobile renderer. (Basically shares the same fix in this PR)
@fire Test case project: rework_scene_thumbnails_test_proj.zip Also updated in main comment.
Tested with my project, I get spammed with errors:
Looks related to custom signals.
Also most of my scenes ended up with black previews for some reason:
No idea what could be causing it. Level scenes all have similar structure and size, but not all of them are black. Non-level scenes (enemies, objects etc.) all have black previews.
I also have a 2D scene with 3D object. Only the 3D object is rendered in the preview, the 2D part is completely skipped (even though the scene is 2D and the object is shown in SubViewport).
@KoBeWi
Tested with my project, I get spammed with errors: Looks related to custom signals.
Confirmed to only happen with custom signals that are connected via the "Node" dock, I'll look into this.
Also most of my scenes ended up with black previews for some reason: No idea what could be causing it. Level scenes all have similar structure and size, but not all of them are black. Non-level > scenes (enemies, objects etc.) all have black previews.
It'll help me a lot if you can provide further information:
- Do you have a 3D node somewhere in those scenes?
- Do you set your default environment's color to black?
- Is there any node in those scenes that is able to render the whole scene black when visible?
I also have a 2D scene with 3D object. Only the 3D object is rendered in the preview, the 2D part is completely skipped.
This is done intentionally, in this PR we follow this sequence of logic:
- Count the scene's Node3D, if is greater than 1, render a 3D preview.
- Count the scene's (Node2D + Control) node, if is greater than 1, render a 2D preview.
- Return an empty image if no conditions above are met.
As I initially thought there's no reason to have 3D nodes inside a 2D scene, if it does, it should be 3D scene with 2D nodes presenting as UI. But after looking at your post I re-think it, the quickest fix now is ignore anything under a SubViewport node and don't accumulate its children to 3d, 2d node counts. So if a 2D scene has SubViewport rendering 3D scene it will still be considered a 2D scene and render a 2D preview.
Secondly, I don't merge 3D + 2D previews for a reason, when have a 3D scene with a 2D scene placed inside used as a UI, the preview image will be filled up by 2D preview even if it's a 3D scene.
Thank you for your time, appreciated.
Do you have a 3D node somewhere in those scenes?
No.
Do you set your default environment's color to black?
Yes.
Is there any node in those scenes that is able to render the whole scene black when visible?
No.
As I said, the level scenes are very similar. A Node2D root with TileMapLayers and some entities. Other scenes are mostly CharacterBody2D with sprite.
I could try copying them to another project and see if the problem remains.
@KoBeWi
I could try copying them to another project and see if the problem remains.
I suspect it'll remain the same, if you have time, can you copy one .tscn that has black previews, try deleting nodes inside it and re-save the file each time until the preview image shows up correctly? It might help us find out what specific node is causing the problem and narrow down the scope.
I'd tested a scene with multiple TileMapLayers and it's fine on my side.
No idea what was that bug, maybe it was caused by old previews (generated before this PR). The reason my scenes stayed black was that I tried to recreate the thumbnail without any changes. After I simply renamed a node and saved scene, the thumbnail was generated correctly. I then proceeded to delete thumbnail cache folder and restart the project. Everything it being generated properly.
Sounds like the old cache should be invalidated somehow. This PR will have no effect if people have old previews (they are stored in editor data folder, so deleting .godot won't help).
Sounds like the old cache should be invalidated somehow.
Okay I'll do versioning for cache files (store a version=2 property), for now it just validates cache with scene file's md5 checksum so that was expected to happen on old projects.
This is done intentionally, in this PR we follow this sequence of logic: Count the scene's Node3D, if is greater than 1, render a 3D preview. Count the scene's (Node2D + Control) node, if is greater than 1, render a 2D preview. Return an empty image if no conditions above are met.
I think it might be clearer for users to simply render 2D or 3D based on the type of the root node
Changes made in commit c8614c6:
- Increment preview thumbnail metadata version to 2, now old projects loaded after this PR will re-generate all thumbnails.
- Fix error when generating previews of a scene that has custom signals connected via "Node" dock.
- When counting 2d/3d nodes in a scene to determine whether to generate a 2d/3d preview, do not count nodes under a
SubViewportnode. On the other hand, if the root node is set toSubViewport, its children will still be counted.
I think it might be clearer for users to simply render 2D or 3D based on the type of the root node
@JoNax97 That can be discussed. The behavior I use now is inherited from the old way how we captured thumbnails from current viewport view, it determines the preview type based on 2d 3d node counts, not the root node, and I don't know if it will confuse the users if that behavior has changed. Like some users might use a "WorldEnvironment" as root like a monster, and still expects the thumbnail to generate its 2d/3d nodes' preview.
@KoBeWi Bugs you encountered yesterday should all be fixed now.
To make previews render correctly, there's some hacky code such as waiting frames from RenderingServer, requesting viewports to render multiple times involved in this PR. I have stress-tested the code (eg: run the game while previews are still generating) and never encountered any bugs or crashes. But if someone tested out some instability, I can move the whole process back to single-threaded, or stop calling RenderingServer directly (Which is already done in this way with Mesh, Material previews, and I see no issue reports) and use a workaround.
Tested with a larger project (1GB in total, 100+ glb and .tscn files, lots of 2K textures)
All thumbnails are created correctly, but have some issues:
- When using ~~Forward~~/Mobile renderer (Multi threaded thumbnail creation), the editor stays responsive, but the viewport flickers (to a point it's irritating) while generating thumbnails
- When using Compatibility renderer (Single threaded thumbnail creation), the editor freezes until all thumbnails in the project are generated (waited for 2 minutes in my project)
I'll investigate issue 1 tomorrow, I feel like it's not a hard to fix issue.
For issue 2 I have no idea how to get around it, since it's a renderer limit. Some "solutions" in my mind:
- Spawn a progress bar saying "Generating scene asset previews..." and tell user to wait.
- Only render previews for assets that are currently visible in filesystem dock's grid view, but a search prompt might still freeze the engine for a while if the results have scene files.
Since re-creating thumbnails for the whole project with 100+ scene files should not occur frequently, I guess I'll just slap a progress bar if the thumbnail creation queue is over 10 or more.
You can avoid blocking the editor completely by waiting a frame between each preview. Though it will make the generation slower and the editor might become laggy.