HTML5 is becoming more and more popular as an emerging field. However, in the context of mobile device hardware performance being weaker than PC, the demand for performance becomes more important. There is a huge difference between HTML5 performance before and after optimization, and few people know how to optimize to improve performance. This article takes the LayaAir engine as an example and explains in detail how to use the engine to optimize the performance of HTML5 through code examples.
Topics include:
Section 1: Code Execution Basics LayaAir engine supports AS3, TypeScript, and JavaScript development. However, no matter which development language is used, JavaScript code is ultimately executed. All the images you see are drawn by the engine, and the update frequency depends on the FPS specified by the developer. For example, if the frame rate is specified to be 60FPS, the execution time of each frame at runtime is one sixtieth of a second. Therefore, the higher the frame rate, the smoother the visual feeling. 60 frames is a full frame. Since the actual operating environment is in the browser, the performance also depends on the efficiency of the JavaScript interpreter. The specified FPS frame rate may not be achieved in a low-performance interpreter, so this part is not something that developers can decide. What developers can do is to improve the FPS frame rate in low-end devices or low-performance browsers through optimization as much as possible. The LayaAir engine redraws every frame. When optimizing performance, in addition to paying attention to the CPU consumption caused by executing logic code in each frame, you also need to pay attention to the number of drawing instructions called per frame and the number of texture submissions to the GPU. Section 2: Benchmarks The performance statistics tool built into the LayaAir engine can be used for benchmarking and real-time detection of current performance. Developers can use the laya.utils.Stat class to display the statistics panel through Stat.show(). The specific code is shown in the following example:
Canvas rendering statistics: Statistics for WebGL rendering: The significance of statistical parameters: FPS:
Sprites:
DrawCall:
Canvas: Three values - the number of canvases redrawn per frame / the number of canvases with a cache type of "normal" / the number of canvases with a cache type of "bitmap". CurMem: WebGL rendering only, indicates the memory usage and video memory usage (the lower the better). Shader: For WebGL rendering only, this indicates the number of shader submissions per frame. Whether in Canvas mode or WebGL mode, we need to focus on the three parameters of DrawCall, Sprite, and Canvas, and then optimize them accordingly. (See "Graphics Rendering Performance") Section 3: Memory Optimization Object Pool Object pooling involves the constant reuse of objects. A certain number of objects are created during the initialization of the application and stored in a pool. When an operation is completed on an object, the object is returned to the pool and can be retrieved when a new object is needed. Since instantiating objects is expensive, using an object pool to reuse objects can reduce the need to instantiate objects. It can also reduce the chances of the garbage collector running, thereby increasing the running speed of the program. The following code demonstrates the use of Laya.utils.Pool:
Create an object pool of size 1000 in initialize. The following code removes all display objects from the display list when the mouse is clicked and reuses them for other tasks later:
After calling Pool.recover, the specified object will be recycled into the pool. Using Handler.create During the development process, Handler is often used to complete asynchronous callbacks. Handler.create uses built-in object pool management, so Handler.create should be used to create callback processors when using Handler objects. The following code uses Handler.create to create a loaded callback processor:
In the above code, after the callback is executed, the Handler will be reclaimed by the object pool. At this point, consider what happens in the following code:
In the above code, the handler returned by Handler.create is used to handle the progress event. At this time, the callback is executed once and then recycled by the object pool, so the progress event is only triggered once. At this time, the four parameters named once need to be set to false:
Freeing up memory The JavaScript runtime cannot start the garbage collector. To ensure that an object can be recycled, delete all references to the object. The destroy function provided by Sprite will help set internal references to null. For example, the following code ensures that the object can be garbage collected:
When an object is set to null, it is not immediately removed from memory. The garbage collector runs only when the system deems that the memory is low enough. Memory allocation (not object deletion) triggers garbage collection. Garbage collection can be CPU intensive and affect performance. Try to limit the use of garbage collection by reusing objects. Also, set references to null whenever possible so that the garbage collector spends less time looking for objects. Sometimes (such as when two objects reference each other) it is not possible to set both references to null at the same time, and the garbage collector will scan for unreachable objects and clear them, which can be more expensive than reference counting. Resource Unloading When the game is running, many resources will always be loaded. These resources should be unloaded in time after use, otherwise they will remain in the memory. The following example demonstrates loading a resource and comparing the resource status before and after unloading:
About filters and masks Try to minimize the use of filter effects. When you apply filters (BlurFilter and GlowFilter) to a display object, the runtime creates two bitmaps in memory. Each of these bitmaps is the same size as the display object. The first bitmap is created as a rasterized version of the display object, and then used to generate the other bitmap with the filter applied:
When you modify a property of a filter or display object, both bitmaps in memory are updated to create the resulting bitmap, which can take up a lot of memory. In addition, this process involves CPU calculations, which can degrade performance when dynamically updated (see "Graphics Rendering Performance - About cacheAs"). ColorFiter needs to calculate each pixel under Canvas rendering, while the GPU consumption under WebGL is negligible. As a best practice, use bitmaps created with image authoring tools to simulate filters whenever possible. Avoiding dynamic bitmap creation at runtime can help reduce CPU or GPU load, especially for images that have filters applied and will not be modified. Section 4: Graphics Rendering Performance Optimizing Sprites
Optimize DrawCall
Optimizing Canvas When optimizing Canvas, we need to be careful not to use cacheAs in the following situations:
You can check the first value of the Canvas statistics to determine whether the Canvas cache is being refreshed. About cacheAs Setting cacheAs can cache the display object as a static image. When cacheAs is used, if the child object changes, it will be automatically re-cached. You can also manually call the reCache method to update the cache. It is recommended to cache complex content that does not change frequently as a static image, which can greatly improve rendering performance. cacheAs has three optional values: "none", "normal" and "bitmap".
After setting cacheAs, you can also set staticCache=true to prevent automatic cache updates. You can also manually call the reCache method to update the cache. cacheAs improves performance in two ways: first, reducing node traversal and vertex calculations; second, reducing drawCalls. Making good use of cacheAs will be a powerful tool for engine performance optimization. The following example draws 10,000 texts:
Below is a screenshot of the runtime on my computer, with the FPS stable at around 52. When we set the container containing the text to cacheAs, as shown in the example below, the performance is greatly improved and the FPS reaches 60 frames.
Text stroke At runtime, text with strokes calls one more drawing instruction than text without strokes. At this time, the amount of CPU usage of text is proportional to the amount of text. Therefore, try to use alternative solutions to achieve the same requirements. For text content that rarely changes, you can use cacheAs to reduce performance overhead. See “Graphics Rendering Performance – About cacheAs”. For text fields whose content changes frequently but use a small number of characters, you can choose to use bitmap fonts. Skip text layout and go straight to rendering In most cases, many texts do not require complex typesetting, and just display a line of text. To meet this requirement, Text provides a method called changeText that can directly skip typesetting.
Text.changeText will directly modify the last instruction of the text drawing in the drawing instructions. This behavior of the previous drawing instructions still existing will cause changeText to be used only in the following situations:
Even so, such needs are still often used in actual programming. Section 5: Reducing CPU usage Reduce dynamic property lookups Any object in JavaScript is dynamic, and you can add properties at will. However, it may be time-consuming to find a property among a large number of properties. If you need to use a property value frequently, you can use a local variable to save it:
Timer LayaAir provides two timer loops to execute code blocks.
When the life cycle of an object ends, remember to clear its internal Timer:
How to get the display object boundary In relative layout, it is often necessary to correctly obtain the bounds of the display object. There are also multiple ways to obtain the bounds of the display object, and it is important to know the differences between them. 1. Use getBounds/getGraphicBounds.
getBounds can meet most needs, but because it needs to calculate the boundaries, it is not suitable for frequent calls. 2. Set the container's autoSize to true.
The above code can correctly get the width and height at runtime. AutoSize will recalculate when the width and height are obtained and the display list status changes (autoSize calculates the width and height through getBoudns). Therefore, it is not advisable to apply autoSize to a container with a large number of child objects. If size is set, autoSize will not take effect. Get the width and height after using loadImage:
loadImage can correctly obtain the width and height only after the loading completion callback function is triggered. 3. Call size settings directly:
Using Graphics.drawTexture does not automatically set the width and height of the container, but you can use the width and height of the Texture to assign it to the container. Undoubtedly, this is the most efficient way. Note: getGraphicsBounds is used to obtain the width and height of the vector drawing. Change frame rate based on activity state There are three frame rate modes: Stage.FRAME_SLOW maintains the FPS at 30; Stage.FRAME_FAST maintains the FPS at 60; and Stage.FRAME_MOUSE selectively maintains the FPS at 30 or 60 frames. Sometimes it is not necessary to run the game at 60FPS, because 30FPS can meet the response of human vision in most cases, but 30FPS may cause discontinuity of the picture when the mouse interacts, so Stage.FRAME_MOUSE came into being. The following example shows how to move the mouse on the canvas at a frame rate of Stage.FRAME_SLOW so that the ball follows the mouse:
At this time, the FPS shows 30, and when the mouse moves, you can feel that the update of the ball position is not coherent. Set Stage.frameRate to Stage.FRAME_MOUSE:
At this time, after the mouse moves, the FPS will be displayed as 60, and the picture fluency will be improved. After the mouse is still for 2 seconds, the FPS will return to 30 frames. Using callLater callLater delays the execution of the code block until the current frame is rendered. If the current operation frequently changes the state of an object, you can consider using callLater to reduce repeated calculations. Consider a shape, for which setting any properties that change its appearance will cause the shape to be redrawn:
Call the following code to change the state:
The console print result is
update is called three times, and the final result is correct, but the first two calls are unnecessary. Try changing the three updates to:
At this point, update will only be called once, and that is the result we want. Image/atlas loading After the image/atlas is loaded, the engine will start processing the image resources. If an atlas is loaded, each sub-image will be processed. If a large number of images are processed at once, this process may cause long-term lag. When loading game resources, you can load resources by level, scene, etc. The fewer images you process at the same time, the faster the game will respond. After using the resources, you can also unload them to free up memory. Section 6: Other Optimization Strategies
|
<<: Android Annotations Quick Start and Practical Analysis
>>: 10+ Apps You Must Uninstall During the National Day Holiday
Introduction In ancient times, people basically c...
It’s been a long time since I updated the article...
Weibo and other online platforms have displayed u...
1. What is the JN.1 variant? JN.1 is the second-g...
The "excitement" of new consumption has...
Source of this article: Knowledge Keer. Reprint p...
On November 12, the State Drug Administration iss...
Humans have conducted many explorations on the mo...
This is the fallen earth It is God's "pi...
Zhihu has become a high-quality question-and-answ...
An old client who I cooperated with on event mark...
In today's information-shock world, reading h...
On September 24, 2019, NIO released its second qu...
LONDON, United Kingdom — Three years after the Un...
Some time ago, both the iOS and Android versions ...