diff --git a/content/creator/sdk7/optimizing/art-optimization.md b/content/creator/sdk7/optimizing/art-optimization.md new file mode 100644 index 00000000..37bd3db7 --- /dev/null +++ b/content/creator/sdk7/optimizing/art-optimization.md @@ -0,0 +1,102 @@ +--- +date: 2021-01-11 +title: Art optimization +description: Optimize your scene's art to load fast and run smoothly for all players. +categories: + - development-guide +type: Document +url: /creator/development-guide/sdk7/art-optimization/ +weight: 1 +--- + +There are several aspects you can optimize in your scenes' art to ensure the best possible experience for players who visit them. This document covers some best practices when building 3D models, textures, and other art assets that can make a big difference in how fast your scene loads and how smoothly it runs for players. + +See [Code optimization]({{< ref "/content/creator/sdk7/optimizing/performance-optimization.md" >}}) for more information on how to optimize your scene's code. + +Keep in mind that many players may be visiting Decentraland using hardware that is not built for gaming, and via the browser, which limits how much of the hardware's processing power is available to use. The experience of visiting your scene should be smooth for everyone. + +The Decentraland explorer enforces many optimizations at engine level. These optimizations make a big difference, but the challenge of rendering multiple user-generated experiences simultaneously is a big one. We need your help to make things run smoothly. + +## Monitor Performance + +The best metric to know how well a scene is performing is the FPS (Frames Per Second). When running a scene in preview, open the debug panel to see it. You should aim to always have 30 FPS or more. + +To see the current FPS (Frames Per Second) of the explorer + +1) Open the debug panel, on the top-right corner of the screen. + + + +2) Check Average FPS in the **PERFORMANCE** tab. + + + +Check this value periodically, as it may vary over time depending on what you're doing in your scene. + +Keep in mind that the performance you experience in preview may differ from that in production: + +- Surrounding neighboring scenes might have a negative impact +- If multiple players are in the scene, rendering all their avatars can have a negative impact on the FPS +- The compression of the scenes' 3D models into asset bundles can have a positive impact +- Some players visiting your scene may be running on less powerful hardware + +It's always a good practice to try deploying your scene first to the [test environment]({{< ref "/content/creator/sdk7/publishing/publishing.md#the-test-server">}}) to do some more thorough testing. + +Always ask players for feedback. Never take for granted that how you experience the scene is the same for everyone else. + + +## Transparent materials + +Transparent materials are always more expensive in terms of performance than opaque materials. Avoid using blended transparencies. Blended transparencies have to bypass quite a few of the rendering optimizations. If possible, favor opaque or alpha tested geometry. See [Alpha materials]({{< ref "/content/creator/3d-modeling/materials.md#Alpha" >}}) and [Transparent Maps]({{< ref "/content/creator/3d-modeling/textures.md#Transparent-Maps" >}}) for more details. + +## Merging meshes + +Merging meshes is a great way to reduce the number of draw calls and improve performance. Instead of executing one draw call per mesh, you can merge them into a single draw call. + +Merge smaller objects which are close to each other and do not move or are animated into a single object. If the objects are on screen together 95% of the time, e.g. a table with chairs, or dishes on a table, then they can be merged together into a single mesh and will all be rendered at the same time. + +This however is not always ideal in cases where the meshes might not all be visible at the same time. Don’t merge all static objects together in a single scene, as objects which are outside of the view will still be rendered if they are merged with other visible objects. Try to merge clusters of visible objects together. + If the meshes are separate, the engine is able to cull the ones that are not visible. See [Meshes on large scenes]({{< ref "/content/creator/3d-modeling/meshes.md#Meshes-on-large-scenes" >}}) for more details. + + +## Face Culling + +Face culling is a great way to improve performance. It allows the engine to skip rendering the faces that are not visible. Only untoggle **backface culling** in your models if you need a model to be renderer in both sides (for example, a group of leafs of a tree made by 3D planes). + +The engine doesn't support any other face culling options than back-face culling, as it is not batch friendly. Billboard style art won’t work with that workflow, instead you can duplicate the mesh and invert normals to achieve the same effect. As an example a quad billboard would become two quads back to back, as a single mesh. + +## Texture atlases + +Texture atlases are a great way to reduce the number of textures in your scene. It allows you to share the same texture across multiple models. See [Shared textures betewen GlTF models]({{< ref "/content/creator/3d-modeling/textures.md#Shared-textures-betewen-GlTFs-models" >}}) for more details. + +You could even use a single texture as an atlas map, shared across all models in the scene. It's better to have 1 large shared texture of 1024x1024 pixels instead of several small ones. + +{{< hint warning >}} +**📔 Note**: Avoid using the same image file for both the albedo texture and the normal map or the emissive map of a material. Use separate files, even if identical. Assigning a same image file to different types of texture properties may introduce unwanted visual artifacts when compressed to asset bundles. +{{< /hint >}} + + +## Other Best practices + + +- Reduce triangle count. Often people will try to create vertex geometry for finer details on an object, instead try to use textures for finer detail, using normal maps, roughness maps etc. Think about merging adjacent meshes and removing non-visible triangles that are in between. Consider utilising simpler billboards to fake complex geometry where possible, e.g. a bush or grass is more often than not a set of billboards instead of finer geometric detail. + +- Merge materials. A lot of users will attempt to avoid textures and use material settings to colour the objects. The downside to this is that every object has its own material and will increase material count quite dramatically. Instead utilise a texture atlas of the colours you require and look up colours per object. There are two advantages to this: material reduction and therefore batching is improved as objects share materials and textures. This means batching in the engine is easier and more efficient. + +- _.glb_ is a compressed format, it will always weigh less than a _.gltf_. On the other hand, with _.gltf_ it's easy to share texture images by exporting textures as a separate file. You can have the best of both worlds by using the [following pipeline](https://github.com/AnalyticalGraphicsInc/gltf-pipeline), that allows you to have _.glb_ models with external texture files. + +- Avoid skinned meshes. They can drag down the performance significantly. + +{{< hint info >}} +**💡 Tip**: Read more on 3D model best practices in the [3D Modeling Section](/creator/3d-modeling/3d-models +{{< /hint >}} + +## Asset Bundle conversion + +After your scene is deployed, the Decentraland content servers run a process to compress every _.gltf_ and _.glb_ model in your scene to asset bundle format. This format is _significantly_ lighter, making scenes a lot faster to load and smoother to run on the browser. This proces is carried out automatically, so you don't need to do anything to trigger it, the process may typically take 15 minutes or more. + +{{< hint warning >}} +**📔 Note**: If you make _any_ change to a 3D model file, even if just a name change, it will be considered a new file, and must be converted to asset bundle format again. + +When planning an event in Decentraland, make sure you deploy your scene several hours in advance, so that the models are all converted to asset bundles by then. +{{< /hint >}} \ No newline at end of file diff --git a/content/creator/sdk7/optimizing/performance-optimization.md b/content/creator/sdk7/optimizing/performance-optimization.md index 86099860..fae6c1f2 100644 --- a/content/creator/sdk7/optimizing/performance-optimization.md +++ b/content/creator/sdk7/optimizing/performance-optimization.md @@ -1,7 +1,7 @@ --- date: 2021-01-11 -title: Performance optimization -description: Optimize your scene to load fast and run smoothly for all players. +title: Code optimization +description: Optimize your scene's code to load fast and run smoothly for all players. categories: - development-guide type: Document @@ -12,15 +12,41 @@ url: /creator/development-guide/sdk7/performance-optimization/ weight: 1 --- -There are several aspects you can optimize in your scenes to ensure the best possible experience for players who visit them. This document covers some best practices that can make a big difference in how fast your scene loads and how smoothly it runs for players that are on it or on neighboring scenes. +There are several aspects you can optimize in your scene's code to ensure the best possible experience for players who visit them. This document covers some best practices that can make a big difference in how fast your scene loads and how smoothly it runs for players that are on it or on neighboring scenes. + +See [Art optimization]({{< ref "/content/creator/sdk7/optimizing/art-optimization.md" >}}) for more information on how to optimize your scene's art. Keep in mind that many players may be visiting Decentraland using hardware that is not built for gaming, and via the browser, which limits how much of the hardware's processing power is available to use. The experience of visiting your scene should be smooth for everyone. -The Decentraland explorer enforces many optimizations at engine level. These optimizations make a big difference, but the challenge of rendering multiple user-generated experiences simultaneously in a browser is a big one. We need your help to make things run smoothly. +## Monitor Performance + +The best metric to know how well a scene is performing is the FPS (Frames Per Second). When running a scene in preview, open the debug panel to see it. You should aim to always have 30 FPS or more. + +To see the current FPS (Frames Per Second) of the explorer + +1) Open the debug panel, on the top-right corner of the screen. + + + +2) Check Average FPS in the **PERFORMANCE** tab. + + + +Check this value periodically, as it may vary over time depending on what you're doing in your scene. + +Keep in mind that the performance you experience in preview may differ from that in production: + +- Surrounding neighboring scenes might have a negative impact +- If multiple players are in the scene, rendering all their avatars can have a negative impact on the FPS +- The compression of the scenes' 3D models into asset bundles can have a positive impact +- Some players visiting your scene may be running on less powerful hardware + +It's always a good practice to try deploying your scene first to the [test environment]({{< ref "/content/creator/sdk7/publishing/publishing.md#the-test-server">}}) to do some more thorough testing. + +Always ask players for feedback. Never take for granted that how you experience the scene is the same for everyone else. -## Timing -### Video playing +## Video playing Playing videos is one of the most expensive things for the engine to handle. If your scene includes videos, make sure that only _ONE_ VideoTexture is in use at a time. You can have dozens of planes sharing the same VideoTexture without significant impact on performance, but as soon as you add a second VideoTexture, its effects on framerate become very noticeable. @@ -34,7 +60,7 @@ You should also avoid having videos playing in regions where they can't be seen. **💡 Tip**: When players are standing outside your scene, VideoTextures are not updated on every frame. This helps reduce the impact for surrounding scenes. It's nevertheless ideal only turn on the playing of any videos when players [step inside your scene]({{< ref "/content/creator/sdk7/interactivity/event-listeners.md#player-enters-or-leaves-scene">}}) . {{< /hint >}} -### Lazy loading +## Lazy loading If your scene is large, or has indoor areas that are not always visible, you can choose to not load the entire set of entities from the very start. Instead, load the content by region as the player visits different parts of the scene. This can significantly reduce the load time of the scene, and also the amount of textures and 3D content that the engine needs to handle on every frame. @@ -56,63 +82,71 @@ You can also toggle animations on or off for entities that are far or occluded. **💡 Tip**: When an entity is far away and small enough, it's culled by the engine. This culling helps at a drawcall level, removing entities from the engine is always better. This culling also doesn't take occlusion by other entities into account, so entities that are not so small but hidden by a wall are still rendered. {{< /hint >}} -### Async blocks +## Mutable vs non-mutable data -Blocks of [async code]({{< ref "/content/creator/sdk7/programming-patterns/async-functions.md" >}}) are processed in a separate thread from the rest of the scene, to prevent blocking the progress of everything else. +When dealing with data, you can choose to deal with mutable or with immutable (read-only) versions of a component. The `.get()` function in a component returns an immutable version of the component. You can only read its values, but can't change any of the properties on it. See [mutable data]({{< ref "/content/creator/sdk7/programming-patterns/mutable-data.md" >}}) for more details. -Any processes that rely on responses from asynchronous services, such as `getPlayerData()` or `getRealm()` should always run in async blocks, as they otherwise block the rest of the scene's loading while waiting for a response. The same applies to any calls to third party servers. +The `.getMutable()` function returns representation of the component that allows you to change its values. Use mutable versions only when you plan to make changes to a component. Dealing with immutable versions of components results in a huge gain in performance. -Note that the scene will be considered fully loaded when everything that isn't async is done. Async processes might still be running when the player enters the scene. Avoid situations where an async process results in the loading of an entity that could potentially get the player stuck inside of its geometry. +```ts +const immutableTransform = Transform.get(myEntity) -### Rely on Events +// the following does NOT work: +// immutableTransform.position.y = 2 -Try to make the scene's logic rely on listening to [events]({{< ref "/content/creator/sdk7/interactivity/event-listeners.md" >}}) as much as possible, instead of running checks every frame. +const mutableTransform = Transform.getMutable(myEntity) -The `update()` function in a [system]({{< ref "/content/creator/sdk7/architecture/systems.md">}}) runs on every frame, 30 times per second (ideally). Avoid doing recurring checks if you can instead subscribe to an event. - -For example, instead of constantly checking the player's wearables, you can subscribe to the `onProfileChanged` event, and check the player's wearables only when they've changed. +// the following DOES change the entity's position +mutableTransform.position.y = 2 +``` -If you must use a system, avoid doing checks or adjustments on every single frame. You can include a timer as part of the update function and only run the check once per every full second, or whatever period makes sense. +It's especially useful to deal with immutable versions of data when iterating over a lot of entities, and especially if the data is not changing. For example, if you're iterating over all the entities in the scene and checking their position, you can use the immutable version of the Transform component to read the position, and only fetch the mutable version when you need to change the position. -## Optimize 3D models -There are several ways in which your 3D models can be optimized to be lighter. +## Async blocks -When working with the [Creator Hub]({{< ref "/content/creator/scene-editor/editor-installation.md" >}}), you can see stats about the resources used by 3D models in your scene, and if they pass any of the [scene limitations]({{< ref "/content/creator/sdk7/optimizing/scene-limitations.md" >}}). - - +Blocks of [async code]({{< ref "/content/creator/sdk7/programming-patterns/async-functions.md" >}}) are processed in a separate thread from the rest of the scene, to prevent blocking the progress of everything else. -You can expand this menu to view details. +Any processes that rely on responses from asynchronous services, such as `getPlayerData()` or `getRealm()` should always run in async blocks, as they otherwise block the rest of the scene's loading while waiting for a response. The same applies to any calls to third party servers. - +Note that the scene will be considered fully loaded when everything that isn't async is done. Async processes might still be running when the player enters the scene. Avoid situations where an async process results in the loading of an entity that could potentially get the player stuck inside of its geometry. -Here are some tips for improving on these metrics: +## Rely on Events -- When possible, share textures across 3D models. A good practice is to use a single texture as an atlas map, shared across all models in the scene. It's better to have 1 large shared texture of 1024x1024 pixels instead of several small ones. +Try to make the scene's logic rely on listening to [events]({{< ref "/content/creator/sdk7/interactivity/event-listeners.md" >}}) as much as possible, instead of running checks every frame. - > Note: Avoid using the same image file for both the albedo texture and the normal map or the emissive map of a material. Use separate files, even if identical. Assigning a same image file to different types of texture properties may introduce unwanted visual artifacts when compressed to asset bundles. +The `update()` function in a [system]({{< ref "/content/creator/sdk7/architecture/systems.md">}}) runs on every frame, 30 times per second (ideally). Avoid doing recurring checks if you can instead subscribe to an event. -- _.glb_ is a compressed format, it will always weigh less than a _.gltf_. On the other hand, with _.gltf_ it's easy to share texture images by exporting textures as a separate file. You can have the best of both worlds by using the [following pipeline](https://github.com/AnalyticalGraphicsInc/gltf-pipeline), that allows you to have _.glb_ models with external texture files. +For example, instead of constantly checking the player's wearables, you can subscribe to the `onProfileChanged` event, and check the player's wearables only when they've changed. -- Avoid using blended transparencies. Blended transparencies have to bypass quite a few of the rendering optimizations. If possible, favor opaque or alpha tested geometry. +Likewise, you can use the `onChange()` method to subscribe to changes in any component, and only run the check when the component's value changes. See [Subscribe to changes]({{< ref "/content/creator/sdk7/architecture/subscribe-to-changes.md" >}}) for more information. -- Avoid skinned meshes. They can drag down the performance significantly. +```ts +MyComponent.onChange(myEntity, (componentData) => { + console.log(componentData.value1) + }) +``` -{{< hint info >}} -**💡 Tip**: Read more on 3D model best practices in the [3D Modeling Section](/creator/3d-modeling/3d-models +{{< hint warning >}} +**📔 Note**: Do not use `onChange()` inside a System, as that would subscribe a new copy of the function on every frame of the game loop, and could potentially lead to crashes. {{< /hint >}} -### Asset Bundle conversion +You can also combine this approach with [Querying components]({{< ref "/content/creator/sdk7/architecture/querying-components.md" >}}), to bulk-subscribe every entity in the scene that has a certain component to their own function. -About once a day, the Decentraland content servers run a process to compress every _.gltf_ and _.glb_ model in every newly deployed scene to asset bundle format. This format is _significantly_ lighter, making scenes a lot faster to load and smoother to run on the browser. +```ts +export function main() { + for (const [entity] of engine.getEntitiesWith(MyComponent)) { + MyComponent.onChange(entity, (componentData) => { + if (!componentData) return + console.log(componentData.value1) + console.log(componentData.value2) + }) + } +} +``` -{{< hint info >}} -**💡 Tip**: When planning an event in Decentraland, make sure you deploy your scene a day in advance, so that the models are all converted to asset bundles by then. If you don't want to spoil the surprize before the event, you can deploy a version of your scene that includes all the final 3D models in the project folder, but where these are not visible or where their size is set to 0. -{{< /hint >}} +If you must use a system, avoid doing checks or adjustments on every single frame. You can include a timer as part of the update function and only run the check once per every full second, or whatever period makes sense. -{{< hint warning >}} -**📔 Note**: If you make _any_ change to a 3D model file, even if just a name change, it will be considered a new file, and must be converted to asset bundle format again. -{{< /hint >}} ## Connectivity @@ -134,31 +168,6 @@ Avoid making adjustments to the UI on every frame, those are especially costly a Avoid having many hidden UI elements, these also have an effect on performance even if not being rendered. When possible, try to create UI components on demand. -## Monitor Performance - -The best metric to know how well a scene is performing is the FPS (Frames Per Second). In preview, you can see the current scene FPS in the debug panel. You should aim to always have 30 FPS or more. - -In the deployed scene, you can toggle the panel that shows these metrics by writing `/showfps` into the chat window. - -One of the main bottlenecks in a scene’s performance is usually the sending of messages between the scene’s code and the engine. - -When you run a scene in preview, note that on the top-right corner it says “Y = Toggle Panel”. Hit Y on the keyboard to open a panel with some useful information that gets updated in real time. - -As you interact with things that involve messages between the SDK and the engine, you’ll notice the ‘Processed Messages’ number grows. You should closely watch the ‘Pending on Queue’ number, it should always be 0 or close to 0. This tells you how many of these messages didn't get to be processed, and got pushed to a queue. If the ‘Pending on Queue’ count starts to grow, then you’ve entered the danger zone and should think about doing more optimizations to your scene. - -{{< hint warning >}} -**📔 Note**: Don’t keep the panel open while you’re not using it, since it has a negative impact on performance. -{{< /hint >}} - -Keep in mind that the performance you experience in preview may differ from that in production: - -- Surrounding neighboring scenes might have a negative impact -- The compression of the scenes' 3D models into asset bundles can have a positive impact -- Some players visiting your scene may be running on less powerful hardware - -It's always a good practice to try deploying your scene first to the [test environment]({{< ref "/content/creator/sdk7/publishing/publishing.md#the-test-server">}}) to do some more thorough testing. - -Always ask players for feedback. Never take for granted that how you experience the scene is the same for everyone else.