mirror of
https://github.com/stan220/godot-docs.git
synced 2026-09-08 20:29:03 +00:00
General fixes for the new optimazation pages
This commit is contained in:
@@ -3,51 +3,51 @@
|
||||
Optimization using MultiMeshes
|
||||
==============================
|
||||
|
||||
For large amount of instances (in the thousands), that need to be constantly processed
|
||||
(and certain amount of control needs to be retained),
|
||||
For large amount of instances (in the thousands), that need to be constantly processed
|
||||
(and certain amount of control needs to be retained),
|
||||
:ref:`using servers directly <doc_using_servers>` is the recommended optimization.
|
||||
|
||||
When the amount of objects reach the hundreds of thousands or millions,
|
||||
none of these approaches are efficient anymore. Still, depending on the requirements, there
|
||||
When the amount of objects reach the hundreds of thousands or millions,
|
||||
none of these approaches are efficient anymore. Still, depending on the requirements, there
|
||||
is one more optimization possible.
|
||||
|
||||
MultiMeshes
|
||||
-----------
|
||||
|
||||
A :ref:`MultiMesh<class_MultiMesh>` is a single draw primitive that can draw up to millions
|
||||
of objects in one go. It's extremely efficient because it uses the GPU hardware to do this
|
||||
A :ref:`MultiMesh<class_MultiMesh>` is a single draw primitive that can draw up to millions
|
||||
of objects in one go. It's extremely efficient because it uses the GPU hardware to do this
|
||||
(In OpenGL ES 2.0, it's less efficient because there is no hardware support for it, though).
|
||||
|
||||
The only drawback is that there is no *screen* or *frustum* culling possible for individual instances.
|
||||
This means, that millions of objects will be *always* or *never* drawn, depending on the visibility
|
||||
of the whole MultiMesh. It is possible to provide a custom visibility rect for them, but it will always
|
||||
The only drawback is that there is no *screen* or *frustum* culling possible for individual instances.
|
||||
This means, that millions of objects will be *always* or *never* drawn, depending on the visibility
|
||||
of the whole MultiMesh. It is possible to provide a custom visibility rect for them, but it will always
|
||||
be *all-or-none* visibility.
|
||||
|
||||
If the objects are simple enough (just a couple of vertices), this is generally not much of a problem
|
||||
as most modern GPUs are optimized for this use case. A workaround is to create several MultiMeshes
|
||||
If the objects are simple enough (just a couple of vertices), this is generally not much of a problem
|
||||
as most modern GPUs are optimized for this use case. A workaround is to create several MultiMeshes
|
||||
for different areas of the world.
|
||||
|
||||
It is also possible to execute some logic inside the vertex shader (using the INSTANCE_ID or
|
||||
It is also possible to execute some logic inside the vertex shader (using the INSTANCE_ID or
|
||||
INSTANCE_CUSTOM built-in constants). For an example of animating thousands of objects in a MultiMesh,
|
||||
see the :ref:`Animating thousands of fish <doc_animating_thousands_of_fish>` tutorial. Information
|
||||
to the shader can be provided via textures (there are floating point :ref:`Image<class_Image>` formats
|
||||
see the :ref:`Animating thousands of fish <doc_animating_thousands_of_fish>` tutorial. Information
|
||||
to the shader can be provided via textures (there are floating point :ref:`Image<class_Image>` formats
|
||||
which are ideal for this).
|
||||
|
||||
Another alternative is to use GDNative and C++, which should be extremely efficient (it's possible
|
||||
to set the entire state for all objects using linear memory via the
|
||||
:ref:`VisualServer.multimesh_set_as_bulk_array() <class_VisualServer_method_multimesh_set_as_bulk_array>`
|
||||
function. This way, the array can be created with multiple threads, then set in one call, providing
|
||||
Another alternative is to use GDNative and C++, which should be extremely efficient (it's possible
|
||||
to set the entire state for all objects using linear memory via the
|
||||
:ref:`VisualServer.multimesh_set_as_bulk_array() <class_VisualServer_method_multimesh_set_as_bulk_array>`
|
||||
function). This way, the array can be created with multiple threads, then set in one call, providing
|
||||
high cache efficiency.
|
||||
|
||||
Finally, it's not required to have all MultiMesh instances visible. The amount of visible ones can be
|
||||
controlled with the *MultiMesh.visible_instance_count* property. The typical workflow is to allocate
|
||||
Finally, it's not required to have all MultiMesh instances visible. The amount of visible ones can be
|
||||
controlled with the *MultiMesh.visible_instance_count* property. The typical workflow is to allocate
|
||||
the maximum amount of instances that will be used,
|
||||
then change the amount visible depending on how many are currently needed.
|
||||
|
||||
Multimesh Example
|
||||
-----------------
|
||||
|
||||
Here is an example of using a MultiMesh from code (using GDScript). Other languages may be more
|
||||
Here is an example of using a MultiMesh from code (using GDScript). Other languages may be more
|
||||
efficient for millions of objects, but for a few thousands, GDScript should be fine.
|
||||
|
||||
.. tabs::
|
||||
@@ -56,17 +56,16 @@ efficient for millions of objects, but for a few thousands, GDScript should be f
|
||||
extends MultiMeshInstance
|
||||
|
||||
func _ready():
|
||||
|
||||
# Create multimesh
|
||||
# Create the multimesh.
|
||||
multimesh = MultiMesh.new()
|
||||
# Set format first
|
||||
# Set the format first.
|
||||
multimesh.transform_format = MultiMesh.TRANSFORM_3D
|
||||
multimesh.color_format = MultiMesh.COLOR_NONE
|
||||
multimesh.custom_data_format = MultiMesh.CUSTOM_DATA_NONE
|
||||
# Then resize (else changing format is not allowed)
|
||||
# Then resize (otherwise, changing the format is not allowed).
|
||||
multimesh.instance_count = 10000
|
||||
# Maybe not all of them should be visible at first
|
||||
# Maybe not all of them should be visible at first.
|
||||
multimesh.visible_instance_count = 1000
|
||||
# Set instance transforms
|
||||
for i in range(multimesh.visible_instance_count):
|
||||
multimesh.set_instance_transform(i,Transform( Basis(), Vector3(i*20,0,0) ) )
|
||||
# Set the transform of the instances.
|
||||
for i in multimesh.visible_instance_count:
|
||||
multimesh.set_instance_transform(i, Transform(Basis(), Vector3(i * 20, 0, 0)))
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
Optimization Using Servers
|
||||
==========================
|
||||
|
||||
Engines like Godot provide increased ease of use thanks to it's high level constructs and features.
|
||||
Most of them are accessed and used via the :ref:`Scene System<doc_scene_tree>`. Using nodes and
|
||||
Engines like Godot provide increased ease of use thanks to it's high level constructs and features.
|
||||
Most of them are accessed and used via the :ref:`Scene System<doc_scene_tree>`. Using nodes and
|
||||
resources simplifies project organization and asset management in complex games.
|
||||
|
||||
There are, of course, always drawbacks:
|
||||
@@ -14,22 +14,22 @@ There are, of course, always drawbacks:
|
||||
* It is not possible to use multiple threads to control them
|
||||
* More memory is needed.
|
||||
|
||||
In many cases, this is not really a problem (Godot is very optimized, and most operations are handled
|
||||
with signals, so no polling is required). Still, sometimes it can be. For example, dealing with
|
||||
tens of thousands of instances for something that needs to be processed every frame can be a bottleneck.
|
||||
In many cases, this is not really a problem (Godot is very optimized, and most operations are handled
|
||||
with signals, so no polling is required). Still, sometimes it can be. For example, dealing with
|
||||
tens of thousands of instances for something that needs to be processed every frame can be a bottleneck.
|
||||
|
||||
This type of situation makes programmers regret they are using a game engine and wish they could go
|
||||
back to a more handcrafted, low level implementation of game code.
|
||||
This type of situation makes programmers regret they are using a game engine and wish they could go
|
||||
back to a more handcrafted, low level implementation of game code.
|
||||
|
||||
Still, Godot is designed to work around this problem.
|
||||
|
||||
Servers
|
||||
-------
|
||||
|
||||
One of the most interesting design decisions for Godot, is the fact that the whole scene system is
|
||||
One of the most interesting design decisions for Godot, is the fact that the whole scene system is
|
||||
*optional*. While it is not currently possible to compile it out, it can be completely bypassed.
|
||||
|
||||
At the core, Godot uses the concept of Servers. They are very low level APIs to control
|
||||
At the core, Godot uses the concept of Servers. They are very low level APIs to control
|
||||
rendering, physics, sound, etc. The scene system is built on top of them and uses them directly. The most common servers are:
|
||||
|
||||
* :ref:`Visual Server<class_VisualServer>`: handles everything related to graphics.
|
||||
@@ -43,38 +43,38 @@ implementations of everything Godot allows you to do.
|
||||
RIDs
|
||||
----
|
||||
|
||||
The key to using servers is understanding Resource ID (RID) objects. These are opaque
|
||||
handles to the sever implementation. They are allocated and freed manually. Almost every
|
||||
The key to using servers is understanding Resource ID (RID) objects. These are opaque
|
||||
handles to the sever implementation. They are allocated and freed manually. Almost every
|
||||
function in the servers requires RIDs to access the actual resource.
|
||||
|
||||
Most Godot nodes and resources contain these RIDs from the servers internally, and they can
|
||||
be obtained with different functions. In fact, anything that inherits :ref:`Resource <class_Resource>`
|
||||
can be directly casted to an RID (not all resources contain an RID, though, in such cases
|
||||
the RID will be empty). In fact, resources can be passed to server APIs as RIDs. Just make
|
||||
sure to keep references to the resources ouside the server, because if the resource is erased,
|
||||
Most Godot nodes and resources contain these RIDs from the servers internally, and they can
|
||||
be obtained with different functions. In fact, anything that inherits :ref:`Resource <class_Resource>`
|
||||
can be directly casted to an RID (not all resources contain an RID, though, in such cases
|
||||
the RID will be empty). In fact, resources can be passed to server APIs as RIDs. Just make
|
||||
sure to keep references to the resources ouside the server, because if the resource is erased,
|
||||
the internal RID is erased too.
|
||||
|
||||
For nodes, there are many functions available:
|
||||
|
||||
* For CanvasItem, the :ref:`CanvasItem.get_canvas_item()<class_CanvasItem_method_get_canvas_item>`
|
||||
method will return the canvas item RID in the server.
|
||||
* For CanvasLayer, the :ref:`CanvasLayer.get_canvas()<class_CanvasLayer_method_get_canvas>`
|
||||
method will return the canvas RID in the server.
|
||||
* For Viewport, the :ref:`Viewport.get_viewport_rid()<class_Viewport_method_get_viewport_rid>`
|
||||
method will return the viewport RID in the server.
|
||||
* For 3D, the :ref:`World<class_World>` resource (obtainable in the *Viewport* and *Spatial* nodes,
|
||||
contains function to get the *VisualServer Scenario*, and the *PhysicsServer Space*. This
|
||||
* For CanvasItem, the :ref:`CanvasItem.get_canvas_item()<class_CanvasItem_method_get_canvas_item>`
|
||||
method will return the canvas item RID in the server.
|
||||
* For CanvasLayer, the :ref:`CanvasLayer.get_canvas()<class_CanvasLayer_method_get_canvas>`
|
||||
method will return the canvas RID in the server.
|
||||
* For Viewport, the :ref:`Viewport.get_viewport_rid()<class_Viewport_method_get_viewport_rid>`
|
||||
method will return the viewport RID in the server.
|
||||
* For 3D, the :ref:`World<class_World>` resource (obtainable in the *Viewport* and *Spatial* nodes)
|
||||
contains functions to get the *VisualServer Scenario*, and the *PhysicsServer Space*. This
|
||||
allows creating 3D objects directly with the server API and using them.
|
||||
* For 2D, the :ref:`World2D<class_World2D>` resource (obtainable in the *Viewport* and *CanvasItem*
|
||||
nodes, contains function to get the *VisualServer Canvas*, and the *Physics2DServer Space*. This
|
||||
* For 2D, the :ref:`World2D<class_World2D>` resource (obtainable in the *Viewport* and *CanvasItem*
|
||||
nodes) contains functions to get the *VisualServer Canvas*, and the *Physics2DServer Space*. This
|
||||
allows creating 2D objects directly with the server API and using them.
|
||||
* The :ref:`VisualInstance<class_VisualInstance>` class, allows getting the scenario *instance* and
|
||||
*instance base* via the :ref:`VisualInstance.get_instance()<class_VisualInstance_method_get_instance>`
|
||||
* The :ref:`VisualInstance<class_VisualInstance>` class, allows getting the scenario *instance* and
|
||||
*instance base* via the :ref:`VisualInstance.get_instance()<class_VisualInstance_method_get_instance>`
|
||||
and :ref:`VisualInstance.get_base()<class_VisualInstance_method_get_base>` respectively.
|
||||
|
||||
Just explore the nodes and resources you are familiar with and find the functions to obtain the server *RIDs*.
|
||||
Just explore the nodes and resources you are familiar with and find the functions to obtain the server *RIDs*.
|
||||
|
||||
It is not advised to control RIDs from objects that already have a node associated. Instead, server
|
||||
It is not advised to control RIDs from objects that already have a node associated. Instead, server
|
||||
functions should always be used for creating and controlling new ones and interacting with the existing ones.
|
||||
|
||||
Creating a Sprite
|
||||
@@ -88,22 +88,21 @@ This is a simple example of how to create a sprite from code and move it using t
|
||||
extends Node2D
|
||||
|
||||
func _ready():
|
||||
|
||||
# Create a canvas item, child of this node
|
||||
# Create a canvas item, child of this node.
|
||||
var ci_rid = VisualServer.canvas_item_create()
|
||||
# Make this node the parent
|
||||
VisualServer.canvas_item_set_parent( ci_rid, get_canvas_item() )
|
||||
# Draw a sprite on it
|
||||
# Remember, keep this reference
|
||||
# Make this node the parent.
|
||||
VisualServer.canvas_item_set_parent(ci_rid, get_canvas_item())
|
||||
# Draw a sprite on it.
|
||||
# Remember, keep this reference.
|
||||
var sprite = load("res://mysprite.png")
|
||||
# Add it, centered
|
||||
VisualServer.canvas_item_add_texture_rect(ci_rid, Rect2( sprite.get_size() / 2, sprite.get_size() ), sprite )
|
||||
# Add the item, rotated 45 degrees and translated
|
||||
var xform = Transform2D().rotated( deg2rad(45) ).translated( Vector2( 20, 30 ) )
|
||||
VisualServer.canvas_item_set_transform( ci_rid, xform )
|
||||
# Add it, centered.
|
||||
VisualServer.canvas_item_add_texture_rect(ci_rid, Rect2(sprite.get_size() / 2, sprite.get_size()), sprite)
|
||||
# Add the item, rotated 45 degrees and translated.
|
||||
var xform = Transform2D().rotated(deg2rad(45)).translated(Vector2(20, 30))
|
||||
VisualServer.canvas_item_set_transform(ci_rid, xform)
|
||||
|
||||
The Canvas Item API in the server allows you to add draw primitives to it. Once added, they can't be modified.
|
||||
The Item needs to be cleared and the primitives re-added (this is not the case for setting the transform,
|
||||
The Canvas Item API in the server allows you to add draw primitives to it. Once added, they can't be modified.
|
||||
The Item needs to be cleared and the primitives re-added (this is not the case for setting the transform,
|
||||
which can be done as many times as desired).
|
||||
|
||||
Primitives are cleared this way:
|
||||
@@ -111,8 +110,8 @@ Primitives are cleared this way:
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
VisualServer.canvas_item_clear( ci_rid )
|
||||
|
||||
VisualServer.canvas_item_clear(ci_rid)
|
||||
|
||||
|
||||
Instantiating a Mesh into 3D space
|
||||
----------------------------------
|
||||
@@ -123,23 +122,22 @@ The 3D APIs are different than the 2D ones, so the instantiation API must be use
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
extends Spatial
|
||||
|
||||
func _ready():
|
||||
|
||||
# Create a visual instance (for 3D)
|
||||
var instance = VisualServer.instance_create()
|
||||
func _ready():
|
||||
# Create a visual instance (for 3D).
|
||||
var instance = VisualServer.instance_create()
|
||||
# Set the scenario from the world, this ensures it
|
||||
# appears with the same objects as the scene
|
||||
# appears with the same objects as the scene.
|
||||
var scenario = get_world().scenario
|
||||
VisualServer.instance_set_scenario(instance,scenario)
|
||||
# add a mesh to it
|
||||
# remember, keep the reference
|
||||
VisualServer.instance_set_scenario(instance, scenario)
|
||||
# Add a mesh to it.
|
||||
# Remember, keep the reference.
|
||||
var mesh = load("res://mymesh.obj")
|
||||
VisualServer.instance_set_base(instance,mesh)
|
||||
# move the mesh around
|
||||
var xform = Transform( Basis(), Vector3(20,100,0) )
|
||||
# Move the mesh around.
|
||||
var xform = Transform(Basis(), Vector3(20, 100, 0))
|
||||
VisualServer.instance_set_transform(instance,xform)
|
||||
|
||||
|
||||
Creating a 2D RigidBody and moving a Sprite with it
|
||||
----------------------------------------------------
|
||||
|
||||
@@ -148,39 +146,38 @@ This creates a *RigidBody* using the *Physics2DServer* API, and moves a *Canvas
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
func _body_moved(state : Physics2DDirectBodyState, index):
|
||||
# Created your own canvas item, use it here
|
||||
VisualServer.canvas_item_set_transform( canvas_item, state.transform )
|
||||
func _body_moved(state, index):
|
||||
# Created your own canvas item, use it here.
|
||||
VisualServer.canvas_item_set_transform(canvas_item, state.transform)
|
||||
|
||||
func _ready():
|
||||
|
||||
# Create the body
|
||||
# Create the body.
|
||||
var body = Physics2DServer.body_create()
|
||||
Physics2DServer.body_set_mode( body, Physics2DServer.BODY_MODE_RIGID )
|
||||
# Add a shape
|
||||
var shape = RectangleShape2D.new()
|
||||
shape.extents = Vector2(10,10)
|
||||
# make sure to keep the shape reference!
|
||||
Physics2DServer.body_add_shape( body, shape ) #
|
||||
# Set space, so it collides in the same space as current scene
|
||||
Physics2DServer.body_set_space( body, get_world_2d().space )
|
||||
# Move initial position
|
||||
Physics2DServer.body_set_state( body, Physics2DServer.BODY_STATE_TRANSFORM, Transform2D(0, Vector2(10,20) ) )
|
||||
Physics2DServer.body_set_mode(body, Physics2DServer.BODY_MODE_RIGID)
|
||||
# Add a shape.
|
||||
var shape = RectangleShape2D.new()
|
||||
shape.extents = Vector2(10, 10)
|
||||
# Make sure to keep the shape reference!
|
||||
Physics2DServer.body_add_shape(body, shape)
|
||||
# Set space, so it collides in the same space as current scene.
|
||||
Physics2DServer.body_set_space(body, get_world_2d().space)
|
||||
# Move initial position.
|
||||
Physics2DServer.body_set_state(body, Physics2DServer.BODY_STATE_TRANSFORM, Transform2D(0, Vector2(10, 20)))
|
||||
# Add the transform callback, when body moves
|
||||
# The last parameter is optional, can be used as index if you have many bodies
|
||||
# And a single callback.
|
||||
Physics2DServer.body_set_force_integration_callback( body, self, "_body_moved", 0)
|
||||
|
||||
# The last parameter is optional, can be used as index
|
||||
# if you have many bodies and a single callback.
|
||||
Physics2DServer.body_set_force_integration_callback(body, self, "_body_moved", 0)
|
||||
|
||||
The 3D version should be very similar, as 2D and 3D physics servers are identical.
|
||||
|
||||
Getting data from the servers
|
||||
-----------------------------
|
||||
|
||||
Try to **never** request any information from *VisualServer*, *PhysicsServer* or *Physics2DServer*
|
||||
by calling functions unless you know what you are doing. These servers will often run asynchronously
|
||||
for performance and calling any function that returns a value will stall them and force them to process
|
||||
anything pending until the function is actually called. This will severely decrease performance if you
|
||||
Try to **never** request any information from *VisualServer*, *PhysicsServer* or *Physics2DServer*
|
||||
by calling functions unless you know what you are doing. These servers will often run asynchronously
|
||||
for performance and calling any function that returns a value will stall them and force them to process
|
||||
anything pending until the function is actually called. This will severely decrease performance if you
|
||||
call them every frame (and it won't be obvious why).
|
||||
|
||||
Because of this, most APIs in such servers are designed so it's not even possible to request information
|
||||
Because of this, most APIs in such servers are designed so it's not even possible to request information
|
||||
back, until it's actual data that can be saved.
|
||||
|
||||
Reference in New Issue
Block a user