summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--SConstruct4
-rw-r--r--core/error/error_macros.h2
-rw-r--r--doc/classes/Input.xml1
-rw-r--r--doc/classes/MultiMesh.xml29
-rw-r--r--doc/classes/ProjectSettings.xml19
-rw-r--r--doc/classes/RenderingServer.xml49
-rw-r--r--doc/classes/VisualShaderNodeIntParameter.xml8
-rw-r--r--doc/classes/XRHandTracker.xml5
-rw-r--r--drivers/gles3/rasterizer_scene_gles3.cpp14
-rw-r--r--drivers/gles3/rasterizer_scene_gles3.h3
-rw-r--r--drivers/gles3/shaders/scene.glsl88
-rw-r--r--drivers/gles3/storage/light_storage.cpp3
-rw-r--r--drivers/gles3/storage/light_storage.h1
-rw-r--r--drivers/gles3/storage/mesh_storage.cpp53
-rw-r--r--drivers/gles3/storage/mesh_storage.h56
-rw-r--r--drivers/gles3/storage/texture_storage.cpp8
-rw-r--r--drivers/gles3/storage/texture_storage.h1
-rw-r--r--editor/animation_bezier_editor.cpp2
-rw-r--r--editor/code_editor.cpp3
-rw-r--r--editor/editor_node.cpp7
-rw-r--r--editor/editor_resource_picker.cpp4
-rw-r--r--editor/plugins/visual_shader_editor_plugin.cpp4
-rw-r--r--main/main.cpp4
-rw-r--r--modules/cvtt/image_compress_cvtt.cpp2
-rw-r--r--modules/gdscript/config.py1
-rw-r--r--modules/gdscript/doc_classes/GDScriptSyntaxHighlighter.xml23
-rw-r--r--modules/gdscript/gdscript_parser.cpp52
-rw-r--r--modules/gdscript/gdscript_parser.h8
-rw-r--r--modules/gdscript/register_types.cpp7
-rw-r--r--modules/gdscript/tests/scripts/completion/argument_options/string_literals/argument_options_inside_string_literal.cfg5
-rw-r--r--modules/gdscript/tests/scripts/completion/argument_options/string_literals/argument_options_inside_string_literal.gd8
-rw-r--r--modules/gdscript/tests/scripts/completion/common/identifiers_in_call.cfg (renamed from modules/gdscript/tests/scripts/completion/common/identifiers.cfg)6
-rw-r--r--modules/gdscript/tests/scripts/completion/common/identifiers_in_call.gd18
-rw-r--r--modules/gdscript/tests/scripts/completion/common/identifiers_in_function_body.cfg25
-rw-r--r--modules/gdscript/tests/scripts/completion/common/identifiers_in_function_body.gd (renamed from modules/gdscript/tests/scripts/completion/common/identifiers.gd)4
-rw-r--r--modules/gdscript/tests/scripts/completion/common/identifiers_in_unclosed_call.cfg25
-rw-r--r--modules/gdscript/tests/scripts/completion/common/identifiers_in_unclosed_call.gd22
-rw-r--r--modules/gdscript/tests/scripts/completion/common/no_completion_in_string.cfg26
-rw-r--r--modules/gdscript/tests/scripts/completion/common/no_completion_in_string.gd19
-rw-r--r--modules/gdscript/tests/scripts/completion/common/self.gd1
-rw-r--r--modules/gdscript/tests/scripts/completion/filter/organized_export.gd3
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/local_infered/local_infered.cfg (renamed from modules/gdscript/tests/scripts/completion/get_node/local_interfered/local_interfered.cfg)0
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/local_infered/local_infered.gd (renamed from modules/gdscript/tests/scripts/completion/get_node/local_interfered/local_interfered.gd)0
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/class_local_infered_scene.cfg (renamed from modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/class_local_interfered_scene.cfg)0
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/class_local_infered_scene.gd (renamed from modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/class_local_interfered_scene.gd)0
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/native_local_infered_scene.cfg (renamed from modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/native_local_interfered_scene.cfg)0
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/native_local_infered_scene.gd (renamed from modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/native_local_interfered_scene.gd)0
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/member_infered/member_infered.cfg (renamed from modules/gdscript/tests/scripts/completion/get_node/member_interfered/member_interfered.cfg)0
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/member_infered/member_infered.gd (renamed from modules/gdscript/tests/scripts/completion/get_node/member_interfered/member_interfered.gd)0
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/class_member_infered_scene.cfg (renamed from modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/class_member_interfered_scene.cfg)0
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/class_member_infered_scene.gd (renamed from modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/class_member_interfered_scene.gd)0
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/native_member_infered_scene.cfg (renamed from modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/native_member_interfered_scene.cfg)0
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/native_member_infered_scene.gd (renamed from modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/native_member_interfered_scene.gd)0
-rw-r--r--modules/gdscript/tests/scripts/completion/types/local/infered.cfg (renamed from modules/gdscript/tests/scripts/completion/types/local/interfered.cfg)0
-rw-r--r--modules/gdscript/tests/scripts/completion/types/local/infered.gd (renamed from modules/gdscript/tests/scripts/completion/types/local/interfered.gd)0
-rw-r--r--modules/gdscript/tests/scripts/completion/types/member/infered.cfg (renamed from modules/gdscript/tests/scripts/completion/types/member/interfered.cfg)0
-rw-r--r--modules/gdscript/tests/scripts/completion/types/member/infered.gd (renamed from modules/gdscript/tests/scripts/completion/types/member/interfered.gd)0
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs10
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs17
-rw-r--r--modules/openxr/extensions/openxr_hand_tracking_extension.cpp77
-rw-r--r--modules/openxr/extensions/openxr_hand_tracking_extension.h3
-rw-r--r--scene/3d/multimesh_instance_3d.cpp19
-rw-r--r--scene/3d/multimesh_instance_3d.h5
-rw-r--r--scene/3d/xr_nodes.cpp20
-rw-r--r--scene/3d/xr_nodes.h2
-rw-r--r--scene/resources/multimesh.cpp28
-rw-r--r--scene/resources/multimesh.h16
-rw-r--r--scene/resources/visual_shader_nodes.cpp36
-rw-r--r--scene/resources/visual_shader_nodes.h5
-rw-r--r--servers/rendering/dummy/rasterizer_scene_dummy.h1
-rw-r--r--servers/rendering/dummy/storage/mesh_storage.cpp10
-rw-r--r--servers/rendering/dummy/storage/mesh_storage.h58
-rw-r--r--servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp13
-rw-r--r--servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h4
-rw-r--r--servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp13
-rw-r--r--servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h5
-rw-r--r--servers/rendering/renderer_rd/renderer_scene_render_rd.cpp9
-rw-r--r--servers/rendering/renderer_rd/renderer_scene_render_rd.h5
-rw-r--r--servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl96
-rw-r--r--servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl3
-rw-r--r--servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl95
-rw-r--r--servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile_inc.glsl3
-rw-r--r--servers/rendering/renderer_rd/storage_rd/light_storage.cpp1
-rw-r--r--servers/rendering/renderer_rd/storage_rd/light_storage.h5
-rw-r--r--servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp53
-rw-r--r--servers/rendering/renderer_rd/storage_rd/mesh_storage.h48
-rw-r--r--servers/rendering/renderer_rd/storage_rd/render_data_rd.h2
-rw-r--r--servers/rendering/renderer_scene_cull.cpp6
-rw-r--r--servers/rendering/renderer_scene_cull.h1
-rw-r--r--servers/rendering/renderer_scene_render.h1
-rw-r--r--servers/rendering/rendering_method.h1
-rw-r--r--servers/rendering/rendering_server_default.h6
-rw-r--r--servers/rendering/shader_language.cpp112
-rw-r--r--servers/rendering/shader_language.h4
-rw-r--r--servers/rendering/storage/mesh_storage.cpp485
-rw-r--r--servers/rendering/storage/mesh_storage.h128
-rw-r--r--servers/rendering_server.cpp9
-rw-r--r--servers/rendering_server.h14
-rw-r--r--servers/xr/xr_hand_tracker.cpp1
-rw-r--r--servers/xr/xr_hand_tracker.h1
100 files changed, 1808 insertions, 246 deletions
diff --git a/SConstruct b/SConstruct
index 0ae8f1a387..599db4b8de 100644
--- a/SConstruct
+++ b/SConstruct
@@ -773,6 +773,10 @@ else:
# MSVC doesn't have clear C standard support, /std only covers C++.
# We apply it to CCFLAGS (both C and C++ code) in case it impacts C features.
env.Prepend(CCFLAGS=["/std:c++17"])
+ # MSVC is non-conforming with the C++ standard by default, so we enable more conformance.
+ # Note that this is still not complete conformance, as certain Windows-related headers
+ # don't compile under complete conformance.
+ env.Prepend(CCFLAGS=["/permissive-"])
# Disable exception handling. Godot doesn't use exceptions anywhere, and this
# saves around 20% of binary size and very significant build time (GH-80513).
diff --git a/core/error/error_macros.h b/core/error/error_macros.h
index d31adb72be..19c16667d0 100644
--- a/core/error/error_macros.h
+++ b/core/error/error_macros.h
@@ -843,6 +843,6 @@ void _physics_interpolation_warning(const char *p_function, const char *p_file,
_physics_interpolation_warning(FUNCTION_STR, __FILE__, __LINE__, m_object_id, m_string)
#define PHYSICS_INTERPOLATION_WARNING(m_string) \
- _physics_interpolation_warning(FUNCTION_STR, __FILE__, __LINE__, UINT64_MAX, m_string)
+ _physics_interpolation_warning(FUNCTION_STR, __FILE__, __LINE__, ObjectID(UINT64_MAX), m_string)
#endif // ERROR_MACROS_H
diff --git a/doc/classes/Input.xml b/doc/classes/Input.xml
index 1e7b63ecce..5fdcc0b26b 100644
--- a/doc/classes/Input.xml
+++ b/doc/classes/Input.xml
@@ -133,6 +133,7 @@
[code]vendor_id[/code]: The USB vendor ID of the device.
[code]product_id[/code]: The USB product ID of the device.
[code]steam_input_index[/code]: The Steam Input gamepad index, if the device is not a Steam Input device this key won't be present.
+ [b]Note:[/b] The returned dictionary is always empty on Web, iOS, Android, and macOS.
</description>
</method>
<method name="get_joy_name">
diff --git a/doc/classes/MultiMesh.xml b/doc/classes/MultiMesh.xml
index 529912171c..e99694c3f0 100644
--- a/doc/classes/MultiMesh.xml
+++ b/doc/classes/MultiMesh.xml
@@ -51,6 +51,24 @@
Returns the [Transform2D] of a specific instance.
</description>
</method>
+ <method name="reset_instance_physics_interpolation">
+ <return type="void" />
+ <param index="0" name="instance" type="int" />
+ <description>
+ When using [i]physics interpolation[/i], this function allows you to prevent interpolation on an instance in the current physics tick.
+ This allows you to move instances instantaneously, and should usually be used when initially placing an instance such as a bullet to prevent graphical glitches.
+ </description>
+ </method>
+ <method name="set_buffer_interpolated">
+ <return type="void" />
+ <param index="0" name="buffer_curr" type="PackedFloat32Array" />
+ <param index="1" name="buffer_prev" type="PackedFloat32Array" />
+ <description>
+ An alternative to setting the [member buffer] property, which can be used with [i]physics interpolation[/i]. This method takes two arrays, and can set the data for the current and previous tick in one go. The renderer will automatically interpolate the data at each frame.
+ This is useful for situations where the order of instances may change from physics tick to tick, such as particle systems.
+ When the order of instances is coherent, the simpler alternative of setting [member buffer] can still be used with interpolation.
+ </description>
+ </method>
<method name="set_instance_color">
<return type="void" />
<param index="0" name="instance" type="int" />
@@ -109,6 +127,11 @@
[Mesh] resource to be instanced.
The looks of the individual instances can be modified using [method set_instance_color] and [method set_instance_custom_data].
</member>
+ <member name="physics_interpolation_quality" type="int" setter="set_physics_interpolation_quality" getter="get_physics_interpolation_quality" enum="MultiMesh.PhysicsInterpolationQuality" default="0">
+ Choose whether to use an interpolation method that favors speed or quality.
+ When using low physics tick rates (typically below 20) or high rates of object rotation, you may get better results from the high quality setting.
+ [b]Note:[/b] Fast quality does not equate to low quality. Except in the special cases mentioned above, the quality should be comparable to high quality.
+ </member>
<member name="transform_2d_array" type="PackedVector2Array" setter="_set_transform_2d_array" getter="_get_transform_2d_array" deprecated="Accessing this property is very slow. Use [method set_instance_transform_2d] and [method get_instance_transform_2d] instead.">
Array containing each [Transform2D] value used by all instances of this mesh, as a [PackedVector2Array]. Each transform is divided into 3 [Vector2] values corresponding to the transforms' [code]x[/code], [code]y[/code], and [code]origin[/code].
</member>
@@ -135,5 +158,11 @@
<constant name="TRANSFORM_3D" value="1" enum="TransformFormat">
Use this when using 3D transforms.
</constant>
+ <constant name="INTERP_QUALITY_FAST" value="0" enum="PhysicsInterpolationQuality">
+ Always interpolate using Basis lerping, which can produce warping artifacts in some situations.
+ </constant>
+ <constant name="INTERP_QUALITY_HIGH" value="1" enum="PhysicsInterpolationQuality">
+ Attempt to interpolate using Basis slerping (spherical linear interpolation) where possible, otherwise fall back to lerping.
+ </constant>
</constants>
</class>
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 1d65f6fb9a..34c41195af 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -2604,6 +2604,10 @@
- Intel GPUs: SYCL libraries
If no GPU acceleration is configured on the system, multi-threaded CPU-based denoising will be performed instead. This CPU-based denoising is significantly slower than the JNLM denoiser in most cases.
</member>
+ <member name="rendering/lightmapping/lightmap_gi/use_bicubic_filter" type="bool" setter="" getter="" default="true">
+ If [code]true[/code], applies a bicubic filter during lightmap sampling. This makes lightmaps look much smoother, at a moderate performance cost.
+ [b]Note:[/b] The bicubic filter exaggerates the 'bleeding' effect that occurs when a lightmap's resolution is low enough.
+ </member>
<member name="rendering/lightmapping/primitive_meshes/texel_size" type="float" setter="" getter="" default="0.2">
The texel_size that is used to calculate the [member Mesh.lightmap_size_hint] on [PrimitiveMesh] resources if [member PrimitiveMesh.add_uv2] is enabled.
</member>
@@ -2947,10 +2951,19 @@
Specify whether to enable eye tracking for this project. Depending on the platform, additional export configuration may be needed.
</member>
<member name="xr/openxr/extensions/hand_interaction_profile" type="bool" setter="" getter="" default="false">
- If true the hand interaction profile extension will be activated if supported by the platform.
+ If [code]true[/code] the hand interaction profile extension will be activated if supported by the platform.
+ </member>
+ <member name="xr/openxr/extensions/hand_tracking" type="bool" setter="" getter="" default="false">
+ If [code]true[/code], the hand tracking extension is enabled if available.
+ [b]Note:[/b] By default hand tracking will only work for data sources chosen by the XR runtime. For SteamVR this is the controller inferred data source, for most other runtimes this is the unobstructed data source. There is no way to query this. If a runtime supports the OpenXR data source extension you can use the [member xr/openxr/extensions/hand_tracking_controller_data_source] and/or [member xr/openxr/extensions/hand_tracking_unobstructed_data_source] to indicate you wish to enable these data sources. If neither is selected the data source extension is not enabled and the XR runtimes default behavior persists.
+ </member>
+ <member name="xr/openxr/extensions/hand_tracking_controller_data_source" type="bool" setter="" getter="" default="false">
+ If [code]true[/code], support for the controller inferred data source is requested. If supported, you will receive hand tracking data even if the user has a controller in hand, with finger positions automatically inferred from controller input and/or sensors.
+ [b]Node:[/b] This requires the OpenXR data source extension and controller inferred handtracking to be supported by the XR runtime. If not supported this setting will be ignored. [member xr/openxr/extensions/hand_tracking] must be enabled for this setting to be used.
</member>
- <member name="xr/openxr/extensions/hand_tracking" type="bool" setter="" getter="" default="true">
- If true we enable the hand tracking extension if available.
+ <member name="xr/openxr/extensions/hand_tracking_unobstructed_data_source" type="bool" setter="" getter="" default="false">
+ If [code]true[/code], support for the unobstructed data source is requested. If supported, you will receive hand tracking data based on the actual finger positions of the user often determined by optical tracking.
+ [b]Node:[/b] This requires the OpenXR data source extension and unobstructed handtracking to be supported by the XR runtime. If not supported this setting will be ignored. [member xr/openxr/extensions/hand_tracking] must be enabled for this setting to be used.
</member>
<member name="xr/openxr/form_factor" type="int" setter="" getter="" default="&quot;0&quot;">
Specify whether OpenXR should be configured for an HMD or a hand held device.
diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml
index d95990d325..c81d5d4fab 100644
--- a/doc/classes/RenderingServer.xml
+++ b/doc/classes/RenderingServer.xml
@@ -2207,6 +2207,13 @@
Set the textures on the given [param lightmap] GI instance to the texture array pointed to by the [param light] RID. If the lightmap texture was baked with [member LightmapGI.directional] set to [code]true[/code], then [param uses_sh] must also be [code]true[/code].
</description>
</method>
+ <method name="lightmaps_set_bicubic_filter">
+ <return type="void" />
+ <param index="0" name="enable" type="bool" />
+ <description>
+ Toggles whether a bicubic filter should be used when lightmaps are sampled. This smoothens their appearance at a performance cost.
+ </description>
+ </method>
<method name="make_sphere_mesh">
<return type="RID" />
<param index="0" name="latitudes" type="int" />
@@ -2559,6 +2566,15 @@
Returns the [Transform2D] of the specified instance. For use when the multimesh is set to use 2D transforms.
</description>
</method>
+ <method name="multimesh_instance_reset_physics_interpolation">
+ <return type="void" />
+ <param index="0" name="multimesh" type="RID" />
+ <param index="1" name="index" type="int" />
+ <description>
+ Prevents physics interpolation for the specified instance during the current physics tick.
+ This is useful when moving an instance to a new location, to give an instantaneous change rather than interpolation from the previous location.
+ </description>
+ </method>
<method name="multimesh_instance_set_color">
<return type="void" />
<param index="0" name="multimesh" type="RID" />
@@ -2616,6 +2632,16 @@
[/codeblock]
</description>
</method>
+ <method name="multimesh_set_buffer_interpolated">
+ <return type="void" />
+ <param index="0" name="multimesh" type="RID" />
+ <param index="1" name="buffer" type="PackedFloat32Array" />
+ <param index="2" name="buffer_previous" type="PackedFloat32Array" />
+ <description>
+ Alternative version of [method multimesh_set_buffer] for use with physics interpolation.
+ Takes both an array of current data and an array of data for the previous physics tick.
+ </description>
+ </method>
<method name="multimesh_set_custom_aabb">
<return type="void" />
<param index="0" name="multimesh" type="RID" />
@@ -2632,6 +2658,23 @@
Sets the mesh to be drawn by the multimesh. Equivalent to [member MultiMesh.mesh].
</description>
</method>
+ <method name="multimesh_set_physics_interpolated">
+ <return type="void" />
+ <param index="0" name="multimesh" type="RID" />
+ <param index="1" name="interpolated" type="bool" />
+ <description>
+ Turns on and off physics interpolation for this MultiMesh resource.
+ </description>
+ </method>
+ <method name="multimesh_set_physics_interpolation_quality">
+ <return type="void" />
+ <param index="0" name="multimesh" type="RID" />
+ <param index="1" name="quality" type="int" enum="RenderingServer.MultimeshPhysicsInterpolationQuality" />
+ <description>
+ Sets the physics interpolation quality for the [MultiMesh].
+ A value of [constant MULTIMESH_INTERP_QUALITY_FAST] gives fast but low quality interpolation, a value of [constant MULTIMESH_INTERP_QUALITY_HIGH] gives slower but higher quality interpolation.
+ </description>
+ </method>
<method name="multimesh_set_visible_instances">
<return type="void" />
<param index="0" name="multimesh" type="RID" />
@@ -4501,6 +4544,12 @@
<constant name="MULTIMESH_TRANSFORM_3D" value="1" enum="MultimeshTransformFormat">
Use [Transform3D] to store MultiMesh transform.
</constant>
+ <constant name="MULTIMESH_INTERP_QUALITY_FAST" value="0" enum="MultimeshPhysicsInterpolationQuality">
+ MultiMesh physics interpolation favors speed over quality.
+ </constant>
+ <constant name="MULTIMESH_INTERP_QUALITY_HIGH" value="1" enum="MultimeshPhysicsInterpolationQuality">
+ MultiMesh physics interpolation favors quality over speed.
+ </constant>
<constant name="LIGHT_PROJECTOR_FILTER_NEAREST" value="0" enum="LightProjectorFilter">
Nearest-neighbor filter for light projectors (use for pixel art light projectors). No mipmaps are used for rendering, which means light projectors at a distance will look sharp but grainy. This has roughly the same performance cost as using mipmaps.
</constant>
diff --git a/doc/classes/VisualShaderNodeIntParameter.xml b/doc/classes/VisualShaderNodeIntParameter.xml
index ed72584b1b..ba17da49a8 100644
--- a/doc/classes/VisualShaderNodeIntParameter.xml
+++ b/doc/classes/VisualShaderNodeIntParameter.xml
@@ -15,6 +15,9 @@
<member name="default_value_enabled" type="bool" setter="set_default_value_enabled" getter="is_default_value_enabled" default="false">
If [code]true[/code], the node will have a custom default value.
</member>
+ <member name="enum_names" type="PackedStringArray" setter="set_enum_names" getter="get_enum_names" default="PackedStringArray()">
+ The names used for the enum select in the editor. [member hint] must be [constant HINT_ENUM] for this to take effect.
+ </member>
<member name="hint" type="int" setter="set_hint" getter="get_hint" enum="VisualShaderNodeIntParameter.Hint" default="0">
Range hint of this node. Use it to customize valid parameter range.
</member>
@@ -38,7 +41,10 @@
<constant name="HINT_RANGE_STEP" value="2" enum="Hint">
The parameter's value must be within the specified range, with the given [member step] between values.
</constant>
- <constant name="HINT_MAX" value="3" enum="Hint">
+ <constant name="HINT_ENUM" value="3" enum="Hint">
+ The parameter uses an enum to associate preset values to names in the editor.
+ </constant>
+ <constant name="HINT_MAX" value="4" enum="Hint">
Represents the size of the [enum Hint] enum.
</constant>
</constants>
diff --git a/doc/classes/XRHandTracker.xml b/doc/classes/XRHandTracker.xml
index 636af6625b..79ea237480 100644
--- a/doc/classes/XRHandTracker.xml
+++ b/doc/classes/XRHandTracker.xml
@@ -107,7 +107,10 @@
<constant name="HAND_TRACKING_SOURCE_CONTROLLER" value="2" enum="HandTrackingSource">
The source of hand tracking data is a controller, meaning that joint positions are inferred from controller inputs.
</constant>
- <constant name="HAND_TRACKING_SOURCE_MAX" value="3" enum="HandTrackingSource">
+ <constant name="HAND_TRACKING_SOURCE_NOT_TRACKED" value="3" enum="HandTrackingSource">
+ No hand tracking data is tracked, this either means the hand is obscured, the controller is turned off, or tracking is not supported for the current input type.
+ </constant>
+ <constant name="HAND_TRACKING_SOURCE_MAX" value="4" enum="HandTrackingSource">
Represents the size of the [enum HandTrackingSource] enum.
</constant>
<constant name="HAND_JOINT_PALM" value="0" enum="HandJoint">
diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp
index 2565e610f7..3ed8042f3f 100644
--- a/drivers/gles3/rasterizer_scene_gles3.cpp
+++ b/drivers/gles3/rasterizer_scene_gles3.cpp
@@ -3210,6 +3210,10 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params,
if (lm->uses_spherical_harmonics) {
spec_constants |= SceneShaderGLES3::USE_SH_LIGHTMAP;
}
+
+ if (lightmap_bicubic_upscale) {
+ spec_constants |= SceneShaderGLES3::LIGHTMAP_BICUBIC_FILTER;
+ }
} else if (inst->lightmap_sh) {
spec_constants |= SceneShaderGLES3::USE_LIGHTMAP_CAPTURE;
} else {
@@ -3352,6 +3356,11 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params,
Vector4 uv_scale(inst->lightmap_uv_scale.position.x, inst->lightmap_uv_scale.position.y, inst->lightmap_uv_scale.size.x, inst->lightmap_uv_scale.size.y);
material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::LIGHTMAP_UV_SCALE, uv_scale, shader->version, instance_variant, spec_constants);
+ if (lightmap_bicubic_upscale) {
+ Vector2 light_texture_size(lm->light_texture_size.x, lm->light_texture_size.y);
+ material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::LIGHTMAP_TEXTURE_SIZE, light_texture_size, shader->version, instance_variant, spec_constants);
+ }
+
float exposure_normalization = 1.0;
if (p_render_data->camera_attributes.is_valid()) {
float enf = RSG::camera_attributes->camera_attributes_get_exposure_normalization_factor(p_render_data->camera_attributes);
@@ -4047,6 +4056,10 @@ void RasterizerSceneGLES3::decals_set_filter(RS::DecalFilter p_filter) {
void RasterizerSceneGLES3::light_projectors_set_filter(RS::LightProjectorFilter p_filter) {
}
+void RasterizerSceneGLES3::lightmaps_set_bicubic_filter(bool p_enable) {
+ lightmap_bicubic_upscale = p_enable;
+}
+
RasterizerSceneGLES3::RasterizerSceneGLES3() {
singleton = this;
@@ -4060,6 +4073,7 @@ RasterizerSceneGLES3::RasterizerSceneGLES3() {
positional_soft_shadow_filter_set_quality((RS::ShadowQuality)(int)GLOBAL_GET("rendering/lights_and_shadows/positional_shadow/soft_shadow_filter_quality"));
directional_soft_shadow_filter_set_quality((RS::ShadowQuality)(int)GLOBAL_GET("rendering/lights_and_shadows/directional_shadow/soft_shadow_filter_quality"));
+ lightmaps_set_bicubic_filter(GLOBAL_GET("rendering/lightmapping/lightmap_gi/use_bicubic_filter"));
{
// Setup Lights
diff --git a/drivers/gles3/rasterizer_scene_gles3.h b/drivers/gles3/rasterizer_scene_gles3.h
index 4c70c43244..e4af8f99e9 100644
--- a/drivers/gles3/rasterizer_scene_gles3.h
+++ b/drivers/gles3/rasterizer_scene_gles3.h
@@ -680,6 +680,8 @@ protected:
bool glow_bicubic_upscale = false;
RS::EnvironmentSSRRoughnessQuality ssr_roughness_quality = RS::ENV_SSR_ROUGHNESS_QUALITY_LOW;
+ bool lightmap_bicubic_upscale = false;
+
/* Sky */
struct SkyGlobals {
@@ -863,6 +865,7 @@ public:
void decals_set_filter(RS::DecalFilter p_filter) override;
void light_projectors_set_filter(RS::LightProjectorFilter p_filter) override;
+ virtual void lightmaps_set_bicubic_filter(bool p_enable) override;
RasterizerSceneGLES3();
~RasterizerSceneGLES3();
diff --git a/drivers/gles3/shaders/scene.glsl b/drivers/gles3/shaders/scene.glsl
index 04ba98a420..3e3b4d11f7 100644
--- a/drivers/gles3/shaders/scene.glsl
+++ b/drivers/gles3/shaders/scene.glsl
@@ -36,6 +36,7 @@ ADDITIVE_OMNI = false
ADDITIVE_SPOT = false
RENDER_MATERIAL = false
SECOND_REFLECTION_PROBE = false
+LIGHTMAP_BICUBIC_FILTER = false
#[vertex]
@@ -583,6 +584,8 @@ void main() {
#define SHADER_IS_SRGB true
+#define FLAGS_NON_UNIFORM_SCALE (1 << 4)
+
/* Varyings */
#if defined(COLOR_USED)
@@ -923,6 +926,10 @@ uniform lowp uint lightmap_slice;
uniform highp vec4 lightmap_uv_scale;
uniform float lightmap_exposure_normalization;
+#ifdef LIGHTMAP_BICUBIC_FILTER
+uniform highp vec2 lightmap_texture_size;
+#endif
+
#ifdef USE_SH_LIGHTMAP
uniform mediump mat3 lightmap_normal_xform;
#endif // USE_SH_LIGHTMAP
@@ -955,6 +962,7 @@ ivec2 multiview_uv(ivec2 uv) {
uniform highp mat4 world_transform;
uniform mediump float opaque_prepass_threshold;
+uniform highp uint model_flags;
#if defined(RENDER_MATERIAL)
layout(location = 0) out vec4 albedo_output_buffer;
@@ -1414,6 +1422,67 @@ void reflection_process(samplerCube reflection_map,
#endif // !MODE_RENDER_DEPTH
+#ifdef LIGHTMAP_BICUBIC_FILTER
+// w0, w1, w2, and w3 are the four cubic B-spline basis functions
+float w0(float a) {
+ return (1.0 / 6.0) * (a * (a * (-a + 3.0) - 3.0) + 1.0);
+}
+
+float w1(float a) {
+ return (1.0 / 6.0) * (a * a * (3.0 * a - 6.0) + 4.0);
+}
+
+float w2(float a) {
+ return (1.0 / 6.0) * (a * (a * (-3.0 * a + 3.0) + 3.0) + 1.0);
+}
+
+float w3(float a) {
+ return (1.0 / 6.0) * (a * a * a);
+}
+
+// g0 and g1 are the two amplitude functions
+float g0(float a) {
+ return w0(a) + w1(a);
+}
+
+float g1(float a) {
+ return w2(a) + w3(a);
+}
+
+// h0 and h1 are the two offset functions
+float h0(float a) {
+ return -1.0 + w1(a) / (w0(a) + w1(a));
+}
+
+float h1(float a) {
+ return 1.0 + w3(a) / (w2(a) + w3(a));
+}
+
+vec4 textureArray_bicubic(sampler2DArray tex, vec3 uv, vec2 texture_size) {
+ vec2 texel_size = vec2(1.0) / texture_size;
+
+ uv.xy = uv.xy * texture_size + vec2(0.5);
+
+ vec2 iuv = floor(uv.xy);
+ vec2 fuv = fract(uv.xy);
+
+ float g0x = g0(fuv.x);
+ float g1x = g1(fuv.x);
+ float h0x = h0(fuv.x);
+ float h1x = h1(fuv.x);
+ float h0y = h0(fuv.y);
+ float h1y = h1(fuv.y);
+
+ vec2 p0 = (vec2(iuv.x + h0x, iuv.y + h0y) - vec2(0.5)) * texel_size;
+ vec2 p1 = (vec2(iuv.x + h1x, iuv.y + h0y) - vec2(0.5)) * texel_size;
+ vec2 p2 = (vec2(iuv.x + h0x, iuv.y + h1y) - vec2(0.5)) * texel_size;
+ vec2 p3 = (vec2(iuv.x + h1x, iuv.y + h1y) - vec2(0.5)) * texel_size;
+
+ return (g0(fuv.y) * (g0x * texture(tex, vec3(p0, uv.z)) + g1x * texture(tex, vec3(p1, uv.z)))) +
+ (g1(fuv.y) * (g0x * texture(tex, vec3(p2, uv.z)) + g1x * texture(tex, vec3(p3, uv.z))));
+}
+#endif //LIGHTMAP_BICUBIC_FILTER
+
void main() {
//lay out everything, whatever is unused is optimized away anyway
vec3 vertex = vertex_interp;
@@ -1521,6 +1590,13 @@ void main() {
vec3 light_vertex = vertex;
#endif //LIGHT_VERTEX_USED
+ highp mat3 model_normal_matrix;
+ if (bool(model_flags & uint(FLAGS_NON_UNIFORM_SCALE))) {
+ model_normal_matrix = transpose(inverse(mat3(model_matrix)));
+ } else {
+ model_normal_matrix = mat3(model_matrix);
+ }
+
{
#CODE : FRAGMENT
}
@@ -1722,10 +1798,18 @@ void main() {
#ifdef USE_SH_LIGHTMAP
uvw.z *= 4.0; // SH textures use 4 times more data.
+
+#ifdef LIGHTMAP_BICUBIC_FILTER
+ vec3 lm_light_l0 = textureArray_bicubic(lightmap_textures, uvw + vec3(0.0, 0.0, 0.0), lightmap_texture_size).rgb;
+ vec3 lm_light_l1n1 = textureArray_bicubic(lightmap_textures, uvw + vec3(0.0, 0.0, 1.0), lightmap_texture_size).rgb;
+ vec3 lm_light_l1_0 = textureArray_bicubic(lightmap_textures, uvw + vec3(0.0, 0.0, 2.0), lightmap_texture_size).rgb;
+ vec3 lm_light_l1p1 = textureArray_bicubic(lightmap_textures, uvw + vec3(0.0, 0.0, 3.0), lightmap_texture_size).rgb;
+#else
vec3 lm_light_l0 = textureLod(lightmap_textures, uvw + vec3(0.0, 0.0, 0.0), 0.0).rgb;
vec3 lm_light_l1n1 = textureLod(lightmap_textures, uvw + vec3(0.0, 0.0, 1.0), 0.0).rgb;
vec3 lm_light_l1_0 = textureLod(lightmap_textures, uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb;
vec3 lm_light_l1p1 = textureLod(lightmap_textures, uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb;
+#endif
vec3 n = normalize(lightmap_normal_xform * normal);
@@ -1740,8 +1824,12 @@ void main() {
specular_light += lm_light_l1p1 * 0.32573 * r.x * lightmap_exposure_normalization;
}
#else
+#ifdef LIGHTMAP_BICUBIC_FILTER
+ ambient_light += textureArray_bicubic(lightmap_textures, uvw, lightmap_texture_size).rgb * lightmap_exposure_normalization;
+#else
ambient_light += textureLod(lightmap_textures, uvw, 0.0).rgb * lightmap_exposure_normalization;
#endif
+#endif
}
#endif // USE_LIGHTMAP
#endif // USE_LIGHTMAP_CAPTURE
diff --git a/drivers/gles3/storage/light_storage.cpp b/drivers/gles3/storage/light_storage.cpp
index f9547502f4..aab1aadf02 100644
--- a/drivers/gles3/storage/light_storage.cpp
+++ b/drivers/gles3/storage/light_storage.cpp
@@ -1046,6 +1046,9 @@ void LightStorage::lightmap_set_textures(RID p_lightmap, RID p_light, bool p_use
lightmap->light_texture = p_light;
lightmap->uses_spherical_harmonics = p_uses_spherical_haromics;
+ Vector3i light_texture_size = GLES3::TextureStorage::get_singleton()->texture_get_size(lightmap->light_texture);
+ lightmap->light_texture_size = Vector2i(light_texture_size.x, light_texture_size.y);
+
GLuint tex = GLES3::TextureStorage::get_singleton()->texture_get_texid(lightmap->light_texture);
glBindTexture(GL_TEXTURE_2D_ARRAY, tex);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
diff --git a/drivers/gles3/storage/light_storage.h b/drivers/gles3/storage/light_storage.h
index b6e64c9492..81e7220439 100644
--- a/drivers/gles3/storage/light_storage.h
+++ b/drivers/gles3/storage/light_storage.h
@@ -180,6 +180,7 @@ struct Lightmap {
bool interior = false;
AABB bounds = AABB(Vector3(), Vector3(1, 1, 1));
float baked_exposure = 1.0;
+ Vector2i light_texture_size;
int32_t array_index = -1; //unassigned
PackedVector3Array points;
PackedColorArray point_sh;
diff --git a/drivers/gles3/storage/mesh_storage.cpp b/drivers/gles3/storage/mesh_storage.cpp
index d8a5b960b8..2da025b82e 100644
--- a/drivers/gles3/storage/mesh_storage.cpp
+++ b/drivers/gles3/storage/mesh_storage.cpp
@@ -1432,15 +1432,17 @@ void MeshStorage::update_mesh_instances() {
/* MULTIMESH API */
-RID MeshStorage::multimesh_allocate() {
+RID MeshStorage::_multimesh_allocate() {
return multimesh_owner.allocate_rid();
}
-void MeshStorage::multimesh_initialize(RID p_rid) {
+void MeshStorage::_multimesh_initialize(RID p_rid) {
multimesh_owner.initialize_rid(p_rid, MultiMesh());
}
-void MeshStorage::multimesh_free(RID p_rid) {
+void MeshStorage::_multimesh_free(RID p_rid) {
+ // Remove from interpolator.
+ _interpolation_data.notify_free_multimesh(p_rid);
_update_dirty_multimeshes();
multimesh_allocate_data(p_rid, 0, RS::MULTIMESH_TRANSFORM_2D);
MultiMesh *multimesh = multimesh_owner.get_or_null(p_rid);
@@ -1448,7 +1450,7 @@ void MeshStorage::multimesh_free(RID p_rid) {
multimesh_owner.free(p_rid);
}
-void MeshStorage::multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors, bool p_use_custom_data) {
+void MeshStorage::_multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors, bool p_use_custom_data) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
@@ -1495,13 +1497,13 @@ void MeshStorage::multimesh_allocate_data(RID p_multimesh, int p_instances, RS::
multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_MULTIMESH);
}
-int MeshStorage::multimesh_get_instance_count(RID p_multimesh) const {
+int MeshStorage::_multimesh_get_instance_count(RID p_multimesh) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, 0);
return multimesh->instances;
}
-void MeshStorage::multimesh_set_mesh(RID p_multimesh, RID p_mesh) {
+void MeshStorage::_multimesh_set_mesh(RID p_multimesh, RID p_mesh) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
if (multimesh->mesh == p_mesh || p_mesh.is_null()) {
@@ -1651,7 +1653,7 @@ void MeshStorage::_multimesh_re_create_aabb(MultiMesh *multimesh, const float *p
multimesh->aabb = aabb;
}
-void MeshStorage::multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform3D &p_transform) {
+void MeshStorage::_multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform3D &p_transform) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
ERR_FAIL_INDEX(p_index, multimesh->instances);
@@ -1681,7 +1683,7 @@ void MeshStorage::multimesh_instance_set_transform(RID p_multimesh, int p_index,
_multimesh_mark_dirty(multimesh, p_index, true);
}
-void MeshStorage::multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) {
+void MeshStorage::_multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
ERR_FAIL_INDEX(p_index, multimesh->instances);
@@ -1707,7 +1709,7 @@ void MeshStorage::multimesh_instance_set_transform_2d(RID p_multimesh, int p_ind
_multimesh_mark_dirty(multimesh, p_index, true);
}
-void MeshStorage::multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) {
+void MeshStorage::_multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
ERR_FAIL_INDEX(p_index, multimesh->instances);
@@ -1727,7 +1729,7 @@ void MeshStorage::multimesh_instance_set_color(RID p_multimesh, int p_index, con
_multimesh_mark_dirty(multimesh, p_index, false);
}
-void MeshStorage::multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) {
+void MeshStorage::_multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
ERR_FAIL_INDEX(p_index, multimesh->instances);
@@ -1746,27 +1748,27 @@ void MeshStorage::multimesh_instance_set_custom_data(RID p_multimesh, int p_inde
_multimesh_mark_dirty(multimesh, p_index, false);
}
-RID MeshStorage::multimesh_get_mesh(RID p_multimesh) const {
+RID MeshStorage::_multimesh_get_mesh(RID p_multimesh) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, RID());
return multimesh->mesh;
}
-void MeshStorage::multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) {
+void MeshStorage::_multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
multimesh->custom_aabb = p_aabb;
multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_AABB);
}
-AABB MeshStorage::multimesh_get_custom_aabb(RID p_multimesh) const {
+AABB MeshStorage::_multimesh_get_custom_aabb(RID p_multimesh) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, AABB());
return multimesh->custom_aabb;
}
-AABB MeshStorage::multimesh_get_aabb(RID p_multimesh) const {
+AABB MeshStorage::_multimesh_get_aabb(RID p_multimesh) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, AABB());
if (multimesh->custom_aabb != AABB()) {
@@ -1778,7 +1780,7 @@ AABB MeshStorage::multimesh_get_aabb(RID p_multimesh) const {
return multimesh->aabb;
}
-Transform3D MeshStorage::multimesh_instance_get_transform(RID p_multimesh, int p_index) const {
+Transform3D MeshStorage::_multimesh_instance_get_transform(RID p_multimesh, int p_index) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, Transform3D());
ERR_FAIL_INDEX_V(p_index, multimesh->instances, Transform3D());
@@ -1809,7 +1811,7 @@ Transform3D MeshStorage::multimesh_instance_get_transform(RID p_multimesh, int p
return t;
}
-Transform2D MeshStorage::multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const {
+Transform2D MeshStorage::_multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, Transform2D());
ERR_FAIL_INDEX_V(p_index, multimesh->instances, Transform2D());
@@ -1834,7 +1836,7 @@ Transform2D MeshStorage::multimesh_instance_get_transform_2d(RID p_multimesh, in
return t;
}
-Color MeshStorage::multimesh_instance_get_color(RID p_multimesh, int p_index) const {
+Color MeshStorage::_multimesh_instance_get_color(RID p_multimesh, int p_index) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, Color());
ERR_FAIL_INDEX_V(p_index, multimesh->instances, Color());
@@ -1858,7 +1860,7 @@ Color MeshStorage::multimesh_instance_get_color(RID p_multimesh, int p_index) co
return c;
}
-Color MeshStorage::multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const {
+Color MeshStorage::_multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, Color());
ERR_FAIL_INDEX_V(p_index, multimesh->instances, Color());
@@ -1882,7 +1884,7 @@ Color MeshStorage::multimesh_instance_get_custom_data(RID p_multimesh, int p_ind
return c;
}
-void MeshStorage::multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer) {
+void MeshStorage::_multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
@@ -1971,7 +1973,7 @@ void MeshStorage::multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_b
}
}
-Vector<float> MeshStorage::multimesh_get_buffer(RID p_multimesh) const {
+Vector<float> MeshStorage::_multimesh_get_buffer(RID p_multimesh) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, Vector<float>());
Vector<float> ret;
@@ -2043,7 +2045,7 @@ Vector<float> MeshStorage::multimesh_get_buffer(RID p_multimesh) const {
}
}
-void MeshStorage::multimesh_set_visible_instances(RID p_multimesh, int p_visible) {
+void MeshStorage::_multimesh_set_visible_instances(RID p_multimesh, int p_visible) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
ERR_FAIL_COND(p_visible < -1 || p_visible > multimesh->instances);
@@ -2065,12 +2067,19 @@ void MeshStorage::multimesh_set_visible_instances(RID p_multimesh, int p_visible
multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_MULTIMESH_VISIBLE_INSTANCES);
}
-int MeshStorage::multimesh_get_visible_instances(RID p_multimesh) const {
+int MeshStorage::_multimesh_get_visible_instances(RID p_multimesh) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, 0);
return multimesh->visible_instances;
}
+MeshStorage::MultiMeshInterpolator *MeshStorage::_multimesh_get_interpolator(RID p_multimesh) const {
+ MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
+ ERR_FAIL_NULL_V_MSG(multimesh, nullptr, "Multimesh not found: " + itos(p_multimesh.get_id()));
+
+ return &multimesh->interpolator;
+}
+
void MeshStorage::_update_dirty_multimeshes() {
while (multimesh_dirty_list) {
MultiMesh *multimesh = multimesh_dirty_list;
diff --git a/drivers/gles3/storage/mesh_storage.h b/drivers/gles3/storage/mesh_storage.h
index d246e7725c..a2edbb9c48 100644
--- a/drivers/gles3/storage/mesh_storage.h
+++ b/drivers/gles3/storage/mesh_storage.h
@@ -205,6 +205,8 @@ struct MultiMesh {
bool dirty = false;
MultiMesh *dirty_list = nullptr;
+ RendererMeshStorage::MultiMeshInterpolator interpolator;
+
Dependency dependency;
};
@@ -493,32 +495,34 @@ public:
MultiMesh *get_multimesh(RID p_rid) { return multimesh_owner.get_or_null(p_rid); };
bool owns_multimesh(RID p_rid) { return multimesh_owner.owns(p_rid); };
- virtual RID multimesh_allocate() override;
- virtual void multimesh_initialize(RID p_rid) override;
- virtual void multimesh_free(RID p_rid) override;
- virtual void multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false) override;
- virtual int multimesh_get_instance_count(RID p_multimesh) const override;
-
- virtual void multimesh_set_mesh(RID p_multimesh, RID p_mesh) override;
- virtual void multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform3D &p_transform) override;
- virtual void multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) override;
- virtual void multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) override;
- virtual void multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) override;
-
- virtual RID multimesh_get_mesh(RID p_multimesh) const override;
- virtual void multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) override;
- virtual AABB multimesh_get_custom_aabb(RID p_multimesh) const override;
- virtual AABB multimesh_get_aabb(RID p_multimesh) const override;
-
- virtual Transform3D multimesh_instance_get_transform(RID p_multimesh, int p_index) const override;
- virtual Transform2D multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const override;
- virtual Color multimesh_instance_get_color(RID p_multimesh, int p_index) const override;
- virtual Color multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const override;
- virtual void multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer) override;
- virtual Vector<float> multimesh_get_buffer(RID p_multimesh) const override;
-
- virtual void multimesh_set_visible_instances(RID p_multimesh, int p_visible) override;
- virtual int multimesh_get_visible_instances(RID p_multimesh) const override;
+ virtual RID _multimesh_allocate() override;
+ virtual void _multimesh_initialize(RID p_rid) override;
+ virtual void _multimesh_free(RID p_rid) override;
+ virtual void _multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false) override;
+ virtual int _multimesh_get_instance_count(RID p_multimesh) const override;
+
+ virtual void _multimesh_set_mesh(RID p_multimesh, RID p_mesh) override;
+ virtual void _multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform3D &p_transform) override;
+ virtual void _multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) override;
+ virtual void _multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) override;
+ virtual void _multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) override;
+
+ virtual RID _multimesh_get_mesh(RID p_multimesh) const override;
+ virtual void _multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) override;
+ virtual AABB _multimesh_get_custom_aabb(RID p_multimesh) const override;
+ virtual AABB _multimesh_get_aabb(RID p_multimesh) const override;
+
+ virtual Transform3D _multimesh_instance_get_transform(RID p_multimesh, int p_index) const override;
+ virtual Transform2D _multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const override;
+ virtual Color _multimesh_instance_get_color(RID p_multimesh, int p_index) const override;
+ virtual Color _multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const override;
+ virtual void _multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer) override;
+ virtual Vector<float> _multimesh_get_buffer(RID p_multimesh) const override;
+
+ virtual void _multimesh_set_visible_instances(RID p_multimesh, int p_visible) override;
+ virtual int _multimesh_get_visible_instances(RID p_multimesh) const override;
+
+ virtual MultiMeshInterpolator *_multimesh_get_interpolator(RID p_multimesh) const override;
void _update_dirty_multimeshes();
diff --git a/drivers/gles3/storage/texture_storage.cpp b/drivers/gles3/storage/texture_storage.cpp
index 3b1373c928..8251c8f52e 100644
--- a/drivers/gles3/storage/texture_storage.cpp
+++ b/drivers/gles3/storage/texture_storage.cpp
@@ -1680,6 +1680,14 @@ uint32_t TextureStorage::texture_get_texid(RID p_texture) const {
return texture->tex_id;
}
+Vector3i TextureStorage::texture_get_size(RID p_texture) const {
+ Texture *texture = texture_owner.get_or_null(p_texture);
+
+ ERR_FAIL_NULL_V(texture, Vector3i(0, 0, 0));
+
+ return Vector3i(texture->width, texture->height, texture->depth);
+}
+
uint32_t TextureStorage::texture_get_width(RID p_texture) const {
Texture *texture = texture_owner.get_or_null(p_texture);
diff --git a/drivers/gles3/storage/texture_storage.h b/drivers/gles3/storage/texture_storage.h
index 1b83efee32..5569abcc73 100644
--- a/drivers/gles3/storage/texture_storage.h
+++ b/drivers/gles3/storage/texture_storage.h
@@ -553,6 +553,7 @@ public:
void texture_set_data(RID p_texture, const Ref<Image> &p_image, int p_layer = 0);
virtual Image::Format texture_get_format(RID p_texture) const override;
uint32_t texture_get_texid(RID p_texture) const;
+ Vector3i texture_get_size(RID p_texture) const;
uint32_t texture_get_width(RID p_texture) const;
uint32_t texture_get_height(RID p_texture) const;
uint32_t texture_get_depth(RID p_texture) const;
diff --git a/editor/animation_bezier_editor.cpp b/editor/animation_bezier_editor.cpp
index 48b9e01fd8..f74bdd8bb9 100644
--- a/editor/animation_bezier_editor.cpp
+++ b/editor/animation_bezier_editor.cpp
@@ -1083,7 +1083,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
if (I.key == REMOVE_ICON) {
if (!read_only) {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
- undo_redo->create_action("Remove Bezier Track");
+ undo_redo->create_action("Remove Bezier Track", UndoRedo::MERGE_DISABLE, animation.ptr());
undo_redo->add_do_method(this, "_update_locked_tracks_after", track);
undo_redo->add_do_method(this, "_update_hidden_tracks_after", track);
diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp
index d24b1edd70..8664c167b5 100644
--- a/editor/code_editor.cpp
+++ b/editor/code_editor.cpp
@@ -994,6 +994,9 @@ Ref<Texture2D> CodeTextEditor::_get_completion_icon(const ScriptLanguage::CodeCo
tex = get_editor_theme_icon(p_option.display);
} else {
tex = EditorNode::get_singleton()->get_class_icon(p_option.display);
+ if (!tex.is_valid()) {
+ tex = get_editor_theme_icon(SNAME("Object"));
+ }
}
} break;
case ScriptLanguage::CODE_COMPLETION_KIND_ENUM:
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index d1dffba2ab..ad95502a2b 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -429,6 +429,7 @@ void EditorNode::_update_from_settings() {
RS::get_singleton()->decals_set_filter(RS::DecalFilter(int(GLOBAL_GET("rendering/textures/decals/filter"))));
RS::get_singleton()->light_projectors_set_filter(RS::LightProjectorFilter(int(GLOBAL_GET("rendering/textures/light_projectors/filter"))));
+ RS::get_singleton()->lightmaps_set_bicubic_filter(GLOBAL_GET("rendering/lightmapping/lightmap_gi/use_bicubic_filter"));
SceneTree *tree = get_tree();
tree->set_debug_collisions_color(GLOBAL_GET("debug/shapes/collision/shape_color"));
@@ -5343,7 +5344,7 @@ bool EditorNode::ensure_main_scene(bool p_from_native) {
if (main_scene.is_empty()) {
current_menu_option = -1;
- pick_main_scene->set_text(TTR("No main scene has ever been defined, select one?\nYou can change it later in \"Project Settings\" under the 'application' category."));
+ pick_main_scene->set_text(TTR("No main scene has ever been defined. Select one?\nYou can change it later in \"Project Settings\" under the 'application' category."));
pick_main_scene->popup_centered();
if (editor_data.get_edited_scene_root()) {
@@ -5358,14 +5359,14 @@ bool EditorNode::ensure_main_scene(bool p_from_native) {
if (!FileAccess::exists(main_scene)) {
current_menu_option = -1;
- pick_main_scene->set_text(vformat(TTR("Selected scene '%s' does not exist, select a valid one?\nYou can change it later in \"Project Settings\" under the 'application' category."), main_scene));
+ pick_main_scene->set_text(vformat(TTR("Selected scene '%s' does not exist. Select a valid one?\nYou can change it later in \"Project Settings\" under the 'application' category."), main_scene));
pick_main_scene->popup_centered();
return false;
}
if (ResourceLoader::get_resource_type(main_scene) != "PackedScene") {
current_menu_option = -1;
- pick_main_scene->set_text(vformat(TTR("Selected scene '%s' is not a scene file, select a valid one?\nYou can change it later in \"Project Settings\" under the 'application' category."), main_scene));
+ pick_main_scene->set_text(vformat(TTR("Selected scene '%s' is not a scene file. Select a valid one?\nYou can change it later in \"Project Settings\" under the 'application' category."), main_scene));
pick_main_scene->popup_centered();
return false;
}
diff --git a/editor/editor_resource_picker.cpp b/editor/editor_resource_picker.cpp
index 4cd44e3020..8935b9ad8a 100644
--- a/editor/editor_resource_picker.cpp
+++ b/editor/editor_resource_picker.cpp
@@ -618,9 +618,9 @@ void EditorResourcePicker::_ensure_allowed_types() const {
const String base = allowed_types[i].strip_edges();
if (base == "BaseMaterial3D") {
allowed_types_with_convert.insert("Texture2D");
- } else if (base == "ShaderMaterial") {
+ } else if (ClassDB::is_parent_class("ShaderMaterial", base)) {
allowed_types_with_convert.insert("Shader");
- } else if (base == "Texture2D") {
+ } else if (ClassDB::is_parent_class("ImageTexture", base)) {
allowed_types_with_convert.insert("Image");
}
}
diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp
index b2669cf1af..d50f5e51d5 100644
--- a/editor/plugins/visual_shader_editor_plugin.cpp
+++ b/editor/plugins/visual_shader_editor_plugin.cpp
@@ -115,8 +115,8 @@ void VSGraphNode::_draw_port(int p_slot_index, Point2i p_pos, bool p_left, const
icon_offset = -port_icon->get_size() * 0.5;
// Draw "shadow"/outline in the connection rim color.
- draw_texture_rect(port_icon, Rect2(p_pos + icon_offset - Size2(2, 2), port_icon->get_size() + Size2(4, 4)), false, p_rim_color);
- draw_texture(port_icon, p_pos + icon_offset, p_color);
+ draw_texture_rect(port_icon, Rect2(p_pos + (icon_offset - Size2(2, 2)) * EDSCALE, (port_icon->get_size() + Size2(4, 4)) * EDSCALE), false, p_rim_color);
+ draw_texture_rect(port_icon, Rect2(p_pos + icon_offset * EDSCALE, port_icon->get_size() * EDSCALE), false, p_color);
}
void VSGraphNode::draw_port(int p_slot_index, Point2i p_pos, bool p_left, const Color &p_color) {
diff --git a/main/main.cpp b/main/main.cpp
index bdae1bb1b0..9ee88af60e 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -2512,7 +2512,9 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
GLOBAL_DEF_BASIC("xr/openxr/startup_alert", true);
// OpenXR project extensions settings.
- GLOBAL_DEF_BASIC("xr/openxr/extensions/hand_tracking", true);
+ GLOBAL_DEF_BASIC("xr/openxr/extensions/hand_tracking", false);
+ GLOBAL_DEF_BASIC("xr/openxr/extensions/hand_tracking_unobstructed_data_source", false); // XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT
+ GLOBAL_DEF_BASIC("xr/openxr/extensions/hand_tracking_controller_data_source", false); // XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT
GLOBAL_DEF_RST_BASIC("xr/openxr/extensions/hand_interaction_profile", false);
GLOBAL_DEF_BASIC("xr/openxr/extensions/eye_gaze_interaction", false);
diff --git a/modules/cvtt/image_compress_cvtt.cpp b/modules/cvtt/image_compress_cvtt.cpp
index 4938d8bff5..a0578bbd9d 100644
--- a/modules/cvtt/image_compress_cvtt.cpp
+++ b/modules/cvtt/image_compress_cvtt.cpp
@@ -149,7 +149,7 @@ void image_compress_cvtt(Image *p_image, Image::UsedChannels p_channels) {
int h = p_image->get_height();
bool is_ldr = (p_image->get_format() <= Image::FORMAT_RGBA8);
- bool is_hdr = (p_image->get_format() >= Image::FORMAT_RH) && (p_image->get_format() <= Image::FORMAT_RGBE9995);
+ bool is_hdr = (p_image->get_format() >= Image::FORMAT_RF) && (p_image->get_format() <= Image::FORMAT_RGBE9995);
if (!is_ldr && !is_hdr) {
return; // Not a usable source format
diff --git a/modules/gdscript/config.py b/modules/gdscript/config.py
index a7d5c406e9..ecd33a5dac 100644
--- a/modules/gdscript/config.py
+++ b/modules/gdscript/config.py
@@ -11,6 +11,7 @@ def get_doc_classes():
return [
"@GDScript",
"GDScript",
+ "GDScriptSyntaxHighlighter",
]
diff --git a/modules/gdscript/doc_classes/GDScriptSyntaxHighlighter.xml b/modules/gdscript/doc_classes/GDScriptSyntaxHighlighter.xml
new file mode 100644
index 0000000000..63a9222901
--- /dev/null
+++ b/modules/gdscript/doc_classes/GDScriptSyntaxHighlighter.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="GDScriptSyntaxHighlighter" inherits="EditorSyntaxHighlighter" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
+ <brief_description>
+ A GDScript syntax highlighter that can be used with [TextEdit] and [CodeEdit] nodes.
+ </brief_description>
+ <description>
+ [b]Note:[/b] This class can only be used for editor plugins because it relies on editor settings.
+ [codeblocks]
+ [gdscript]
+ var code_preview = TextEdit.new()
+ var highlighter = GDScriptSyntaxHighlighter.new()
+ code_preview.syntax_highlighter = highlighter
+ [/gdscript]
+ [csharp]
+ var codePreview = new TextEdit();
+ var highlighter = new GDScriptSyntaxHighlighter();
+ codePreview.SyntaxHighlighter = highlighter;
+ [/csharp]
+ [/codeblocks]
+ </description>
+ <tutorials>
+ </tutorials>
+</class>
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 54bb152f7f..ecef852b4b 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -245,8 +245,26 @@ void GDScriptParser::apply_pending_warnings() {
}
#endif
-void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node, int p_argument, bool p_force) {
- if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) {
+void GDScriptParser::override_completion_context(const Node *p_for_node, CompletionType p_type, Node *p_node, int p_argument) {
+ if (!for_completion) {
+ return;
+ }
+ if (completion_context.node != p_for_node) {
+ return;
+ }
+ CompletionContext context;
+ context.type = p_type;
+ context.current_class = current_class;
+ context.current_function = current_function;
+ context.current_suite = current_suite;
+ context.current_line = tokenizer->get_cursor_line();
+ context.current_argument = p_argument;
+ context.node = p_node;
+ completion_context = context;
+}
+
+void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node, int p_argument) {
+ if (!for_completion) {
return;
}
if (previous.cursor_place != GDScriptTokenizerText::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizerText::CURSOR_END && current.cursor_place == GDScriptTokenizerText::CURSOR_NONE) {
@@ -264,8 +282,8 @@ void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node
completion_context = context;
}
-void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Type p_builtin_type, bool p_force) {
- if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) {
+void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Type p_builtin_type) {
+ if (!for_completion) {
return;
}
if (previous.cursor_place != GDScriptTokenizerText::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizerText::CURSOR_END && current.cursor_place == GDScriptTokenizerText::CURSOR_NONE) {
@@ -1620,7 +1638,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
advance();
// Arguments.
push_completion_call(annotation);
- make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, 0, true);
+ make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, 0);
int argument_index = 0;
do {
if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {
@@ -1628,7 +1646,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
break;
}
- make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index, true);
+ make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index);
set_last_completion_call_arg(argument_index++);
ExpressionNode *argument = parse_expression(false);
if (argument == nullptr) {
@@ -2569,8 +2587,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_literal(ExpressionNode *p_
}
LiteralNode *literal = alloc_node<LiteralNode>();
- complete_extents(literal);
literal->value = previous.literal;
+ reset_extents(literal, p_previous_operand);
+ update_extents(literal);
+ make_completion_context(COMPLETION_NONE, literal, -1);
+ complete_extents(literal);
return literal;
}
@@ -3065,12 +3086,12 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_attribute(ExpressionNode *
const IdentifierNode *id = static_cast<const IdentifierNode *>(p_previous_operand);
Variant::Type builtin_type = get_builtin_type(id->name);
if (builtin_type < Variant::VARIANT_MAX) {
- make_completion_context(COMPLETION_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD, builtin_type, true);
+ make_completion_context(COMPLETION_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD, builtin_type);
is_builtin = true;
}
}
if (!is_builtin) {
- make_completion_context(COMPLETION_ATTRIBUTE, attribute, -1, true);
+ make_completion_context(COMPLETION_ATTRIBUTE, attribute, -1);
}
}
@@ -3195,23 +3216,24 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
push_completion_call(call);
int argument_index = 0;
do {
- make_completion_context(ct, call, argument_index++, true);
+ make_completion_context(ct, call, argument_index);
if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {
// Allow for trailing comma.
break;
}
- bool use_identifier_completion = current.cursor_place == GDScriptTokenizerText::CURSOR_END || current.cursor_place == GDScriptTokenizerText::CURSOR_MIDDLE;
ExpressionNode *argument = parse_expression(false);
if (argument == nullptr) {
push_error(R"(Expected expression as the function argument.)");
} else {
call->arguments.push_back(argument);
- if (argument->type == Node::IDENTIFIER && use_identifier_completion) {
- completion_context.type = COMPLETION_IDENTIFIER;
+ if (argument->type == Node::LITERAL) {
+ override_completion_context(argument, ct, call, argument_index);
}
}
+
ct = COMPLETION_CALL_ARGUMENTS;
+ argument_index++;
} while (match(GDScriptTokenizer::Token::COMMA));
pop_completion_call();
@@ -3224,7 +3246,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p_previous_operand, bool p_can_assign) {
// We want code completion after a DOLLAR even if the current code is invalid.
- make_completion_context(COMPLETION_GET_NODE, nullptr, -1, true);
+ make_completion_context(COMPLETION_GET_NODE, nullptr, -1);
if (!current.is_node_name() && !check(GDScriptTokenizer::Token::LITERAL) && !check(GDScriptTokenizer::Token::SLASH) && !check(GDScriptTokenizer::Token::PERCENT)) {
push_error(vformat(R"(Expected node path as string or identifier after "%s".)", previous.get_name()));
@@ -3281,7 +3303,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p
path_state = PATH_STATE_SLASH;
}
- make_completion_context(COMPLETION_GET_NODE, get_node, context_argument++, true);
+ make_completion_context(COMPLETION_GET_NODE, get_node, context_argument++);
if (match(GDScriptTokenizer::Token::LITERAL)) {
if (previous.literal.get_type() != Variant::STRING) {
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 60ee477656..2999fb11e4 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -1455,9 +1455,11 @@ private:
}
void apply_pending_warnings();
#endif
-
- void make_completion_context(CompletionType p_type, Node *p_node, int p_argument = -1, bool p_force = false);
- void make_completion_context(CompletionType p_type, Variant::Type p_builtin_type, bool p_force = false);
+ void make_completion_context(CompletionType p_type, Node *p_node, int p_argument = -1);
+ void make_completion_context(CompletionType p_type, Variant::Type p_builtin_type);
+ // In some cases it might become necessary to alter the completion context after parsing a subexpression.
+ // For example to not override COMPLETE_CALL_ARGUMENTS with COMPLETION_NONE from string literals.
+ void override_completion_context(const Node *p_for_node, CompletionType p_type, Node *p_node, int p_argument = -1);
void push_completion_call(Node *p_call);
void pop_completion_call();
void set_last_completion_call_arg(int p_argument);
diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp
index 59e387eece..055f8e4110 100644
--- a/modules/gdscript/register_types.cpp
+++ b/modules/gdscript/register_types.cpp
@@ -165,6 +165,13 @@ void initialize_gdscript_module(ModuleInitializationLevel p_level) {
gdscript_translation_parser_plugin.instantiate();
EditorTranslationParser::get_singleton()->add_parser(gdscript_translation_parser_plugin, EditorTranslationParser::STANDARD);
+ } else if (p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) {
+ ClassDB::APIType prev_api = ClassDB::get_current_api();
+ ClassDB::set_current_api(ClassDB::API_EDITOR);
+
+ GDREGISTER_CLASS(GDScriptSyntaxHighlighter);
+
+ ClassDB::set_current_api(prev_api);
}
#endif // TOOLS_ENABLED
}
diff --git a/modules/gdscript/tests/scripts/completion/argument_options/string_literals/argument_options_inside_string_literal.cfg b/modules/gdscript/tests/scripts/completion/argument_options/string_literals/argument_options_inside_string_literal.cfg
new file mode 100644
index 0000000000..be9bd510e1
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/argument_options/string_literals/argument_options_inside_string_literal.cfg
@@ -0,0 +1,5 @@
+[output]
+include=[
+ {"insert_text": "\"property_of_a\""},
+ {"insert_text": "\"name\""},
+]
diff --git a/modules/gdscript/tests/scripts/completion/argument_options/string_literals/argument_options_inside_string_literal.gd b/modules/gdscript/tests/scripts/completion/argument_options/string_literals/argument_options_inside_string_literal.gd
new file mode 100644
index 0000000000..a8e04a62a7
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/argument_options/string_literals/argument_options_inside_string_literal.gd
@@ -0,0 +1,8 @@
+extends Node
+
+const A = preload ("res://completion/class_a.notest.gd")
+
+func _ready() -> void:
+ var a := A.new()
+ var tween := get_tree().create_tween()
+ tween.tween_property(a, "➡")
diff --git a/modules/gdscript/tests/scripts/completion/common/identifiers.cfg b/modules/gdscript/tests/scripts/completion/common/identifiers_in_call.cfg
index 871a404e3a..5f08f9c265 100644
--- a/modules/gdscript/tests/scripts/completion/common/identifiers.cfg
+++ b/modules/gdscript/tests/scripts/completion/common/identifiers_in_call.cfg
@@ -11,11 +11,15 @@ include=[
{"display": "func_of_a"},
{"display": "signal_of_a"},
- ; GDScript: self.gd
+ ; GDScript: identifiers.gd
{"display": "test_signal_1"},
{"display": "test_signal_2"},
{"display": "test_var_1"},
{"display": "test_var_2"},
{"display": "test_func_1"},
{"display": "test_func_2"},
+ {"display": "test_parameter_1"},
+ {"display": "test_parameter_2"},
+ {"display": "local_test_var_1"},
+ {"display": "local_test_var_2"},
]
diff --git a/modules/gdscript/tests/scripts/completion/common/identifiers_in_call.gd b/modules/gdscript/tests/scripts/completion/common/identifiers_in_call.gd
new file mode 100644
index 0000000000..91488c25aa
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/common/identifiers_in_call.gd
@@ -0,0 +1,18 @@
+extends "res://completion/class_a.notest.gd"
+
+signal test_signal_1(a)
+signal test_signal_2(a: int)
+
+var test_var_1
+var test_var_2: int
+
+func test_func_1(t):
+ pass
+
+func test_func_2(t: int) -> void:
+ pass
+
+func _init(test_parameter_1, test_parameter_2: String):
+ var local_test_var_1
+ var local_test_var_2: int
+ print(t➡)
diff --git a/modules/gdscript/tests/scripts/completion/common/identifiers_in_function_body.cfg b/modules/gdscript/tests/scripts/completion/common/identifiers_in_function_body.cfg
new file mode 100644
index 0000000000..5f08f9c265
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/common/identifiers_in_function_body.cfg
@@ -0,0 +1,25 @@
+scene="res://completion/get_node/get_node.tscn"
+[output]
+include=[
+ ; Node
+ {"display": "add_child"},
+ {"display": "owner"},
+ {"display": "child_entered_tree"},
+
+ ; GDScript: class_a.notest.gd
+ {"display": "property_of_a"},
+ {"display": "func_of_a"},
+ {"display": "signal_of_a"},
+
+ ; GDScript: identifiers.gd
+ {"display": "test_signal_1"},
+ {"display": "test_signal_2"},
+ {"display": "test_var_1"},
+ {"display": "test_var_2"},
+ {"display": "test_func_1"},
+ {"display": "test_func_2"},
+ {"display": "test_parameter_1"},
+ {"display": "test_parameter_2"},
+ {"display": "local_test_var_1"},
+ {"display": "local_test_var_2"},
+]
diff --git a/modules/gdscript/tests/scripts/completion/common/identifiers.gd b/modules/gdscript/tests/scripts/completion/common/identifiers_in_function_body.gd
index efbafbee8e..a2f5b7bc23 100644
--- a/modules/gdscript/tests/scripts/completion/common/identifiers.gd
+++ b/modules/gdscript/tests/scripts/completion/common/identifiers_in_function_body.gd
@@ -12,5 +12,7 @@ func test_func_1(t):
func test_func_2(t: int) -> void:
pass
-func _init():
+func _init(test_parameter_1, test_parameter_2: String):
+ var local_test_var_1
+ var local_test_var_2: int
t➡
diff --git a/modules/gdscript/tests/scripts/completion/common/identifiers_in_unclosed_call.cfg b/modules/gdscript/tests/scripts/completion/common/identifiers_in_unclosed_call.cfg
new file mode 100644
index 0000000000..5f08f9c265
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/common/identifiers_in_unclosed_call.cfg
@@ -0,0 +1,25 @@
+scene="res://completion/get_node/get_node.tscn"
+[output]
+include=[
+ ; Node
+ {"display": "add_child"},
+ {"display": "owner"},
+ {"display": "child_entered_tree"},
+
+ ; GDScript: class_a.notest.gd
+ {"display": "property_of_a"},
+ {"display": "func_of_a"},
+ {"display": "signal_of_a"},
+
+ ; GDScript: identifiers.gd
+ {"display": "test_signal_1"},
+ {"display": "test_signal_2"},
+ {"display": "test_var_1"},
+ {"display": "test_var_2"},
+ {"display": "test_func_1"},
+ {"display": "test_func_2"},
+ {"display": "test_parameter_1"},
+ {"display": "test_parameter_2"},
+ {"display": "local_test_var_1"},
+ {"display": "local_test_var_2"},
+]
diff --git a/modules/gdscript/tests/scripts/completion/common/identifiers_in_unclosed_call.gd b/modules/gdscript/tests/scripts/completion/common/identifiers_in_unclosed_call.gd
new file mode 100644
index 0000000000..fed0b869c4
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/common/identifiers_in_unclosed_call.gd
@@ -0,0 +1,22 @@
+# godotengine/godot#92226
+extends "res://completion/class_a.notest.gd"
+
+signal test_signal_1(a)
+signal test_signal_2(a: int)
+
+var test_var_1
+var test_var_2: int
+
+func test_func_1(t):
+ pass
+
+func test_func_2(t: int) -> void:
+ pass
+
+func _init(test_parameter_1, test_parameter_2: String):
+ var local_test_var_1
+ var local_test_var_2: int
+ print(t➡
+
+ if true:
+ pass
diff --git a/modules/gdscript/tests/scripts/completion/common/no_completion_in_string.cfg b/modules/gdscript/tests/scripts/completion/common/no_completion_in_string.cfg
new file mode 100644
index 0000000000..462846c9b2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/common/no_completion_in_string.cfg
@@ -0,0 +1,26 @@
+scene="res://completion/get_node/get_node.tscn"
+[output]
+exclude=[
+ ; Node
+ {"display": "add_child"},
+ {"display": "owner"},
+ {"display": "child_entered_tree"},
+ {"display": "add_child"},
+
+ ; GDScript: class_a.notest.gd
+ {"display": "property_of_a"},
+ {"display": "func_of_a"},
+ {"display": "signal_of_a"},
+
+ ; GDScript: no_completion_in_string.gd
+ {"display": "test_signal_1"},
+ {"display": "test_signal_2"},
+ {"display": "test_var_1"},
+ {"display": "test_var_2"},
+ {"display": "test_func_1"},
+ {"display": "test_func_2"},
+ {"display": "test_parameter_1"},
+ {"display": "test_parameter_2"},
+ {"display": "local_test_var_1"},
+ {"display": "local_test_var_2"},
+]
diff --git a/modules/gdscript/tests/scripts/completion/common/no_completion_in_string.gd b/modules/gdscript/tests/scripts/completion/common/no_completion_in_string.gd
new file mode 100644
index 0000000000..da52af9fe3
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/common/no_completion_in_string.gd
@@ -0,0 +1,19 @@
+# godotengine/godot#62945
+extends "res://completion/class_a.notest.gd"
+
+signal test_signal_1(a)
+signal test_signal_2(a: int)
+
+var test_var_1
+var test_var_2: int
+
+func test_func_1(t):
+ pass
+
+func test_func_2(t: int) -> void:
+ pass
+
+func _init(test_parameter_1, test_parameter_2: String):
+ var local_test_var_1
+ var local_test_var_2: int
+ var a = "➡"
diff --git a/modules/gdscript/tests/scripts/completion/common/self.gd b/modules/gdscript/tests/scripts/completion/common/self.gd
index 9ad2fbea51..ed181af0c5 100644
--- a/modules/gdscript/tests/scripts/completion/common/self.gd
+++ b/modules/gdscript/tests/scripts/completion/common/self.gd
@@ -14,3 +14,4 @@ func test_func_2(t: int) -> void:
func _init():
self.➡
+ pass
diff --git a/modules/gdscript/tests/scripts/completion/filter/organized_export.gd b/modules/gdscript/tests/scripts/completion/filter/organized_export.gd
index 189608904c..9fa9618cee 100644
--- a/modules/gdscript/tests/scripts/completion/filter/organized_export.gd
+++ b/modules/gdscript/tests/scripts/completion/filter/organized_export.gd
@@ -5,4 +5,5 @@ extends CPUParticles2D
@export_subgroup("Test Subgroup")
func _init():
- ➡
+ t➡
+ pass
diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_interfered/local_interfered.cfg b/modules/gdscript/tests/scripts/completion/get_node/local_infered/local_infered.cfg
index ae7d34d87d..ae7d34d87d 100644
--- a/modules/gdscript/tests/scripts/completion/get_node/local_interfered/local_interfered.cfg
+++ b/modules/gdscript/tests/scripts/completion/get_node/local_infered/local_infered.cfg
diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_interfered/local_interfered.gd b/modules/gdscript/tests/scripts/completion/get_node/local_infered/local_infered.gd
index 7710c2d13b..7710c2d13b 100644
--- a/modules/gdscript/tests/scripts/completion/get_node/local_interfered/local_interfered.gd
+++ b/modules/gdscript/tests/scripts/completion/get_node/local_infered/local_infered.gd
diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/class_local_interfered_scene.cfg b/modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/class_local_infered_scene.cfg
index 9c580b711d..9c580b711d 100644
--- a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/class_local_interfered_scene.cfg
+++ b/modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/class_local_infered_scene.cfg
diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/class_local_interfered_scene.gd b/modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/class_local_infered_scene.gd
index 6b29bf5526..6b29bf5526 100644
--- a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/class_local_interfered_scene.gd
+++ b/modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/class_local_infered_scene.gd
diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/native_local_interfered_scene.cfg b/modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/native_local_infered_scene.cfg
index 446198dd35..446198dd35 100644
--- a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/native_local_interfered_scene.cfg
+++ b/modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/native_local_infered_scene.cfg
diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/native_local_interfered_scene.gd b/modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/native_local_infered_scene.gd
index 7710c2d13b..7710c2d13b 100644
--- a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/native_local_interfered_scene.gd
+++ b/modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/native_local_infered_scene.gd
diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_interfered/member_interfered.cfg b/modules/gdscript/tests/scripts/completion/get_node/member_infered/member_infered.cfg
index ae7d34d87d..ae7d34d87d 100644
--- a/modules/gdscript/tests/scripts/completion/get_node/member_interfered/member_interfered.cfg
+++ b/modules/gdscript/tests/scripts/completion/get_node/member_infered/member_infered.cfg
diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_interfered/member_interfered.gd b/modules/gdscript/tests/scripts/completion/get_node/member_infered/member_infered.gd
index 97b288334e..97b288334e 100644
--- a/modules/gdscript/tests/scripts/completion/get_node/member_interfered/member_interfered.gd
+++ b/modules/gdscript/tests/scripts/completion/get_node/member_infered/member_infered.gd
diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/class_member_interfered_scene.cfg b/modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/class_member_infered_scene.cfg
index 9c580b711d..9c580b711d 100644
--- a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/class_member_interfered_scene.cfg
+++ b/modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/class_member_infered_scene.cfg
diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/class_member_interfered_scene.gd b/modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/class_member_infered_scene.gd
index 402fd1d275..402fd1d275 100644
--- a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/class_member_interfered_scene.gd
+++ b/modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/class_member_infered_scene.gd
diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/native_member_interfered_scene.cfg b/modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/native_member_infered_scene.cfg
index 446198dd35..446198dd35 100644
--- a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/native_member_interfered_scene.cfg
+++ b/modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/native_member_infered_scene.cfg
diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/native_member_interfered_scene.gd b/modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/native_member_infered_scene.gd
index 97b288334e..97b288334e 100644
--- a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/native_member_interfered_scene.gd
+++ b/modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/native_member_infered_scene.gd
diff --git a/modules/gdscript/tests/scripts/completion/types/local/interfered.cfg b/modules/gdscript/tests/scripts/completion/types/local/infered.cfg
index 8b68d51a89..8b68d51a89 100644
--- a/modules/gdscript/tests/scripts/completion/types/local/interfered.cfg
+++ b/modules/gdscript/tests/scripts/completion/types/local/infered.cfg
diff --git a/modules/gdscript/tests/scripts/completion/types/local/interfered.gd b/modules/gdscript/tests/scripts/completion/types/local/infered.gd
index f003c366a4..f003c366a4 100644
--- a/modules/gdscript/tests/scripts/completion/types/local/interfered.gd
+++ b/modules/gdscript/tests/scripts/completion/types/local/infered.gd
diff --git a/modules/gdscript/tests/scripts/completion/types/member/interfered.cfg b/modules/gdscript/tests/scripts/completion/types/member/infered.cfg
index 8b68d51a89..8b68d51a89 100644
--- a/modules/gdscript/tests/scripts/completion/types/member/interfered.cfg
+++ b/modules/gdscript/tests/scripts/completion/types/member/infered.cfg
diff --git a/modules/gdscript/tests/scripts/completion/types/member/interfered.gd b/modules/gdscript/tests/scripts/completion/types/member/infered.gd
index 069abd7891..069abd7891 100644
--- a/modules/gdscript/tests/scripts/completion/types/member/interfered.gd
+++ b/modules/gdscript/tests/scripts/completion/types/member/infered.gd
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
index 5cc2a8026e..901700067d 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
@@ -241,11 +241,17 @@ namespace Godot.Bridge
if (outIconPath != null)
{
- var iconAttr = scriptType.GetCustomAttributes(inherit: false)
+ IconAttribute? iconAttr = scriptType.GetCustomAttributes(inherit: false)
.OfType<IconAttribute>()
.FirstOrDefault();
- *outIconPath = Marshaling.ConvertStringToNative(iconAttr?.Path);
+ if (!string.IsNullOrEmpty(iconAttr?.Path))
+ {
+ string iconPath = iconAttr.Path.IsAbsolutePath()
+ ? iconAttr.Path.SimplifyPath()
+ : scriptPathStr.GetBaseDir().PathJoin(iconAttr.Path).SimplifyPath();
+ *outIconPath = Marshaling.ConvertStringToNative(iconPath);
+ }
}
if (outBaseType != null)
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs
index a5fa89d3bf..f943a3049d 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs
@@ -47,7 +47,7 @@ namespace Godot
{
get
{
- real_t detSign = Mathf.Sign(BasisDeterminant());
+ real_t detSign = Mathf.Sign(Determinant());
return new Vector2(X.Length(), detSign * Y.Length());
}
}
@@ -59,7 +59,7 @@ namespace Godot
{
get
{
- real_t detSign = Mathf.Sign(BasisDeterminant());
+ real_t detSign = Mathf.Sign(Determinant());
return Mathf.Acos(X.Normalized().Dot(detSign * Y.Normalized())) - Mathf.Pi * 0.5f;
}
}
@@ -135,7 +135,7 @@ namespace Godot
/// <returns>The inverse transformation matrix.</returns>
public readonly Transform2D AffineInverse()
{
- real_t det = BasisDeterminant();
+ real_t det = Determinant();
if (det == 0)
throw new InvalidOperationException("Matrix determinant is zero and cannot be inverted.");
@@ -157,15 +157,16 @@ namespace Godot
/// <summary>
/// Returns the determinant of the basis matrix. If the basis is
- /// uniformly scaled, its determinant is the square of the scale.
+ /// uniformly scaled, then its determinant equals the square of the
+ /// scale factor.
///
- /// A negative determinant means the Y scale is negative.
- /// A zero determinant means the basis isn't invertible,
- /// and is usually considered invalid.
+ /// A negative determinant means the basis was flipped, so one part of
+ /// the scale is negative. A zero determinant means the basis isn't
+ /// invertible, and is usually considered invalid.
/// </summary>
/// <returns>The determinant of the basis matrix.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private readonly real_t BasisDeterminant()
+ public readonly real_t Determinant()
{
return (X.X * Y.Y) - (X.Y * Y.X);
}
diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp
index d9a66aa827..6d6231f6fa 100644
--- a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp
+++ b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp
@@ -58,9 +58,14 @@ OpenXRHandTrackingExtension::~OpenXRHandTrackingExtension() {
HashMap<String, bool *> OpenXRHandTrackingExtension::get_requested_extensions() {
HashMap<String, bool *> request_extensions;
+ unobstructed_data_source = GLOBAL_GET("xr/openxr/extensions/hand_tracking_unobstructed_data_source");
+ controller_data_source = GLOBAL_GET("xr/openxr/extensions/hand_tracking_controller_data_source");
+
request_extensions[XR_EXT_HAND_TRACKING_EXTENSION_NAME] = &hand_tracking_ext;
request_extensions[XR_EXT_HAND_JOINTS_MOTION_RANGE_EXTENSION_NAME] = &hand_motion_range_ext;
- request_extensions[XR_EXT_HAND_TRACKING_DATA_SOURCE_EXTENSION_NAME] = &hand_tracking_source_ext;
+ if (unobstructed_data_source || controller_data_source) {
+ request_extensions[XR_EXT_HAND_TRACKING_DATA_SOURCE_EXTENSION_NAME] = &hand_tracking_source_ext;
+ }
return request_extensions;
}
@@ -141,10 +146,18 @@ void OpenXRHandTrackingExtension::on_process() {
void *next_pointer = nullptr;
// Originally not all XR runtimes supported hand tracking data sourced both from controllers and normal hand tracking.
- // With this extension we can indicate we accept input from both sources so hand tracking data is consistently provided
- // on runtimes that support this.
- XrHandTrackingDataSourceEXT data_sources[2] = { XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT, XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT };
- XrHandTrackingDataSourceInfoEXT data_source_info = { XR_TYPE_HAND_TRACKING_DATA_SOURCE_INFO_EXT, next_pointer, 2, data_sources };
+ // With this extension we can indicate we wish to accept input from either or both sources.
+ // This functionality is subject to the abilities of the XR runtime and requires the data source extension.
+ // Note: If the data source extension is not available, no guarantees can be made on what the XR runtime supports.
+ uint32_t data_source_count = 0;
+ XrHandTrackingDataSourceEXT data_sources[2];
+ if (unobstructed_data_source) {
+ data_sources[data_source_count++] = XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT;
+ }
+ if (controller_data_source) {
+ data_sources[data_source_count++] = XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT;
+ }
+ XrHandTrackingDataSourceInfoEXT data_source_info = { XR_TYPE_HAND_TRACKING_DATA_SOURCE_INFO_EXT, next_pointer, data_source_count, data_sources };
if (hand_tracking_source_ext) {
// If supported include this info
next_pointer = &data_source_info;
@@ -224,7 +237,9 @@ void OpenXRHandTrackingExtension::on_process() {
if (XR_FAILED(result)) {
// not successful? then we do nothing.
print_line("OpenXR: Failed to get tracking for hand", i, "[", OpenXRAPI::get_singleton()->get_error_string(result), "]");
+ godot_tracker->set_hand_tracking_source(XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN);
godot_tracker->set_has_tracking_data(false);
+ godot_tracker->invalidate_pose("default");
continue;
}
@@ -235,8 +250,6 @@ void OpenXRHandTrackingExtension::on_process() {
}
if (hand_trackers[i].locations.isActive) {
- godot_tracker->set_has_tracking_data(true);
-
// SKELETON_RIG_HUMANOID bone adjustment. This rotation performs:
// OpenXR Z+ -> Godot Humanoid Y- (Back along the bone)
// OpenXR Y+ -> Godot Humanoid Z- (Out the back of the hand)
@@ -245,7 +258,6 @@ void OpenXRHandTrackingExtension::on_process() {
for (int joint = 0; joint < XR_HAND_JOINT_COUNT_EXT; joint++) {
const XrHandJointLocationEXT &location = hand_trackers[i].joint_locations[joint];
const XrHandJointVelocityEXT &velocity = hand_trackers[i].joint_velocities[joint];
- const XrHandTrackingDataSourceStateEXT &data_source = hand_trackers[i].data_source;
const XrPosef &pose = location.pose;
Transform3D transform;
@@ -285,23 +297,35 @@ void OpenXRHandTrackingExtension::on_process() {
godot_tracker->set_hand_joint_radius((XRHandTracker::HandJoint)joint, location.radius);
if (joint == XR_HAND_JOINT_PALM_EXT) {
- XRHandTracker::HandTrackingSource source = XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN;
- if (data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT) {
- source = XRHandTracker::HAND_TRACKING_SOURCE_UNOBSTRUCTED;
- } else if (data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT) {
- source = XRHandTracker::HAND_TRACKING_SOURCE_CONTROLLER;
- }
-
- godot_tracker->set_hand_tracking_source(source);
if (location.locationFlags & XR_SPACE_LOCATION_POSITION_TRACKED_BIT) {
+ XrHandTrackingDataSourceStateEXT &data_source = hand_trackers[i].data_source;
+
+ XRHandTracker::HandTrackingSource source = XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN;
+ if (hand_tracking_source_ext) {
+ if (!data_source.isActive) {
+ source = XRHandTracker::HAND_TRACKING_SOURCE_NOT_TRACKED;
+ } else if (data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT) {
+ source = XRHandTracker::HAND_TRACKING_SOURCE_UNOBSTRUCTED;
+ } else if (data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT) {
+ source = XRHandTracker::HAND_TRACKING_SOURCE_CONTROLLER;
+ } else {
+ // Data source shouldn't be active, if new data sources are added to OpenXR we need to enable them.
+ WARN_PRINT_ONCE("Unknown active data source found!");
+ source = XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN;
+ }
+ }
+ godot_tracker->set_hand_tracking_source(source);
+ godot_tracker->set_has_tracking_data(true);
godot_tracker->set_pose("default", transform, linear_velocity, angular_velocity);
} else {
+ godot_tracker->set_hand_tracking_source(hand_tracking_source_ext ? XRHandTracker::HAND_TRACKING_SOURCE_NOT_TRACKED : XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN);
godot_tracker->set_has_tracking_data(false);
godot_tracker->invalidate_pose("default");
}
}
}
} else {
+ godot_tracker->set_hand_tracking_source(hand_tracking_source_ext ? XRHandTracker::HAND_TRACKING_SOURCE_NOT_TRACKED : XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN);
godot_tracker->set_has_tracking_data(false);
godot_tracker->invalidate_pose("default");
}
@@ -349,16 +373,17 @@ XrHandJointsMotionRangeEXT OpenXRHandTrackingExtension::get_motion_range(HandTra
OpenXRHandTrackingExtension::HandTrackedSource OpenXRHandTrackingExtension::get_hand_tracking_source(HandTrackedHands p_hand) const {
ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, OPENXR_SOURCE_UNKNOWN);
- if (hand_tracking_source_ext && hand_trackers[p_hand].data_source.isActive) {
- switch (hand_trackers[p_hand].data_source.dataSource) {
- case XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT:
- return OPENXR_SOURCE_UNOBSTRUCTED;
-
- case XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT:
- return OPENXR_SOURCE_CONTROLLER;
-
- default:
- return OPENXR_SOURCE_UNKNOWN;
+ if (hand_tracking_source_ext) {
+ if (!hand_trackers[p_hand].data_source.isActive) {
+ return OPENXR_SOURCE_NOT_TRACKED;
+ } else if (hand_trackers[p_hand].data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT) {
+ return OPENXR_SOURCE_UNOBSTRUCTED;
+ } else if (hand_trackers[p_hand].data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT) {
+ return OPENXR_SOURCE_CONTROLLER;
+ } else {
+ // Data source shouldn't be active, if new data sources are added to OpenXR we need to enable them.
+ WARN_PRINT_ONCE("Unknown active data source found!");
+ return OPENXR_SOURCE_UNKNOWN;
}
}
diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.h b/modules/openxr/extensions/openxr_hand_tracking_extension.h
index f709bc05c2..2c34ff7f21 100644
--- a/modules/openxr/extensions/openxr_hand_tracking_extension.h
+++ b/modules/openxr/extensions/openxr_hand_tracking_extension.h
@@ -48,6 +48,7 @@ public:
OPENXR_SOURCE_UNKNOWN,
OPENXR_SOURCE_UNOBSTRUCTED,
OPENXR_SOURCE_CONTROLLER,
+ OPENXR_SOURCE_NOT_TRACKED,
OPENXR_SOURCE_MAX
};
@@ -110,6 +111,8 @@ private:
bool hand_tracking_ext = false;
bool hand_motion_range_ext = false;
bool hand_tracking_source_ext = false;
+ bool unobstructed_data_source = false;
+ bool controller_data_source = false;
// functions
void cleanup_hand_tracking();
diff --git a/scene/3d/multimesh_instance_3d.cpp b/scene/3d/multimesh_instance_3d.cpp
index 55d6e49e6c..2eef1dbbf4 100644
--- a/scene/3d/multimesh_instance_3d.cpp
+++ b/scene/3d/multimesh_instance_3d.cpp
@@ -30,16 +30,35 @@
#include "multimesh_instance_3d.h"
+void MultiMeshInstance3D::_refresh_interpolated() {
+ if (is_inside_tree() && multimesh.is_valid()) {
+ bool interpolated = is_physics_interpolated_and_enabled();
+ multimesh->set_physics_interpolated(interpolated);
+ }
+}
+
+void MultiMeshInstance3D::_physics_interpolated_changed() {
+ VisualInstance3D::_physics_interpolated_changed();
+ _refresh_interpolated();
+}
+
void MultiMeshInstance3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_multimesh", "multimesh"), &MultiMeshInstance3D::set_multimesh);
ClassDB::bind_method(D_METHOD("get_multimesh"), &MultiMeshInstance3D::get_multimesh);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "multimesh", PROPERTY_HINT_RESOURCE_TYPE, "MultiMesh"), "set_multimesh", "get_multimesh");
}
+void MultiMeshInstance3D::_notification(int p_what) {
+ if (p_what == NOTIFICATION_ENTER_TREE) {
+ _refresh_interpolated();
+ }
+}
+
void MultiMeshInstance3D::set_multimesh(const Ref<MultiMesh> &p_multimesh) {
multimesh = p_multimesh;
if (multimesh.is_valid()) {
set_base(multimesh->get_rid());
+ _refresh_interpolated();
} else {
set_base(RID());
}
diff --git a/scene/3d/multimesh_instance_3d.h b/scene/3d/multimesh_instance_3d.h
index 404f31d1e3..c9507b1047 100644
--- a/scene/3d/multimesh_instance_3d.h
+++ b/scene/3d/multimesh_instance_3d.h
@@ -39,9 +39,12 @@ class MultiMeshInstance3D : public GeometryInstance3D {
Ref<MultiMesh> multimesh;
+ void _refresh_interpolated();
+
protected:
+ virtual void _physics_interpolated_changed() override;
static void _bind_methods();
- // bind helpers
+ void _notification(int p_what);
public:
void set_multimesh(const Ref<MultiMesh> &p_multimesh);
diff --git a/scene/3d/xr_nodes.cpp b/scene/3d/xr_nodes.cpp
index 3f4b962641..b71f9bc0c4 100644
--- a/scene/3d/xr_nodes.cpp
+++ b/scene/3d/xr_nodes.cpp
@@ -303,6 +303,8 @@ StringName XRNode3D::get_pose_name() const {
void XRNode3D::set_show_when_tracked(bool p_show) {
show_when_tracked = p_show;
+
+ _update_visibility();
}
bool XRNode3D::get_show_when_tracked() const {
@@ -361,6 +363,9 @@ void XRNode3D::_bind_tracker() {
if (pose.is_valid()) {
set_transform(pose->get_adjusted_transform());
_set_has_tracking_data(pose->get_has_tracking_data());
+ } else {
+ // Pose has been invalidated or was never set.
+ _set_has_tracking_data(false);
}
}
}
@@ -407,6 +412,10 @@ void XRNode3D::_pose_lost_tracking(const Ref<XRPose> &p_pose) {
}
void XRNode3D::_set_has_tracking_data(bool p_has_tracking_data) {
+ // Always update our visibility, we may have set our tracking data
+ // when conditions weren't right.
+ _update_visibility();
+
// Ignore if the has_tracking_data state isn't changing.
if (p_has_tracking_data == has_tracking_data) {
return;
@@ -415,10 +424,19 @@ void XRNode3D::_set_has_tracking_data(bool p_has_tracking_data) {
// Handle change of has_tracking_data.
has_tracking_data = p_has_tracking_data;
emit_signal(SNAME("tracking_changed"), has_tracking_data);
+}
+void XRNode3D::_update_visibility() {
// If configured, show or hide the node based on tracking data.
if (show_when_tracked) {
- set_visible(has_tracking_data);
+ // Only react to this if we have a primary interface.
+ XRServer *xr_server = XRServer::get_singleton();
+ if (xr_server != nullptr) {
+ Ref<XRInterface> xr_interface = xr_server->get_primary_interface();
+ if (xr_interface.is_valid()) {
+ set_visible(has_tracking_data);
+ }
+ }
}
}
diff --git a/scene/3d/xr_nodes.h b/scene/3d/xr_nodes.h
index a42f6d470f..94c3923433 100644
--- a/scene/3d/xr_nodes.h
+++ b/scene/3d/xr_nodes.h
@@ -95,6 +95,8 @@ protected:
void _pose_lost_tracking(const Ref<XRPose> &p_pose);
void _set_has_tracking_data(bool p_has_tracking_data);
+ void _update_visibility();
+
public:
void _validate_property(PropertyInfo &p_property) const;
void set_tracker(const StringName &p_tracker_name);
diff --git a/scene/resources/multimesh.cpp b/scene/resources/multimesh.cpp
index 8cddfb5840..bf3caa1edd 100644
--- a/scene/resources/multimesh.cpp
+++ b/scene/resources/multimesh.cpp
@@ -202,6 +202,10 @@ Vector<float> MultiMesh::get_buffer() const {
return RS::get_singleton()->multimesh_get_buffer(multimesh);
}
+void MultiMesh::set_buffer_interpolated(const Vector<float> &p_buffer_curr, const Vector<float> &p_buffer_prev) {
+ RS::get_singleton()->multimesh_set_buffer_interpolated(multimesh, p_buffer_curr, p_buffer_prev);
+}
+
void MultiMesh::set_mesh(const Ref<Mesh> &p_mesh) {
mesh = p_mesh;
if (!mesh.is_null()) {
@@ -236,6 +240,11 @@ int MultiMesh::get_visible_instance_count() const {
return visible_instance_count;
}
+void MultiMesh::set_physics_interpolation_quality(PhysicsInterpolationQuality p_quality) {
+ _physics_interpolation_quality = p_quality;
+ RenderingServer::get_singleton()->multimesh_set_physics_interpolation_quality(multimesh, (RS::MultimeshPhysicsInterpolationQuality)p_quality);
+}
+
void MultiMesh::set_instance_transform(int p_instance, const Transform3D &p_transform) {
RenderingServer::get_singleton()->multimesh_instance_set_transform(multimesh, p_instance, p_transform);
}
@@ -269,6 +278,14 @@ Color MultiMesh::get_instance_custom_data(int p_instance) const {
return RenderingServer::get_singleton()->multimesh_instance_get_custom_data(multimesh, p_instance);
}
+void MultiMesh::reset_instance_physics_interpolation(int p_instance) {
+ RenderingServer::get_singleton()->multimesh_instance_reset_physics_interpolation(multimesh, p_instance);
+}
+
+void MultiMesh::set_physics_interpolated(bool p_interpolated) {
+ RenderingServer::get_singleton()->multimesh_set_physics_interpolated(multimesh, p_interpolated);
+}
+
void MultiMesh::set_custom_aabb(const AABB &p_custom) {
custom_aabb = p_custom;
RS::get_singleton()->multimesh_set_custom_aabb(multimesh, custom_aabb);
@@ -328,6 +345,8 @@ void MultiMesh::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_instance_count"), &MultiMesh::get_instance_count);
ClassDB::bind_method(D_METHOD("set_visible_instance_count", "count"), &MultiMesh::set_visible_instance_count);
ClassDB::bind_method(D_METHOD("get_visible_instance_count"), &MultiMesh::get_visible_instance_count);
+ ClassDB::bind_method(D_METHOD("set_physics_interpolation_quality", "quality"), &MultiMesh::set_physics_interpolation_quality);
+ ClassDB::bind_method(D_METHOD("get_physics_interpolation_quality"), &MultiMesh::get_physics_interpolation_quality);
ClassDB::bind_method(D_METHOD("set_instance_transform", "instance", "transform"), &MultiMesh::set_instance_transform);
ClassDB::bind_method(D_METHOD("set_instance_transform_2d", "instance", "transform"), &MultiMesh::set_instance_transform_2d);
ClassDB::bind_method(D_METHOD("get_instance_transform", "instance"), &MultiMesh::get_instance_transform);
@@ -336,6 +355,7 @@ void MultiMesh::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_instance_color", "instance"), &MultiMesh::get_instance_color);
ClassDB::bind_method(D_METHOD("set_instance_custom_data", "instance", "custom_data"), &MultiMesh::set_instance_custom_data);
ClassDB::bind_method(D_METHOD("get_instance_custom_data", "instance"), &MultiMesh::get_instance_custom_data);
+ ClassDB::bind_method(D_METHOD("reset_instance_physics_interpolation", "instance"), &MultiMesh::reset_instance_physics_interpolation);
ClassDB::bind_method(D_METHOD("set_custom_aabb", "aabb"), &MultiMesh::set_custom_aabb);
ClassDB::bind_method(D_METHOD("get_custom_aabb"), &MultiMesh::get_custom_aabb);
ClassDB::bind_method(D_METHOD("get_aabb"), &MultiMesh::get_aabb);
@@ -343,6 +363,8 @@ void MultiMesh::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_buffer"), &MultiMesh::get_buffer);
ClassDB::bind_method(D_METHOD("set_buffer", "buffer"), &MultiMesh::set_buffer);
+ ClassDB::bind_method(D_METHOD("set_buffer_interpolated", "buffer_curr", "buffer_prev"), &MultiMesh::set_buffer_interpolated);
+
ADD_PROPERTY(PropertyInfo(Variant::INT, "transform_format", PROPERTY_HINT_ENUM, "2D,3D"), "set_transform_format", "get_transform_format");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_colors"), "set_use_colors", "is_using_colors");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_custom_data"), "set_use_custom_data", "is_using_custom_data");
@@ -369,8 +391,14 @@ void MultiMesh::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::PACKED_COLOR_ARRAY, "custom_data_array", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "_set_custom_data_array", "_get_custom_data_array");
#endif
+ ADD_GROUP("Physics Interpolation", "physics_interpolation");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "physics_interpolation_quality", PROPERTY_HINT_ENUM, "Fast,High"), "set_physics_interpolation_quality", "get_physics_interpolation_quality");
+
BIND_ENUM_CONSTANT(TRANSFORM_2D);
BIND_ENUM_CONSTANT(TRANSFORM_3D);
+
+ BIND_ENUM_CONSTANT(INTERP_QUALITY_FAST);
+ BIND_ENUM_CONSTANT(INTERP_QUALITY_HIGH);
}
MultiMesh::MultiMesh() {
diff --git a/scene/resources/multimesh.h b/scene/resources/multimesh.h
index d7bcb13162..03505bb4d1 100644
--- a/scene/resources/multimesh.h
+++ b/scene/resources/multimesh.h
@@ -44,6 +44,11 @@ public:
TRANSFORM_3D = RS::MULTIMESH_TRANSFORM_3D
};
+ enum PhysicsInterpolationQuality {
+ INTERP_QUALITY_FAST,
+ INTERP_QUALITY_HIGH,
+ };
+
private:
Ref<Mesh> mesh;
RID multimesh;
@@ -53,6 +58,7 @@ private:
bool use_custom_data = false;
int instance_count = 0;
int visible_instance_count = -1;
+ PhysicsInterpolationQuality _physics_interpolation_quality = INTERP_QUALITY_FAST;
protected:
static void _bind_methods();
@@ -74,6 +80,8 @@ protected:
void set_buffer(const Vector<float> &p_buffer);
Vector<float> get_buffer() const;
+ void set_buffer_interpolated(const Vector<float> &p_buffer_curr, const Vector<float> &p_buffer_prev);
+
public:
void set_mesh(const Ref<Mesh> &p_mesh);
Ref<Mesh> get_mesh() const;
@@ -93,6 +101,9 @@ public:
void set_visible_instance_count(int p_count);
int get_visible_instance_count() const;
+ void set_physics_interpolation_quality(PhysicsInterpolationQuality p_quality);
+ PhysicsInterpolationQuality get_physics_interpolation_quality() const { return _physics_interpolation_quality; }
+
void set_instance_transform(int p_instance, const Transform3D &p_transform);
void set_instance_transform_2d(int p_instance, const Transform2D &p_transform);
Transform3D get_instance_transform(int p_instance) const;
@@ -104,6 +115,10 @@ public:
void set_instance_custom_data(int p_instance, const Color &p_custom_data);
Color get_instance_custom_data(int p_instance) const;
+ void reset_instance_physics_interpolation(int p_instance);
+
+ void set_physics_interpolated(bool p_interpolated);
+
void set_custom_aabb(const AABB &p_custom);
AABB get_custom_aabb() const;
@@ -116,5 +131,6 @@ public:
};
VARIANT_ENUM_CAST(MultiMesh::TransformFormat);
+VARIANT_ENUM_CAST(MultiMesh::PhysicsInterpolationQuality);
#endif // MULTIMESH_H
diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp
index 5f70a24fcd..5e148c9276 100644
--- a/scene/resources/visual_shader_nodes.cpp
+++ b/scene/resources/visual_shader_nodes.cpp
@@ -5368,6 +5368,20 @@ String VisualShaderNodeIntParameter::generate_global(Shader::Mode p_mode, Visual
code += _get_qual_str() + "uniform int " + get_parameter_name() + " : hint_range(" + itos(hint_range_min) + ", " + itos(hint_range_max) + ")";
} else if (hint == HINT_RANGE_STEP) {
code += _get_qual_str() + "uniform int " + get_parameter_name() + " : hint_range(" + itos(hint_range_min) + ", " + itos(hint_range_max) + ", " + itos(hint_range_step) + ")";
+ } else if (hint == HINT_ENUM) {
+ code += _get_qual_str() + "uniform int " + get_parameter_name() + " : hint_enum(";
+
+ bool first = true;
+ for (const String &_name : hint_enum_names) {
+ if (first) {
+ first = false;
+ } else {
+ code += ", ";
+ }
+ code += "\"" + _name.c_escape() + "\"";
+ }
+
+ code += ")";
} else {
code += _get_qual_str() + "uniform int " + get_parameter_name();
}
@@ -5439,6 +5453,18 @@ int VisualShaderNodeIntParameter::get_step() const {
return hint_range_step;
}
+void VisualShaderNodeIntParameter::set_enum_names(const PackedStringArray &p_names) {
+ if (hint_enum_names == p_names) {
+ return;
+ }
+ hint_enum_names = p_names;
+ emit_changed();
+}
+
+PackedStringArray VisualShaderNodeIntParameter::get_enum_names() const {
+ return hint_enum_names;
+}
+
void VisualShaderNodeIntParameter::set_default_value_enabled(bool p_default_value_enabled) {
if (default_value_enabled == p_default_value_enabled) {
return;
@@ -5476,22 +5502,27 @@ void VisualShaderNodeIntParameter::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_step", "value"), &VisualShaderNodeIntParameter::set_step);
ClassDB::bind_method(D_METHOD("get_step"), &VisualShaderNodeIntParameter::get_step);
+ ClassDB::bind_method(D_METHOD("set_enum_names", "names"), &VisualShaderNodeIntParameter::set_enum_names);
+ ClassDB::bind_method(D_METHOD("get_enum_names"), &VisualShaderNodeIntParameter::get_enum_names);
+
ClassDB::bind_method(D_METHOD("set_default_value_enabled", "enabled"), &VisualShaderNodeIntParameter::set_default_value_enabled);
ClassDB::bind_method(D_METHOD("is_default_value_enabled"), &VisualShaderNodeIntParameter::is_default_value_enabled);
ClassDB::bind_method(D_METHOD("set_default_value", "value"), &VisualShaderNodeIntParameter::set_default_value);
ClassDB::bind_method(D_METHOD("get_default_value"), &VisualShaderNodeIntParameter::get_default_value);
- ADD_PROPERTY(PropertyInfo(Variant::INT, "hint", PROPERTY_HINT_ENUM, "None,Range,Range + Step"), "set_hint", "get_hint");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "hint", PROPERTY_HINT_ENUM, "None,Range,Range + Step,Enum"), "set_hint", "get_hint");
ADD_PROPERTY(PropertyInfo(Variant::INT, "min"), "set_min", "get_min");
ADD_PROPERTY(PropertyInfo(Variant::INT, "max"), "set_max", "get_max");
ADD_PROPERTY(PropertyInfo(Variant::INT, "step"), "set_step", "get_step");
+ ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "enum_names"), "set_enum_names", "get_enum_names");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "default_value_enabled"), "set_default_value_enabled", "is_default_value_enabled");
ADD_PROPERTY(PropertyInfo(Variant::INT, "default_value"), "set_default_value", "get_default_value");
BIND_ENUM_CONSTANT(HINT_NONE);
BIND_ENUM_CONSTANT(HINT_RANGE);
BIND_ENUM_CONSTANT(HINT_RANGE_STEP);
+ BIND_ENUM_CONSTANT(HINT_ENUM);
BIND_ENUM_CONSTANT(HINT_MAX);
}
@@ -5513,6 +5544,9 @@ Vector<StringName> VisualShaderNodeIntParameter::get_editable_properties() const
if (hint == HINT_RANGE_STEP) {
props.push_back("step");
}
+ if (hint == HINT_ENUM) {
+ props.push_back("enum_names");
+ }
props.push_back("default_value_enabled");
if (default_value_enabled) {
props.push_back("default_value");
diff --git a/scene/resources/visual_shader_nodes.h b/scene/resources/visual_shader_nodes.h
index 7a37ffa0e0..279599ef9c 100644
--- a/scene/resources/visual_shader_nodes.h
+++ b/scene/resources/visual_shader_nodes.h
@@ -2115,6 +2115,7 @@ public:
HINT_NONE,
HINT_RANGE,
HINT_RANGE_STEP,
+ HINT_ENUM,
HINT_MAX,
};
@@ -2123,6 +2124,7 @@ private:
int hint_range_min = 0;
int hint_range_max = 100;
int hint_range_step = 1;
+ PackedStringArray hint_enum_names;
bool default_value_enabled = false;
int default_value = 0;
@@ -2158,6 +2160,9 @@ public:
void set_step(int p_value);
int get_step() const;
+ void set_enum_names(const PackedStringArray &p_names);
+ PackedStringArray get_enum_names() const;
+
void set_default_value_enabled(bool p_enabled);
bool is_default_value_enabled() const;
diff --git a/servers/rendering/dummy/rasterizer_scene_dummy.h b/servers/rendering/dummy/rasterizer_scene_dummy.h
index 083493003f..a699a58b1f 100644
--- a/servers/rendering/dummy/rasterizer_scene_dummy.h
+++ b/servers/rendering/dummy/rasterizer_scene_dummy.h
@@ -186,6 +186,7 @@ public:
virtual void decals_set_filter(RS::DecalFilter p_filter) override {}
virtual void light_projectors_set_filter(RS::LightProjectorFilter p_filter) override {}
+ virtual void lightmaps_set_bicubic_filter(bool p_enable) override {}
RasterizerSceneDummy() {}
~RasterizerSceneDummy() {}
diff --git a/servers/rendering/dummy/storage/mesh_storage.cpp b/servers/rendering/dummy/storage/mesh_storage.cpp
index 0b7ade1762..3f90c80826 100644
--- a/servers/rendering/dummy/storage/mesh_storage.cpp
+++ b/servers/rendering/dummy/storage/mesh_storage.cpp
@@ -64,22 +64,22 @@ void MeshStorage::mesh_clear(RID p_mesh) {
m->surfaces.clear();
}
-RID MeshStorage::multimesh_allocate() {
+RID MeshStorage::_multimesh_allocate() {
return multimesh_owner.allocate_rid();
}
-void MeshStorage::multimesh_initialize(RID p_rid) {
+void MeshStorage::_multimesh_initialize(RID p_rid) {
multimesh_owner.initialize_rid(p_rid, DummyMultiMesh());
}
-void MeshStorage::multimesh_free(RID p_rid) {
+void MeshStorage::_multimesh_free(RID p_rid) {
DummyMultiMesh *multimesh = multimesh_owner.get_or_null(p_rid);
ERR_FAIL_NULL(multimesh);
multimesh_owner.free(p_rid);
}
-void MeshStorage::multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer) {
+void MeshStorage::_multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer) {
DummyMultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
multimesh->buffer.resize(p_buffer.size());
@@ -87,7 +87,7 @@ void MeshStorage::multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_b
memcpy(cache_data, p_buffer.ptr(), p_buffer.size() * sizeof(float));
}
-Vector<float> MeshStorage::multimesh_get_buffer(RID p_multimesh) const {
+Vector<float> MeshStorage::_multimesh_get_buffer(RID p_multimesh) const {
DummyMultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, Vector<float>());
diff --git a/servers/rendering/dummy/storage/mesh_storage.h b/servers/rendering/dummy/storage/mesh_storage.h
index d98b2e2ee7..ec19562147 100644
--- a/servers/rendering/dummy/storage/mesh_storage.h
+++ b/servers/rendering/dummy/storage/mesh_storage.h
@@ -146,34 +146,36 @@ public:
bool owns_multimesh(RID p_rid) { return multimesh_owner.owns(p_rid); }
- virtual RID multimesh_allocate() override;
- virtual void multimesh_initialize(RID p_rid) override;
- virtual void multimesh_free(RID p_rid) override;
-
- virtual void multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false) override {}
- virtual int multimesh_get_instance_count(RID p_multimesh) const override { return 0; }
-
- virtual void multimesh_set_mesh(RID p_multimesh, RID p_mesh) override {}
- virtual void multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform3D &p_transform) override {}
- virtual void multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) override {}
- virtual void multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) override {}
- virtual void multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) override {}
-
- virtual void multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) override {}
- virtual AABB multimesh_get_custom_aabb(RID p_multimesh) const override { return AABB(); }
-
- virtual RID multimesh_get_mesh(RID p_multimesh) const override { return RID(); }
- virtual AABB multimesh_get_aabb(RID p_multimesh) const override { return AABB(); }
-
- virtual Transform3D multimesh_instance_get_transform(RID p_multimesh, int p_index) const override { return Transform3D(); }
- virtual Transform2D multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const override { return Transform2D(); }
- virtual Color multimesh_instance_get_color(RID p_multimesh, int p_index) const override { return Color(); }
- virtual Color multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const override { return Color(); }
- virtual void multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer) override;
- virtual Vector<float> multimesh_get_buffer(RID p_multimesh) const override;
-
- virtual void multimesh_set_visible_instances(RID p_multimesh, int p_visible) override {}
- virtual int multimesh_get_visible_instances(RID p_multimesh) const override { return 0; }
+ virtual RID _multimesh_allocate() override;
+ virtual void _multimesh_initialize(RID p_rid) override;
+ virtual void _multimesh_free(RID p_rid) override;
+
+ virtual void _multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false) override {}
+ virtual int _multimesh_get_instance_count(RID p_multimesh) const override { return 0; }
+
+ virtual void _multimesh_set_mesh(RID p_multimesh, RID p_mesh) override {}
+ virtual void _multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform3D &p_transform) override {}
+ virtual void _multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) override {}
+ virtual void _multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) override {}
+ virtual void _multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) override {}
+
+ virtual void _multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) override {}
+ virtual AABB _multimesh_get_custom_aabb(RID p_multimesh) const override { return AABB(); }
+
+ virtual RID _multimesh_get_mesh(RID p_multimesh) const override { return RID(); }
+ virtual AABB _multimesh_get_aabb(RID p_multimesh) const override { return AABB(); }
+
+ virtual Transform3D _multimesh_instance_get_transform(RID p_multimesh, int p_index) const override { return Transform3D(); }
+ virtual Transform2D _multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const override { return Transform2D(); }
+ virtual Color _multimesh_instance_get_color(RID p_multimesh, int p_index) const override { return Color(); }
+ virtual Color _multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const override { return Color(); }
+ virtual void _multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer) override;
+ virtual Vector<float> _multimesh_get_buffer(RID p_multimesh) const override;
+
+ virtual void _multimesh_set_visible_instances(RID p_multimesh, int p_visible) override {}
+ virtual int _multimesh_get_visible_instances(RID p_multimesh) const override { return 0; }
+
+ MultiMeshInterpolator *_multimesh_get_interpolator(RID p_multimesh) const override { return nullptr; }
/* SKELETON API */
diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
index cb655f9b04..b97e38da4d 100644
--- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
+++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
@@ -1103,9 +1103,17 @@ void RenderForwardClustered::_setup_lightmaps(const RenderDataRD *p_render_data,
RID lightmap = light_storage->lightmap_instance_get_lightmap(p_lightmaps[i]);
+ // Transform (for directional lightmaps).
Basis to_lm = light_storage->lightmap_instance_get_transform(p_lightmaps[i]).basis.inverse() * p_cam_transform.basis;
to_lm = to_lm.inverse().transposed(); //will transform normals
RendererRD::MaterialStorage::store_transform_3x3(to_lm, scene_state.lightmaps[i].normal_xform);
+
+ // Light texture size.
+ Vector2i lightmap_size = light_storage->lightmap_get_light_texture_size(lightmap);
+ scene_state.lightmaps[i].texture_size[0] = lightmap_size[0];
+ scene_state.lightmaps[i].texture_size[1] = lightmap_size[1];
+
+ // Exposure.
scene_state.lightmaps[i].exposure_normalization = 1.0;
if (p_render_data->camera_attributes.is_valid()) {
float baked_exposure = light_storage->lightmap_get_baked_exposure_normalization(lightmap);
@@ -4242,6 +4250,11 @@ void RenderForwardClustered::_update_shader_quality_settings() {
spec_constants.push_back(sc);
+ sc.constant_id = SPEC_CONSTANT_USE_LIGHTMAP_BICUBIC_FILTER;
+ sc.bool_value = lightmap_filter_bicubic_get();
+
+ spec_constants.push_back(sc);
+
scene_shader.set_default_specialization_constants(spec_constants);
base_uniforms_changed(); //also need this
diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h
index 0aa4a0667e..5d14653db6 100644
--- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h
+++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h
@@ -73,6 +73,7 @@ class RenderForwardClustered : public RendererSceneRenderRD {
SPEC_CONSTANT_DECAL_FILTER = 10,
SPEC_CONSTANT_PROJECTOR_FILTER = 11,
SPEC_CONSTANT_USE_DEPTH_FOG = 12,
+ SPEC_CONSTANT_USE_LIGHTMAP_BICUBIC_FILTER = 13,
};
enum {
@@ -235,8 +236,9 @@ class RenderForwardClustered : public RendererSceneRenderRD {
struct LightmapData {
float normal_xform[12];
- float pad[3];
+ float texture_size[2];
float exposure_normalization;
+ float pad;
};
struct LightmapCaptureData {
diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
index c03dd96062..412fa643b1 100644
--- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
+++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
@@ -563,9 +563,17 @@ void RenderForwardMobile::_setup_lightmaps(const RenderDataRD *p_render_data, co
RID lightmap = light_storage->lightmap_instance_get_lightmap(p_lightmaps[i]);
+ // Transform (for directional lightmaps).
Basis to_lm = light_storage->lightmap_instance_get_transform(p_lightmaps[i]).basis.inverse() * p_cam_transform.basis;
to_lm = to_lm.inverse().transposed(); //will transform normals
RendererRD::MaterialStorage::store_transform_3x3(to_lm, scene_state.lightmaps[i].normal_xform);
+
+ // Light texture size.
+ Vector2i lightmap_size = light_storage->lightmap_get_light_texture_size(lightmap);
+ scene_state.lightmaps[i].texture_size[0] = lightmap_size[0];
+ scene_state.lightmaps[i].texture_size[1] = lightmap_size[1];
+
+ // Exposure.
scene_state.lightmaps[i].exposure_normalization = 1.0;
if (p_render_data->camera_attributes.is_valid()) {
float baked_exposure = light_storage->lightmap_get_baked_exposure_normalization(lightmap);
@@ -2781,6 +2789,11 @@ void RenderForwardMobile::_update_shader_quality_settings() {
spec_constants.push_back(sc);
+ sc.constant_id = SPEC_CONSTANT_USE_LIGHTMAP_BICUBIC_FILTER;
+ sc.bool_value = lightmap_filter_bicubic_get();
+
+ spec_constants.push_back(sc);
+
scene_shader.set_default_specialization_constants(spec_constants);
base_uniforms_changed(); //also need this
diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h
index 34260bd701..fc60c770e8 100644
--- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h
+++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h
@@ -82,7 +82,7 @@ private:
SPEC_CONSTANT_DISABLE_FOG = 14,
SPEC_CONSTANT_USE_DEPTH_FOG = 16,
SPEC_CONSTANT_IS_MULTIMESH = 17,
-
+ SPEC_CONSTANT_USE_LIGHTMAP_BICUBIC_FILTER = 18,
};
enum {
@@ -208,8 +208,9 @@ private:
struct LightmapData {
float normal_xform[12];
- float pad[3];
+ float texture_size[2];
float exposure_normalization;
+ float pad;
};
struct LightmapCaptureData {
diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
index 0ebed49ee9..7d6d5018d0 100644
--- a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
@@ -1040,6 +1040,14 @@ void RendererSceneRenderRD::light_projectors_set_filter(RenderingServer::LightPr
_update_shader_quality_settings();
}
+void RendererSceneRenderRD::lightmaps_set_bicubic_filter(bool p_enable) {
+ if (lightmap_filter_bicubic == p_enable) {
+ return;
+ }
+ lightmap_filter_bicubic = p_enable;
+ _update_shader_quality_settings();
+}
+
int RendererSceneRenderRD::get_roughness_layers() const {
return sky.roughness_layers;
}
@@ -1483,6 +1491,7 @@ void RendererSceneRenderRD::init() {
decals_set_filter(RS::DecalFilter(int(GLOBAL_GET("rendering/textures/decals/filter"))));
light_projectors_set_filter(RS::LightProjectorFilter(int(GLOBAL_GET("rendering/textures/light_projectors/filter"))));
+ lightmaps_set_bicubic_filter(GLOBAL_GET("rendering/lightmapping/lightmap_gi/use_bicubic_filter"));
cull_argument.set_page_pool(&cull_argument_pool);
diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.h b/servers/rendering/renderer_rd/renderer_scene_render_rd.h
index a8e8e638cd..022a4560f8 100644
--- a/servers/rendering/renderer_rd/renderer_scene_render_rd.h
+++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.h
@@ -133,6 +133,7 @@ private:
float *directional_soft_shadow_kernel = nullptr;
float *penumbra_shadow_kernel = nullptr;
float *soft_shadow_kernel = nullptr;
+ bool lightmap_filter_bicubic = false;
int directional_penumbra_shadow_samples = 0;
int directional_soft_shadow_samples = 0;
int penumbra_shadow_samples = 0;
@@ -262,6 +263,7 @@ public:
virtual void decals_set_filter(RS::DecalFilter p_filter) override;
virtual void light_projectors_set_filter(RS::LightProjectorFilter p_filter) override;
+ virtual void lightmaps_set_bicubic_filter(bool p_enable) override;
_FORCE_INLINE_ RS::ShadowQuality shadows_quality_get() const {
return shadows_quality;
@@ -292,6 +294,9 @@ public:
_FORCE_INLINE_ int directional_penumbra_shadow_samples_get() const {
return directional_penumbra_shadow_samples;
}
+ _FORCE_INLINE_ bool lightmap_filter_bicubic_get() const {
+ return lightmap_filter_bicubic;
+ }
_FORCE_INLINE_ int directional_soft_shadow_samples_get() const {
return directional_soft_shadow_samples;
}
diff --git a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl
index cf15497c8c..5706f670eb 100644
--- a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl
+++ b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl
@@ -657,6 +657,7 @@ layout(constant_id = 9) const uint sc_directional_penumbra_shadow_samples = 4;
layout(constant_id = 10) const bool sc_decal_use_mipmaps = true;
layout(constant_id = 11) const bool sc_projector_use_mipmaps = true;
layout(constant_id = 12) const bool sc_use_depth_fog = false;
+layout(constant_id = 13) const bool sc_use_lightmap_bicubic_filter = false;
// not used in clustered renderer but we share some code with the mobile renderer that requires this.
const float sc_luminance_multiplier = 1.0;
@@ -701,6 +702,67 @@ layout(location = 9) in float dp_clip;
layout(location = 10) in flat uint instance_index_interp;
+#ifdef USE_LIGHTMAP
+// w0, w1, w2, and w3 are the four cubic B-spline basis functions
+float w0(float a) {
+ return (1.0 / 6.0) * (a * (a * (-a + 3.0) - 3.0) + 1.0);
+}
+
+float w1(float a) {
+ return (1.0 / 6.0) * (a * a * (3.0 * a - 6.0) + 4.0);
+}
+
+float w2(float a) {
+ return (1.0 / 6.0) * (a * (a * (-3.0 * a + 3.0) + 3.0) + 1.0);
+}
+
+float w3(float a) {
+ return (1.0 / 6.0) * (a * a * a);
+}
+
+// g0 and g1 are the two amplitude functions
+float g0(float a) {
+ return w0(a) + w1(a);
+}
+
+float g1(float a) {
+ return w2(a) + w3(a);
+}
+
+// h0 and h1 are the two offset functions
+float h0(float a) {
+ return -1.0 + w1(a) / (w0(a) + w1(a));
+}
+
+float h1(float a) {
+ return 1.0 + w3(a) / (w2(a) + w3(a));
+}
+
+vec4 textureArray_bicubic(texture2DArray tex, vec3 uv, vec2 texture_size) {
+ vec2 texel_size = vec2(1.0) / texture_size;
+
+ uv.xy = uv.xy * texture_size + vec2(0.5);
+
+ vec2 iuv = floor(uv.xy);
+ vec2 fuv = fract(uv.xy);
+
+ float g0x = g0(fuv.x);
+ float g1x = g1(fuv.x);
+ float h0x = h0(fuv.x);
+ float h1x = h1(fuv.x);
+ float h0y = h0(fuv.y);
+ float h1y = h1(fuv.y);
+
+ vec2 p0 = (vec2(iuv.x + h0x, iuv.y + h0y) - vec2(0.5)) * texel_size;
+ vec2 p1 = (vec2(iuv.x + h1x, iuv.y + h0y) - vec2(0.5)) * texel_size;
+ vec2 p2 = (vec2(iuv.x + h0x, iuv.y + h1y) - vec2(0.5)) * texel_size;
+ vec2 p3 = (vec2(iuv.x + h1x, iuv.y + h1y) - vec2(0.5)) * texel_size;
+
+ return (g0(fuv.y) * (g0x * texture(sampler2DArray(tex, SAMPLER_LINEAR_CLAMP), vec3(p0, uv.z)) + g1x * texture(sampler2DArray(tex, SAMPLER_LINEAR_CLAMP), vec3(p1, uv.z)))) +
+ (g1(fuv.y) * (g0x * texture(sampler2DArray(tex, SAMPLER_LINEAR_CLAMP), vec3(p2, uv.z)) + g1x * texture(sampler2DArray(tex, SAMPLER_LINEAR_CLAMP), vec3(p3, uv.z))));
+}
+#endif //USE_LIGHTMAP
+
#ifdef USE_MULTIVIEW
#ifdef has_VK_KHR_multiview
#define ViewIndex gl_ViewIndex
@@ -1030,6 +1092,13 @@ void fragment_shader(in SceneData scene_data) {
vec3 light_vertex = vertex;
#endif //LIGHT_VERTEX_USED
+ mat3 model_normal_matrix;
+ if (bool(instances.data[instance_index].flags & INSTANCE_FLAGS_NON_UNIFORM_SCALE)) {
+ model_normal_matrix = transpose(inverse(mat3(read_model_matrix)));
+ } else {
+ model_normal_matrix = mat3(read_model_matrix);
+ }
+
mat4 read_view_matrix = scene_data.view_matrix;
vec2 read_viewport_size = scene_data.viewport_size;
{
@@ -1442,10 +1511,23 @@ void fragment_shader(in SceneData scene_data) {
if (uses_sh) {
uvw.z *= 4.0; //SH textures use 4 times more data
- vec3 lm_light_l0 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 0.0), 0.0).rgb;
- vec3 lm_light_l1n1 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 1.0), 0.0).rgb;
- vec3 lm_light_l1_0 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb;
- vec3 lm_light_l1p1 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb;
+
+ vec3 lm_light_l0;
+ vec3 lm_light_l1n1;
+ vec3 lm_light_l1_0;
+ vec3 lm_light_l1p1;
+
+ if (sc_use_lightmap_bicubic_filter) {
+ lm_light_l0 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 0.0), lightmaps.data[ofs].light_texture_size).rgb;
+ lm_light_l1n1 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 1.0), lightmaps.data[ofs].light_texture_size).rgb;
+ lm_light_l1_0 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 2.0), lightmaps.data[ofs].light_texture_size).rgb;
+ lm_light_l1p1 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 3.0), lightmaps.data[ofs].light_texture_size).rgb;
+ } else {
+ lm_light_l0 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 0.0), 0.0).rgb;
+ lm_light_l1n1 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 1.0), 0.0).rgb;
+ lm_light_l1_0 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb;
+ lm_light_l1p1 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb;
+ }
vec3 n = normalize(lightmaps.data[ofs].normal_xform * normal);
float en = lightmaps.data[ofs].exposure_normalization;
@@ -1462,7 +1544,11 @@ void fragment_shader(in SceneData scene_data) {
}
} else {
- ambient_light += textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw, 0.0).rgb * lightmaps.data[ofs].exposure_normalization;
+ if (sc_use_lightmap_bicubic_filter) {
+ ambient_light += textureArray_bicubic(lightmap_textures[ofs], uvw, lightmaps.data[ofs].light_texture_size).rgb * lightmaps.data[ofs].exposure_normalization;
+ } else {
+ ambient_light += textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw, 0.0).rgb * lightmaps.data[ofs].exposure_normalization;
+ }
}
}
#else
diff --git a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl
index 441cf3c80c..18322a0619 100644
--- a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl
+++ b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl
@@ -94,8 +94,9 @@ directional_lights;
struct Lightmap {
mat3 normal_xform;
- vec3 pad;
+ vec2 light_texture_size;
float exposure_normalization;
+ vec2 pad;
};
layout(set = 0, binding = 7, std140) restrict readonly buffer Lightmaps {
diff --git a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl
index e7623e0062..4e1da64151 100644
--- a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl
+++ b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl
@@ -521,6 +521,7 @@ layout(constant_id = 9) const bool sc_disable_omni_lights = false;
layout(constant_id = 10) const bool sc_disable_spot_lights = false;
layout(constant_id = 11) const bool sc_disable_reflection_probes = false;
layout(constant_id = 12) const bool sc_disable_directional_lights = false;
+layout(constant_id = 18) const bool sc_use_lightmap_bicubic_filter = false;
#endif //!MODE_UNSHADED
@@ -567,6 +568,67 @@ layout(location = 9) highp in float dp_clip;
#endif
+#ifdef USE_LIGHTMAP
+// w0, w1, w2, and w3 are the four cubic B-spline basis functions
+float w0(float a) {
+ return (1.0 / 6.0) * (a * (a * (-a + 3.0) - 3.0) + 1.0);
+}
+
+float w1(float a) {
+ return (1.0 / 6.0) * (a * a * (3.0 * a - 6.0) + 4.0);
+}
+
+float w2(float a) {
+ return (1.0 / 6.0) * (a * (a * (-3.0 * a + 3.0) + 3.0) + 1.0);
+}
+
+float w3(float a) {
+ return (1.0 / 6.0) * (a * a * a);
+}
+
+// g0 and g1 are the two amplitude functions
+float g0(float a) {
+ return w0(a) + w1(a);
+}
+
+float g1(float a) {
+ return w2(a) + w3(a);
+}
+
+// h0 and h1 are the two offset functions
+float h0(float a) {
+ return -1.0 + w1(a) / (w0(a) + w1(a));
+}
+
+float h1(float a) {
+ return 1.0 + w3(a) / (w2(a) + w3(a));
+}
+
+vec4 textureArray_bicubic(texture2DArray tex, vec3 uv, vec2 texture_size) {
+ vec2 texel_size = vec2(1.0) / texture_size;
+
+ uv.xy = uv.xy * texture_size + vec2(0.5);
+
+ vec2 iuv = floor(uv.xy);
+ vec2 fuv = fract(uv.xy);
+
+ float g0x = g0(fuv.x);
+ float g1x = g1(fuv.x);
+ float h0x = h0(fuv.x);
+ float h1x = h1(fuv.x);
+ float h0y = h0(fuv.y);
+ float h1y = h1(fuv.y);
+
+ vec2 p0 = (vec2(iuv.x + h0x, iuv.y + h0y) - vec2(0.5)) * texel_size;
+ vec2 p1 = (vec2(iuv.x + h1x, iuv.y + h0y) - vec2(0.5)) * texel_size;
+ vec2 p2 = (vec2(iuv.x + h0x, iuv.y + h1y) - vec2(0.5)) * texel_size;
+ vec2 p3 = (vec2(iuv.x + h1x, iuv.y + h1y) - vec2(0.5)) * texel_size;
+
+ return (g0(fuv.y) * (g0x * texture(sampler2DArray(tex, SAMPLER_LINEAR_CLAMP), vec3(p0, uv.z)) + g1x * texture(sampler2DArray(tex, SAMPLER_LINEAR_CLAMP), vec3(p1, uv.z)))) +
+ (g1(fuv.y) * (g0x * texture(sampler2DArray(tex, SAMPLER_LINEAR_CLAMP), vec3(p2, uv.z)) + g1x * texture(sampler2DArray(tex, SAMPLER_LINEAR_CLAMP), vec3(p3, uv.z))));
+}
+#endif //USE_LIGHTMAP
+
#ifdef USE_MULTIVIEW
#ifdef has_VK_KHR_multiview
#define ViewIndex gl_ViewIndex
@@ -839,6 +901,13 @@ void main() {
vec3 light_vertex = vertex;
#endif //LIGHT_VERTEX_USED
+ mat3 model_normal_matrix;
+ if (bool(instances.data[draw_call.instance_index].flags & INSTANCE_FLAGS_NON_UNIFORM_SCALE)) {
+ model_normal_matrix = transpose(inverse(mat3(read_model_matrix)));
+ } else {
+ model_normal_matrix = mat3(read_model_matrix);
+ }
+
mat4 read_view_matrix = scene_data.view_matrix;
vec2 read_viewport_size = scene_data.viewport_size;
@@ -1202,10 +1271,22 @@ void main() {
if (uses_sh) {
uvw.z *= 4.0; //SH textures use 4 times more data
- vec3 lm_light_l0 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 0.0), 0.0).rgb;
- vec3 lm_light_l1n1 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 1.0), 0.0).rgb;
- vec3 lm_light_l1_0 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb;
- vec3 lm_light_l1p1 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb;
+ vec3 lm_light_l0;
+ vec3 lm_light_l1n1;
+ vec3 lm_light_l1_0;
+ vec3 lm_light_l1p1;
+
+ if (sc_use_lightmap_bicubic_filter) {
+ lm_light_l0 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 0.0), lightmaps.data[ofs].light_texture_size).rgb;
+ lm_light_l1n1 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 1.0), lightmaps.data[ofs].light_texture_size).rgb;
+ lm_light_l1_0 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 2.0), lightmaps.data[ofs].light_texture_size).rgb;
+ lm_light_l1p1 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 3.0), lightmaps.data[ofs].light_texture_size).rgb;
+ } else {
+ lm_light_l0 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 0.0), 0.0).rgb;
+ lm_light_l1n1 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 1.0), 0.0).rgb;
+ lm_light_l1_0 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb;
+ lm_light_l1p1 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb;
+ }
vec3 n = normalize(lightmaps.data[ofs].normal_xform * normal);
float exposure_normalization = lightmaps.data[ofs].exposure_normalization;
@@ -1222,7 +1303,11 @@ void main() {
}
} else {
- ambient_light += textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw, 0.0).rgb * lightmaps.data[ofs].exposure_normalization;
+ if (sc_use_lightmap_bicubic_filter) {
+ ambient_light += textureArray_bicubic(lightmap_textures[ofs], uvw, lightmaps.data[ofs].light_texture_size).rgb * lightmaps.data[ofs].exposure_normalization;
+ } else {
+ ambient_light += textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw, 0.0).rgb * lightmaps.data[ofs].exposure_normalization;
+ }
}
}
diff --git a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile_inc.glsl b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile_inc.glsl
index 7674e905e1..d971ff04c5 100644
--- a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile_inc.glsl
+++ b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile_inc.glsl
@@ -71,8 +71,9 @@ directional_lights;
struct Lightmap {
mediump mat3 normal_xform;
- vec3 pad;
+ vec2 light_texture_size;
float exposure_normalization;
+ float pad;
};
layout(set = 0, binding = 7, std140) restrict readonly buffer Lightmaps {
diff --git a/servers/rendering/renderer_rd/storage_rd/light_storage.cpp b/servers/rendering/renderer_rd/storage_rd/light_storage.cpp
index 3d294ca8cb..c217c0fa9a 100644
--- a/servers/rendering/renderer_rd/storage_rd/light_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/light_storage.cpp
@@ -1813,6 +1813,7 @@ void LightStorage::lightmap_set_textures(RID p_lightmap, RID p_light, bool p_use
}
t->lightmap_users.insert(p_lightmap);
+ lm->light_texture_size = Vector2i(t->width, t->height);
if (using_lightmap_array) {
if (lm->array_index < 0) {
diff --git a/servers/rendering/renderer_rd/storage_rd/light_storage.h b/servers/rendering/renderer_rd/storage_rd/light_storage.h
index f152cc5dae..94ab219dc2 100644
--- a/servers/rendering/renderer_rd/storage_rd/light_storage.h
+++ b/servers/rendering/renderer_rd/storage_rd/light_storage.h
@@ -332,6 +332,7 @@ private:
bool interior = false;
AABB bounds = AABB(Vector3(), Vector3(1, 1, 1));
float baked_exposure = 1.0;
+ Vector2i light_texture_size;
int32_t array_index = -1; //unassigned
PackedVector3Array points;
PackedColorArray point_sh;
@@ -985,6 +986,10 @@ public:
const Lightmap *lm = lightmap_owner.get_or_null(p_lightmap);
return lm->uses_spherical_harmonics;
}
+ _FORCE_INLINE_ Vector2i lightmap_get_light_texture_size(RID p_lightmap) const {
+ const Lightmap *lm = lightmap_owner.get_or_null(p_lightmap);
+ return lm->light_texture_size;
+ }
_FORCE_INLINE_ uint64_t lightmap_array_get_version() const {
ERR_FAIL_COND_V(!using_lightmap_array, 0); //only for arrays
return lightmap_array_version;
diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
index a95c4ec83a..539bdcbbd0 100644
--- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
@@ -1381,14 +1381,16 @@ void MeshStorage::_mesh_surface_generate_version_for_input_mask(Mesh::Surface::V
////////////////// MULTIMESH
-RID MeshStorage::multimesh_allocate() {
+RID MeshStorage::_multimesh_allocate() {
return multimesh_owner.allocate_rid();
}
-void MeshStorage::multimesh_initialize(RID p_rid) {
+void MeshStorage::_multimesh_initialize(RID p_rid) {
multimesh_owner.initialize_rid(p_rid, MultiMesh());
}
-void MeshStorage::multimesh_free(RID p_rid) {
+void MeshStorage::_multimesh_free(RID p_rid) {
+ // Remove from interpolator.
+ _interpolation_data.notify_free_multimesh(p_rid);
_update_dirty_multimeshes();
multimesh_allocate_data(p_rid, 0, RS::MULTIMESH_TRANSFORM_2D);
MultiMesh *multimesh = multimesh_owner.get_or_null(p_rid);
@@ -1396,7 +1398,7 @@ void MeshStorage::multimesh_free(RID p_rid) {
multimesh_owner.free(p_rid);
}
-void MeshStorage::multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors, bool p_use_custom_data) {
+void MeshStorage::_multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors, bool p_use_custom_data) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
@@ -1506,13 +1508,13 @@ bool MeshStorage::_multimesh_uses_motion_vectors_offsets(RID p_multimesh) {
return _multimesh_uses_motion_vectors(multimesh);
}
-int MeshStorage::multimesh_get_instance_count(RID p_multimesh) const {
+int MeshStorage::_multimesh_get_instance_count(RID p_multimesh) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, 0);
return multimesh->instances;
}
-void MeshStorage::multimesh_set_mesh(RID p_multimesh, RID p_mesh) {
+void MeshStorage::_multimesh_set_mesh(RID p_multimesh, RID p_mesh) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
if (multimesh->mesh == p_mesh) {
@@ -1702,7 +1704,7 @@ void MeshStorage::_multimesh_re_create_aabb(MultiMesh *multimesh, const float *p
multimesh->aabb = aabb;
}
-void MeshStorage::multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform3D &p_transform) {
+void MeshStorage::_multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform3D &p_transform) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
ERR_FAIL_INDEX(p_index, multimesh->instances);
@@ -1739,7 +1741,7 @@ void MeshStorage::multimesh_instance_set_transform(RID p_multimesh, int p_index,
_multimesh_mark_dirty(multimesh, p_index, true);
}
-void MeshStorage::multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) {
+void MeshStorage::_multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
ERR_FAIL_INDEX(p_index, multimesh->instances);
@@ -1766,7 +1768,7 @@ void MeshStorage::multimesh_instance_set_transform_2d(RID p_multimesh, int p_ind
_multimesh_mark_dirty(multimesh, p_index, true);
}
-void MeshStorage::multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) {
+void MeshStorage::_multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
ERR_FAIL_INDEX(p_index, multimesh->instances);
@@ -1789,7 +1791,7 @@ void MeshStorage::multimesh_instance_set_color(RID p_multimesh, int p_index, con
_multimesh_mark_dirty(multimesh, p_index, false);
}
-void MeshStorage::multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) {
+void MeshStorage::_multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
ERR_FAIL_INDEX(p_index, multimesh->instances);
@@ -1812,7 +1814,7 @@ void MeshStorage::multimesh_instance_set_custom_data(RID p_multimesh, int p_inde
_multimesh_mark_dirty(multimesh, p_index, false);
}
-RID MeshStorage::multimesh_get_mesh(RID p_multimesh) const {
+RID MeshStorage::_multimesh_get_mesh(RID p_multimesh) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, RID());
@@ -1826,7 +1828,7 @@ Dependency *MeshStorage::multimesh_get_dependency(RID p_multimesh) const {
return &multimesh->dependency;
}
-Transform3D MeshStorage::multimesh_instance_get_transform(RID p_multimesh, int p_index) const {
+Transform3D MeshStorage::_multimesh_instance_get_transform(RID p_multimesh, int p_index) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, Transform3D());
ERR_FAIL_INDEX_V(p_index, multimesh->instances, Transform3D());
@@ -1857,7 +1859,7 @@ Transform3D MeshStorage::multimesh_instance_get_transform(RID p_multimesh, int p
return t;
}
-Transform2D MeshStorage::multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const {
+Transform2D MeshStorage::_multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, Transform2D());
ERR_FAIL_INDEX_V(p_index, multimesh->instances, Transform2D());
@@ -1882,7 +1884,7 @@ Transform2D MeshStorage::multimesh_instance_get_transform_2d(RID p_multimesh, in
return t;
}
-Color MeshStorage::multimesh_instance_get_color(RID p_multimesh, int p_index) const {
+Color MeshStorage::_multimesh_instance_get_color(RID p_multimesh, int p_index) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, Color());
ERR_FAIL_INDEX_V(p_index, multimesh->instances, Color());
@@ -1905,7 +1907,7 @@ Color MeshStorage::multimesh_instance_get_color(RID p_multimesh, int p_index) co
return c;
}
-Color MeshStorage::multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const {
+Color MeshStorage::_multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, Color());
ERR_FAIL_INDEX_V(p_index, multimesh->instances, Color());
@@ -1928,7 +1930,7 @@ Color MeshStorage::multimesh_instance_get_custom_data(RID p_multimesh, int p_ind
return c;
}
-void MeshStorage::multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer) {
+void MeshStorage::_multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
ERR_FAIL_COND(p_buffer.size() != (multimesh->instances * (int)multimesh->stride_cache));
@@ -1975,7 +1977,7 @@ void MeshStorage::multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_b
}
}
-Vector<float> MeshStorage::multimesh_get_buffer(RID p_multimesh) const {
+Vector<float> MeshStorage::_multimesh_get_buffer(RID p_multimesh) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, Vector<float>());
if (multimesh->buffer.is_null()) {
@@ -1997,7 +1999,7 @@ Vector<float> MeshStorage::multimesh_get_buffer(RID p_multimesh) const {
}
}
-void MeshStorage::multimesh_set_visible_instances(RID p_multimesh, int p_visible) {
+void MeshStorage::_multimesh_set_visible_instances(RID p_multimesh, int p_visible) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
ERR_FAIL_COND(p_visible < -1 || p_visible > multimesh->instances);
@@ -2019,26 +2021,26 @@ void MeshStorage::multimesh_set_visible_instances(RID p_multimesh, int p_visible
multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_MULTIMESH_VISIBLE_INSTANCES);
}
-int MeshStorage::multimesh_get_visible_instances(RID p_multimesh) const {
+int MeshStorage::_multimesh_get_visible_instances(RID p_multimesh) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, 0);
return multimesh->visible_instances;
}
-void MeshStorage::multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) {
+void MeshStorage::_multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
multimesh->custom_aabb = p_aabb;
multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_AABB);
}
-AABB MeshStorage::multimesh_get_custom_aabb(RID p_multimesh) const {
+AABB MeshStorage::_multimesh_get_custom_aabb(RID p_multimesh) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, AABB());
return multimesh->custom_aabb;
}
-AABB MeshStorage::multimesh_get_aabb(RID p_multimesh) const {
+AABB MeshStorage::_multimesh_get_aabb(RID p_multimesh) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, AABB());
if (multimesh->custom_aabb != AABB()) {
@@ -2051,6 +2053,13 @@ AABB MeshStorage::multimesh_get_aabb(RID p_multimesh) const {
return multimesh->aabb;
}
+MeshStorage::MultiMeshInterpolator *MeshStorage::_multimesh_get_interpolator(RID p_multimesh) const {
+ MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
+ ERR_FAIL_NULL_V_MSG(multimesh, nullptr, "Multimesh not found: " + itos(p_multimesh.get_id()));
+
+ return &multimesh->interpolator;
+}
+
void MeshStorage::_update_dirty_multimeshes() {
while (multimesh_dirty_list) {
MultiMesh *multimesh = multimesh_dirty_list;
diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h
index 5491f637bc..4344db783d 100644
--- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h
+++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h
@@ -244,6 +244,8 @@ private:
bool dirty = false;
MultiMesh *dirty_list = nullptr;
+ RendererMeshStorage::MultiMeshInterpolator interpolator;
+
Dependency dependency;
};
@@ -621,36 +623,38 @@ public:
bool owns_multimesh(RID p_rid) { return multimesh_owner.owns(p_rid); };
- virtual RID multimesh_allocate() override;
- virtual void multimesh_initialize(RID p_multimesh) override;
- virtual void multimesh_free(RID p_rid) override;
+ virtual RID _multimesh_allocate() override;
+ virtual void _multimesh_initialize(RID p_multimesh) override;
+ virtual void _multimesh_free(RID p_rid) override;
+
+ virtual void _multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false) override;
+ virtual int _multimesh_get_instance_count(RID p_multimesh) const override;
- virtual void multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false) override;
- virtual int multimesh_get_instance_count(RID p_multimesh) const override;
+ virtual void _multimesh_set_mesh(RID p_multimesh, RID p_mesh) override;
+ virtual void _multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform3D &p_transform) override;
+ virtual void _multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) override;
+ virtual void _multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) override;
+ virtual void _multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) override;
- virtual void multimesh_set_mesh(RID p_multimesh, RID p_mesh) override;
- virtual void multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform3D &p_transform) override;
- virtual void multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) override;
- virtual void multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) override;
- virtual void multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) override;
+ virtual RID _multimesh_get_mesh(RID p_multimesh) const override;
- virtual RID multimesh_get_mesh(RID p_multimesh) const override;
+ virtual Transform3D _multimesh_instance_get_transform(RID p_multimesh, int p_index) const override;
+ virtual Transform2D _multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const override;
+ virtual Color _multimesh_instance_get_color(RID p_multimesh, int p_index) const override;
+ virtual Color _multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const override;
- virtual Transform3D multimesh_instance_get_transform(RID p_multimesh, int p_index) const override;
- virtual Transform2D multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const override;
- virtual Color multimesh_instance_get_color(RID p_multimesh, int p_index) const override;
- virtual Color multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const override;
+ virtual void _multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer) override;
+ virtual Vector<float> _multimesh_get_buffer(RID p_multimesh) const override;
- virtual void multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer) override;
- virtual Vector<float> multimesh_get_buffer(RID p_multimesh) const override;
+ virtual void _multimesh_set_visible_instances(RID p_multimesh, int p_visible) override;
+ virtual int _multimesh_get_visible_instances(RID p_multimesh) const override;
- virtual void multimesh_set_visible_instances(RID p_multimesh, int p_visible) override;
- virtual int multimesh_get_visible_instances(RID p_multimesh) const override;
+ virtual void _multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) override;
+ virtual AABB _multimesh_get_custom_aabb(RID p_multimesh) const override;
- virtual void multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) override;
- virtual AABB multimesh_get_custom_aabb(RID p_multimesh) const override;
+ virtual AABB _multimesh_get_aabb(RID p_multimesh) const override;
- virtual AABB multimesh_get_aabb(RID p_multimesh) const override;
+ virtual MultiMeshInterpolator *_multimesh_get_interpolator(RID p_multimesh) const override;
void _update_dirty_multimeshes();
void _multimesh_get_motion_vectors_offsets(RID p_multimesh, uint32_t &r_current_offset, uint32_t &r_prev_offset);
diff --git a/servers/rendering/renderer_rd/storage_rd/render_data_rd.h b/servers/rendering/renderer_rd/storage_rd/render_data_rd.h
index 6ebb85442b..888527e1ef 100644
--- a/servers/rendering/renderer_rd/storage_rd/render_data_rd.h
+++ b/servers/rendering/renderer_rd/storage_rd/render_data_rd.h
@@ -73,6 +73,8 @@ public:
uint32_t directional_light_count = 0;
bool directional_light_soft_shadows = false;
+ bool lightmap_bicubic_filter = false;
+
RenderingMethod::RenderInfo *render_info = nullptr;
/* Viewport data */
diff --git a/servers/rendering/renderer_scene_cull.cpp b/servers/rendering/renderer_scene_cull.cpp
index 06753c3fb7..1d25dec633 100644
--- a/servers/rendering/renderer_scene_cull.cpp
+++ b/servers/rendering/renderer_scene_cull.cpp
@@ -4392,7 +4392,8 @@ void RendererSceneCull::set_scene_render(RendererSceneRender *p_scene_render) {
/* INTERPOLATION API */
void RendererSceneCull::update_interpolation_tick(bool p_process) {
- // TODO (MultiMesh): Update interpolation in storage.
+ // MultiMesh: Update interpolation in storage.
+ RSG::mesh_storage->update_interpolation_tick(p_process);
// INSTANCES
@@ -4450,7 +4451,8 @@ void RendererSceneCull::update_interpolation_tick(bool p_process) {
}
void RendererSceneCull::update_interpolation_frame(bool p_process) {
- // TODO (MultiMesh): Update interpolation in storage.
+ // MultiMesh: Update interpolation in storage.
+ RSG::mesh_storage->update_interpolation_frame(p_process);
if (p_process) {
real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
diff --git a/servers/rendering/renderer_scene_cull.h b/servers/rendering/renderer_scene_cull.h
index 53f1f2d246..972f66d325 100644
--- a/servers/rendering/renderer_scene_cull.h
+++ b/servers/rendering/renderer_scene_cull.h
@@ -1418,6 +1418,7 @@ public:
PASS1(decals_set_filter, RS::DecalFilter)
PASS1(light_projectors_set_filter, RS::LightProjectorFilter)
+ PASS1(lightmaps_set_bicubic_filter, bool)
virtual void update();
diff --git a/servers/rendering/renderer_scene_render.h b/servers/rendering/renderer_scene_render.h
index 719efa4df2..3446f5dd1b 100644
--- a/servers/rendering/renderer_scene_render.h
+++ b/servers/rendering/renderer_scene_render.h
@@ -338,6 +338,7 @@ public:
virtual void decals_set_filter(RS::DecalFilter p_filter) = 0;
virtual void light_projectors_set_filter(RS::LightProjectorFilter p_filter) = 0;
+ virtual void lightmaps_set_bicubic_filter(bool p_enable) = 0;
virtual void update() = 0;
virtual ~RendererSceneRender() {}
diff --git a/servers/rendering/rendering_method.h b/servers/rendering/rendering_method.h
index 57fbf97d8c..f6212faf08 100644
--- a/servers/rendering/rendering_method.h
+++ b/servers/rendering/rendering_method.h
@@ -349,6 +349,7 @@ public:
virtual void decals_set_filter(RS::DecalFilter p_filter) = 0;
virtual void light_projectors_set_filter(RS::LightProjectorFilter p_filter) = 0;
+ virtual void lightmaps_set_bicubic_filter(bool p_enable) = 0;
virtual bool free(RID p_rid) = 0;
diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h
index 039f038b8f..60fa546e16 100644
--- a/servers/rendering/rendering_server_default.h
+++ b/servers/rendering/rendering_server_default.h
@@ -344,6 +344,11 @@ public:
FUNC2(multimesh_set_buffer, RID, const Vector<float> &)
FUNC1RC(Vector<float>, multimesh_get_buffer, RID)
+ FUNC3(multimesh_set_buffer_interpolated, RID, const Vector<float> &, const Vector<float> &)
+ FUNC2(multimesh_set_physics_interpolated, RID, bool)
+ FUNC2(multimesh_set_physics_interpolation_quality, RID, MultimeshPhysicsInterpolationQuality)
+ FUNC2(multimesh_instance_reset_physics_interpolation, RID, int)
+
FUNC2(multimesh_set_visible_instances, RID, int)
FUNC1RC(int, multimesh_get_visible_instances, RID)
@@ -762,6 +767,7 @@ public:
FUNC1(directional_soft_shadow_filter_set_quality, ShadowQuality);
FUNC1(decals_set_filter, RS::DecalFilter);
FUNC1(light_projectors_set_filter, RS::LightProjectorFilter);
+ FUNC1(lightmaps_set_bicubic_filter, bool);
/* CAMERA ATTRIBUTES */
diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp
index 1e54523775..f8d00ba7ec 100644
--- a/servers/rendering/shader_language.cpp
+++ b/servers/rendering/shader_language.cpp
@@ -96,6 +96,7 @@ const char *ShaderLanguage::token_names[TK_MAX] = {
"FLOAT_CONSTANT",
"INT_CONSTANT",
"UINT_CONSTANT",
+ "STRING_CONSTANT",
"TYPE_VOID",
"TYPE_BOOL",
"TYPE_BVEC2",
@@ -212,6 +213,7 @@ const char *ShaderLanguage::token_names[TK_MAX] = {
"HINT_ANISOTROPY_TEXTURE",
"HINT_SOURCE_COLOR",
"HINT_RANGE",
+ "HINT_ENUM",
"HINT_INSTANCE_INDEX",
"HINT_SCREEN_TEXTURE",
"HINT_NORMAL_ROUGHNESS_TEXTURE",
@@ -365,6 +367,7 @@ const ShaderLanguage::KeyWord ShaderLanguage::keyword_list[] = {
{ TK_HINT_SOURCE_COLOR, "source_color", CF_UNSPECIFIED, {}, {} },
{ TK_HINT_RANGE, "hint_range", CF_UNSPECIFIED, {}, {} },
+ { TK_HINT_ENUM, "hint_enum", CF_UNSPECIFIED, {}, {} },
{ TK_HINT_INSTANCE_INDEX, "instance_index", CF_UNSPECIFIED, {}, {} },
// sampler hints
@@ -512,7 +515,54 @@ ShaderLanguage::Token ShaderLanguage::_get_token() {
return _make_token(TK_OP_NOT);
} break;
- //case '"' //string - no strings in shader
+ case '"': {
+ String _content = "";
+ bool _previous_backslash = false;
+
+ while (true) {
+ bool _ended = false;
+ char32_t c = GETCHAR(0);
+ if (c == 0) {
+ return _make_token(TK_ERROR, "EOF reached before string termination.");
+ }
+ switch (c) {
+ case '"': {
+ if (_previous_backslash) {
+ _content += '"';
+ _previous_backslash = false;
+ } else {
+ _ended = true;
+ }
+ break;
+ }
+ case '\\': {
+ if (_previous_backslash) {
+ _content += '\\';
+ }
+ _previous_backslash = !_previous_backslash;
+ break;
+ }
+ case '\n': {
+ return _make_token(TK_ERROR, "Unexpected end of string.");
+ }
+ default: {
+ if (!_previous_backslash) {
+ _content += c;
+ } else {
+ return _make_token(TK_ERROR, "Only \\\" and \\\\ escape characters supported.");
+ }
+ break;
+ }
+ }
+
+ char_idx++;
+ if (_ended) {
+ break;
+ }
+ }
+
+ return _make_token(TK_STRING_CONSTANT, _content);
+ } break;
//case '\'' //string - no strings in shader
case '{':
return _make_token(TK_CURLY_BRACKET_OPEN);
@@ -1127,6 +1177,9 @@ String ShaderLanguage::get_uniform_hint_name(ShaderNode::Uniform::Hint p_hint) {
case ShaderNode::Uniform::HINT_RANGE: {
result = "hint_range";
} break;
+ case ShaderNode::Uniform::HINT_ENUM: {
+ result = "hint_enum";
+ } break;
case ShaderNode::Uniform::HINT_SOURCE_COLOR: {
result = "source_color";
} break;
@@ -4146,6 +4199,11 @@ PropertyInfo ShaderLanguage::uniform_to_property_info(const ShaderNode::Uniform
if (p_uniform.array_size > 0) {
pi.type = Variant::PACKED_INT32_ARRAY;
// TODO: Handle range and encoding for for unsigned values.
+ } else if (p_uniform.hint == ShaderLanguage::ShaderNode::Uniform::HINT_ENUM) {
+ pi.type = Variant::INT;
+ pi.hint = PROPERTY_HINT_ENUM;
+ String hint_string;
+ pi.hint_string = String(",").join(p_uniform.hint_enum_names);
} else {
pi.type = Variant::INT;
pi.hint = PROPERTY_HINT_RANGE;
@@ -8988,6 +9046,40 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f
new_hint = ShaderNode::Uniform::HINT_RANGE;
} break;
+ case TK_HINT_ENUM: {
+ if (type != TYPE_INT) {
+ _set_error(vformat(RTR("Enum hint is for '%s' only."), "int"));
+ return ERR_PARSE_ERROR;
+ }
+
+ tk = _get_token();
+ if (tk.type != TK_PARENTHESIS_OPEN) {
+ _set_expected_after_error("(", "hint_enum");
+ return ERR_PARSE_ERROR;
+ }
+
+ while (true) {
+ tk = _get_token();
+
+ if (tk.type != TK_STRING_CONSTANT) {
+ _set_error(RTR("Expected a string constant."));
+ return ERR_PARSE_ERROR;
+ }
+
+ uniform.hint_enum_names.push_back(tk.text);
+
+ tk = _get_token();
+
+ if (tk.type == TK_PARENTHESIS_CLOSE) {
+ break;
+ } else if (tk.type != TK_COMMA) {
+ _set_error(RTR("Expected ',' or ')' after string constant."));
+ return ERR_PARSE_ERROR;
+ }
+ }
+
+ new_hint = ShaderNode::Uniform::HINT_ENUM;
+ } break;
case TK_HINT_INSTANCE_INDEX: {
if (custom_instance_index != -1) {
_set_error(vformat(RTR("Can only specify '%s' once."), "instance_index"));
@@ -9082,7 +9174,9 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f
default:
break;
}
- if (((new_filter != FILTER_DEFAULT || new_repeat != REPEAT_DEFAULT) || (new_hint != ShaderNode::Uniform::HINT_NONE && new_hint != ShaderNode::Uniform::HINT_SOURCE_COLOR && new_hint != ShaderNode::Uniform::HINT_RANGE)) && !is_sampler_type(type)) {
+
+ bool is_sampler_hint = new_hint != ShaderNode::Uniform::HINT_NONE && new_hint != ShaderNode::Uniform::HINT_SOURCE_COLOR && new_hint != ShaderNode::Uniform::HINT_RANGE && new_hint != ShaderNode::Uniform::HINT_ENUM;
+ if (((new_filter != FILTER_DEFAULT || new_repeat != REPEAT_DEFAULT) || is_sampler_hint) && !is_sampler_type(type)) {
_set_error(RTR("This hint is only for sampler types."));
return ERR_PARSE_ERROR;
}
@@ -10785,15 +10879,21 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_
}
} else if ((completion_base == DataType::TYPE_INT || completion_base == DataType::TYPE_FLOAT) && !completion_base_array) {
if (current_uniform_hint == ShaderNode::Uniform::HINT_NONE) {
- ScriptLanguage::CodeCompletionOption option("hint_range", ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT);
+ Vector<String> options;
if (completion_base == DataType::TYPE_INT) {
- option.insert_text = "hint_range(0, 100, 1)";
+ options.push_back("hint_range(0, 100, 1)");
+ options.push_back("hint_enum(\"Zero\", \"One\", \"Two\")");
} else {
- option.insert_text = "hint_range(0.0, 1.0, 0.1)";
+ options.push_back("hint_range(0.0, 1.0, 0.1)");
}
- r_options->push_back(option);
+ for (const String &option_text : options) {
+ String hint_name = option_text.substr(0, option_text.find_char(char32_t('(')));
+ ScriptLanguage::CodeCompletionOption option(hint_name, ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT);
+ option.insert_text = option_text;
+ r_options->push_back(option);
+ }
}
} else if ((int(completion_base) > int(TYPE_MAT4) && int(completion_base) < int(TYPE_STRUCT))) {
Vector<String> options;
diff --git a/servers/rendering/shader_language.h b/servers/rendering/shader_language.h
index 366cdd303f..1b5df7e90f 100644
--- a/servers/rendering/shader_language.h
+++ b/servers/rendering/shader_language.h
@@ -59,6 +59,7 @@ public:
TK_FLOAT_CONSTANT,
TK_INT_CONSTANT,
TK_UINT_CONSTANT,
+ TK_STRING_CONSTANT,
TK_TYPE_VOID,
TK_TYPE_BOOL,
TK_TYPE_BVEC2,
@@ -175,6 +176,7 @@ public:
TK_HINT_ANISOTROPY_TEXTURE,
TK_HINT_SOURCE_COLOR,
TK_HINT_RANGE,
+ TK_HINT_ENUM,
TK_HINT_INSTANCE_INDEX,
TK_HINT_SCREEN_TEXTURE,
TK_HINT_NORMAL_ROUGHNESS_TEXTURE,
@@ -623,6 +625,7 @@ public:
enum Hint {
HINT_NONE,
HINT_RANGE,
+ HINT_ENUM,
HINT_SOURCE_COLOR,
HINT_NORMAL,
HINT_ROUGHNESS_NORMAL,
@@ -661,6 +664,7 @@ public:
TextureFilter filter = FILTER_DEFAULT;
TextureRepeat repeat = REPEAT_DEFAULT;
float hint_range[3];
+ PackedStringArray hint_enum_names;
int instance_index = 0;
String group;
String subgroup;
diff --git a/servers/rendering/storage/mesh_storage.cpp b/servers/rendering/storage/mesh_storage.cpp
new file mode 100644
index 0000000000..221ebaa277
--- /dev/null
+++ b/servers/rendering/storage/mesh_storage.cpp
@@ -0,0 +1,485 @@
+/**************************************************************************/
+/* mesh_storage.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "mesh_storage.h"
+
+#include "core/math/transform_interpolator.h"
+
+#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED)
+#include "core/config/project_settings.h"
+#endif
+
+RID RendererMeshStorage::multimesh_allocate() {
+ return _multimesh_allocate();
+}
+
+void RendererMeshStorage::multimesh_initialize(RID p_rid) {
+ _multimesh_initialize(p_rid);
+}
+
+void RendererMeshStorage::multimesh_free(RID p_rid) {
+ _multimesh_free(p_rid);
+}
+
+void RendererMeshStorage::multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors, bool p_use_custom_data) {
+ MultiMeshInterpolator *mmi = _multimesh_get_interpolator(p_multimesh);
+ if (mmi) {
+ mmi->_transform_format = p_transform_format;
+ mmi->_use_colors = p_use_colors;
+ mmi->_use_custom_data = p_use_custom_data;
+ mmi->_num_instances = p_instances;
+
+ mmi->_vf_size_xform = p_transform_format == RS::MULTIMESH_TRANSFORM_2D ? 8 : 12;
+ mmi->_vf_size_color = p_use_colors ? 4 : 0;
+ mmi->_vf_size_data = p_use_custom_data ? 4 : 0;
+
+ mmi->_stride = mmi->_vf_size_xform + mmi->_vf_size_color + mmi->_vf_size_data;
+
+ int size_in_floats = p_instances * mmi->_stride;
+ mmi->_data_curr.resize_zeroed(size_in_floats);
+ mmi->_data_prev.resize_zeroed(size_in_floats);
+ mmi->_data_interpolated.resize_zeroed(size_in_floats);
+ }
+
+ _multimesh_allocate_data(p_multimesh, p_instances, p_transform_format, p_use_colors, p_use_custom_data);
+}
+
+int RendererMeshStorage::multimesh_get_instance_count(RID p_multimesh) const {
+ return _multimesh_get_instance_count(p_multimesh);
+}
+
+void RendererMeshStorage::multimesh_set_mesh(RID p_multimesh, RID p_mesh) {
+ _multimesh_set_mesh(p_multimesh, p_mesh);
+}
+
+void RendererMeshStorage::multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform3D &p_transform) {
+ MultiMeshInterpolator *mmi = _multimesh_get_interpolator(p_multimesh);
+ if (mmi && mmi->interpolated) {
+ ERR_FAIL_COND(p_index >= mmi->_num_instances);
+ ERR_FAIL_COND(mmi->_vf_size_xform != 12);
+
+ int start = p_index * mmi->_stride;
+ float *ptr = mmi->_data_curr.ptrw();
+ ptr += start;
+
+ const Transform3D &t = p_transform;
+ ptr[0] = t.basis.rows[0][0];
+ ptr[1] = t.basis.rows[0][1];
+ ptr[2] = t.basis.rows[0][2];
+ ptr[3] = t.origin.x;
+ ptr[4] = t.basis.rows[1][0];
+ ptr[5] = t.basis.rows[1][1];
+ ptr[6] = t.basis.rows[1][2];
+ ptr[7] = t.origin.y;
+ ptr[8] = t.basis.rows[2][0];
+ ptr[9] = t.basis.rows[2][1];
+ ptr[10] = t.basis.rows[2][2];
+ ptr[11] = t.origin.z;
+
+ _multimesh_add_to_interpolation_lists(p_multimesh, *mmi);
+
+#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED)
+ if (!Engine::get_singleton()->is_in_physics_frame()) {
+ PHYSICS_INTERPOLATION_WARNING("MultiMesh interpolation is being triggered from outside physics process, this might lead to issues");
+ }
+#endif
+
+ return;
+ }
+
+ _multimesh_instance_set_transform(p_multimesh, p_index, p_transform);
+}
+
+void RendererMeshStorage::multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) {
+ _multimesh_instance_set_transform_2d(p_multimesh, p_index, p_transform);
+}
+
+void RendererMeshStorage::multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) {
+ MultiMeshInterpolator *mmi = _multimesh_get_interpolator(p_multimesh);
+ if (mmi && mmi->interpolated) {
+ ERR_FAIL_COND(p_index >= mmi->_num_instances);
+ ERR_FAIL_COND(mmi->_vf_size_color == 0);
+
+ int start = (p_index * mmi->_stride) + mmi->_vf_size_xform;
+ float *ptr = mmi->_data_curr.ptrw();
+ ptr += start;
+
+ if (mmi->_vf_size_color == 4) {
+ for (int n = 0; n < 4; n++) {
+ ptr[n] = p_color.components[n];
+ }
+ } else {
+#ifdef DEV_ENABLED
+ // The options are currently 4 or zero, but just in case this changes in future...
+ ERR_FAIL_COND(mmi->_vf_size_color != 0);
+#endif
+ }
+ _multimesh_add_to_interpolation_lists(p_multimesh, *mmi);
+ return;
+ }
+
+ _multimesh_instance_set_color(p_multimesh, p_index, p_color);
+}
+
+void RendererMeshStorage::multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) {
+ MultiMeshInterpolator *mmi = _multimesh_get_interpolator(p_multimesh);
+ if (mmi && mmi->interpolated) {
+ ERR_FAIL_COND(p_index >= mmi->_num_instances);
+ ERR_FAIL_COND(mmi->_vf_size_data == 0);
+
+ int start = (p_index * mmi->_stride) + mmi->_vf_size_xform + mmi->_vf_size_color;
+ float *ptr = mmi->_data_curr.ptrw();
+ ptr += start;
+
+ if (mmi->_vf_size_data == 4) {
+ for (int n = 0; n < 4; n++) {
+ ptr[n] = p_color.components[n];
+ }
+ } else {
+#ifdef DEV_ENABLED
+ // The options are currently 4 or zero, but just in case this changes in future...
+ ERR_FAIL_COND(mmi->_vf_size_data != 0);
+#endif
+ }
+ _multimesh_add_to_interpolation_lists(p_multimesh, *mmi);
+ return;
+ }
+
+ _multimesh_instance_set_custom_data(p_multimesh, p_index, p_color);
+}
+
+void RendererMeshStorage::multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) {
+ _multimesh_set_custom_aabb(p_multimesh, p_aabb);
+}
+
+AABB RendererMeshStorage::multimesh_get_custom_aabb(RID p_multimesh) const {
+ return _multimesh_get_custom_aabb(p_multimesh);
+}
+
+RID RendererMeshStorage::multimesh_get_mesh(RID p_multimesh) const {
+ return _multimesh_get_mesh(p_multimesh);
+}
+
+Transform3D RendererMeshStorage::multimesh_instance_get_transform(RID p_multimesh, int p_index) const {
+ return _multimesh_instance_get_transform(p_multimesh, p_index);
+}
+
+Transform2D RendererMeshStorage::multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const {
+ return _multimesh_instance_get_transform_2d(p_multimesh, p_index);
+}
+
+Color RendererMeshStorage::multimesh_instance_get_color(RID p_multimesh, int p_index) const {
+ return _multimesh_instance_get_color(p_multimesh, p_index);
+}
+
+Color RendererMeshStorage::multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const {
+ return _multimesh_instance_get_custom_data(p_multimesh, p_index);
+}
+
+void RendererMeshStorage::multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer) {
+ MultiMeshInterpolator *mmi = _multimesh_get_interpolator(p_multimesh);
+ if (mmi && mmi->interpolated) {
+ ERR_FAIL_COND_MSG(p_buffer.size() != mmi->_data_curr.size(), vformat("Buffer should have %d elements, got %d instead.", mmi->_data_curr.size(), p_buffer.size()));
+
+ mmi->_data_curr = p_buffer;
+ _multimesh_add_to_interpolation_lists(p_multimesh, *mmi);
+
+#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED)
+ if (!Engine::get_singleton()->is_in_physics_frame()) {
+ PHYSICS_INTERPOLATION_WARNING("MultiMesh interpolation is being triggered from outside physics process, this might lead to issues");
+ }
+#endif
+
+ return;
+ }
+
+ _multimesh_set_buffer(p_multimesh, p_buffer);
+}
+
+Vector<float> RendererMeshStorage::multimesh_get_buffer(RID p_multimesh) const {
+ return _multimesh_get_buffer(p_multimesh);
+}
+
+void RendererMeshStorage::multimesh_set_buffer_interpolated(RID p_multimesh, const Vector<float> &p_buffer, const Vector<float> &p_buffer_prev) {
+ MultiMeshInterpolator *mmi = _multimesh_get_interpolator(p_multimesh);
+ if (mmi) {
+ ERR_FAIL_COND_MSG(p_buffer.size() != mmi->_data_curr.size(), vformat("Buffer for current frame should have %d elements, got %d instead.", mmi->_data_curr.size(), p_buffer.size()));
+ ERR_FAIL_COND_MSG(p_buffer_prev.size() != mmi->_data_prev.size(), vformat("Buffer for previous frame should have %d elements, got %d instead.", mmi->_data_prev.size(), p_buffer_prev.size()));
+
+ // We are assuming that mmi->interpolated is the case. (Can possibly assert this?)
+ // Even if this flag hasn't been set - just calling this function suggests interpolation is desired.
+ mmi->_data_prev = p_buffer_prev;
+ mmi->_data_curr = p_buffer;
+ _multimesh_add_to_interpolation_lists(p_multimesh, *mmi);
+
+#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED)
+ if (!Engine::get_singleton()->is_in_physics_frame()) {
+ PHYSICS_INTERPOLATION_WARNING("MultiMesh interpolation is being triggered from outside physics process, this might lead to issues");
+ }
+#endif
+ }
+}
+
+void RendererMeshStorage::multimesh_set_physics_interpolated(RID p_multimesh, bool p_interpolated) {
+ MultiMeshInterpolator *mmi = _multimesh_get_interpolator(p_multimesh);
+ if (mmi) {
+ mmi->interpolated = p_interpolated;
+ }
+}
+
+void RendererMeshStorage::multimesh_set_physics_interpolation_quality(RID p_multimesh, RS::MultimeshPhysicsInterpolationQuality p_quality) {
+ ERR_FAIL_COND((p_quality < 0) || (p_quality > 1));
+ MultiMeshInterpolator *mmi = _multimesh_get_interpolator(p_multimesh);
+ if (mmi) {
+ mmi->quality = (int)p_quality;
+ }
+}
+
+void RendererMeshStorage::multimesh_instance_reset_physics_interpolation(RID p_multimesh, int p_index) {
+ MultiMeshInterpolator *mmi = _multimesh_get_interpolator(p_multimesh);
+ if (mmi) {
+ ERR_FAIL_INDEX(p_index, mmi->_num_instances);
+
+ float *w = mmi->_data_prev.ptrw();
+ const float *r = mmi->_data_curr.ptr();
+ int start = p_index * mmi->_stride;
+
+ for (int n = 0; n < mmi->_stride; n++) {
+ w[start + n] = r[start + n];
+ }
+ }
+}
+
+void RendererMeshStorage::multimesh_set_visible_instances(RID p_multimesh, int p_visible) {
+ return _multimesh_set_visible_instances(p_multimesh, p_visible);
+}
+
+int RendererMeshStorage::multimesh_get_visible_instances(RID p_multimesh) const {
+ return _multimesh_get_visible_instances(p_multimesh);
+}
+
+AABB RendererMeshStorage::multimesh_get_aabb(RID p_multimesh) const {
+ return _multimesh_get_aabb(p_multimesh);
+}
+
+void RendererMeshStorage::_multimesh_add_to_interpolation_lists(RID p_multimesh, MultiMeshInterpolator &r_mmi) {
+ if (!r_mmi.on_interpolate_update_list) {
+ r_mmi.on_interpolate_update_list = true;
+ _interpolation_data.multimesh_interpolate_update_list.push_back(p_multimesh);
+ }
+
+ if (!r_mmi.on_transform_update_list) {
+ r_mmi.on_transform_update_list = true;
+ _interpolation_data.multimesh_transform_update_list_curr->push_back(p_multimesh);
+ }
+}
+
+void RendererMeshStorage::InterpolationData::notify_free_multimesh(RID p_rid) {
+ // If the instance was on any of the lists, remove.
+ multimesh_interpolate_update_list.erase_multiple_unordered(p_rid);
+ multimesh_transform_update_lists[0].erase_multiple_unordered(p_rid);
+ multimesh_transform_update_lists[1].erase_multiple_unordered(p_rid);
+}
+
+void RendererMeshStorage::update_interpolation_tick(bool p_process) {
+ // Detect any that were on the previous transform list that are no longer active,
+ // we should remove them from the interpolate list.
+
+ for (unsigned int n = 0; n < _interpolation_data.multimesh_transform_update_list_prev->size(); n++) {
+ const RID &rid = (*_interpolation_data.multimesh_transform_update_list_prev)[n];
+
+ bool active = true;
+
+ // No longer active? (Either the instance deleted or no longer being transformed.)
+
+ MultiMeshInterpolator *mmi = _multimesh_get_interpolator(rid);
+ if (mmi && !mmi->on_transform_update_list) {
+ active = false;
+ mmi->on_interpolate_update_list = false;
+
+ // Make sure the most recent transform is set...
+ mmi->_data_interpolated = mmi->_data_curr; // TODO: Copy data rather than use Packed = function?
+
+ // ... and that both prev and current are the same, just in case of any interpolations.
+ mmi->_data_prev = mmi->_data_curr;
+ }
+
+ if (!mmi) {
+ active = false;
+ }
+
+ if (!active) {
+ _interpolation_data.multimesh_interpolate_update_list.erase(rid);
+ }
+ }
+
+ if (p_process) {
+ for (unsigned int i = 0; i < _interpolation_data.multimesh_transform_update_list_curr->size(); i++) {
+ const RID &rid = (*_interpolation_data.multimesh_transform_update_list_curr)[i];
+
+ MultiMeshInterpolator *mmi = _multimesh_get_interpolator(rid);
+ if (mmi) {
+ // Reset for next tick.
+ mmi->on_transform_update_list = false;
+ mmi->_data_prev = mmi->_data_curr;
+ }
+ }
+ }
+
+ // If any have left the transform list, remove from the interpolate list.
+
+ // We maintain a mirror list for the transform updates, so we can detect when an instance
+ // is no longer being transformed, and remove it from the interpolate list.
+ SWAP(_interpolation_data.multimesh_transform_update_list_curr, _interpolation_data.multimesh_transform_update_list_prev);
+
+ // Prepare for the next iteration.
+ _interpolation_data.multimesh_transform_update_list_curr->clear();
+}
+
+void RendererMeshStorage::update_interpolation_frame(bool p_process) {
+ if (p_process) {
+ // Only need 32 bits for interpolation, don't use real_t.
+ float f = Engine::get_singleton()->get_physics_interpolation_fraction();
+
+ for (unsigned int c = 0; c < _interpolation_data.multimesh_interpolate_update_list.size(); c++) {
+ const RID &rid = _interpolation_data.multimesh_interpolate_update_list[c];
+
+ // We could use the TransformInterpolator here to slerp transforms, but that might be too expensive,
+ // so just using a Basis lerp for now.
+ MultiMeshInterpolator *mmi = _multimesh_get_interpolator(rid);
+ if (mmi) {
+ // Make sure arrays are the correct size.
+ DEV_ASSERT(mmi->_data_prev.size() == mmi->_data_curr.size());
+
+ if (mmi->_data_interpolated.size() < mmi->_data_curr.size()) {
+ mmi->_data_interpolated.resize(mmi->_data_curr.size());
+ }
+ DEV_ASSERT(mmi->_data_interpolated.size() >= mmi->_data_curr.size());
+
+ DEV_ASSERT((mmi->_data_curr.size() % mmi->_stride) == 0);
+ int num = mmi->_data_curr.size() / mmi->_stride;
+
+ const float *pf_prev = mmi->_data_prev.ptr();
+ const float *pf_curr = mmi->_data_curr.ptr();
+ float *pf_int = mmi->_data_interpolated.ptrw();
+
+ bool use_lerp = mmi->quality == 0;
+
+ // Temporary transform (needed for swizzling).
+ Transform3D tp, tc, tr; // (transform prev, curr and result)
+
+ // Test for cache friendliness versus doing branchless.
+ for (int n = 0; n < num; n++) {
+ // Transform.
+ if (use_lerp) {
+ for (int i = 0; i < mmi->_vf_size_xform; i++) {
+ pf_int[i] = Math::lerp(pf_prev[i], pf_curr[i], f);
+ }
+ } else {
+ // Silly swizzling, this will slow things down.
+ // No idea why it is using this format...
+ // ... maybe due to the shader.
+ tp.basis.rows[0][0] = pf_prev[0];
+ tp.basis.rows[0][1] = pf_prev[1];
+ tp.basis.rows[0][2] = pf_prev[2];
+ tp.basis.rows[1][0] = pf_prev[4];
+ tp.basis.rows[1][1] = pf_prev[5];
+ tp.basis.rows[1][2] = pf_prev[6];
+ tp.basis.rows[2][0] = pf_prev[8];
+ tp.basis.rows[2][1] = pf_prev[9];
+ tp.basis.rows[2][2] = pf_prev[10];
+ tp.origin.x = pf_prev[3];
+ tp.origin.y = pf_prev[7];
+ tp.origin.z = pf_prev[11];
+
+ tc.basis.rows[0][0] = pf_curr[0];
+ tc.basis.rows[0][1] = pf_curr[1];
+ tc.basis.rows[0][2] = pf_curr[2];
+ tc.basis.rows[1][0] = pf_curr[4];
+ tc.basis.rows[1][1] = pf_curr[5];
+ tc.basis.rows[1][2] = pf_curr[6];
+ tc.basis.rows[2][0] = pf_curr[8];
+ tc.basis.rows[2][1] = pf_curr[9];
+ tc.basis.rows[2][2] = pf_curr[10];
+ tc.origin.x = pf_curr[3];
+ tc.origin.y = pf_curr[7];
+ tc.origin.z = pf_curr[11];
+
+ TransformInterpolator::interpolate_transform_3d(tp, tc, tr, f);
+
+ pf_int[0] = tr.basis.rows[0][0];
+ pf_int[1] = tr.basis.rows[0][1];
+ pf_int[2] = tr.basis.rows[0][2];
+ pf_int[4] = tr.basis.rows[1][0];
+ pf_int[5] = tr.basis.rows[1][1];
+ pf_int[6] = tr.basis.rows[1][2];
+ pf_int[8] = tr.basis.rows[2][0];
+ pf_int[9] = tr.basis.rows[2][1];
+ pf_int[10] = tr.basis.rows[2][2];
+ pf_int[3] = tr.origin.x;
+ pf_int[7] = tr.origin.y;
+ pf_int[11] = tr.origin.z;
+ }
+
+ pf_prev += mmi->_vf_size_xform;
+ pf_curr += mmi->_vf_size_xform;
+ pf_int += mmi->_vf_size_xform;
+
+ // Color.
+ if (mmi->_vf_size_color == 4) {
+ for (int i = 0; i < 4; i++) {
+ pf_int[i] = Math::lerp(pf_prev[i], pf_curr[i], f);
+ }
+
+ pf_prev += 4;
+ pf_curr += 4;
+ pf_int += 4;
+ }
+
+ // Custom data.
+ if (mmi->_vf_size_data == 4) {
+ for (int i = 0; i < 4; i++) {
+ pf_int[i] = Math::lerp(pf_prev[i], pf_curr[i], f);
+ }
+
+ pf_prev += 4;
+ pf_curr += 4;
+ pf_int += 4;
+ }
+ }
+
+ _multimesh_set_buffer(rid, mmi->_data_interpolated);
+
+ // TODO: Make sure AABBs are constantly up to date through the interpolation?
+ // NYI.
+ }
+ }
+ }
+}
diff --git a/servers/rendering/storage/mesh_storage.h b/servers/rendering/storage/mesh_storage.h
index 39fd4f393d..ecd2a967d0 100644
--- a/servers/rendering/storage/mesh_storage.h
+++ b/servers/rendering/storage/mesh_storage.h
@@ -89,39 +89,110 @@ public:
virtual void update_mesh_instances() = 0;
/* MULTIMESH API */
+ struct MultiMeshInterpolator {
+ RS::MultimeshTransformFormat _transform_format = RS::MULTIMESH_TRANSFORM_3D;
+ bool _use_colors = false;
+ bool _use_custom_data = false;
- virtual RID multimesh_allocate() = 0;
- virtual void multimesh_initialize(RID p_rid) = 0;
- virtual void multimesh_free(RID p_rid) = 0;
+ // The stride of the buffer in floats.
+ int _stride = 0;
- virtual void multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false) = 0;
+ // Vertex format sizes in floats.
+ int _vf_size_xform = 0;
+ int _vf_size_color = 0;
+ int _vf_size_data = 0;
- virtual int multimesh_get_instance_count(RID p_multimesh) const = 0;
+ // Set by allocate, can be used to prevent indexing out of range.
+ int _num_instances = 0;
- virtual void multimesh_set_mesh(RID p_multimesh, RID p_mesh) = 0;
- virtual void multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform3D &p_transform) = 0;
- virtual void multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) = 0;
- virtual void multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) = 0;
- virtual void multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) = 0;
+ // Quality determines whether to use lerp or slerp etc.
+ int quality = 0;
+ bool interpolated = false;
+ bool on_interpolate_update_list = false;
+ bool on_transform_update_list = false;
- virtual void multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) = 0;
- virtual AABB multimesh_get_custom_aabb(RID p_multimesh) const = 0;
+ Vector<float> _data_prev;
+ Vector<float> _data_curr;
+ Vector<float> _data_interpolated;
+ };
- virtual RID multimesh_get_mesh(RID p_multimesh) const = 0;
+ virtual RID multimesh_allocate();
+ virtual void multimesh_initialize(RID p_rid);
+ virtual void multimesh_free(RID p_rid);
- virtual Transform3D multimesh_instance_get_transform(RID p_multimesh, int p_index) const = 0;
- virtual Transform2D multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const = 0;
- virtual Color multimesh_instance_get_color(RID p_multimesh, int p_index) const = 0;
- virtual Color multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const = 0;
+ virtual void multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false);
- virtual void multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer) = 0;
- virtual Vector<float> multimesh_get_buffer(RID p_multimesh) const = 0;
+ virtual int multimesh_get_instance_count(RID p_multimesh) const;
- virtual void multimesh_set_visible_instances(RID p_multimesh, int p_visible) = 0;
- virtual int multimesh_get_visible_instances(RID p_multimesh) const = 0;
+ virtual void multimesh_set_mesh(RID p_multimesh, RID p_mesh);
+ virtual void multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform3D &p_transform);
+ virtual void multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform);
+ virtual void multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color);
+ virtual void multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color);
- virtual AABB multimesh_get_aabb(RID p_multimesh) const = 0;
+ virtual void multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb);
+ virtual AABB multimesh_get_custom_aabb(RID p_multimesh) const;
+ virtual RID multimesh_get_mesh(RID p_multimesh) const;
+
+ virtual Transform3D multimesh_instance_get_transform(RID p_multimesh, int p_index) const;
+ virtual Transform2D multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const;
+ virtual Color multimesh_instance_get_color(RID p_multimesh, int p_index) const;
+ virtual Color multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const;
+
+ virtual void multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer);
+ virtual Vector<float> multimesh_get_buffer(RID p_multimesh) const;
+
+ virtual void multimesh_set_buffer_interpolated(RID p_multimesh, const Vector<float> &p_buffer, const Vector<float> &p_buffer_prev);
+ virtual void multimesh_set_physics_interpolated(RID p_multimesh, bool p_interpolated);
+ virtual void multimesh_set_physics_interpolation_quality(RID p_multimesh, RS::MultimeshPhysicsInterpolationQuality p_quality);
+ virtual void multimesh_instance_reset_physics_interpolation(RID p_multimesh, int p_index);
+
+ virtual void multimesh_set_visible_instances(RID p_multimesh, int p_visible);
+ virtual int multimesh_get_visible_instances(RID p_multimesh) const;
+
+ virtual AABB multimesh_get_aabb(RID p_multimesh) const;
+
+ virtual RID _multimesh_allocate() = 0;
+ virtual void _multimesh_initialize(RID p_rid) = 0;
+ virtual void _multimesh_free(RID p_rid) = 0;
+
+ virtual void _multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false) = 0;
+
+ virtual int _multimesh_get_instance_count(RID p_multimesh) const = 0;
+
+ virtual void _multimesh_set_mesh(RID p_multimesh, RID p_mesh) = 0;
+ virtual void _multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform3D &p_transform) = 0;
+ virtual void _multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) = 0;
+ virtual void _multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) = 0;
+ virtual void _multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) = 0;
+
+ virtual void _multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) = 0;
+ virtual AABB _multimesh_get_custom_aabb(RID p_multimesh) const = 0;
+
+ virtual RID _multimesh_get_mesh(RID p_multimesh) const = 0;
+
+ virtual Transform3D _multimesh_instance_get_transform(RID p_multimesh, int p_index) const = 0;
+ virtual Transform2D _multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const = 0;
+ virtual Color _multimesh_instance_get_color(RID p_multimesh, int p_index) const = 0;
+ virtual Color _multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const = 0;
+
+ virtual void _multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer) = 0;
+ virtual Vector<float> _multimesh_get_buffer(RID p_multimesh) const = 0;
+
+ virtual void _multimesh_set_visible_instances(RID p_multimesh, int p_visible) = 0;
+ virtual int _multimesh_get_visible_instances(RID p_multimesh) const = 0;
+
+ virtual AABB _multimesh_get_aabb(RID p_multimesh) const = 0;
+
+ // Multimesh is responsible for allocating / destroying a MultiMeshInterpolator object.
+ // This allows shared functionality for interpolation across backends.
+ virtual MultiMeshInterpolator *_multimesh_get_interpolator(RID p_multimesh) const = 0;
+
+private:
+ void _multimesh_add_to_interpolation_lists(RID p_multimesh, MultiMeshInterpolator &r_mmi);
+
+public:
/* SKELETON API */
virtual RID skeleton_allocate() = 0;
@@ -137,6 +208,19 @@ public:
virtual void skeleton_set_base_transform_2d(RID p_skeleton, const Transform2D &p_base_transform) = 0;
virtual void skeleton_update_dependency(RID p_base, DependencyTracker *p_instance) = 0;
+
+ /* INTERPOLATION */
+
+ struct InterpolationData {
+ void notify_free_multimesh(RID p_rid);
+ LocalVector<RID> multimesh_interpolate_update_list;
+ LocalVector<RID> multimesh_transform_update_lists[2];
+ LocalVector<RID> *multimesh_transform_update_list_curr = &multimesh_transform_update_lists[0];
+ LocalVector<RID> *multimesh_transform_update_list_prev = &multimesh_transform_update_lists[1];
+ } _interpolation_data;
+
+ void update_interpolation_tick(bool p_process = true);
+ void update_interpolation_frame(bool p_process = true);
};
#endif // MESH_STORAGE_H
diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp
index 9fc67b04b1..0f0d15a52f 100644
--- a/servers/rendering_server.cpp
+++ b/servers/rendering_server.cpp
@@ -2439,8 +2439,15 @@ void RenderingServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("multimesh_set_buffer", "multimesh", "buffer"), &RenderingServer::multimesh_set_buffer);
ClassDB::bind_method(D_METHOD("multimesh_get_buffer", "multimesh"), &RenderingServer::multimesh_get_buffer);
+ ClassDB::bind_method(D_METHOD("multimesh_set_buffer_interpolated", "multimesh", "buffer", "buffer_previous"), &RenderingServer::multimesh_set_buffer_interpolated);
+ ClassDB::bind_method(D_METHOD("multimesh_set_physics_interpolated", "multimesh", "interpolated"), &RenderingServer::multimesh_set_physics_interpolated);
+ ClassDB::bind_method(D_METHOD("multimesh_set_physics_interpolation_quality", "multimesh", "quality"), &RenderingServer::multimesh_set_physics_interpolation_quality);
+ ClassDB::bind_method(D_METHOD("multimesh_instance_reset_physics_interpolation", "multimesh", "index"), &RenderingServer::multimesh_instance_reset_physics_interpolation);
+
BIND_ENUM_CONSTANT(MULTIMESH_TRANSFORM_2D);
BIND_ENUM_CONSTANT(MULTIMESH_TRANSFORM_3D);
+ BIND_ENUM_CONSTANT(MULTIMESH_INTERP_QUALITY_FAST);
+ BIND_ENUM_CONSTANT(MULTIMESH_INTERP_QUALITY_HIGH);
/* SKELETON API */
@@ -2477,6 +2484,7 @@ void RenderingServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("light_directional_set_sky_mode", "light", "mode"), &RenderingServer::light_directional_set_sky_mode);
ClassDB::bind_method(D_METHOD("light_projectors_set_filter", "filter"), &RenderingServer::light_projectors_set_filter);
+ ClassDB::bind_method(D_METHOD("lightmaps_set_bicubic_filter", "enable"), &RenderingServer::lightmaps_set_bicubic_filter);
BIND_ENUM_CONSTANT(LIGHT_PROJECTOR_FILTER_NEAREST);
BIND_ENUM_CONSTANT(LIGHT_PROJECTOR_FILTER_LINEAR);
@@ -3618,6 +3626,7 @@ void RenderingServer::init() {
GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "rendering/lightmapping/probe_capture/update_speed", PROPERTY_HINT_RANGE, "0.001,256,0.001"), 15);
GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "rendering/lightmapping/primitive_meshes/texel_size", PROPERTY_HINT_RANGE, "0.001,100,0.001"), 0.2);
+ GLOBAL_DEF("rendering/lightmapping/lightmap_gi/use_bicubic_filter", true);
GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/global_illumination/sdfgi/probe_ray_count", PROPERTY_HINT_ENUM, "8 (Fastest),16,32,64,96,128 (Slowest)"), 1);
GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/global_illumination/sdfgi/frames_to_converge", PROPERTY_HINT_ENUM, "5 (Less Latency but Lower Quality),10,15,20,25,30 (More Latency but Higher Quality)"), 5);
diff --git a/servers/rendering_server.h b/servers/rendering_server.h
index 86d9d5dbd3..d8b6651833 100644
--- a/servers/rendering_server.h
+++ b/servers/rendering_server.h
@@ -426,6 +426,11 @@ public:
MULTIMESH_TRANSFORM_3D,
};
+ enum MultimeshPhysicsInterpolationQuality {
+ MULTIMESH_INTERP_QUALITY_FAST,
+ MULTIMESH_INTERP_QUALITY_HIGH,
+ };
+
virtual void multimesh_allocate_data(RID p_multimesh, int p_instances, MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false) = 0;
virtual int multimesh_get_instance_count(RID p_multimesh) const = 0;
@@ -449,6 +454,12 @@ public:
virtual void multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer) = 0;
virtual Vector<float> multimesh_get_buffer(RID p_multimesh) const = 0;
+ // Interpolation.
+ virtual void multimesh_set_buffer_interpolated(RID p_multimesh, const Vector<float> &p_buffer_curr, const Vector<float> &p_buffer_prev) = 0;
+ virtual void multimesh_set_physics_interpolated(RID p_multimesh, bool p_interpolated) = 0;
+ virtual void multimesh_set_physics_interpolation_quality(RID p_multimesh, MultimeshPhysicsInterpolationQuality p_quality) = 0;
+ virtual void multimesh_instance_reset_physics_interpolation(RID p_multimesh, int p_index) = 0;
+
virtual void multimesh_set_visible_instances(RID p_multimesh, int p_visible) = 0;
virtual int multimesh_get_visible_instances(RID p_multimesh) const = 0;
@@ -687,6 +698,8 @@ public:
virtual void lightmap_set_probe_capture_update_speed(float p_speed) = 0;
+ virtual void lightmaps_set_bicubic_filter(bool p_enable) = 0;
+
/* PARTICLES API */
virtual RID particles_create() = 0;
@@ -1789,6 +1802,7 @@ VARIANT_ENUM_CAST(RenderingServer::ArrayCustomFormat);
VARIANT_ENUM_CAST(RenderingServer::PrimitiveType);
VARIANT_ENUM_CAST(RenderingServer::BlendShapeMode);
VARIANT_ENUM_CAST(RenderingServer::MultimeshTransformFormat);
+VARIANT_ENUM_CAST(RenderingServer::MultimeshPhysicsInterpolationQuality);
VARIANT_ENUM_CAST(RenderingServer::LightType);
VARIANT_ENUM_CAST(RenderingServer::LightParam);
VARIANT_ENUM_CAST(RenderingServer::LightBakeMode);
diff --git a/servers/xr/xr_hand_tracker.cpp b/servers/xr/xr_hand_tracker.cpp
index abfe2e9867..2f64737ed6 100644
--- a/servers/xr/xr_hand_tracker.cpp
+++ b/servers/xr/xr_hand_tracker.cpp
@@ -60,6 +60,7 @@ void XRHandTracker::_bind_methods() {
BIND_ENUM_CONSTANT(HAND_TRACKING_SOURCE_UNKNOWN);
BIND_ENUM_CONSTANT(HAND_TRACKING_SOURCE_UNOBSTRUCTED);
BIND_ENUM_CONSTANT(HAND_TRACKING_SOURCE_CONTROLLER);
+ BIND_ENUM_CONSTANT(HAND_TRACKING_SOURCE_NOT_TRACKED);
BIND_ENUM_CONSTANT(HAND_TRACKING_SOURCE_MAX);
BIND_ENUM_CONSTANT(HAND_JOINT_PALM);
diff --git a/servers/xr/xr_hand_tracker.h b/servers/xr/xr_hand_tracker.h
index c7c18a31f8..7837df3e0a 100644
--- a/servers/xr/xr_hand_tracker.h
+++ b/servers/xr/xr_hand_tracker.h
@@ -42,6 +42,7 @@ public:
HAND_TRACKING_SOURCE_UNKNOWN,
HAND_TRACKING_SOURCE_UNOBSTRUCTED,
HAND_TRACKING_SOURCE_CONTROLLER,
+ HAND_TRACKING_SOURCE_NOT_TRACKED,
HAND_TRACKING_SOURCE_MAX
};