diff options
96 files changed, 2417 insertions, 663 deletions
diff --git a/core/io/image.cpp b/core/io/image.cpp index 16dd66fc98..6a049014bc 100644 --- a/core/io/image.cpp +++ b/core/io/image.cpp @@ -3524,6 +3524,7 @@ Ref<Image> Image::get_image_from_mipmap(int p_mipamp) const { void Image::bump_map_to_normal_map(float bump_scale) { ERR_FAIL_COND(!_can_modify(format)); + clear_mipmaps(); convert(Image::FORMAT_RF); Vector<uint8_t> result_image; //rgba output diff --git a/core/object/object.h b/core/object/object.h index fa3003cc1f..8c647cda40 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -89,8 +89,8 @@ enum PropertyHint { PROPERTY_HINT_SAVE_FILE, ///< a file path must be passed, hint_text (optionally) is a filter "*.png,*.wav,*.doc,". This opens a save dialog PROPERTY_HINT_GLOBAL_SAVE_FILE, ///< a file path must be passed, hint_text (optionally) is a filter "*.png,*.wav,*.doc,". This opens a save dialog PROPERTY_HINT_INT_IS_OBJECTID, - PROPERTY_HINT_ARRAY_TYPE, PROPERTY_HINT_INT_IS_POINTER, + PROPERTY_HINT_ARRAY_TYPE, PROPERTY_HINT_LOCALE_ID, PROPERTY_HINT_LOCALIZABLE_STRING, PROPERTY_HINT_NODE_TYPE, ///< a node object type diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index 9a887dc105..087ce09215 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -1836,10 +1836,6 @@ static void _register_variant_builtin_methods() { bind_static_method(Color, hex64, sarray("hex"), varray()); bind_static_method(Color, html, sarray("rgba"), varray()); bind_static_method(Color, html_is_valid, sarray("color"), varray()); - bind_static_method(Color, find_named_color, sarray("name"), varray()); - bind_static_method(Color, get_named_color_count, sarray(), varray()); - bind_static_method(Color, get_named_color_name, sarray("idx"), varray()); - bind_static_method(Color, get_named_color, sarray("idx"), varray()); bind_static_method(Color, from_string, sarray("str", "default"), varray()); bind_static_method(Color, from_hsv, sarray("h", "s", "v", "alpha"), varray(1.0)); bind_static_method(Color, from_ok_hsl, sarray("h", "s", "l", "alpha"), varray(1.0)); diff --git a/core/variant/variant_op.cpp b/core/variant/variant_op.cpp index 301fd00d26..25bc241e9b 100644 --- a/core/variant/variant_op.cpp +++ b/core/variant/variant_op.cpp @@ -240,8 +240,6 @@ void Variant::_register_variant_operators() { register_op<OperatorEvaluatorAdd<double, double, int64_t>>(Variant::OP_ADD, Variant::FLOAT, Variant::INT); register_op<OperatorEvaluatorAdd<double, double, double>>(Variant::OP_ADD, Variant::FLOAT, Variant::FLOAT); register_op<OperatorEvaluatorAdd<String, String, String>>(Variant::OP_ADD, Variant::STRING, Variant::STRING); - register_op<OperatorEvaluatorAdd<String, char32_t, String>>(Variant::OP_ADD, Variant::INT, Variant::STRING); - register_op<OperatorEvaluatorAdd<String, String, char32_t>>(Variant::OP_ADD, Variant::STRING, Variant::INT); register_op<OperatorEvaluatorAdd<Vector2, Vector2, Vector2>>(Variant::OP_ADD, Variant::VECTOR2, Variant::VECTOR2); register_op<OperatorEvaluatorAdd<Vector2i, Vector2i, Vector2i>>(Variant::OP_ADD, Variant::VECTOR2I, Variant::VECTOR2I); register_op<OperatorEvaluatorAdd<Vector3, Vector3, Vector3>>(Variant::OP_ADD, Variant::VECTOR3, Variant::VECTOR3); @@ -561,6 +559,7 @@ void Variant::_register_variant_operators() { register_op<OperatorEvaluatorAlwaysFalse<Variant::OP_EQUAL, Variant::AABB, Variant::NIL>>(Variant::OP_EQUAL, Variant::AABB, Variant::NIL); register_op<OperatorEvaluatorAlwaysFalse<Variant::OP_EQUAL, Variant::BASIS, Variant::NIL>>(Variant::OP_EQUAL, Variant::BASIS, Variant::NIL); register_op<OperatorEvaluatorAlwaysFalse<Variant::OP_EQUAL, Variant::TRANSFORM3D, Variant::NIL>>(Variant::OP_EQUAL, Variant::TRANSFORM3D, Variant::NIL); + register_op<OperatorEvaluatorAlwaysFalse<Variant::OP_EQUAL, Variant::PROJECTION, Variant::NIL>>(Variant::OP_EQUAL, Variant::PROJECTION, Variant::NIL); register_op<OperatorEvaluatorAlwaysFalse<Variant::OP_EQUAL, Variant::COLOR, Variant::NIL>>(Variant::OP_EQUAL, Variant::COLOR, Variant::NIL); register_op<OperatorEvaluatorAlwaysFalse<Variant::OP_EQUAL, Variant::STRING_NAME, Variant::NIL>>(Variant::OP_EQUAL, Variant::STRING_NAME, Variant::NIL); register_op<OperatorEvaluatorAlwaysFalse<Variant::OP_EQUAL, Variant::NODE_PATH, Variant::NIL>>(Variant::OP_EQUAL, Variant::NODE_PATH, Variant::NIL); @@ -597,6 +596,7 @@ void Variant::_register_variant_operators() { register_op<OperatorEvaluatorAlwaysFalse<Variant::OP_EQUAL, Variant::NIL, Variant::AABB>>(Variant::OP_EQUAL, Variant::NIL, Variant::AABB); register_op<OperatorEvaluatorAlwaysFalse<Variant::OP_EQUAL, Variant::NIL, Variant::BASIS>>(Variant::OP_EQUAL, Variant::NIL, Variant::BASIS); register_op<OperatorEvaluatorAlwaysFalse<Variant::OP_EQUAL, Variant::NIL, Variant::TRANSFORM3D>>(Variant::OP_EQUAL, Variant::NIL, Variant::TRANSFORM3D); + register_op<OperatorEvaluatorAlwaysFalse<Variant::OP_EQUAL, Variant::NIL, Variant::PROJECTION>>(Variant::OP_EQUAL, Variant::NIL, Variant::PROJECTION); register_op<OperatorEvaluatorAlwaysFalse<Variant::OP_EQUAL, Variant::NIL, Variant::COLOR>>(Variant::OP_EQUAL, Variant::NIL, Variant::COLOR); register_op<OperatorEvaluatorAlwaysFalse<Variant::OP_EQUAL, Variant::NIL, Variant::STRING_NAME>>(Variant::OP_EQUAL, Variant::NIL, Variant::STRING_NAME); register_op<OperatorEvaluatorAlwaysFalse<Variant::OP_EQUAL, Variant::NIL, Variant::NODE_PATH>>(Variant::OP_EQUAL, Variant::NIL, Variant::NODE_PATH); @@ -682,6 +682,7 @@ void Variant::_register_variant_operators() { register_op<OperatorEvaluatorAlwaysTrue<Variant::OP_NOT_EQUAL, Variant::AABB, Variant::NIL>>(Variant::OP_NOT_EQUAL, Variant::AABB, Variant::NIL); register_op<OperatorEvaluatorAlwaysTrue<Variant::OP_NOT_EQUAL, Variant::BASIS, Variant::NIL>>(Variant::OP_NOT_EQUAL, Variant::BASIS, Variant::NIL); register_op<OperatorEvaluatorAlwaysTrue<Variant::OP_NOT_EQUAL, Variant::TRANSFORM3D, Variant::NIL>>(Variant::OP_NOT_EQUAL, Variant::TRANSFORM3D, Variant::NIL); + register_op<OperatorEvaluatorAlwaysTrue<Variant::OP_NOT_EQUAL, Variant::PROJECTION, Variant::NIL>>(Variant::OP_NOT_EQUAL, Variant::PROJECTION, Variant::NIL); register_op<OperatorEvaluatorAlwaysTrue<Variant::OP_NOT_EQUAL, Variant::COLOR, Variant::NIL>>(Variant::OP_NOT_EQUAL, Variant::COLOR, Variant::NIL); register_op<OperatorEvaluatorAlwaysTrue<Variant::OP_NOT_EQUAL, Variant::STRING_NAME, Variant::NIL>>(Variant::OP_NOT_EQUAL, Variant::STRING_NAME, Variant::NIL); register_op<OperatorEvaluatorAlwaysTrue<Variant::OP_NOT_EQUAL, Variant::NODE_PATH, Variant::NIL>>(Variant::OP_NOT_EQUAL, Variant::NODE_PATH, Variant::NIL); @@ -718,6 +719,7 @@ void Variant::_register_variant_operators() { register_op<OperatorEvaluatorAlwaysTrue<Variant::OP_NOT_EQUAL, Variant::NIL, Variant::AABB>>(Variant::OP_NOT_EQUAL, Variant::NIL, Variant::AABB); register_op<OperatorEvaluatorAlwaysTrue<Variant::OP_NOT_EQUAL, Variant::NIL, Variant::BASIS>>(Variant::OP_NOT_EQUAL, Variant::NIL, Variant::BASIS); register_op<OperatorEvaluatorAlwaysTrue<Variant::OP_NOT_EQUAL, Variant::NIL, Variant::TRANSFORM3D>>(Variant::OP_NOT_EQUAL, Variant::NIL, Variant::TRANSFORM3D); + register_op<OperatorEvaluatorAlwaysTrue<Variant::OP_NOT_EQUAL, Variant::NIL, Variant::PROJECTION>>(Variant::OP_NOT_EQUAL, Variant::NIL, Variant::PROJECTION); register_op<OperatorEvaluatorAlwaysTrue<Variant::OP_NOT_EQUAL, Variant::NIL, Variant::COLOR>>(Variant::OP_NOT_EQUAL, Variant::NIL, Variant::COLOR); register_op<OperatorEvaluatorAlwaysTrue<Variant::OP_NOT_EQUAL, Variant::NIL, Variant::STRING_NAME>>(Variant::OP_NOT_EQUAL, Variant::NIL, Variant::STRING_NAME); register_op<OperatorEvaluatorAlwaysTrue<Variant::OP_NOT_EQUAL, Variant::NIL, Variant::NODE_PATH>>(Variant::OP_NOT_EQUAL, Variant::NIL, Variant::NODE_PATH); diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index b89eb121b6..e30034495c 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -2760,9 +2760,9 @@ </constant> <constant name="PROPERTY_HINT_INT_IS_OBJECTID" value="39" enum="PropertyHint"> </constant> - <constant name="PROPERTY_HINT_INT_IS_POINTER" value="41" enum="PropertyHint"> + <constant name="PROPERTY_HINT_INT_IS_POINTER" value="40" enum="PropertyHint"> </constant> - <constant name="PROPERTY_HINT_ARRAY_TYPE" value="40" enum="PropertyHint"> + <constant name="PROPERTY_HINT_ARRAY_TYPE" value="41" enum="PropertyHint"> </constant> <constant name="PROPERTY_HINT_LOCALE_ID" value="42" enum="PropertyHint"> Hints that a [String] property is a locale code. Editing it will show a locale dialog for picking language and country. diff --git a/doc/classes/BaseButton.xml b/doc/classes/BaseButton.xml index 47e1f85c97..638934bc9e 100644 --- a/doc/classes/BaseButton.xml +++ b/doc/classes/BaseButton.xml @@ -68,6 +68,9 @@ <member name="shortcut" type="Shortcut" setter="set_shortcut" getter="get_shortcut"> [Shortcut] associated to the button. </member> + <member name="shortcut_feedback" type="bool" setter="set_shortcut_feedback" getter="is_shortcut_feedback"> + If [code]true[/code], the button will appear pressed when its shortcut is activated. If [code]false[/code] and [member toggle_mode] is [code]false[/code], the shortcut will activate the button without appearing to press the button. + </member> <member name="shortcut_in_tooltip" type="bool" setter="set_shortcut_in_tooltip" getter="is_shortcut_in_tooltip_enabled"> If [code]true[/code], the button will add information about its shortcut in the tooltip. </member> diff --git a/doc/classes/CanvasItem.xml b/doc/classes/CanvasItem.xml index 34837478f4..ee09b016ac 100644 --- a/doc/classes/CanvasItem.xml +++ b/doc/classes/CanvasItem.xml @@ -456,6 +456,13 @@ Returns this item's transform in relation to the viewport. </description> </method> + <method name="get_visibility_layer_bit" qualifiers="const"> + <return type="bool" /> + <param index="0" name="layer" type="int" /> + <description> + Returns an individual bit on the rendering visibility layer. + </description> + </method> <method name="get_world_2d" qualifiers="const"> <return type="World2D" /> <description> @@ -527,6 +534,14 @@ If [param enable] is [code]true[/code], this node will receive [constant NOTIFICATION_TRANSFORM_CHANGED] when its global transform changes. </description> </method> + <method name="set_visibility_layer_bit"> + <return type="void" /> + <param index="0" name="layer" type="int" /> + <param index="1" name="enabled" type="bool" /> + <description> + Set/clear individual bits on the rendering visibility layer. This simplifies editing this [CanvasItem]'s visibility layer. + </description> + </method> <method name="show"> <return type="void" /> <description> @@ -565,6 +580,9 @@ <member name="use_parent_material" type="bool" setter="set_use_parent_material" getter="get_use_parent_material" default="false"> If [code]true[/code], the parent [CanvasItem]'s [member material] property is used as this one's material. </member> + <member name="visibility_layer" type="int" setter="set_visibility_layer" getter="get_visibility_layer" default="1"> + The rendering layer in which this [CanvasItem] is rendered by [Viewport] nodes. A [Viewport] will render a [CanvasItem] if it and all its parents share a layer with the [Viewport]'s canvas cull mask. + </member> <member name="visible" type="bool" setter="set_visible" getter="is_visible" default="true"> If [code]true[/code], this [CanvasItem] is drawn. The node is only visible if all of its antecedents are visible as well (in other words, [method is_visible_in_tree] must return [code]true[/code]). [b]Note:[/b] For controls that inherit [Popup], the correct way to make them visible is to call one of the multiple [code]popup*()[/code] functions instead. diff --git a/doc/classes/Color.xml b/doc/classes/Color.xml index a11d7157f1..a14ca15ef1 100644 --- a/doc/classes/Color.xml +++ b/doc/classes/Color.xml @@ -141,17 +141,6 @@ [/codeblocks] </description> </method> - <method name="find_named_color" qualifiers="static"> - <return type="int" /> - <param index="0" name="name" type="String" /> - <description> - Returns the index of a named color. Use [method get_named_color] to get the actual color. - [codeblock] - var idx = Color.find_named_color("khaki") - modulate = Color.get_named_color(idx) - [/codeblock] - </description> - </method> <method name="from_hsv" qualifiers="static"> <return type="Color" /> <param index="0" name="h" type="float" /> @@ -211,26 +200,6 @@ [b]Note:[/b] [method get_luminance] relies on the colour being in the linear color space to return an accurate relative luminance value. If the color is in the sRGB color space, use [method srgb_to_linear] to convert it to the linear color space first. </description> </method> - <method name="get_named_color" qualifiers="static"> - <return type="Color" /> - <param index="0" name="idx" type="int" /> - <description> - Returns a named color with the given index. You can get the index from [method find_named_color] or iteratively from [method get_named_color_count]. - </description> - </method> - <method name="get_named_color_count" qualifiers="static"> - <return type="int" /> - <description> - Returns the number of available named colors. - </description> - </method> - <method name="get_named_color_name" qualifiers="static"> - <return type="String" /> - <param index="0" name="idx" type="int" /> - <description> - Returns the name of a color with the given index. You can get the index from [method find_named_color] or iteratively from [method get_named_color_count]. - </description> - </method> <method name="hex" qualifiers="static"> <return type="Color" /> <param index="0" name="hex" type="int" /> diff --git a/doc/classes/Curve2D.xml b/doc/classes/Curve2D.xml index cc4124d084..ccdf085319 100644 --- a/doc/classes/Curve2D.xml +++ b/doc/classes/Curve2D.xml @@ -102,6 +102,22 @@ Cubic interpolation tends to follow the curves better, but linear is faster (and often, precise enough). </description> </method> + <method name="sample_baked_with_rotation" qualifiers="const"> + <return type="Transform2D" /> + <param index="0" name="offset" type="float" /> + <param index="1" name="cubic" type="bool" default="false" /> + <param index="2" name="loop" type="bool" default="true" /> + <param index="3" name="lookahead" type="float" default="4.0" /> + <description> + Similar to [method sample_baked], but returns [Transform2D] that includes a rotation along the curve. Returns empty transform if length of the curve is [code]0[/code]. + Use [param loop] to smooth the tangent at the end of the curve. [param lookahead] defines the distance to a nearby point for calculating the tangent vector. + [codeblock] + var transform = curve.sample_baked_with_rotation(offset) + position = transform.get_origin() + rotation = transform.get_rotation() + [/codeblock] + </description> + </method> <method name="samplef" qualifiers="const"> <return type="Vector2" /> <param index="0" name="fofs" type="float" /> diff --git a/doc/classes/MultiplayerPeer.xml b/doc/classes/MultiplayerPeer.xml index ba6c60a088..6661fbfe0b 100644 --- a/doc/classes/MultiplayerPeer.xml +++ b/doc/classes/MultiplayerPeer.xml @@ -13,6 +13,20 @@ <link title="WebRTC Signaling Demo">https://godotengine.org/asset-library/asset/537</link> </tutorials> <methods> + <method name="close"> + <return type="void" /> + <description> + Immediately close the multiplayer peer returning to the state [constant CONNECTION_DISCONNECTED]. Connected peers will be dropped without emitting [signal peer_disconnected]. + </description> + </method> + <method name="disconnect_peer"> + <return type="void" /> + <param index="0" name="peer" type="int" /> + <param index="1" name="force" type="bool" default="false" /> + <description> + Disconnects the given [param peer] from this host. If [param force] is [code]true[/code] the [signal peer_disconnected] signal will not be emitted for this peer. + </description> + </method> <method name="generate_unique_id" qualifiers="const"> <return type="int" /> <description> @@ -79,7 +93,7 @@ [b]Note:[/b] The default channel ([code]0[/code]) actually works as 3 separate channels (one for each [enum TransferMode]) so that [constant TRANSFER_MODE_RELIABLE] and [constant TRANSFER_MODE_UNRELIABLE_ORDERED] does not interact with each other by default. Refer to the specific network API documentation (e.g. ENet or WebRTC) to learn how to set up channels correctly. </member> <member name="transfer_mode" type="int" setter="set_transfer_mode" getter="get_transfer_mode" enum="MultiplayerPeer.TransferMode" default="2"> - The manner in which to send packets to the [code]target_peer[/code]. See [enum TransferMode]. + The manner in which to send packets to the target peer. See [enum TransferMode], and the [method set_target_peer] method. </member> </members> <signals> diff --git a/doc/classes/MultiplayerPeerExtension.xml b/doc/classes/MultiplayerPeerExtension.xml index 18bc18e6e7..5717135b80 100644 --- a/doc/classes/MultiplayerPeerExtension.xml +++ b/doc/classes/MultiplayerPeerExtension.xml @@ -9,6 +9,20 @@ <tutorials> </tutorials> <methods> + <method name="_close" qualifiers="virtual"> + <return type="void" /> + <description> + Called when the multiplayer peer should be immediately closed (see [method MultiplayerPeer.close]). + </description> + </method> + <method name="_disconnect_peer" qualifiers="virtual"> + <return type="void" /> + <param index="0" name="p_peer" type="int" /> + <param index="1" name="p_force" type="bool" /> + <description> + Called when the connected [param p_peer] should be forcibly disconnected (see [method MultiplayerPeer.disconnect_peer]). + </description> + </method> <method name="_get_available_packet_count" qualifiers="virtual const"> <return type="int" /> <description> diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml index 35fe7bdbd2..1c02d58299 100644 --- a/doc/classes/RenderingServer.xml +++ b/doc/classes/RenderingServer.xml @@ -499,6 +499,14 @@ Sets if the [CanvasItem] uses its parent's material. </description> </method> + <method name="canvas_item_set_visibility_layer"> + <return type="void" /> + <param index="0" name="item" type="RID" /> + <param index="1" name="visibility_layer" type="int" /> + <description> + Sets the rendering visibility layer associated with this [CanvasItem]. Only [Viewport] nodes with a matching rendering mask will render this [CanvasItem]. + </description> + </method> <method name="canvas_item_set_visibility_notifier"> <return type="void" /> <param index="0" name="item" type="RID" /> @@ -3143,6 +3151,14 @@ If [code]true[/code], sets the viewport active, else sets it inactive. </description> </method> + <method name="viewport_set_canvas_cull_mask"> + <return type="void" /> + <param index="0" name="viewport" type="RID" /> + <param index="1" name="canvas_cull_mask" type="int" /> + <description> + Sets the rendering mask associated with this [Viewport]. Only [CanvasItem] nodes with a matching rendering visibility layer will be rendered by this [Viewport]. + </description> + </method> <method name="viewport_set_canvas_stacking"> <return type="void" /> <param index="0" name="viewport" type="RID" /> diff --git a/doc/classes/String.xml b/doc/classes/String.xml index 320b9fd737..089f6f65a4 100644 --- a/doc/classes/String.xml +++ b/doc/classes/String.xml @@ -940,12 +940,6 @@ <description> </description> </operator> - <operator name="operator +"> - <return type="String" /> - <param index="0" name="right" type="int" /> - <description> - </description> - </operator> <operator name="operator <"> <return type="bool" /> <param index="0" name="right" type="String" /> diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml index 5706d098e8..78013a8f4b 100644 --- a/doc/classes/Viewport.xml +++ b/doc/classes/Viewport.xml @@ -45,6 +45,13 @@ Returns the currently active 3D camera. </description> </method> + <method name="get_canvas_cull_mask_bit" qualifiers="const"> + <return type="bool" /> + <param index="0" name="layer" type="int" /> + <description> + Returns an individual bit on the rendering layer mask. + </description> + </method> <method name="get_final_transform" qualifiers="const"> <return type="Transform2D" /> <description> @@ -176,6 +183,14 @@ If none of the methods handle the event and [member physics_object_picking] is [code]true[/code], the event is used for physics object picking. </description> </method> + <method name="set_canvas_cull_mask_bit"> + <return type="void" /> + <param index="0" name="layer" type="int" /> + <param index="1" name="enable" type="bool" /> + <description> + Set/clear individual bits on the rendering layer mask. This simplifies editing this [Viewport]'s layers. + </description> + </method> <method name="set_input_as_handled"> <return type="void" /> <description> @@ -205,6 +220,9 @@ <member name="audio_listener_enable_3d" type="bool" setter="set_as_audio_listener_3d" getter="is_audio_listener_3d" default="false"> If [code]true[/code], the viewport will process 3D audio streams. </member> + <member name="canvas_cull_mask" type="int" setter="set_canvas_cull_mask" getter="get_canvas_cull_mask" default="4294967295"> + The rendering layers in which this [Viewport] renders [CanvasItem] nodes. + </member> <member name="canvas_item_default_texture_filter" type="int" setter="set_default_canvas_item_texture_filter" getter="get_default_canvas_item_texture_filter" enum="Viewport.DefaultCanvasItemTextureFilter" default="1"> Sets the default filter mode used by [CanvasItem]s in this Viewport. See [enum DefaultCanvasItemTextureFilter] for options. </member> diff --git a/doc/classes/int.xml b/doc/classes/int.xml index 689cb7fe8e..65ab5d4656 100644 --- a/doc/classes/int.xml +++ b/doc/classes/int.xml @@ -206,13 +206,6 @@ </description> </operator> <operator name="operator +"> - <return type="String" /> - <param index="0" name="right" type="String" /> - <description> - Adds Unicode character with code [int] to the [String]. - </description> - </operator> - <operator name="operator +"> <return type="float" /> <param index="0" name="right" type="float" /> <description> diff --git a/drivers/gles3/effects/copy_effects.cpp b/drivers/gles3/effects/copy_effects.cpp index 92c29a4264..3acbcf6b53 100644 --- a/drivers/gles3/effects/copy_effects.cpp +++ b/drivers/gles3/effects/copy_effects.cpp @@ -117,16 +117,12 @@ CopyEffects::~CopyEffects() { void CopyEffects::copy_to_rect(const Rect2 &p_rect) { copy.shader.version_bind_shader(copy.shader_version, CopyShaderGLES3::MODE_COPY_SECTION); copy.shader.version_set_uniform(CopyShaderGLES3::COPY_SECTION, p_rect.position.x, p_rect.position.y, p_rect.size.x, p_rect.size.y, copy.shader_version, CopyShaderGLES3::MODE_COPY_SECTION); - glBindVertexArray(quad_array); - glDrawArrays(GL_TRIANGLES, 0, 6); - glBindVertexArray(0); + draw_screen_quad(); } void CopyEffects::copy_screen() { copy.shader.version_bind_shader(copy.shader_version, CopyShaderGLES3::MODE_DEFAULT); - glBindVertexArray(screen_triangle_array); - glDrawArrays(GL_TRIANGLES, 0, 3); - glBindVertexArray(0); + draw_screen_triangle(); } void CopyEffects::bilinear_blur(GLuint p_source_texture, int p_mipmap_count, const Rect2i &p_region) { @@ -158,8 +154,19 @@ void CopyEffects::set_color(const Color &p_color, const Rect2i &p_region) { copy.shader.version_bind_shader(copy.shader_version, CopyShaderGLES3::MODE_SIMPLE_COLOR); copy.shader.version_set_uniform(CopyShaderGLES3::COPY_SECTION, p_region.position.x, p_region.position.y, p_region.size.x, p_region.size.y, copy.shader_version, CopyShaderGLES3::MODE_SIMPLE_COLOR); copy.shader.version_set_uniform(CopyShaderGLES3::COLOR_IN, p_color, copy.shader_version, CopyShaderGLES3::MODE_SIMPLE_COLOR); + draw_screen_quad(); +} + +void CopyEffects::draw_screen_triangle() { + glBindVertexArray(screen_triangle_array); + glDrawArrays(GL_TRIANGLES, 0, 3); + glBindVertexArray(0); +} + +void CopyEffects::draw_screen_quad() { glBindVertexArray(quad_array); glDrawArrays(GL_TRIANGLES, 0, 6); glBindVertexArray(0); } + #endif // GLES3_ENABLED diff --git a/drivers/gles3/effects/copy_effects.h b/drivers/gles3/effects/copy_effects.h index 7f16b4e0f3..a817c3f0dc 100644 --- a/drivers/gles3/effects/copy_effects.h +++ b/drivers/gles3/effects/copy_effects.h @@ -65,6 +65,8 @@ public: void copy_screen(); void bilinear_blur(GLuint p_source_texture, int p_mipmap_count, const Rect2i &p_region); void set_color(const Color &p_color, const Rect2i &p_region); + void draw_screen_triangle(); + void draw_screen_quad(); }; } //namespace GLES3 diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp index 5d0edda6c5..29252c8677 100644 --- a/drivers/gles3/rasterizer_canvas_gles3.cpp +++ b/drivers/gles3/rasterizer_canvas_gles3.cpp @@ -36,26 +36,13 @@ #include "rasterizer_scene_gles3.h" #include "core/config/project_settings.h" +#include "core/math/geometry_2d.h" #include "servers/rendering/rendering_server_default.h" #include "storage/config.h" #include "storage/material_storage.h" #include "storage/mesh_storage.h" #include "storage/texture_storage.h" -#ifndef GLES_OVER_GL -#define glClearDepth glClearDepthf -#endif - -//static const GLenum gl_primitive[] = { -// GL_POINTS, -// GL_LINES, -// GL_LINE_STRIP, -// GL_LINE_LOOP, -// GL_TRIANGLES, -// GL_TRIANGLE_STRIP, -// GL_TRIANGLE_FAN -//}; - void RasterizerCanvasGLES3::_update_transform_2d_to_mat4(const Transform2D &p_transform, float *p_mat4) { p_mat4[0] = p_transform.columns[0][0]; p_mat4[1] = p_transform.columns[0][1]; @@ -174,7 +161,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_ state.light_uniforms[index].position[0] = -canvas_light_dir.x; state.light_uniforms[index].position[1] = -canvas_light_dir.y; - //_update_transform_2d_to_mat2x4(clight->shadow.directional_xform, state.light_uniforms[index].shadow_matrix); + _update_transform_2d_to_mat2x4(clight->shadow.directional_xform, state.light_uniforms[index].shadow_matrix); state.light_uniforms[index].height = l->height; //0..1 here @@ -185,8 +172,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_ state.light_uniforms[index].color[3] = l->energy; //use alpha for energy, so base color can go separate - /* - if (state.shadow_fb.is_valid()) { + if (state.shadow_fb != 0) { state.light_uniforms[index].shadow_pixel_size = (1.0 / state.shadow_texture_size) * (1.0 + l->shadow_smooth); state.light_uniforms[index].shadow_z_far_inv = 1.0 / clight->shadow.z_far; state.light_uniforms[index].shadow_y_ofs = clight->shadow.y_offset; @@ -195,15 +181,13 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_ state.light_uniforms[index].shadow_z_far_inv = 1.0; state.light_uniforms[index].shadow_y_ofs = 0; } - */ state.light_uniforms[index].flags = l->blend_mode << LIGHT_FLAGS_BLEND_SHIFT; state.light_uniforms[index].flags |= l->shadow_filter << LIGHT_FLAGS_FILTER_SHIFT; - /* + if (clight->shadow.enabled) { state.light_uniforms[index].flags |= LIGHT_FLAGS_HAS_SHADOW; } - */ l->render_index_cache = index; @@ -252,24 +236,22 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_ state.light_uniforms[index].color[3] = l->energy; //use alpha for energy, so base color can go separate - /* - if (state.shadow_fb.is_valid()) { - state.light_uniforms[index].shadow_pixel_size = (1.0 / state.shadow_texture_size) * (1.0 + l->shadow_smooth); - state.light_uniforms[index].shadow_z_far_inv = 1.0 / clight->shadow.z_far; - state.light_uniforms[index].shadow_y_ofs = clight->shadow.y_offset; - } else { - state.light_uniforms[index].shadow_pixel_size = 1.0; - state.light_uniforms[index].shadow_z_far_inv = 1.0; - state.light_uniforms[index].shadow_y_ofs = 0; - } - */ + if (state.shadow_fb != 0) { + state.light_uniforms[index].shadow_pixel_size = (1.0 / state.shadow_texture_size) * (1.0 + l->shadow_smooth); + state.light_uniforms[index].shadow_z_far_inv = 1.0 / clight->shadow.z_far; + state.light_uniforms[index].shadow_y_ofs = clight->shadow.y_offset; + } else { + state.light_uniforms[index].shadow_pixel_size = 1.0; + state.light_uniforms[index].shadow_z_far_inv = 1.0; + state.light_uniforms[index].shadow_y_ofs = 0; + } + state.light_uniforms[index].flags = l->blend_mode << LIGHT_FLAGS_BLEND_SHIFT; state.light_uniforms[index].flags |= l->shadow_filter << LIGHT_FLAGS_FILTER_SHIFT; - /* + if (clight->shadow.enabled) { state.light_uniforms[index].flags |= LIGHT_FLAGS_HAS_SHADOW; } - */ if (clight->texture.is_valid()) { Rect2 atlas_rect = GLES3::TextureStorage::get_singleton()->texture_atlas_get_texture_rect(clight->texture); @@ -313,6 +295,13 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_ } glActiveTexture(GL_TEXTURE0 + GLES3::Config::get_singleton()->max_texture_image_units - 2); glBindTexture(GL_TEXTURE_2D, texture_atlas); + GLuint shadow_tex = state.shadow_texture; + if (shadow_tex == 0) { + GLES3::Texture *tex = texture_storage->get_texture(texture_storage->texture_gl_get_default(GLES3::DEFAULT_GL_TEXTURE_WHITE)); + shadow_tex = tex->tex_id; + } + glActiveTexture(GL_TEXTURE0 + GLES3::Config::get_singleton()->max_texture_image_units - 3); + glBindTexture(GL_TEXTURE_2D, shadow_tex); } { @@ -342,8 +331,6 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_ state_buffer.screen_pixel_size[0] = 1.0 / render_target_size.x; state_buffer.screen_pixel_size[1] = 1.0 / render_target_size.y; - glViewport(0, 0, render_target_size.x, render_target_size.y); - state_buffer.time = state.time; state_buffer.use_pixel_snap = p_snap_2d_vertices_to_pixel; @@ -366,6 +353,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_ state_buffer.sdf_to_tex[3] = -sdf_tex_rect.position.y / sdf_tex_rect.size.height; state_buffer.tex_to_sdf = 1.0 / ((canvas_scale.x + canvas_scale.y) * 0.5); + glBindBufferBase(GL_UNIFORM_BUFFER, BASE_UNIFORM_LOCATION, state.canvas_instance_data_buffers[state.current_buffer].state_ubo); glBufferData(GL_UNIFORM_BUFFER, sizeof(StateBuffer), &state_buffer, GL_STREAM_DRAW); @@ -375,11 +363,17 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_ glBindBuffer(GL_UNIFORM_BUFFER, 0); } + glActiveTexture(GL_TEXTURE0 + GLES3::Config::get_singleton()->max_texture_image_units - 5); + glBindTexture(GL_TEXTURE_2D, texture_storage->render_target_get_sdf_texture(p_to_render_target)); + { state.default_filter = p_default_filter; state.default_repeat = p_default_repeat; } + Size2 render_target_size = texture_storage->render_target_get_size(p_to_render_target); + glViewport(0, 0, render_target_size.x, render_target_size.y); + r_sdf_used = false; int item_count = 0; bool backbuffer_cleared = false; @@ -587,6 +581,13 @@ void RasterizerCanvasGLES3::_render_items(RID p_to_render_target, int p_item_cou _record_item_commands(ci, p_canvas_transform_inverse, current_clip, blend_mode, p_lights, index, batch_broken); } + if (r_last_index >= index) { + // Nothing to render, just return. + state.current_batch_index = 0; + state.canvas_instance_batches.clear(); + return; + } + // Copy over all data needed for rendering. glBindBuffer(GL_UNIFORM_BUFFER, state.canvas_instance_data_buffers[state.current_buffer].ubo); #ifdef WEB_ENABLED @@ -621,6 +622,7 @@ void RasterizerCanvasGLES3::_render_items(RID p_to_render_target, int p_item_cou CanvasShaderGLES3::ShaderVariant variant = state.canvas_instance_batches[i].shader_variant; uint64_t specialization = 0; specialization |= uint64_t(state.canvas_instance_batches[i].lights_disabled); + specialization |= uint64_t(!GLES3::Config::get_singleton()->float_texture_supported) << 1; _bind_material(material_data, variant, specialization); GLES3::CanvasShaderData::BlendMode blend_mode = state.canvas_instance_batches[i].blend_mode; @@ -1395,28 +1397,516 @@ void RasterizerCanvasGLES3::light_set_texture(RID p_rid, RID p_texture) { } void RasterizerCanvasGLES3::light_set_use_shadow(RID p_rid, bool p_enable) { + CanvasLight *cl = canvas_light_owner.get_or_null(p_rid); + ERR_FAIL_COND(!cl); + + cl->shadow.enabled = p_enable; } void RasterizerCanvasGLES3::light_update_shadow(RID p_rid, int p_shadow_index, const Transform2D &p_light_xform, int p_light_mask, float p_near, float p_far, LightOccluderInstance *p_occluders) { + GLES3::Config *config = GLES3::Config::get_singleton(); + + CanvasLight *cl = canvas_light_owner.get_or_null(p_rid); + ERR_FAIL_COND(!cl->shadow.enabled); + + _update_shadow_atlas(); + + cl->shadow.z_far = p_far; + cl->shadow.y_offset = float(p_shadow_index * 2 + 1) / float(data.max_lights_per_render * 2); + + glBindFramebuffer(GL_FRAMEBUFFER, state.shadow_fb); + glViewport(0, p_shadow_index * 2, state.shadow_texture_size, 2); + + glDepthMask(GL_TRUE); + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + glDisable(GL_BLEND); + + glEnable(GL_SCISSOR_TEST); + glScissor(0, p_shadow_index * 2, state.shadow_texture_size, 2); + glClearColor(p_far, p_far, p_far, 1.0); + glClearDepth(1.0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glCullFace(GL_BACK); + glDisable(GL_CULL_FACE); + RS::CanvasOccluderPolygonCullMode cull_mode = RS::CANVAS_OCCLUDER_POLYGON_CULL_DISABLED; + + CanvasOcclusionShaderGLES3::ShaderVariant variant = config->float_texture_supported ? CanvasOcclusionShaderGLES3::MODE_SHADOW : CanvasOcclusionShaderGLES3::MODE_SHADOW_RGBA; + shadow_render.shader.version_bind_shader(shadow_render.shader_version, variant); + + for (int i = 0; i < 4; i++) { + glViewport((state.shadow_texture_size / 4) * i, p_shadow_index * 2, (state.shadow_texture_size / 4), 2); + + Projection projection; + { + real_t fov = 90; + real_t nearp = p_near; + real_t farp = p_far; + real_t aspect = 1.0; + + real_t ymax = nearp * Math::tan(Math::deg_to_rad(fov * 0.5)); + real_t ymin = -ymax; + real_t xmin = ymin * aspect; + real_t xmax = ymax * aspect; + + projection.set_frustum(xmin, xmax, ymin, ymax, nearp, farp); + } + + Vector3 cam_target = Basis::from_euler(Vector3(0, 0, Math_TAU * ((i + 3) / 4.0))).xform(Vector3(0, 1, 0)); + + projection = projection * Projection(Transform3D().looking_at(cam_target, Vector3(0, 0, -1)).affine_inverse()); + shadow_render.shader.version_set_uniform(CanvasOcclusionShaderGLES3::PROJECTION, projection, shadow_render.shader_version, variant); + + static const Vector2 directions[4] = { Vector2(1, 0), Vector2(0, 1), Vector2(-1, 0), Vector2(0, -1) }; + shadow_render.shader.version_set_uniform(CanvasOcclusionShaderGLES3::DIRECTION, directions[i].x, directions[i].y, shadow_render.shader_version, variant); + shadow_render.shader.version_set_uniform(CanvasOcclusionShaderGLES3::Z_FAR, p_far, shadow_render.shader_version, variant); + + LightOccluderInstance *instance = p_occluders; + + while (instance) { + OccluderPolygon *co = occluder_polygon_owner.get_or_null(instance->occluder); + + if (!co || co->vertex_array == 0 || !(p_light_mask & instance->light_mask)) { + instance = instance->next; + continue; + } + + Transform2D modelview = p_light_xform * instance->xform_cache; + shadow_render.shader.version_set_uniform(CanvasOcclusionShaderGLES3::MODELVIEW1, modelview.columns[0][0], modelview.columns[1][0], 0, modelview.columns[2][0], shadow_render.shader_version, variant); + shadow_render.shader.version_set_uniform(CanvasOcclusionShaderGLES3::MODELVIEW2, modelview.columns[0][1], modelview.columns[1][1], 0, modelview.columns[2][1], shadow_render.shader_version, variant); + + if (co->cull_mode != cull_mode) { + if (co->cull_mode == RS::CANVAS_OCCLUDER_POLYGON_CULL_DISABLED) { + glDisable(GL_CULL_FACE); + } else { + if (cull_mode == RS::CANVAS_OCCLUDER_POLYGON_CULL_DISABLED) { + // Last time was disabled, so enable and set proper face. + glEnable(GL_CULL_FACE); + } + glCullFace(co->cull_mode == RS::CANVAS_OCCLUDER_POLYGON_CULL_CLOCKWISE ? GL_FRONT : GL_BACK); + } + cull_mode = co->cull_mode; + } + + glBindVertexArray(co->vertex_array); + glDrawElements(GL_TRIANGLES, 3 * co->line_point_count, GL_UNSIGNED_SHORT, 0); + + instance = instance->next; + } + } + + glBindVertexArray(0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDepthMask(GL_FALSE); + glDisable(GL_DEPTH_TEST); + glDisable(GL_SCISSOR_TEST); } void RasterizerCanvasGLES3::light_update_directional_shadow(RID p_rid, int p_shadow_index, const Transform2D &p_light_xform, int p_light_mask, float p_cull_distance, const Rect2 &p_clip_rect, LightOccluderInstance *p_occluders) { + GLES3::Config *config = GLES3::Config::get_singleton(); + + CanvasLight *cl = canvas_light_owner.get_or_null(p_rid); + ERR_FAIL_COND(!cl->shadow.enabled); + + _update_shadow_atlas(); + + Vector2 light_dir = p_light_xform.columns[1].normalized(); + + Vector2 center = p_clip_rect.get_center(); + + float to_edge_distance = ABS(light_dir.dot(p_clip_rect.get_support(light_dir)) - light_dir.dot(center)); + + Vector2 from_pos = center - light_dir * (to_edge_distance + p_cull_distance); + float distance = to_edge_distance * 2.0 + p_cull_distance; + float half_size = p_clip_rect.size.length() * 0.5; //shadow length, must keep this no matter the angle + + cl->shadow.z_far = distance; + cl->shadow.y_offset = float(p_shadow_index * 2 + 1) / float(data.max_lights_per_render * 2); + + Transform2D to_light_xform; + + to_light_xform[2] = from_pos; + to_light_xform[1] = light_dir; + to_light_xform[0] = -light_dir.orthogonal(); + + to_light_xform.invert(); + + glBindFramebuffer(GL_FRAMEBUFFER, state.shadow_fb); + glViewport(0, p_shadow_index * 2, state.shadow_texture_size, 2); + + glDepthMask(GL_TRUE); + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + glDisable(GL_BLEND); + + glEnable(GL_SCISSOR_TEST); + glScissor(0, p_shadow_index * 2, state.shadow_texture_size, 2); + glClearColor(1.0, 1.0, 1.0, 1.0); + glClearDepth(1.0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glCullFace(GL_BACK); + glDisable(GL_CULL_FACE); + RS::CanvasOccluderPolygonCullMode cull_mode = RS::CANVAS_OCCLUDER_POLYGON_CULL_DISABLED; + + CanvasOcclusionShaderGLES3::ShaderVariant variant = config->float_texture_supported ? CanvasOcclusionShaderGLES3::MODE_SHADOW : CanvasOcclusionShaderGLES3::MODE_SHADOW_RGBA; + shadow_render.shader.version_bind_shader(shadow_render.shader_version, variant); + + Projection projection; + projection.set_orthogonal(-half_size, half_size, -0.5, 0.5, 0.0, distance); + projection = projection * Projection(Transform3D().looking_at(Vector3(0, 1, 0), Vector3(0, 0, -1)).affine_inverse()); + + shadow_render.shader.version_set_uniform(CanvasOcclusionShaderGLES3::PROJECTION, projection, shadow_render.shader_version, variant); + shadow_render.shader.version_set_uniform(CanvasOcclusionShaderGLES3::DIRECTION, 0.0, 1.0, shadow_render.shader_version, variant); + shadow_render.shader.version_set_uniform(CanvasOcclusionShaderGLES3::Z_FAR, distance, shadow_render.shader_version, variant); + + LightOccluderInstance *instance = p_occluders; + + while (instance) { + OccluderPolygon *co = occluder_polygon_owner.get_or_null(instance->occluder); + + if (!co || co->vertex_array == 0 || !(p_light_mask & instance->light_mask)) { + instance = instance->next; + continue; + } + + Transform2D modelview = to_light_xform * instance->xform_cache; + shadow_render.shader.version_set_uniform(CanvasOcclusionShaderGLES3::MODELVIEW1, modelview.columns[0][0], modelview.columns[1][0], 0, modelview.columns[2][0], shadow_render.shader_version, variant); + shadow_render.shader.version_set_uniform(CanvasOcclusionShaderGLES3::MODELVIEW2, modelview.columns[0][1], modelview.columns[1][1], 0, modelview.columns[2][1], shadow_render.shader_version, variant); + + if (co->cull_mode != cull_mode) { + if (co->cull_mode == RS::CANVAS_OCCLUDER_POLYGON_CULL_DISABLED) { + glDisable(GL_CULL_FACE); + } else { + if (cull_mode == RS::CANVAS_OCCLUDER_POLYGON_CULL_DISABLED) { + // Last time was disabled, so enable and set proper face. + glEnable(GL_CULL_FACE); + } + glCullFace(co->cull_mode == RS::CANVAS_OCCLUDER_POLYGON_CULL_CLOCKWISE ? GL_FRONT : GL_BACK); + } + cull_mode = co->cull_mode; + } + + glBindVertexArray(co->vertex_array); + glDrawElements(GL_TRIANGLES, 3 * co->line_point_count, GL_UNSIGNED_SHORT, 0); + + instance = instance->next; + } + + Transform2D to_shadow; + to_shadow.columns[0].x = 1.0 / -(half_size * 2.0); + to_shadow.columns[2].x = 0.5; + + cl->shadow.directional_xform = to_shadow * to_light_xform; + + glBindVertexArray(0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDepthMask(GL_FALSE); + glDisable(GL_DEPTH_TEST); + glDisable(GL_SCISSOR_TEST); + glDisable(GL_CULL_FACE); +} + +void RasterizerCanvasGLES3::_update_shadow_atlas() { + GLES3::Config *config = GLES3::Config::get_singleton(); + + if (state.shadow_fb == 0) { + glActiveTexture(GL_TEXTURE0); + + glGenFramebuffers(1, &state.shadow_fb); + glBindFramebuffer(GL_FRAMEBUFFER, state.shadow_fb); + + glGenRenderbuffers(1, &state.shadow_depth_buffer); + glBindRenderbuffer(GL_RENDERBUFFER, state.shadow_depth_buffer); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, state.shadow_texture_size, data.max_lights_per_render * 2); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, state.shadow_depth_buffer); + + glGenTextures(1, &state.shadow_texture); + glBindTexture(GL_TEXTURE_2D, state.shadow_texture); + if (config->float_texture_supported) { + glTexImage2D(GL_TEXTURE_2D, 0, GL_R32F, state.shadow_texture_size, data.max_lights_per_render * 2, 0, GL_RED, GL_FLOAT, nullptr); + } else { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, state.shadow_texture_size, data.max_lights_per_render * 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + } + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, state.shadow_texture, 0); + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + glDeleteFramebuffers(1, &state.shadow_fb); + glDeleteTextures(1, &state.shadow_texture); + glDeleteRenderbuffers(1, &state.shadow_depth_buffer); + state.shadow_fb = 0; + state.shadow_texture = 0; + state.shadow_depth_buffer = 0; + WARN_PRINT("Could not create CanvasItem shadow atlas, status: " + GLES3::TextureStorage::get_singleton()->get_framebuffer_error(status)); + } + glBindFramebuffer(GL_FRAMEBUFFER, GLES3::TextureStorage::system_fbo); + } } void RasterizerCanvasGLES3::render_sdf(RID p_render_target, LightOccluderInstance *p_occluders) { + GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton(); + + GLuint fb = texture_storage->render_target_get_sdf_framebuffer(p_render_target); + Rect2i rect = texture_storage->render_target_get_sdf_rect(p_render_target); + + Transform2D to_sdf; + to_sdf.columns[0] *= rect.size.width; + to_sdf.columns[1] *= rect.size.height; + to_sdf.columns[2] = rect.position; + + Transform2D to_clip; + to_clip.columns[0] *= 2.0; + to_clip.columns[1] *= 2.0; + to_clip.columns[2] = -Vector2(1.0, 1.0); + + to_clip = to_clip * to_sdf.affine_inverse(); + + glBindFramebuffer(GL_FRAMEBUFFER, fb); + glViewport(0, 0, rect.size.width, rect.size.height); + + glDepthMask(GL_FALSE); + glDisable(GL_DEPTH_TEST); + glDisable(GL_BLEND); + glDisable(GL_CULL_FACE); + glDisable(GL_SCISSOR_TEST); + + glClearColor(0.0, 0.0, 0.0, 0.0); + glClear(GL_COLOR_BUFFER_BIT); + + CanvasOcclusionShaderGLES3::ShaderVariant variant = CanvasOcclusionShaderGLES3::MODE_SDF; + shadow_render.shader.version_bind_shader(shadow_render.shader_version, variant); + + shadow_render.shader.version_set_uniform(CanvasOcclusionShaderGLES3::PROJECTION, Projection(), shadow_render.shader_version, variant); + shadow_render.shader.version_set_uniform(CanvasOcclusionShaderGLES3::DIRECTION, 0.0, 0.0, shadow_render.shader_version, variant); + shadow_render.shader.version_set_uniform(CanvasOcclusionShaderGLES3::Z_FAR, 0.0, shadow_render.shader_version, variant); + + LightOccluderInstance *instance = p_occluders; + + while (instance) { + OccluderPolygon *oc = occluder_polygon_owner.get_or_null(instance->occluder); + + if (!oc || oc->sdf_vertex_array == 0) { + instance = instance->next; + continue; + } + + Transform2D modelview = to_clip * instance->xform_cache; + shadow_render.shader.version_set_uniform(CanvasOcclusionShaderGLES3::MODELVIEW1, modelview.columns[0][0], modelview.columns[1][0], 0, modelview.columns[2][0], shadow_render.shader_version, variant); + shadow_render.shader.version_set_uniform(CanvasOcclusionShaderGLES3::MODELVIEW2, modelview.columns[0][1], modelview.columns[1][1], 0, modelview.columns[2][1], shadow_render.shader_version, variant); + + glBindVertexArray(oc->sdf_vertex_array); + glDrawElements(oc->sdf_is_lines ? GL_LINES : GL_TRIANGLES, oc->sdf_index_count, GL_UNSIGNED_INT, 0); + + instance = instance->next; + } + + texture_storage->render_target_sdf_process(p_render_target); //done rendering, process it + glBindVertexArray(0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); } RID RasterizerCanvasGLES3::occluder_polygon_create() { - return RID(); + OccluderPolygon occluder; + + return occluder_polygon_owner.make_rid(occluder); } void RasterizerCanvasGLES3::occluder_polygon_set_shape(RID p_occluder, const Vector<Vector2> &p_points, bool p_closed) { + OccluderPolygon *oc = occluder_polygon_owner.get_or_null(p_occluder); + ERR_FAIL_COND(!oc); + + Vector<Vector2> lines; + + if (p_points.size()) { + int lc = p_points.size() * 2; + + lines.resize(lc - (p_closed ? 0 : 2)); + { + Vector2 *w = lines.ptrw(); + const Vector2 *r = p_points.ptr(); + + int max = lc / 2; + if (!p_closed) { + max--; + } + for (int i = 0; i < max; i++) { + Vector2 a = r[i]; + Vector2 b = r[(i + 1) % (lc / 2)]; + w[i * 2 + 0] = a; + w[i * 2 + 1] = b; + } + } + } + + if (oc->line_point_count != lines.size() && oc->vertex_array != 0) { + glDeleteVertexArrays(1, &oc->vertex_array); + glDeleteBuffers(1, &oc->vertex_buffer); + glDeleteBuffers(1, &oc->index_buffer); + + oc->vertex_array = 0; + oc->vertex_buffer = 0; + oc->index_buffer = 0; + } + + if (lines.size()) { + Vector<uint8_t> geometry; + Vector<uint8_t> indices; + int lc = lines.size(); + + geometry.resize(lc * 6 * sizeof(float)); + indices.resize(lc * 3 * sizeof(uint16_t)); + + { + uint8_t *vw = geometry.ptrw(); + float *vwptr = reinterpret_cast<float *>(vw); + uint8_t *iw = indices.ptrw(); + uint16_t *iwptr = (uint16_t *)iw; + + const Vector2 *lr = lines.ptr(); + + const int POLY_HEIGHT = 16384; + + for (int i = 0; i < lc / 2; i++) { + vwptr[i * 12 + 0] = lr[i * 2 + 0].x; + vwptr[i * 12 + 1] = lr[i * 2 + 0].y; + vwptr[i * 12 + 2] = POLY_HEIGHT; + + vwptr[i * 12 + 3] = lr[i * 2 + 1].x; + vwptr[i * 12 + 4] = lr[i * 2 + 1].y; + vwptr[i * 12 + 5] = POLY_HEIGHT; + + vwptr[i * 12 + 6] = lr[i * 2 + 1].x; + vwptr[i * 12 + 7] = lr[i * 2 + 1].y; + vwptr[i * 12 + 8] = -POLY_HEIGHT; + + vwptr[i * 12 + 9] = lr[i * 2 + 0].x; + vwptr[i * 12 + 10] = lr[i * 2 + 0].y; + vwptr[i * 12 + 11] = -POLY_HEIGHT; + + iwptr[i * 6 + 0] = i * 4 + 0; + iwptr[i * 6 + 1] = i * 4 + 1; + iwptr[i * 6 + 2] = i * 4 + 2; + + iwptr[i * 6 + 3] = i * 4 + 2; + iwptr[i * 6 + 4] = i * 4 + 3; + iwptr[i * 6 + 5] = i * 4 + 0; + } + } + + if (oc->vertex_array == 0) { + oc->line_point_count = lc; + glGenVertexArrays(1, &oc->vertex_array); + glBindVertexArray(oc->vertex_array); + glGenBuffers(1, &oc->vertex_buffer); + glBindBuffer(GL_ARRAY_BUFFER, oc->vertex_buffer); + + glBufferData(GL_ARRAY_BUFFER, lc * 6 * sizeof(float), geometry.ptr(), GL_STATIC_DRAW); + glEnableVertexAttribArray(RS::ARRAY_VERTEX); + glVertexAttribPointer(RS::ARRAY_VERTEX, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), nullptr); + + glGenBuffers(1, &oc->index_buffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, oc->index_buffer); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, 3 * lc * sizeof(uint16_t), indices.ptr(), GL_STATIC_DRAW); + glBindVertexArray(0); + } else { + glBindVertexArray(oc->vertex_array); + glBindBuffer(GL_ARRAY_BUFFER, oc->vertex_buffer); + glBufferData(GL_ARRAY_BUFFER, lc * 6 * sizeof(float), geometry.ptr(), GL_STATIC_DRAW); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, oc->index_buffer); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, 3 * lc * sizeof(uint16_t), indices.ptr(), GL_STATIC_DRAW); + } + } + + // sdf + + Vector<int> sdf_indices; + + if (p_points.size()) { + if (p_closed) { + sdf_indices = Geometry2D::triangulate_polygon(p_points); + oc->sdf_is_lines = false; + } else { + int max = p_points.size(); + sdf_indices.resize(max * 2); + + int *iw = sdf_indices.ptrw(); + for (int i = 0; i < max; i++) { + iw[i * 2 + 0] = i; + iw[i * 2 + 1] = (i + 1) % max; + } + oc->sdf_is_lines = true; + } + } + + if (oc->sdf_index_count != sdf_indices.size() && oc->sdf_point_count != p_points.size() && oc->sdf_vertex_array != 0) { + glDeleteVertexArrays(1, &oc->sdf_vertex_array); + glDeleteBuffers(1, &oc->sdf_vertex_buffer); + glDeleteBuffers(1, &oc->sdf_index_buffer); + + oc->sdf_vertex_array = 0; + oc->sdf_vertex_buffer = 0; + oc->sdf_index_buffer = 0; + + oc->sdf_index_count = sdf_indices.size(); + oc->sdf_point_count = p_points.size(); + } + + if (sdf_indices.size()) { + if (oc->sdf_vertex_array == 0) { + oc->sdf_index_count = sdf_indices.size(); + oc->sdf_point_count = p_points.size(); + glGenVertexArrays(1, &oc->sdf_vertex_array); + glBindVertexArray(oc->sdf_vertex_array); + glGenBuffers(1, &oc->sdf_vertex_buffer); + glBindBuffer(GL_ARRAY_BUFFER, oc->sdf_vertex_buffer); + + glBufferData(GL_ARRAY_BUFFER, p_points.size() * 2 * sizeof(float), p_points.to_byte_array().ptr(), GL_STATIC_DRAW); + glEnableVertexAttribArray(RS::ARRAY_VERTEX); + glVertexAttribPointer(RS::ARRAY_VERTEX, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), nullptr); + + glGenBuffers(1, &oc->sdf_index_buffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, oc->sdf_index_buffer); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sdf_indices.size() * sizeof(uint32_t), sdf_indices.to_byte_array().ptr(), GL_STATIC_DRAW); + glBindVertexArray(0); + } else { + glBindBuffer(GL_ARRAY_BUFFER, oc->sdf_vertex_buffer); + glBufferData(GL_ARRAY_BUFFER, p_points.size() * 2 * sizeof(float), p_points.to_byte_array().ptr(), GL_STATIC_DRAW); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, oc->sdf_index_buffer); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sdf_indices.size() * sizeof(uint32_t), sdf_indices.to_byte_array().ptr(), GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + } + } } void RasterizerCanvasGLES3::occluder_polygon_set_cull_mode(RID p_occluder, RS::CanvasOccluderPolygonCullMode p_mode) { + OccluderPolygon *oc = occluder_polygon_owner.get_or_null(p_occluder); + ERR_FAIL_COND(!oc); + oc->cull_mode = p_mode; } void RasterizerCanvasGLES3::set_shadow_texture_size(int p_size) { + GLES3::Config *config = GLES3::Config::get_singleton(); + p_size = nearest_power_of_2_templated(p_size); + if (p_size == state.shadow_texture_size) { + return; + } + + if (p_size > config->max_texture_size) { + p_size = config->max_texture_size; + WARN_PRINT("Attempting to set CanvasItem shadow atlas size to " + itos(p_size) + " which is beyond limit of " + itos(config->max_texture_size) + "supported by hardware."); + } + + state.shadow_texture_size = p_size; } bool RasterizerCanvasGLES3::free(RID p_rid) { @@ -1424,6 +1914,9 @@ bool RasterizerCanvasGLES3::free(RID p_rid) { CanvasLight *cl = canvas_light_owner.get_or_null(p_rid); ERR_FAIL_COND_V(!cl, false); canvas_light_owner.free(p_rid); + } else if (occluder_polygon_owner.owns(p_rid)) { + occluder_polygon_set_shape(p_rid, Vector<Vector2>(), false); + occluder_polygon_owner.free(p_rid); } else { return false; } @@ -1623,16 +2116,16 @@ void RasterizerCanvasGLES3::reset_canvas() { glEnable(GL_BLEND); glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); + glActiveTexture(GL_TEXTURE0 + GLES3::Config::get_singleton()->max_texture_image_units - 2); + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE0 + GLES3::Config::get_singleton()->max_texture_image_units - 3); + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE0); + glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); } -void RasterizerCanvasGLES3::canvas_debug_viewport_shadows(Light *p_lights_with_shadow) { -} - -void RasterizerCanvasGLES3::canvas_light_shadow_buffer_update(RID p_buffer, const Transform2D &p_light_xform, int p_light_mask, float p_near, float p_far, LightOccluderInstance *p_occluders, Projection *p_xform_cache) { -} - void RasterizerCanvasGLES3::draw_lens_distortion_rect(const Rect2 &p_rect, float p_k1, float p_k2, const Vector2 &p_eye_center, float p_oversample) { } @@ -1663,7 +2156,6 @@ RendererCanvasRender::PolygonID RasterizerCanvasGLES3::request_polygon(const Vec polygon_buffer.resize(buffer_size * sizeof(float)); { glBindBuffer(GL_ARRAY_BUFFER, pb.vertex_buffer); - glBufferData(GL_ARRAY_BUFFER, stride * vertex_count * sizeof(float), nullptr, GL_STATIC_DRAW); // TODO may not be necessary uint8_t *r = polygon_buffer.ptrw(); float *fptr = reinterpret_cast<float *>(r); uint32_t *uptr = (uint32_t *)r; @@ -1772,7 +2264,6 @@ RendererCanvasRender::PolygonID RasterizerCanvasGLES3::request_polygon(const Vec } glGenBuffers(1, &pb.index_buffer); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, pb.index_buffer); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, p_indices.size() * 4, nullptr, GL_STATIC_DRAW); // TODO may not be necessary glBufferData(GL_ELEMENT_ARRAY_BUFFER, p_indices.size() * 4, index_buffer.ptr(), GL_STATIC_DRAW); pb.count = p_indices.size(); } @@ -2064,6 +2555,9 @@ RasterizerCanvasGLES3::RasterizerCanvasGLES3() { data.canvas_shader_default_version = GLES3::MaterialStorage::get_singleton()->shaders.canvas_shader.version_create(); GLES3::MaterialStorage::get_singleton()->shaders.canvas_shader.version_bind_shader(data.canvas_shader_default_version, CanvasShaderGLES3::MODE_QUAD); + shadow_render.shader.initialize(); + shadow_render.shader_version = shadow_render.shader.version_create(); + { default_canvas_group_shader = material_storage->shader_allocate(); material_storage->shader_initialize(default_canvas_group_shader); @@ -2116,9 +2610,11 @@ void fragment() { } RasterizerCanvasGLES3::~RasterizerCanvasGLES3() { - GLES3::MaterialStorage *material_storage = GLES3::MaterialStorage::get_singleton(); + singleton = nullptr; + GLES3::MaterialStorage *material_storage = GLES3::MaterialStorage::get_singleton(); material_storage->shaders.canvas_shader.version_free(data.canvas_shader_default_version); + shadow_render.shader.version_free(shadow_render.shader_version); material_storage->material_free(default_canvas_group_material); material_storage->shader_free(default_canvas_group_shader); material_storage->material_free(default_clip_children_material); @@ -2134,6 +2630,15 @@ RasterizerCanvasGLES3::~RasterizerCanvasGLES3() { GLES3::TextureStorage::get_singleton()->canvas_texture_free(default_canvas_texture); memdelete_arr(state.instance_data_array); memdelete_arr(state.light_uniforms); + + if (state.shadow_fb != 0) { + glDeleteFramebuffers(1, &state.shadow_fb); + glDeleteTextures(1, &state.shadow_texture); + glDeleteRenderbuffers(1, &state.shadow_depth_buffer); + state.shadow_fb = 0; + state.shadow_texture = 0; + state.shadow_depth_buffer = 0; + } } #endif // GLES3_ENABLED diff --git a/drivers/gles3/rasterizer_canvas_gles3.h b/drivers/gles3/rasterizer_canvas_gles3.h index aee2782b62..d672d05e14 100644 --- a/drivers/gles3/rasterizer_canvas_gles3.h +++ b/drivers/gles3/rasterizer_canvas_gles3.h @@ -40,6 +40,7 @@ #include "storage/texture_storage.h" #include "shaders/canvas.glsl.gen.h" +#include "shaders/canvas_occlusion.glsl.gen.h" class RasterizerSceneGLES3; @@ -102,10 +103,40 @@ class RasterizerCanvasGLES3 : public RendererCanvasRender { struct CanvasLight { RID texture; + struct { + bool enabled = false; + float z_far; + float y_offset; + Transform2D directional_xform; + } shadow; }; RID_Owner<CanvasLight> canvas_light_owner; + struct OccluderPolygon { + RS::CanvasOccluderPolygonCullMode cull_mode = RS::CANVAS_OCCLUDER_POLYGON_CULL_DISABLED; + int line_point_count = 0; + GLuint vertex_buffer = 0; + GLuint vertex_array = 0; + GLuint index_buffer = 0; + + int sdf_point_count = 0; + int sdf_index_count = 0; + GLuint sdf_vertex_buffer = 0; + GLuint sdf_vertex_array = 0; + GLuint sdf_index_buffer = 0; + bool sdf_is_lines = false; + }; + + RID_Owner<OccluderPolygon> occluder_polygon_owner; + + void _update_shadow_atlas(); + + struct { + CanvasOcclusionShaderGLES3 shader; + RID shader_version; + } shadow_render; + struct LightUniform { float matrix[8]; //light to texture coordinate matrix float shadow_matrix[8]; //light to shadow coordinate matrix @@ -153,9 +184,9 @@ public: }; struct PolygonBuffers { - GLuint vertex_buffer; - GLuint vertex_array; - GLuint index_buffer; + GLuint vertex_buffer = 0; + GLuint vertex_array = 0; + GLuint index_buffer = 0; int count = 0; bool color_disabled = false; Color color; @@ -265,6 +296,11 @@ public: LightUniform *light_uniforms = nullptr; + GLuint shadow_texture = 0; + GLuint shadow_depth_buffer = 0; + GLuint shadow_fb = 0; + int shadow_texture_size = 2048; + bool using_directional_lights = false; RID current_tex = RID(); @@ -295,9 +331,6 @@ public: void draw_lens_distortion_rect(const Rect2 &p_rect, float p_k1, float p_k2, const Vector2 &p_eye_center, float p_oversample); void reset_canvas(); - void canvas_light_shadow_buffer_update(RID p_buffer, const Transform2D &p_light_xform, int p_light_mask, float p_near, float p_far, LightOccluderInstance *p_occluders, Projection *p_xform_cache); - - virtual void canvas_debug_viewport_shadows(Light *p_lights_with_shadow) override; RID light_create() override; void light_set_texture(RID p_rid, RID p_texture) override; diff --git a/drivers/gles3/shader_gles3.h b/drivers/gles3/shader_gles3.h index 760b5e5ddb..f5b4a8cb3c 100644 --- a/drivers/gles3/shader_gles3.h +++ b/drivers/gles3/shader_gles3.h @@ -208,8 +208,10 @@ protected: spec = version->variants[p_variant].lookup_ptr(specialization_default_mask); } - ERR_FAIL_COND(!spec); // Should never happen - ERR_FAIL_COND(!spec->ok); // Should never happen + if (!spec || !spec->ok) { + WARN_PRINT_ONCE("shader failed to compile, unable to bind shader."); + return; + } glUseProgram(spec->id); current_shader = spec; diff --git a/drivers/gles3/shaders/SCsub b/drivers/gles3/shaders/SCsub index 83ffe8b1e1..b8bb08ec34 100644 --- a/drivers/gles3/shaders/SCsub +++ b/drivers/gles3/shaders/SCsub @@ -17,3 +17,5 @@ if "GLES3_GLSL" in env["BUILDERS"]: env.GLES3_GLSL("scene.glsl") env.GLES3_GLSL("sky.glsl") env.GLES3_GLSL("cubemap_filter.glsl") + env.GLES3_GLSL("canvas_occlusion.glsl") + env.GLES3_GLSL("canvas_sdf.glsl") diff --git a/drivers/gles3/shaders/canvas.glsl b/drivers/gles3/shaders/canvas.glsl index 23db41802e..ca806304c5 100644 --- a/drivers/gles3/shaders/canvas.glsl +++ b/drivers/gles3/shaders/canvas.glsl @@ -10,6 +10,7 @@ mode_instanced = #define USE_ATTRIBUTES \n#define USE_INSTANCING #[specializations] DISABLE_LIGHTING = false +USE_RGBA_SHADOWS = false #[vertex] @@ -213,8 +214,8 @@ void main() { #ifndef DISABLE_LIGHTING uniform sampler2D atlas_texture; //texunit:-2 +uniform sampler2D shadow_atlas_texture; //texunit:-3 #endif // DISABLE_LIGHTING -//uniform sampler2D shadow_atlas_texture; //texunit:-3 uniform sampler2D screen_texture; //texunit:-4 uniform sampler2D sdf_texture; //texunit:-5 uniform sampler2D normal_texture; //texunit:-6 @@ -245,6 +246,35 @@ layout(std140) uniform MaterialUniforms{ #endif #GLOBALS + +float vec4_to_float(vec4 p_vec) { + return dot(p_vec, vec4(1.0 / (255.0 * 255.0 * 255.0), 1.0 / (255.0 * 255.0), 1.0 / 255.0, 1.0)) * 2.0 - 1.0; +} + +vec2 screen_uv_to_sdf(vec2 p_uv) { + return screen_to_sdf * p_uv; +} + +float texture_sdf(vec2 p_sdf) { + vec2 uv = p_sdf * sdf_to_tex.xy + sdf_to_tex.zw; + float d = vec4_to_float(texture(sdf_texture, uv)); + d *= SDF_MAX_LENGTH; + return d * tex_to_sdf; +} + +vec2 texture_sdf_normal(vec2 p_sdf) { + vec2 uv = p_sdf * sdf_to_tex.xy + sdf_to_tex.zw; + + const float EPSILON = 0.001; + return normalize(vec2( + vec4_to_float(texture(sdf_texture, uv + vec2(EPSILON, 0.0))) - vec4_to_float(texture(sdf_texture, uv - vec2(EPSILON, 0.0))), + vec4_to_float(texture(sdf_texture, uv + vec2(0.0, EPSILON))) - vec4_to_float(texture(sdf_texture, uv - vec2(0.0, EPSILON))))); +} + +vec2 sdf_to_screen_uv(vec2 p_sdf) { + return p_sdf * sdf_to_screen; +} + #ifndef DISABLE_LIGHTING #ifdef LIGHT_CODE_USED @@ -299,6 +329,70 @@ vec3 light_normal_compute(vec3 light_vec, vec3 normal, vec3 base_color, vec3 lig } } +#ifdef USE_RGBA_SHADOWS + +#define SHADOW_DEPTH(m_uv) (dot(textureLod(shadow_atlas_texture, (m_uv), 0.0), vec4(1.0 / (255.0 * 255.0 * 255.0), 1.0 / (255.0 * 255.0), 1.0 / 255.0, 1.0)) * 2.0 - 1.0) + +#else + +#define SHADOW_DEPTH(m_uv) (textureLod(shadow_atlas_texture, (m_uv), 0.0).r) + +#endif + +#define SHADOW_TEST(m_uv) \ + { \ + highp float sd = SHADOW_DEPTH(m_uv); \ + shadow += step(sd, shadow_uv.z / shadow_uv.w); \ + } + +//float distance = length(shadow_pos); +vec4 light_shadow_compute(uint light_base, vec4 light_color, vec4 shadow_uv +#ifdef LIGHT_CODE_USED + , + vec3 shadow_modulate +#endif +) { + float shadow = 0.0; + uint shadow_mode = light_array[light_base].flags & LIGHT_FLAGS_FILTER_MASK; + + if (shadow_mode == LIGHT_FLAGS_SHADOW_NEAREST) { + SHADOW_TEST(shadow_uv.xy); + } else if (shadow_mode == LIGHT_FLAGS_SHADOW_PCF5) { + vec2 shadow_pixel_size = vec2(light_array[light_base].shadow_pixel_size, 0.0); + SHADOW_TEST(shadow_uv.xy - shadow_pixel_size * 2.0); + SHADOW_TEST(shadow_uv.xy - shadow_pixel_size); + SHADOW_TEST(shadow_uv.xy); + SHADOW_TEST(shadow_uv.xy + shadow_pixel_size); + SHADOW_TEST(shadow_uv.xy + shadow_pixel_size * 2.0); + shadow /= 5.0; + } else { //PCF13 + vec2 shadow_pixel_size = vec2(light_array[light_base].shadow_pixel_size, 0.0); + SHADOW_TEST(shadow_uv.xy - shadow_pixel_size * 6.0); + SHADOW_TEST(shadow_uv.xy - shadow_pixel_size * 5.0); + SHADOW_TEST(shadow_uv.xy - shadow_pixel_size * 4.0); + SHADOW_TEST(shadow_uv.xy - shadow_pixel_size * 3.0); + SHADOW_TEST(shadow_uv.xy - shadow_pixel_size * 2.0); + SHADOW_TEST(shadow_uv.xy - shadow_pixel_size); + SHADOW_TEST(shadow_uv.xy); + SHADOW_TEST(shadow_uv.xy + shadow_pixel_size); + SHADOW_TEST(shadow_uv.xy + shadow_pixel_size * 2.0); + SHADOW_TEST(shadow_uv.xy + shadow_pixel_size * 3.0); + SHADOW_TEST(shadow_uv.xy + shadow_pixel_size * 4.0); + SHADOW_TEST(shadow_uv.xy + shadow_pixel_size * 5.0); + SHADOW_TEST(shadow_uv.xy + shadow_pixel_size * 6.0); + shadow /= 13.0; + } + + vec4 shadow_color = unpackUnorm4x8(light_array[light_base].shadow_color); +#ifdef LIGHT_CODE_USED + shadow_color.rgb *= shadow_modulate; +#endif + + shadow_color.a *= light_color.a; //respect light alpha + + return mix(light_color, shadow_color, shadow); +} + void light_blend_compute(uint light_base, vec4 light_color, inout vec3 color) { uint blend_mode = light_array[light_base].flags & LIGHT_FLAGS_BLEND_MASK; @@ -527,6 +621,19 @@ void main() { } #endif + if (bool(light_array[light_base].flags & LIGHT_FLAGS_HAS_SHADOW)) { + vec2 shadow_pos = (vec4(shadow_vertex, 0.0, 1.0) * mat4(light_array[light_base].shadow_matrix[0], light_array[light_base].shadow_matrix[1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0))).xy; //multiply inverse given its transposed. Optimizer removes useless operations. + + vec4 shadow_uv = vec4(shadow_pos.x, light_array[light_base].shadow_y_ofs, shadow_pos.y * light_array[light_base].shadow_zfar_inv, 1.0); + + light_color = light_shadow_compute(light_base, light_color, shadow_uv +#ifdef LIGHT_CODE_USED + , + shadow_modulate.rgb +#endif + ); + } + light_blend_compute(light_base, light_color, color.rgb); } @@ -584,6 +691,46 @@ void main() { light_color.a = 0.0; } + if (bool(light_array[light_base].flags & LIGHT_FLAGS_HAS_SHADOW)) { + vec2 shadow_pos = (vec4(shadow_vertex, 0.0, 1.0) * mat4(light_array[light_base].shadow_matrix[0], light_array[light_base].shadow_matrix[1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0))).xy; //multiply inverse given its transposed. Optimizer removes useless operations. + + vec2 pos_norm = normalize(shadow_pos); + vec2 pos_abs = abs(pos_norm); + vec2 pos_box = pos_norm / max(pos_abs.x, pos_abs.y); + vec2 pos_rot = pos_norm * mat2(vec2(0.7071067811865476, -0.7071067811865476), vec2(0.7071067811865476, 0.7071067811865476)); //is there a faster way to 45 degrees rot? + float tex_ofs; + float dist; + if (pos_rot.y > 0.0) { + if (pos_rot.x > 0.0) { + tex_ofs = pos_box.y * 0.125 + 0.125; + dist = shadow_pos.x; + } else { + tex_ofs = pos_box.x * -0.125 + (0.25 + 0.125); + dist = shadow_pos.y; + } + } else { + if (pos_rot.x < 0.0) { + tex_ofs = pos_box.y * -0.125 + (0.5 + 0.125); + dist = -shadow_pos.x; + } else { + tex_ofs = pos_box.x * 0.125 + (0.75 + 0.125); + dist = -shadow_pos.y; + } + } + + dist *= light_array[light_base].shadow_zfar_inv; + + //float distance = length(shadow_pos); + vec4 shadow_uv = vec4(tex_ofs, light_array[light_base].shadow_y_ofs, dist, 1.0); + + light_color = light_shadow_compute(light_base, light_color, shadow_uv +#ifdef LIGHT_CODE_USED + , + shadow_modulate.rgb +#endif + ); + } + light_blend_compute(light_base, light_color, color.rgb); } #endif diff --git a/drivers/gles3/shaders/canvas_occlusion.glsl b/drivers/gles3/shaders/canvas_occlusion.glsl new file mode 100644 index 0000000000..512800839a --- /dev/null +++ b/drivers/gles3/shaders/canvas_occlusion.glsl @@ -0,0 +1,68 @@ +/* clang-format off */ +#[modes] + +mode_sdf = +mode_shadow = #define MODE_SHADOW +mode_shadow_RGBA = #define MODE_SHADOW \n#define USE_RGBA_SHADOWS + +#[specializations] + +#[vertex] + +layout(location = 0) in vec3 vertex; + +uniform highp mat4 projection; +uniform highp vec4 modelview1; +uniform highp vec4 modelview2; +uniform highp vec2 direction; +uniform highp float z_far; + +#ifdef MODE_SHADOW +out float depth; +#endif + +void main() { + highp vec4 vtx = vec4(vertex, 1.0) * mat4(modelview1, modelview2, vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0)); + +#ifdef MODE_SHADOW + depth = dot(direction, vtx.xy); +#endif + gl_Position = projection * vtx; +} + +#[fragment] + + +uniform highp mat4 projection; +uniform highp vec4 modelview1; +uniform highp vec4 modelview2; +uniform highp vec2 direction; +uniform highp float z_far; + +#ifdef MODE_SHADOW +in highp float depth; +#endif + +#ifdef USE_RGBA_SHADOWS +layout(location = 0) out lowp vec4 out_buf; +#else +layout(location = 0) out highp float out_buf; +#endif + +void main() { + float out_depth = 1.0; + +#ifdef MODE_SHADOW + out_depth = depth / z_far; +#endif + +#ifdef USE_RGBA_SHADOWS + out_depth = clamp(out_depth, -1.0, 1.0); + out_depth = out_depth * 0.5 + 0.5; + highp vec4 comp = fract(out_depth * vec4(255.0 * 255.0 * 255.0, 255.0 * 255.0, 255.0, 1.0)); + comp -= comp.xxyz * vec4(0.0, 1.0 / 255.0, 1.0 / 255.0, 1.0 / 255.0); + out_buf = comp; +#else + out_buf = out_depth; +#endif +} diff --git a/drivers/gles3/shaders/canvas_sdf.glsl b/drivers/gles3/shaders/canvas_sdf.glsl new file mode 100644 index 0000000000..424ec22457 --- /dev/null +++ b/drivers/gles3/shaders/canvas_sdf.glsl @@ -0,0 +1,205 @@ +/* clang-format off */ +#[modes] + +mode_load = #define MODE_LOAD +mode_load_shrink = #define MODE_LOAD_SHRINK +mode_process = #define MODE_PROCESS +mode_store = #define MODE_STORE +mode_store_shrink = #define MODE_STORE_SHRINK + +#[specializations] + +#[vertex] + +layout(location = 0) in vec2 vertex_attrib; + +/* clang-format on */ + +uniform ivec2 size; +uniform int stride; +uniform int shift; +uniform ivec2 base_size; + +void main() { + gl_Position = vec4(vertex_attrib, 1.0, 1.0); +} + +/* clang-format off */ +#[fragment] + +#define SDF_MAX_LENGTH 16384.0 + +#if defined(MODE_LOAD) || defined(MODE_LOAD_SHRINK) +uniform lowp sampler2D src_pixels;//texunit:0 +#else +uniform highp isampler2D src_process;//texunit:0 +#endif + +uniform ivec2 size; +uniform int stride; +uniform int shift; +uniform ivec2 base_size; + +#if defined(MODE_LOAD) || defined(MODE_LOAD_SHRINK) || defined(MODE_PROCESS) +layout(location = 0) out ivec4 distance_field; +#else +layout(location = 0) out vec4 distance_field; +#endif + +vec4 float_to_vec4(float p_float) { + highp vec4 comp = fract(p_float * vec4(255.0 * 255.0 * 255.0, 255.0 * 255.0, 255.0, 1.0)); + comp -= comp.xxyz * vec4(0.0, 1.0 / 255.0, 1.0 / 255.0, 1.0 / 255.0); + return comp; +} + +void main() { + ivec2 pos = ivec2(gl_FragCoord.xy); + +#ifdef MODE_LOAD + + bool solid = texelFetch(src_pixels, pos, 0).r > 0.5; + distance_field = solid ? ivec4(ivec2(-32767), 0, 0) : ivec4(ivec2(32767), 0, 0); +#endif + +#ifdef MODE_LOAD_SHRINK + + int s = 1 << shift; + ivec2 base = pos << shift; + ivec2 center = base + ivec2(shift); + + ivec2 rel = ivec2(32767); + float d = 1e20; + int found = 0; + int solid_found = 0; + for (int i = 0; i < s; i++) { + for (int j = 0; j < s; j++) { + ivec2 src_pos = base + ivec2(i, j); + if (any(greaterThanEqual(src_pos, base_size))) { + continue; + } + bool solid = texelFetch(src_pixels, src_pos, 0).r > 0.5; + if (solid) { + float dist = length(vec2(src_pos - center)); + if (dist < d) { + d = dist; + rel = src_pos; + } + solid_found++; + } + found++; + } + } + + if (solid_found == found) { + //mark solid only if all are solid + rel = ivec2(-32767); + } + + distance_field = ivec4(rel, 0, 0); +#endif + +#ifdef MODE_PROCESS + + ivec2 base = pos << shift; + ivec2 center = base + ivec2(shift); + + ivec2 rel = texelFetch(src_process, pos, 0).xy; + + bool solid = rel.x < 0; + + if (solid) { + rel = -rel - ivec2(1); + } + + if (center != rel) { + //only process if it does not point to itself + const int ofs_table_size = 8; + const ivec2 ofs_table[ofs_table_size] = ivec2[]( + ivec2(-1, -1), + ivec2(0, -1), + ivec2(+1, -1), + + ivec2(-1, 0), + ivec2(+1, 0), + + ivec2(-1, +1), + ivec2(0, +1), + ivec2(+1, +1)); + + float dist = length(vec2(rel - center)); + for (int i = 0; i < ofs_table_size; i++) { + ivec2 src_pos = pos + ofs_table[i] * stride; + if (any(lessThan(src_pos, ivec2(0))) || any(greaterThanEqual(src_pos, size))) { + continue; + } + ivec2 src_rel = texelFetch(src_process, src_pos, 0).xy; + bool src_solid = src_rel.x < 0; + if (src_solid) { + src_rel = -src_rel - ivec2(1); + } + + if (src_solid != solid) { + src_rel = ivec2(src_pos << shift); //point to itself if of different type + } + + float src_dist = length(vec2(src_rel - center)); + if (src_dist < dist) { + dist = src_dist; + rel = src_rel; + } + } + } + + if (solid) { + rel = -rel - ivec2(1); + } + + distance_field = ivec4(rel, 0, 0); +#endif + +#ifdef MODE_STORE + + ivec2 rel = texelFetch(src_process, pos, 0).xy; + + bool solid = rel.x < 0; + + if (solid) { + rel = -rel - ivec2(1); + } + + float d = length(vec2(rel - pos)); + + if (solid) { + d = -d; + } + + d /= SDF_MAX_LENGTH; + d = clamp(d, -1.0, 1.0); + distance_field = float_to_vec4(d*0.5+0.5); + +#endif + +#ifdef MODE_STORE_SHRINK + + ivec2 base = pos << shift; + ivec2 center = base + ivec2(shift); + + ivec2 rel = texelFetch(src_process, pos, 0).xy; + + bool solid = rel.x < 0; + + if (solid) { + rel = -rel - ivec2(1); + } + + float d = length(vec2(rel - center)); + + if (solid) { + d = -d; + } + d /= SDF_MAX_LENGTH; + d = clamp(d, -1.0, 1.0); + distance_field = float_to_vec4(d*0.5+0.5); + +#endif +} diff --git a/drivers/gles3/shaders/canvas_shadow.glsl b/drivers/gles3/shaders/canvas_shadow.glsl deleted file mode 100644 index 94485abd11..0000000000 --- a/drivers/gles3/shaders/canvas_shadow.glsl +++ /dev/null @@ -1,60 +0,0 @@ -/* clang-format off */ -[vertex] - -#ifdef USE_GLES_OVER_GL -#define lowp -#define mediump -#define highp -#else -precision highp float; -precision highp int; -#endif - -layout(location = 0) in highp vec3 vertex; - -uniform highp mat4 projection_matrix; -/* clang-format on */ -uniform highp mat4 light_matrix; -uniform highp mat4 model_matrix; -uniform highp float distance_norm; - -out highp vec4 position_interp; - -void main() { - gl_Position = projection_matrix * (light_matrix * (model_matrix * vec4(vertex, 1.0))); - position_interp = gl_Position; -} - -/* clang-format off */ -[fragment] - -#ifdef USE_GLES_OVER_GL -#define lowp -#define mediump -#define highp -#else -#if defined(USE_HIGHP_PRECISION) -precision highp float; -precision highp int; -#else -precision mediump float; -precision mediump int; -#endif -#endif - -in highp vec4 position_interp; -/* clang-format on */ - -void main() { - highp float depth = ((position_interp.z / position_interp.w) + 1.0) * 0.5 + 0.0; // bias - -#ifdef USE_RGBA_SHADOWS - - highp vec4 comp = fract(depth * vec4(255.0 * 255.0 * 255.0, 255.0 * 255.0, 255.0, 1.0)); - comp -= comp.xxyz * vec4(0.0, 1.0 / 255.0, 1.0 / 255.0, 1.0 / 255.0); - frag_color = comp; -#else - - frag_color = vec4(depth); -#endif -} diff --git a/drivers/gles3/storage/config.cpp b/drivers/gles3/storage/config.cpp index 97a6692166..9b496c0999 100644 --- a/drivers/gles3/storage/config.cpp +++ b/drivers/gles3/storage/config.cpp @@ -71,7 +71,7 @@ Config::Config() { s3tc_supported = true; rgtc_supported = true; //RGTC - core since OpenGL version 3.0 #else - float_texture_supported = extensions.has("GL_ARB_texture_float") || extensions.has("GL_OES_texture_float"); + float_texture_supported = extensions.has("GL_EXT_color_buffer_float"); etc2_supported = true; #if defined(ANDROID_ENABLED) || defined(IOS_ENABLED) // Some Android devices report support for S3TC but we don't expect that and don't export the textures. @@ -84,24 +84,14 @@ Config::Config() { rgtc_supported = extensions.has("GL_EXT_texture_compression_rgtc") || extensions.has("GL_ARB_texture_compression_rgtc") || extensions.has("EXT_texture_compression_rgtc"); #endif -#ifdef GLES_OVER_GL - use_rgba_2d_shadows = false; -#else - use_rgba_2d_shadows = !(float_texture_supported && extensions.has("GL_EXT_texture_rg")); -#endif - glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &max_vertex_texture_image_units); glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &max_texture_image_units); glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size); glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &max_uniform_buffer_size); - glGetIntegerv(GL_MAX_VIEWPORT_DIMS, &max_viewport_size); + glGetIntegerv(GL_MAX_VIEWPORT_DIMS, max_viewport_size); glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &uniform_buffer_offset_alignment); - // the use skeleton software path should be used if either float texture is not supported, - // OR max_vertex_texture_image_units is zero - use_skeleton_software = (float_texture_supported == false) || (max_vertex_texture_image_units == 0); - support_anisotropic_filter = extensions.has("GL_EXT_texture_filter_anisotropic"); if (support_anisotropic_filter) { glGetFloatv(_GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &anisotropic_level); diff --git a/drivers/gles3/storage/config.h b/drivers/gles3/storage/config.h index d4b38acd18..87202fde84 100644 --- a/drivers/gles3/storage/config.h +++ b/drivers/gles3/storage/config.h @@ -56,15 +56,13 @@ private: public: bool use_nearest_mip_filter = false; - bool use_skeleton_software = false; bool use_depth_prepass = true; - bool use_rgba_2d_shadows = false; int max_vertex_texture_image_units = 0; int max_texture_image_units = 0; int max_texture_size = 0; + int max_viewport_size[2] = { 0, 0 }; int max_uniform_buffer_size = 0; - int max_viewport_size = 0; int max_renderable_elements = 0; int max_renderable_lights = 0; int max_lights_per_object = 0; diff --git a/drivers/gles3/storage/mesh_storage.cpp b/drivers/gles3/storage/mesh_storage.cpp index 11ce31856d..9ec0fc0286 100644 --- a/drivers/gles3/storage/mesh_storage.cpp +++ b/drivers/gles3/storage/mesh_storage.cpp @@ -1004,7 +1004,7 @@ void MeshStorage::multimesh_set_mesh(RID p_multimesh, RID p_mesh) { #define MULTIMESH_DIRTY_REGION_SIZE 512 void MeshStorage::_multimesh_make_local(MultiMesh *multimesh) const { - if (multimesh->data_cache.size() > 0) { + if (multimesh->data_cache.size() > 0 || multimesh->instances == 0) { return; //already local } ERR_FAIL_COND(multimesh->data_cache.size() > 0); @@ -1421,7 +1421,7 @@ Vector<float> MeshStorage::multimesh_get_buffer(RID p_multimesh) const { MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh); ERR_FAIL_COND_V(!multimesh, Vector<float>()); Vector<float> ret; - if (multimesh->buffer == 0) { + if (multimesh->buffer == 0 || multimesh->instances == 0) { return Vector<float>(); } else if (multimesh->data_cache.size()) { ret = multimesh->data_cache; diff --git a/drivers/gles3/storage/texture_storage.cpp b/drivers/gles3/storage/texture_storage.cpp index 524ff14cc9..11151c4100 100644 --- a/drivers/gles3/storage/texture_storage.cpp +++ b/drivers/gles3/storage/texture_storage.cpp @@ -207,6 +207,11 @@ TextureStorage::TextureStorage() { glBindTexture(GL_TEXTURE_2D, 0); + { + sdf_shader.shader.initialize(); + sdf_shader.shader_version = sdf_shader.shader.version_create(); + } + #ifdef GLES_OVER_GL glEnable(GL_PROGRAM_POINT_SIZE); #endif @@ -222,6 +227,7 @@ TextureStorage::~TextureStorage() { texture_atlas.texture = 0; glDeleteFramebuffers(1, &texture_atlas.framebuffer); texture_atlas.framebuffer = 0; + sdf_shader.shader.version_free(sdf_shader.shader_version); } //TODO, move back to storage @@ -276,55 +282,6 @@ void TextureStorage::canvas_texture_set_texture_repeat(RID p_canvas_texture, RS: ct->texture_repeat = p_repeat; } -/* CANVAS SHADOW */ - -RID TextureStorage::canvas_light_shadow_buffer_create(int p_width) { - Config *config = Config::get_singleton(); - CanvasLightShadow *cls = memnew(CanvasLightShadow); - - if (p_width > config->max_texture_size) { - p_width = config->max_texture_size; - } - - cls->size = p_width; - cls->height = 16; - - glActiveTexture(GL_TEXTURE0); - - glGenFramebuffers(1, &cls->fbo); - glBindFramebuffer(GL_FRAMEBUFFER, cls->fbo); - - glGenRenderbuffers(1, &cls->depth); - glBindRenderbuffer(GL_RENDERBUFFER, cls->depth); - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, cls->size, cls->height); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, cls->depth); - - glGenTextures(1, &cls->distance); - glBindTexture(GL_TEXTURE_2D, cls->distance); - if (config->use_rgba_2d_shadows) { - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, cls->size, cls->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); - } else { - glTexImage2D(GL_TEXTURE_2D, 0, GL_R32F, cls->size, cls->height, 0, GL_RED, GL_FLOAT, nullptr); - } - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, cls->distance, 0); - - GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - //printf("errnum: %x\n",status); - glBindFramebuffer(GL_FRAMEBUFFER, GLES3::TextureStorage::system_fbo); - - if (status != GL_FRAMEBUFFER_COMPLETE) { - memdelete(cls); - ERR_FAIL_COND_V(status != GL_FRAMEBUFFER_COMPLETE, RID()); - } - - return canvas_light_shadow_owner.make_rid(cls); -} - /* Texture API */ Ref<Image> TextureStorage::_get_gl_image_and_format(const Ref<Image> &p_image, Image::Format p_format, Image::Format &r_real_format, GLenum &r_gl_format, GLenum &r_gl_internal_format, GLenum &r_gl_type, bool &r_compressed, bool p_force_decompress) const { @@ -790,6 +747,7 @@ Ref<Image> TextureStorage::texture_2d_get(RID p_texture) const { #ifdef GLES_OVER_GL // OpenGL 3.3 supports glGetTexImage which is faster and simpler than glReadPixels. + // It also allows for reading compressed textures, mipmaps, and more formats. Vector<uint8_t> data; int data_size = Image::get_image_data_size(texture->alloc_width, texture->alloc_height, texture->real_format, texture->mipmaps > 1); @@ -826,8 +784,65 @@ Ref<Image> TextureStorage::texture_2d_get(RID p_texture) const { image->convert(texture->format); } #else - // Support for Web and Mobile will come later. - Ref<Image> image; + + Vector<uint8_t> data; + + // On web and mobile we always read an RGBA8 image with no mipmaps. + int data_size = Image::get_image_data_size(texture->alloc_width, texture->alloc_height, Image::FORMAT_RGBA8, false); + + data.resize(data_size * 2); //add some memory at the end, just in case for buggy drivers + uint8_t *w = data.ptrw(); + + GLuint temp_framebuffer; + glGenFramebuffers(1, &temp_framebuffer); + + GLuint temp_color_texture; + glGenTextures(1, &temp_color_texture); + + glBindFramebuffer(GL_FRAMEBUFFER, temp_framebuffer); + + glBindTexture(GL_TEXTURE_2D, temp_color_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture->alloc_width, texture->alloc_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, temp_color_texture, 0); + + glDepthMask(GL_FALSE); + glDisable(GL_DEPTH_TEST); + glDisable(GL_CULL_FACE); + glDisable(GL_BLEND); + glDepthFunc(GL_LEQUAL); + glColorMask(1, 1, 1, 1); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture->tex_id); + + glViewport(0, 0, texture->alloc_width, texture->alloc_height); + glClearColor(0.0, 0.0, 0.0, 0.0); + glClear(GL_COLOR_BUFFER_BIT); + + CopyEffects::get_singleton()->copy_to_rect(Rect2i(0, 0, 1.0, 1.0)); + + glReadPixels(0, 0, texture->alloc_width, texture->alloc_height, GL_RGBA, GL_UNSIGNED_BYTE, &w[0]); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDeleteTextures(1, &temp_color_texture); + glDeleteFramebuffers(1, &temp_framebuffer); + + data.resize(data_size); + + ERR_FAIL_COND_V(data.size() == 0, Ref<Image>()); + Ref<Image> image = Image::create_from_data(texture->width, texture->height, false, Image::FORMAT_RGBA8, data); + ERR_FAIL_COND_V(image->is_empty(), Ref<Image>()); + + if (texture->format != Image::FORMAT_RGBA8) { + image->convert(texture->format); + } + + if (texture->mipmaps > 1) { + image->generate_mipmaps(); + } + #endif #ifdef TOOLS_ENABLED @@ -1599,6 +1614,7 @@ void TextureStorage::_clear_render_target(RenderTarget *rt) { rt->backbuffer = 0; rt->backbuffer_fbo = 0; } + _render_target_clear_sdf(rt); } RID TextureStorage::render_target_create() { @@ -1784,13 +1800,271 @@ void TextureStorage::render_target_do_clear_request(RID p_render_target) { } void TextureStorage::render_target_set_sdf_size_and_scale(RID p_render_target, RS::ViewportSDFOversize p_size, RS::ViewportSDFScale p_scale) { + RenderTarget *rt = render_target_owner.get_or_null(p_render_target); + ERR_FAIL_COND(!rt); + if (rt->sdf_oversize == p_size && rt->sdf_scale == p_scale) { + return; + } + + rt->sdf_oversize = p_size; + rt->sdf_scale = p_scale; + + _render_target_clear_sdf(rt); +} + +Rect2i TextureStorage::_render_target_get_sdf_rect(const RenderTarget *rt) const { + Size2i margin; + int scale; + switch (rt->sdf_oversize) { + case RS::VIEWPORT_SDF_OVERSIZE_100_PERCENT: { + scale = 100; + } break; + case RS::VIEWPORT_SDF_OVERSIZE_120_PERCENT: { + scale = 120; + } break; + case RS::VIEWPORT_SDF_OVERSIZE_150_PERCENT: { + scale = 150; + } break; + case RS::VIEWPORT_SDF_OVERSIZE_200_PERCENT: { + scale = 200; + } break; + default: { + } + } + + margin = (rt->size * scale / 100) - rt->size; + + Rect2i r(Vector2i(), rt->size); + r.position -= margin; + r.size += margin * 2; + + return r; } Rect2i TextureStorage::render_target_get_sdf_rect(RID p_render_target) const { - return Rect2i(); + const RenderTarget *rt = render_target_owner.get_or_null(p_render_target); + ERR_FAIL_COND_V(!rt, Rect2i()); + + return _render_target_get_sdf_rect(rt); } void TextureStorage::render_target_mark_sdf_enabled(RID p_render_target, bool p_enabled) { + RenderTarget *rt = render_target_owner.get_or_null(p_render_target); + ERR_FAIL_COND(!rt); + + rt->sdf_enabled = p_enabled; +} + +bool TextureStorage::render_target_is_sdf_enabled(RID p_render_target) const { + const RenderTarget *rt = render_target_owner.get_or_null(p_render_target); + ERR_FAIL_COND_V(!rt, false); + + return rt->sdf_enabled; +} + +GLuint TextureStorage::render_target_get_sdf_texture(RID p_render_target) { + RenderTarget *rt = render_target_owner.get_or_null(p_render_target); + ERR_FAIL_COND_V(!rt, 0); + if (rt->sdf_texture_read == 0) { + Texture *texture = texture_owner.get_or_null(default_gl_textures[DEFAULT_GL_TEXTURE_BLACK]); + return texture->tex_id; + } + + return rt->sdf_texture_read; +} + +void TextureStorage::_render_target_allocate_sdf(RenderTarget *rt) { + ERR_FAIL_COND(rt->sdf_texture_write_fb != 0); + + Size2i size = _render_target_get_sdf_rect(rt).size; + + glGenTextures(1, &rt->sdf_texture_write); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, rt->sdf_texture_write); + glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, size.width, size.height, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glGenFramebuffers(1, &rt->sdf_texture_write_fb); + glBindFramebuffer(GL_FRAMEBUFFER, rt->sdf_texture_write_fb); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, rt->sdf_texture_write, 0); + + int scale; + switch (rt->sdf_scale) { + case RS::VIEWPORT_SDF_SCALE_100_PERCENT: { + scale = 100; + } break; + case RS::VIEWPORT_SDF_SCALE_50_PERCENT: { + scale = 50; + } break; + case RS::VIEWPORT_SDF_SCALE_25_PERCENT: { + scale = 25; + } break; + default: { + scale = 100; + } break; + } + + rt->process_size = size * scale / 100; + rt->process_size.x = MAX(rt->process_size.x, 1); + rt->process_size.y = MAX(rt->process_size.y, 1); + + glGenTextures(2, rt->sdf_texture_process); + glBindTexture(GL_TEXTURE_2D, rt->sdf_texture_process[0]); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RG16I, rt->process_size.width, rt->process_size.height, 0, GL_RG_INTEGER, GL_SHORT, nullptr); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glBindTexture(GL_TEXTURE_2D, rt->sdf_texture_process[1]); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RG16I, rt->process_size.width, rt->process_size.height, 0, GL_RG_INTEGER, GL_SHORT, nullptr); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glGenTextures(1, &rt->sdf_texture_read); + glBindTexture(GL_TEXTURE_2D, rt->sdf_texture_read); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, rt->process_size.width, rt->process_size.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +} + +void TextureStorage::_render_target_clear_sdf(RenderTarget *rt) { + if (rt->sdf_texture_write_fb != 0) { + glDeleteTextures(1, &rt->sdf_texture_read); + glDeleteTextures(1, &rt->sdf_texture_write); + glDeleteTextures(2, rt->sdf_texture_process); + glDeleteFramebuffers(1, &rt->sdf_texture_write_fb); + rt->sdf_texture_read = 0; + rt->sdf_texture_write = 0; + rt->sdf_texture_process[0] = 0; + rt->sdf_texture_process[1] = 0; + rt->sdf_texture_write_fb = 0; + } +} + +GLuint TextureStorage::render_target_get_sdf_framebuffer(RID p_render_target) { + RenderTarget *rt = render_target_owner.get_or_null(p_render_target); + ERR_FAIL_COND_V(!rt, 0); + + if (rt->sdf_texture_write_fb == 0) { + _render_target_allocate_sdf(rt); + } + + return rt->sdf_texture_write_fb; +} +void TextureStorage::render_target_sdf_process(RID p_render_target) { + CopyEffects *copy_effects = CopyEffects::get_singleton(); + + RenderTarget *rt = render_target_owner.get_or_null(p_render_target); + ERR_FAIL_COND(!rt); + ERR_FAIL_COND(rt->sdf_texture_write_fb == 0); + + Rect2i r = _render_target_get_sdf_rect(rt); + + Size2i size = r.size; + int32_t shift = 0; + + bool shrink = false; + + switch (rt->sdf_scale) { + case RS::VIEWPORT_SDF_SCALE_50_PERCENT: { + size[0] >>= 1; + size[1] >>= 1; + shift = 1; + shrink = true; + } break; + case RS::VIEWPORT_SDF_SCALE_25_PERCENT: { + size[0] >>= 2; + size[1] >>= 2; + shift = 2; + shrink = true; + } break; + default: { + }; + } + + GLuint temp_fb; + glGenFramebuffers(1, &temp_fb); + glBindFramebuffer(GL_FRAMEBUFFER, temp_fb); + + // Load + CanvasSdfShaderGLES3::ShaderVariant variant = shrink ? CanvasSdfShaderGLES3::MODE_LOAD_SHRINK : CanvasSdfShaderGLES3::MODE_LOAD; + sdf_shader.shader.version_bind_shader(sdf_shader.shader_version, variant); + sdf_shader.shader.version_set_uniform(CanvasSdfShaderGLES3::BASE_SIZE, r.size, sdf_shader.shader_version, variant); + sdf_shader.shader.version_set_uniform(CanvasSdfShaderGLES3::SIZE, size, sdf_shader.shader_version, variant); + sdf_shader.shader.version_set_uniform(CanvasSdfShaderGLES3::STRIDE, 0, sdf_shader.shader_version, variant); + sdf_shader.shader.version_set_uniform(CanvasSdfShaderGLES3::SHIFT, shift, sdf_shader.shader_version, variant); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, rt->sdf_texture_write); + + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, rt->sdf_texture_process[0], 0); + glViewport(0, 0, size.width, size.height); + glEnable(GL_SCISSOR_TEST); + glScissor(0, 0, size.width, size.height); + + copy_effects->draw_screen_triangle(); + + // Process + + int stride = nearest_power_of_2_templated(MAX(size.width, size.height) / 2); + + variant = CanvasSdfShaderGLES3::MODE_PROCESS; + sdf_shader.shader.version_bind_shader(sdf_shader.shader_version, variant); + sdf_shader.shader.version_set_uniform(CanvasSdfShaderGLES3::BASE_SIZE, r.size, sdf_shader.shader_version, variant); + sdf_shader.shader.version_set_uniform(CanvasSdfShaderGLES3::SIZE, size, sdf_shader.shader_version, variant); + sdf_shader.shader.version_set_uniform(CanvasSdfShaderGLES3::STRIDE, stride, sdf_shader.shader_version, variant); + sdf_shader.shader.version_set_uniform(CanvasSdfShaderGLES3::SHIFT, shift, sdf_shader.shader_version, variant); + + bool swap = false; + + //jumpflood + while (stride > 0) { + glBindTexture(GL_TEXTURE_2D, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, rt->sdf_texture_process[swap ? 0 : 1], 0); + glBindTexture(GL_TEXTURE_2D, rt->sdf_texture_process[swap ? 1 : 0]); + + sdf_shader.shader.version_set_uniform(CanvasSdfShaderGLES3::STRIDE, stride, sdf_shader.shader_version, variant); + + copy_effects->draw_screen_triangle(); + + stride /= 2; + swap = !swap; + } + + // Store + variant = shrink ? CanvasSdfShaderGLES3::MODE_STORE_SHRINK : CanvasSdfShaderGLES3::MODE_STORE; + sdf_shader.shader.version_bind_shader(sdf_shader.shader_version, variant); + sdf_shader.shader.version_set_uniform(CanvasSdfShaderGLES3::BASE_SIZE, r.size, sdf_shader.shader_version, variant); + sdf_shader.shader.version_set_uniform(CanvasSdfShaderGLES3::SIZE, size, sdf_shader.shader_version, variant); + sdf_shader.shader.version_set_uniform(CanvasSdfShaderGLES3::STRIDE, stride, sdf_shader.shader_version, variant); + sdf_shader.shader.version_set_uniform(CanvasSdfShaderGLES3::SHIFT, shift, sdf_shader.shader_version, variant); + + glBindTexture(GL_TEXTURE_2D, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, rt->sdf_texture_read, 0); + glBindTexture(GL_TEXTURE_2D, rt->sdf_texture_process[swap ? 1 : 0]); + + copy_effects->draw_screen_triangle(); + + glBindTexture(GL_TEXTURE_2D, 0); + glBindFramebuffer(GL_FRAMEBUFFER, system_fbo); + glDeleteFramebuffers(1, &temp_fb); + glDisable(GL_SCISSOR_TEST); } void TextureStorage::render_target_copy_to_back_buffer(RID p_render_target, const Rect2i &p_region, bool p_gen_mipmaps) { diff --git a/drivers/gles3/storage/texture_storage.h b/drivers/gles3/storage/texture_storage.h index dbe39428ac..0b025c4370 100644 --- a/drivers/gles3/storage/texture_storage.h +++ b/drivers/gles3/storage/texture_storage.h @@ -39,6 +39,8 @@ #include "servers/rendering/renderer_compositor.h" #include "servers/rendering/storage/texture_storage.h" +#include "../shaders/canvas_sdf.glsl.gen.h" + // This must come first to avoid windows.h mess #include "platform_config.h" #ifndef OPENGL_INCLUDE_H @@ -84,18 +86,8 @@ namespace GLES3 { #define _GL_TEXTURE_EXTERNAL_OES 0x8D65 -#ifdef GLES_OVER_GL -#define _GL_HALF_FLOAT_OES 0x140B -#else -#define _GL_HALF_FLOAT_OES 0x8D61 -#endif - #define _EXT_TEXTURE_CUBE_MAP_SEAMLESS 0x884F -#define _RED_OES 0x1903 - -#define _DEPTH_COMPONENT24_OES 0x81A6 - #ifndef GLES_OVER_GL #define glClearDepth glClearDepthf #endif //!GLES_OVER_GL @@ -128,17 +120,6 @@ struct CanvasTexture { RS::CanvasItemTextureRepeat texture_repeat = RS::CANVAS_ITEM_TEXTURE_REPEAT_DEFAULT; }; -/* CANVAS SHADOW */ - -struct CanvasLightShadow { - RID self; - int size; - int height; - GLuint fbo; - GLuint depth; - GLuint distance; //for older devices -}; - struct RenderTarget; struct Texture { @@ -337,6 +318,15 @@ struct RenderTarget { GLuint color_type = GL_UNSIGNED_BYTE; Image::Format image_format = Image::FORMAT_RGBA8; + GLuint sdf_texture_write = 0; + GLuint sdf_texture_write_fb = 0; + GLuint sdf_texture_process[2] = { 0, 0 }; + GLuint sdf_texture_read = 0; + RS::ViewportSDFOversize sdf_oversize = RS::VIEWPORT_SDF_OVERSIZE_120_PERCENT; + RS::ViewportSDFScale sdf_scale = RS::VIEWPORT_SDF_SCALE_50_PERCENT; + Size2i process_size; + bool sdf_enabled = false; + bool is_transparent = false; bool direct_to_screen = false; @@ -362,10 +352,6 @@ private: RID_Owner<CanvasTexture, true> canvas_texture_owner; - /* CANVAS SHADOW */ - - RID_PtrOwner<CanvasLightShadow> canvas_light_shadow_owner; - /* Texture API */ mutable RID_Owner<Texture> texture_owner; @@ -411,6 +397,14 @@ private: void _clear_render_target(RenderTarget *rt); void _update_render_target(RenderTarget *rt); void _create_render_target_backbuffer(RenderTarget *rt); + void _render_target_allocate_sdf(RenderTarget *rt); + void _render_target_clear_sdf(RenderTarget *rt); + Rect2i _render_target_get_sdf_rect(const RenderTarget *rt) const; + + struct RenderTargetSDF { + CanvasSdfShaderGLES3 shader; + RID shader_version; + } sdf_shader; public: static TextureStorage *get_singleton(); @@ -437,10 +431,6 @@ public: virtual void canvas_texture_set_texture_filter(RID p_item, RS::CanvasItemTextureFilter p_filter) override; virtual void canvas_texture_set_texture_repeat(RID p_item, RS::CanvasItemTextureRepeat p_repeat) override; - /* CANVAS SHADOW */ - - RID canvas_light_shadow_buffer_create(int p_width); - /* Texture API */ Texture *get_texture(RID p_rid) { @@ -586,9 +576,13 @@ public: void render_target_disable_clear_request(RID p_render_target) override; void render_target_do_clear_request(RID p_render_target) override; - void render_target_set_sdf_size_and_scale(RID p_render_target, RS::ViewportSDFOversize p_size, RS::ViewportSDFScale p_scale) override; - Rect2i render_target_get_sdf_rect(RID p_render_target) const override; - void render_target_mark_sdf_enabled(RID p_render_target, bool p_enabled) override; + virtual void render_target_set_sdf_size_and_scale(RID p_render_target, RS::ViewportSDFOversize p_size, RS::ViewportSDFScale p_scale) override; + virtual Rect2i render_target_get_sdf_rect(RID p_render_target) const override; + GLuint render_target_get_sdf_texture(RID p_render_target); + GLuint render_target_get_sdf_framebuffer(RID p_render_target); + void render_target_sdf_process(RID p_render_target); + virtual void render_target_mark_sdf_enabled(RID p_render_target, bool p_enabled) override; + bool render_target_is_sdf_enabled(RID p_render_target) const; void render_target_copy_to_back_buffer(RID p_render_target, const Rect2i &p_region, bool p_gen_mipmaps); void render_target_clear_back_buffer(RID p_render_target, const Rect2i &p_region, const Color &p_color); diff --git a/drivers/gles3/storage/utilities.cpp b/drivers/gles3/storage/utilities.cpp index a2d1ba376a..8e7e218bb9 100644 --- a/drivers/gles3/storage/utilities.cpp +++ b/drivers/gles3/storage/utilities.cpp @@ -71,6 +71,11 @@ Utilities::~Utilities() { Vector<uint8_t> Utilities::buffer_get_data(GLenum p_target, GLuint p_buffer, uint32_t p_buffer_size) { Vector<uint8_t> ret; + + if (p_buffer_size == 0) { + return ret; + } + ret.resize(p_buffer_size); glBindBuffer(p_target, p_buffer); @@ -363,7 +368,7 @@ Size2i Utilities::get_maximum_viewport_size() const { return Size2i(); } - return Size2i(config->max_viewport_size, config->max_viewport_size); + return Size2i(config->max_viewport_size[0], config->max_viewport_size[1]); } #endif // GLES3_ENABLED diff --git a/editor/editor_build_profile.cpp b/editor/editor_build_profile.cpp index 7fd27692b0..96c69c0bd1 100644 --- a/editor/editor_build_profile.cpp +++ b/editor/editor_build_profile.cpp @@ -875,7 +875,7 @@ EditorBuildProfileManager::EditorBuildProfileManager() { import_profile = memnew(EditorFileDialog); add_child(import_profile); import_profile->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); - import_profile->add_filter("*.build", TTR("Egine Build Profile")); + import_profile->add_filter("*.build", TTR("Engine Build Profile")); import_profile->connect("files_selected", callable_mp(this, &EditorBuildProfileManager::_import_profile)); import_profile->set_title(TTR("Load Profile")); import_profile->set_access(EditorFileDialog::ACCESS_FILESYSTEM); @@ -883,7 +883,7 @@ EditorBuildProfileManager::EditorBuildProfileManager() { export_profile = memnew(EditorFileDialog); add_child(export_profile); export_profile->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); - export_profile->add_filter("*.build", TTR("Egine Build Profile")); + export_profile->add_filter("*.build", TTR("Engine Build Profile")); export_profile->connect("file_selected", callable_mp(this, &EditorBuildProfileManager::_export_profile)); export_profile->set_title(TTR("Export Profile")); export_profile->set_access(EditorFileDialog::ACCESS_FILESYSTEM); diff --git a/editor/editor_fonts.cpp b/editor/editor_fonts.cpp index 90f3f8d2f5..51ebc31df3 100644 --- a/editor/editor_fonts.cpp +++ b/editor/editor_fonts.cpp @@ -57,6 +57,24 @@ Ref<FontFile> load_external_font(const String &p_path, TextServer::Hinting p_hin return font; } +Ref<SystemFont> load_system_font(const PackedStringArray &p_names, TextServer::Hinting p_hinting, TextServer::FontAntialiasing p_aa, bool p_autohint, TextServer::SubpixelPositioning p_font_subpixel_positioning, bool p_msdf = false, TypedArray<Font> *r_fallbacks = nullptr) { + Ref<SystemFont> font; + font.instantiate(); + + font->set_font_names(p_names); + font->set_multichannel_signed_distance_field(p_msdf); + font->set_antialiasing(p_aa); + font->set_hinting(p_hinting); + font->set_force_autohinter(p_autohint); + font->set_subpixel_positioning(p_font_subpixel_positioning); + + if (r_fallbacks != nullptr) { + r_fallbacks->push_back(font); + } + + return font; +} + Ref<FontFile> load_internal_font(const uint8_t *p_data, size_t p_size, TextServer::Hinting p_hinting, TextServer::FontAntialiasing p_aa, bool p_autohint, TextServer::SubpixelPositioning p_font_subpixel_positioning, bool p_msdf = false, TypedArray<Font> *r_fallbacks = nullptr) { Ref<FontFile> font; font.instantiate(); @@ -166,6 +184,20 @@ void editor_register_fonts(Ref<Theme> p_theme) { Ref<FontFile> thai_font_bold = load_internal_font(_font_NotoSansThaiUI_Bold, _font_NotoSansThaiUI_Bold_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, false, &fallbacks_bold); Ref<FontVariation> fallback_font_bold = make_bold_font(fallback_font, embolden_strength, &fallbacks_bold); Ref<FontVariation> japanese_font_bold = make_bold_font(japanese_font, embolden_strength, &fallbacks_bold); + + if (OS::get_singleton()->has_feature("system_fonts")) { + PackedStringArray emoji_font_names; + emoji_font_names.push_back("Apple Color Emoji"); + emoji_font_names.push_back("Segoe UI Emoji"); + emoji_font_names.push_back("Noto Color Emoji"); + emoji_font_names.push_back("Twitter Color Emoji"); + emoji_font_names.push_back("OpenMoji"); + emoji_font_names.push_back("EmojiOne Color"); + Ref<SystemFont> emoji_font = load_system_font(emoji_font_names, font_hinting, font_antialiasing, true, font_subpixel_positioning, false); + fallbacks.push_back(emoji_font); + fallbacks_bold.push_back(emoji_font); + } + default_font_bold->set_fallbacks(fallbacks_bold); default_font_bold_msdf->set_fallbacks(fallbacks_bold); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index c8a6f43dbb..be1f0be279 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -4083,7 +4083,7 @@ void EditorNode::_quick_opened() { List<String> scene_extensions; ResourceLoader::get_recognized_extensions_for_type("PackedScene", &scene_extensions); - if (open_scene_dialog || scene_extensions.find(files[i].get_extension())) { + if (open_scene_dialog || scene_extensions.find(files[i].get_extension().to_lower())) { open_request(res_path); } else { load_resource(res_path); diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 608e7ce3cb..62bcf0b193 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -1283,42 +1283,71 @@ void FileSystemDock::_try_duplicate_item(const FileOrFolder &p_item, const Strin } Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES); - print_verbose("Duplicating " + old_path + " -> " + new_path); - Error err = p_item.is_file ? da->copy(old_path, new_path) : da->copy_dir(old_path, new_path); - if (err == OK) { - // Move/Rename any corresponding import settings too. - if (p_item.is_file && FileAccess::exists(old_path + ".import")) { - err = da->copy(old_path + ".import", new_path + ".import"); + + if (p_item.is_file) { + print_verbose("Duplicating " + old_path + " -> " + new_path); + + // Create the directory structure. + da->make_dir_recursive(new_path.get_base_dir()); + + if (FileAccess::exists(old_path + ".import")) { + Error err = da->copy(old_path, new_path); if (err != OK) { - EditorNode::get_singleton()->add_io_error(TTR("Error duplicating:") + "\n" + old_path + ".import\n"); + EditorNode::get_singleton()->add_io_error(TTR("Error duplicating:") + "\n" + old_path + ": " + error_names[err] + "\n"); + return; } // Remove uid from .import file to avoid conflict. Ref<ConfigFile> cfg; cfg.instantiate(); - cfg->load(new_path + ".import"); + cfg->load(old_path + ".import"); cfg->erase_section_key("remap", "uid"); - cfg->save(new_path + ".import"); - } else if (p_item.is_file && (old_path.get_extension() == "tscn" || old_path.get_extension() == "tres")) { - // FIXME: Quick hack to fix text resources. This should be fixed properly. - Ref<FileAccess> file = FileAccess::open(old_path, FileAccess::READ, &err); - if (err == OK) { - PackedStringArray lines = file->get_as_utf8_string().split("\n"); - String line = lines[0]; - - if (line.contains("uid")) { - line = line.substr(0, line.find(" uid")) + "]"; - lines.write[0] = line; + err = cfg->save(new_path + ".import"); + if (err != OK) { + EditorNode::get_singleton()->add_io_error(TTR("Error duplicating:") + "\n" + old_path + ".import: " + error_names[err] + "\n"); + return; + } + } else { + // Files which do not use an uid can just be copied. + if (ResourceLoader::get_resource_uid(old_path) == ResourceUID::INVALID_ID) { + Error err = da->copy(old_path, new_path); + if (err != OK) { + EditorNode::get_singleton()->add_io_error(TTR("Error duplicating:") + "\n" + old_path + ": " + error_names[err] + "\n"); + } + return; + } - Ref<FileAccess> file2 = FileAccess::open(new_path, FileAccess::WRITE, &err); - if (err == OK) { - file2->store_string(String("\n").join(lines)); - } + // Load the resource and save it again in the new location (this generates a new UID). + Error err; + Ref<Resource> res = ResourceLoader::load(old_path, "", ResourceFormatLoader::CACHE_MODE_REUSE, &err); + if (err == OK && res.is_valid()) { + err = ResourceSaver::save(res, new_path, ResourceSaver::FLAG_COMPRESS); + if (err != OK) { + EditorNode::get_singleton()->add_io_error(TTR("Error duplicating:") + " " + vformat(TTR("Failed to save resource at %s: %s"), new_path, error_names[err])); } + } else if (err != OK) { + // When loading files like text files the error is OK but the resource is still null. + // We can ignore such files. + EditorNode::get_singleton()->add_io_error(TTR("Error duplicating:") + " " + vformat(TTR("Failed to load resource at %s: %s"), new_path, error_names[err])); } } } else { - EditorNode::get_singleton()->add_io_error(TTR("Error duplicating:") + "\n" + old_path + "\n"); + // Recursively duplicate all files inside the folder. + Ref<DirAccess> old_dir = DirAccess::open(old_path); + Ref<FileAccess> file_access = FileAccess::create(FileAccess::ACCESS_RESOURCES); + old_dir->set_include_navigational(false); + old_dir->list_dir_begin(); + for (String f = old_dir->_get_next(); !f.is_empty(); f = old_dir->_get_next()) { + if (f.get_extension() == "import") { + continue; + } + if (file_access->file_exists(old_path + f)) { + _try_duplicate_item(FileOrFolder(old_path + f, true), new_path + f); + } else if (da->dir_exists(old_path + f)) { + _try_duplicate_item(FileOrFolder(old_path + f, false), new_path + f); + } + } + old_dir->list_dir_end(); } } diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 291b939fe9..3f6769a5ad 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -5751,8 +5751,10 @@ bool CanvasItemEditorViewport::can_drop_data(const Point2 &p_point, const Varian ResourceLoader::get_recognized_extensions_for_type("Texture2D", &texture_extensions); for (int i = 0; i < files.size(); i++) { + String extension = files[i].get_extension().to_lower(); + // Check if dragged files with texture or scene extension can be created at least once. - if (texture_extensions.find(files[i].get_extension()) || scene_extensions.find(files[i].get_extension())) { + if (texture_extensions.find(extension) || scene_extensions.find(extension)) { Ref<Resource> res = ResourceLoader::load(files[i]); if (res.is_null()) { continue; diff --git a/editor/plugins/curve_editor_plugin.cpp b/editor/plugins/curve_editor_plugin.cpp index 4aee9b879e..6149127fcc 100644 --- a/editor/plugins/curve_editor_plugin.cpp +++ b/editor/plugins/curve_editor_plugin.cpp @@ -755,10 +755,10 @@ void CurveEditor::_draw() { float width = view_size.x - 60 * EDSCALE; if (_selected_point > 0 && _selected_point + 1 < curve.get_point_count()) { text_color.a *= 0.4; - draw_multiline_string(font, Vector2(50 * EDSCALE, font_height), TTR("Hold Shift to edit tangents individually"), HORIZONTAL_ALIGNMENT_LEFT, width, -1, font_size, text_color); + draw_multiline_string(font, Vector2(50 * EDSCALE, font_height), TTR("Hold Shift to edit tangents individually"), HORIZONTAL_ALIGNMENT_LEFT, width, font_size, -1, text_color); } else if (curve.get_point_count() == 0) { text_color.a *= 0.4; - draw_multiline_string(font, Vector2(50 * EDSCALE, font_height), TTR("Right click to add point"), HORIZONTAL_ALIGNMENT_LEFT, width, -1, font_size, text_color); + draw_multiline_string(font, Vector2(50 * EDSCALE, font_height), TTR("Right click to add point"), HORIZONTAL_ALIGNMENT_LEFT, width, font_size, -1, text_color); } } diff --git a/editor/plugins/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp index 0f0d60c171..0af2a13df2 100644 --- a/editor/plugins/node_3d_editor_gizmos.cpp +++ b/editor/plugins/node_3d_editor_gizmos.cpp @@ -110,7 +110,9 @@ void EditorNode3DGizmo::clear() { collision_mesh = Ref<TriangleMesh>(); instances.clear(); handles.clear(); + handle_ids.clear(); secondary_handles.clear(); + secondary_handle_ids.clear(); } void EditorNode3DGizmo::redraw() { @@ -406,12 +408,15 @@ void EditorNode3DGizmo::add_handles(const Vector<Vector3> &p_handles, const Ref< return; } - ERR_FAIL_COND(!spatial_node); + ERR_FAIL_NULL(spatial_node); + + Vector<Vector3> &handle_list = p_secondary ? secondary_handles : handles; + Vector<int> &id_list = p_secondary ? secondary_handle_ids : handle_ids; if (p_ids.is_empty()) { - ERR_FAIL_COND_MSG((!handles.is_empty() && !handle_ids.is_empty()) || (!secondary_handles.is_empty() && !secondary_handle_ids.is_empty()), "Fail"); + ERR_FAIL_COND_MSG(!id_list.is_empty(), "IDs must be provided for all handles, as handles with IDs already exist."); } else { - ERR_FAIL_COND_MSG(handles.size() != handle_ids.size() || secondary_handles.size() != secondary_handle_ids.size(), "Fail"); + ERR_FAIL_COND_MSG(p_handles.size() != p_ids.size(), "The number of IDs should be the same as the number of handles."); } bool is_current_hover_gizmo = Node3DEditor::get_singleton()->get_current_hover_gizmo() == this; @@ -464,19 +469,17 @@ void EditorNode3DGizmo::add_handles(const Vector<Vector3> &p_handles, const Ref< } instances.push_back(ins); - Vector<Vector3> &h = p_secondary ? secondary_handles : handles; - int current_size = h.size(); - h.resize(current_size + p_handles.size()); + int current_size = handle_list.size(); + handle_list.resize(current_size + p_handles.size()); for (int i = 0; i < p_handles.size(); i++) { - h.write[current_size + i] = p_handles[i]; + handle_list.write[current_size + i] = p_handles[i]; } if (!p_ids.is_empty()) { - Vector<int> &ids = p_secondary ? secondary_handle_ids : handle_ids; - current_size = ids.size(); - ids.resize(current_size + p_ids.size()); + current_size = id_list.size(); + id_list.resize(current_size + p_ids.size()); for (int i = 0; i < p_ids.size(); i++) { - ids.write[current_size + i] = p_ids[i]; + id_list.write[current_size + i] = p_ids[i]; } } } diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 5309a028a9..6a9568b2a2 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -2664,7 +2664,7 @@ void Node3DEditorViewport::_notification(int p_what) { return; } if (preview_node->is_inside_tree()) { - preview_node_pos = _get_instance_position(preview_node_viewport_pos); + preview_node_pos = spatial_editor->snap_point(_get_instance_position(preview_node_viewport_pos)); Transform3D preview_gl_transform = Transform3D(Basis(), preview_node_pos); preview_node->set_global_transform(preview_gl_transform); if (!preview_node->is_visible()) { @@ -3748,24 +3748,45 @@ void Node3DEditorViewport::assign_pending_data_pointers(Node3D *p_preview_node, Vector3 Node3DEditorViewport::_get_instance_position(const Point2 &p_pos) const { const float MAX_DISTANCE = 50.0; + const float FALLBACK_DISTANCE = 5.0; Vector3 world_ray = _get_ray(p_pos); Vector3 world_pos = _get_ray_pos(p_pos); - Vector3 point = world_pos + world_ray * MAX_DISTANCE; - PhysicsDirectSpaceState3D *ss = get_tree()->get_root()->get_world_3d()->get_direct_space_state(); PhysicsDirectSpaceState3D::RayParameters ray_params; ray_params.from = world_pos; - ray_params.to = world_pos + world_ray * MAX_DISTANCE; + ray_params.to = world_pos + world_ray * camera->get_far(); PhysicsDirectSpaceState3D::RayResult result; if (ss->intersect_ray(ray_params, result)) { - point = result.position; + return result.position; + } + + const bool is_orthogonal = camera->get_projection() == Camera3D::PROJECTION_ORTHOGONAL; + + // The XZ plane. + Vector3 intersection; + Plane plane(Vector3(0, 1, 0)); + if (plane.intersects_ray(world_pos, world_ray, &intersection)) { + if (is_orthogonal || world_pos.distance_to(intersection) <= MAX_DISTANCE) { + return intersection; + } } - return point; + // Plane facing the camera using fallback distance. + if (is_orthogonal) { + plane = Plane(world_ray, cursor.pos - world_ray * (cursor.distance - FALLBACK_DISTANCE)); + } else { + plane = Plane(world_ray, world_pos + world_ray * FALLBACK_DISTANCE); + } + if (plane.intersects_ray(world_pos, world_ray, &intersection)) { + return intersection; + } + + // Not likely, but just in case... + return world_pos + world_ray * FALLBACK_DISTANCE; } AABB Node3DEditorViewport::_calculate_spatial_bounds(const Node3D *p_parent, bool p_exclude_top_level_transform) { @@ -4045,7 +4066,7 @@ bool Node3DEditorViewport::_create_instance(Node *parent, String &path, const Po gl_transform = parent_node3d->get_global_gizmo_transform(); } - gl_transform.origin = spatial_editor->snap_point(preview_node_pos); + gl_transform.origin = preview_node_pos; gl_transform.basis *= node3d->get_transform().basis; editor_data->get_undo_redo()->add_do_method(instantiated_scene, "set_global_transform", gl_transform); @@ -4129,11 +4150,13 @@ bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant ResourceLoader::get_recognized_extensions_for_type("Texture", &texture_extensions); for (int i = 0; i < files.size(); i++) { + String extension = files[i].get_extension().to_lower(); + // Check if dragged files with mesh or scene extension can be created at least once. - if (mesh_extensions.find(files[i].get_extension()) || - scene_extensions.find(files[i].get_extension()) || - material_extensions.find(files[i].get_extension()) || - texture_extensions.find(files[i].get_extension())) { + if (mesh_extensions.find(extension) || + scene_extensions.find(extension) || + material_extensions.find(extension) || + texture_extensions.find(extension)) { Ref<Resource> res = ResourceLoader::load(files[i]); if (res.is_null()) { continue; diff --git a/editor/plugins/texture_region_editor_plugin.cpp b/editor/plugins/texture_region_editor_plugin.cpp index f0a08cb4f5..a7a8d526d0 100644 --- a/editor/plugins/texture_region_editor_plugin.cpp +++ b/editor/plugins/texture_region_editor_plugin.cpp @@ -86,8 +86,8 @@ void TextureRegionEditor::_region_draw() { mtx.scale_basis(Vector2(draw_zoom, draw_zoom)); RS::get_singleton()->canvas_item_add_set_transform(edit_draw->get_canvas_item(), mtx); - edit_draw->draw_rect(Rect2(Point2(), base_tex->get_size()), Color(0.5, 0.5, 0.5, 0.5), false); - edit_draw->draw_texture(base_tex, Point2()); + edit_draw->draw_rect(Rect2(Point2(), preview_tex->get_size()), Color(0.5, 0.5, 0.5, 0.5), false); + edit_draw->draw_texture(preview_tex, Point2()); RS::get_singleton()->canvas_item_add_set_transform(edit_draw->get_canvas_item(), Transform2D()); const Color color = get_theme_color(SNAME("mono_color"), SNAME("Editor")); @@ -905,6 +905,13 @@ void TextureRegionEditor::edit(Object *p_obj) { if (atlas_tex.is_valid()) { atlas_tex->disconnect("changed", callable_mp(this, &TextureRegionEditor::_texture_changed)); } + + node_sprite_2d = nullptr; + node_sprite_3d = nullptr; + node_ninepatch = nullptr; + obj_styleBox = Ref<StyleBoxTexture>(nullptr); + atlas_tex = Ref<AtlasTexture>(nullptr); + if (p_obj) { node_sprite_2d = Object::cast_to<Sprite2D>(p_obj); node_sprite_3d = Object::cast_to<Sprite3D>(p_obj); @@ -926,13 +933,8 @@ void TextureRegionEditor::edit(Object *p_obj) { p_obj->connect("texture_changed", callable_mp(this, &TextureRegionEditor::_texture_changed)); } _edit_region(); - } else { - node_sprite_2d = nullptr; - node_sprite_3d = nullptr; - node_ninepatch = nullptr; - obj_styleBox = Ref<StyleBoxTexture>(nullptr); - atlas_tex = Ref<AtlasTexture>(nullptr); } + edit_draw->queue_redraw(); popup_centered_ratio(0.5); request_center = true; @@ -946,20 +948,80 @@ void TextureRegionEditor::_texture_changed() { } void TextureRegionEditor::_edit_region() { + CanvasItem::TextureFilter filter = CanvasItem::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS; + Ref<Texture2D> texture = nullptr; if (atlas_tex.is_valid()) { texture = atlas_tex->get_atlas(); } else if (node_sprite_2d) { texture = node_sprite_2d->get_texture(); + filter = node_sprite_2d->get_texture_filter_in_tree(); } else if (node_sprite_3d) { texture = node_sprite_3d->get_texture(); + + StandardMaterial3D::TextureFilter filter_3d = node_sprite_3d->get_texture_filter(); + + switch (filter_3d) { + case StandardMaterial3D::TEXTURE_FILTER_NEAREST: + filter = CanvasItem::TEXTURE_FILTER_NEAREST; + break; + case StandardMaterial3D::TEXTURE_FILTER_LINEAR: + filter = CanvasItem::TEXTURE_FILTER_LINEAR; + break; + case StandardMaterial3D::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS: + filter = CanvasItem::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS; + break; + case StandardMaterial3D::TEXTURE_FILTER_LINEAR_WITH_MIPMAPS: + filter = CanvasItem::TEXTURE_FILTER_LINEAR_WITH_MIPMAPS; + break; + case StandardMaterial3D::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS_ANISOTROPIC: + filter = CanvasItem::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS_ANISOTROPIC; + break; + case StandardMaterial3D::TEXTURE_FILTER_LINEAR_WITH_MIPMAPS_ANISOTROPIC: + filter = CanvasItem::TEXTURE_FILTER_LINEAR_WITH_MIPMAPS_ANISOTROPIC; + break; + default: + // fallback to project default + filter = CanvasItem::TEXTURE_FILTER_PARENT_NODE; + break; + } } else if (node_ninepatch) { texture = node_ninepatch->get_texture(); + filter = node_ninepatch->get_texture_filter_in_tree(); } else if (obj_styleBox.is_valid()) { texture = obj_styleBox->get_texture(); } + // occurs when get_texture_filter_in_tree reaches the scene root + if (filter == CanvasItem::TEXTURE_FILTER_PARENT_NODE) { + SubViewport *root = EditorNode::get_singleton()->get_scene_root(); + + if (root != nullptr) { + Viewport::DefaultCanvasItemTextureFilter filter_default = root->get_default_canvas_item_texture_filter(); + + // depending on default filter, set filter to match, otherwise fall back on nearest w/ mipmaps + switch (filter_default) { + case DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_NEAREST: + filter = CanvasItem::TEXTURE_FILTER_NEAREST; + break; + case DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_LINEAR: + filter = CanvasItem::TEXTURE_FILTER_LINEAR; + break; + case DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_LINEAR_WITH_MIPMAPS: + filter = CanvasItem::TEXTURE_FILTER_LINEAR_WITH_MIPMAPS; + break; + case DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_NEAREST_WITH_MIPMAPS: + default: + filter = CanvasItem::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS; + break; + } + } else { + filter = CanvasItem::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS; + } + } + if (texture.is_null()) { + preview_tex->set_diffuse_texture(nullptr); _zoom_reset(); hscroll->hide(); vscroll->hide(); @@ -967,6 +1029,9 @@ void TextureRegionEditor::_edit_region() { return; } + preview_tex->set_texture_filter(filter); + preview_tex->set_diffuse_texture(texture); + if (cache_map.has(texture->get_rid())) { autoslice_cache = cache_map[texture->get_rid()]; autoslice_is_dirty = false; @@ -1002,6 +1067,8 @@ TextureRegionEditor::TextureRegionEditor() { atlas_tex = Ref<AtlasTexture>(nullptr); undo_redo = EditorNode::get_singleton()->get_undo_redo(); + preview_tex = Ref<CanvasTexture>(memnew(CanvasTexture)); + snap_step = Vector2(10, 10); snap_separation = Vector2(0, 0); snap_mode = SNAP_NONE; diff --git a/editor/plugins/texture_region_editor_plugin.h b/editor/plugins/texture_region_editor_plugin.h index e3bbaf49fc..7eda4f469f 100644 --- a/editor/plugins/texture_region_editor_plugin.h +++ b/editor/plugins/texture_region_editor_plugin.h @@ -86,6 +86,8 @@ class TextureRegionEditor : public AcceptDialog { Ref<StyleBoxTexture> obj_styleBox; Ref<AtlasTexture> atlas_tex; + Ref<CanvasTexture> preview_tex; + Rect2 rect; Rect2 rect_prev; float prev_margin = 0.0f; diff --git a/main/main.cpp b/main/main.cpp index ac23086f36..2a08b03b96 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -1543,6 +1543,11 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph if (default_renderer_mobile.is_empty()) { default_renderer_mobile = "gl_compatibility"; } + // Default to Compatibility when using the project manager. + if (rendering_driver.is_empty() && rendering_method.is_empty() && project_manager) { + rendering_driver = "opengl3"; + rendering_method = "gl_compatibility"; + } #endif if (renderer_hints.is_empty()) { ERR_PRINT("No renderers available."); diff --git a/modules/enet/doc_classes/ENetMultiplayerPeer.xml b/modules/enet/doc_classes/ENetMultiplayerPeer.xml index f02fc893fd..2e0d1f5079 100644 --- a/modules/enet/doc_classes/ENetMultiplayerPeer.xml +++ b/modules/enet/doc_classes/ENetMultiplayerPeer.xml @@ -21,13 +21,6 @@ [b]Note:[/b] The [code]host[/code] must have exactly one peer in the [constant ENetPacketPeer.STATE_CONNECTED] state. </description> </method> - <method name="close_connection"> - <return type="void" /> - <param index="0" name="wait_usec" type="int" default="100" /> - <description> - Closes the connection. Ignored if no connection is currently established. If this is a server it tries to notify all clients before forcibly disconnecting them. If this is a client it simply closes the connection to the server. - </description> - </method> <method name="create_client"> <return type="int" enum="Error" /> <param index="0" name="address" type="String" /> @@ -37,7 +30,7 @@ <param index="4" name="out_bandwidth" type="int" default="0" /> <param index="5" name="local_port" type="int" default="0" /> <description> - Create client that connects to a server at [code]address[/code] using specified [code]port[/code]. The given address needs to be either a fully qualified domain name (e.g. [code]"www.example.com"[/code]) or an IP address in IPv4 or IPv6 format (e.g. [code]"192.168.1.1"[/code]). The [code]port[/code] is the port the server is listening on. The [code]channel_count[/code] parameter can be used to specify the number of ENet channels allocated for the connection. The [code]in_bandwidth[/code] and [code]out_bandwidth[/code] parameters can be used to limit the incoming and outgoing bandwidth to the given number of bytes per second. The default of 0 means unlimited bandwidth. Note that ENet will strategically drop packets on specific sides of a connection between peers to ensure the peer's bandwidth is not overwhelmed. The bandwidth parameters also determine the window size of a connection which limits the amount of reliable packets that may be in transit at any given time. Returns [constant OK] if a client was created, [constant ERR_ALREADY_IN_USE] if this ENetMultiplayerPeer instance already has an open connection (in which case you need to call [method close_connection] first) or [constant ERR_CANT_CREATE] if the client could not be created. If [code]local_port[/code] is specified, the client will also listen to the given port; this is useful for some NAT traversal techniques. + Create client that connects to a server at [code]address[/code] using specified [code]port[/code]. The given address needs to be either a fully qualified domain name (e.g. [code]"www.example.com"[/code]) or an IP address in IPv4 or IPv6 format (e.g. [code]"192.168.1.1"[/code]). The [code]port[/code] is the port the server is listening on. The [code]channel_count[/code] parameter can be used to specify the number of ENet channels allocated for the connection. The [code]in_bandwidth[/code] and [code]out_bandwidth[/code] parameters can be used to limit the incoming and outgoing bandwidth to the given number of bytes per second. The default of 0 means unlimited bandwidth. Note that ENet will strategically drop packets on specific sides of a connection between peers to ensure the peer's bandwidth is not overwhelmed. The bandwidth parameters also determine the window size of a connection which limits the amount of reliable packets that may be in transit at any given time. Returns [constant OK] if a client was created, [constant ERR_ALREADY_IN_USE] if this ENetMultiplayerPeer instance already has an open connection (in which case you need to call [method MultiplayerPeer.close] first) or [constant ERR_CANT_CREATE] if the client could not be created. If [code]local_port[/code] is specified, the client will also listen to the given port; this is useful for some NAT traversal techniques. </description> </method> <method name="create_mesh"> @@ -55,7 +48,7 @@ <param index="3" name="in_bandwidth" type="int" default="0" /> <param index="4" name="out_bandwidth" type="int" default="0" /> <description> - Create server that listens to connections via [code]port[/code]. The port needs to be an available, unused port between 0 and 65535. Note that ports below 1024 are privileged and may require elevated permissions depending on the platform. To change the interface the server listens on, use [method set_bind_ip]. The default IP is the wildcard [code]"*"[/code], which listens on all available interfaces. [code]max_clients[/code] is the maximum number of clients that are allowed at once, any number up to 4095 may be used, although the achievable number of simultaneous clients may be far lower and depends on the application. For additional details on the bandwidth parameters, see [method create_client]. Returns [constant OK] if a server was created, [constant ERR_ALREADY_IN_USE] if this ENetMultiplayerPeer instance already has an open connection (in which case you need to call [method close_connection] first) or [constant ERR_CANT_CREATE] if the server could not be created. + Create server that listens to connections via [code]port[/code]. The port needs to be an available, unused port between 0 and 65535. Note that ports below 1024 are privileged and may require elevated permissions depending on the platform. To change the interface the server listens on, use [method set_bind_ip]. The default IP is the wildcard [code]"*"[/code], which listens on all available interfaces. [code]max_clients[/code] is the maximum number of clients that are allowed at once, any number up to 4095 may be used, although the achievable number of simultaneous clients may be far lower and depends on the application. For additional details on the bandwidth parameters, see [method create_client]. Returns [constant OK] if a server was created, [constant ERR_ALREADY_IN_USE] if this ENetMultiplayerPeer instance already has an open connection (in which case you need to call [method MultiplayerPeer.close] first) or [constant ERR_CANT_CREATE] if the server could not be created. </description> </method> <method name="get_peer" qualifiers="const"> diff --git a/modules/enet/enet_multiplayer_peer.cpp b/modules/enet/enet_multiplayer_peer.cpp index c93db3b972..31ae643b59 100644 --- a/modules/enet/enet_multiplayer_peer.cpp +++ b/modules/enet/enet_multiplayer_peer.cpp @@ -146,92 +146,24 @@ void ENetMultiplayerPeer::_store_packet(int32_t p_source, ENetConnection::Event incoming_packets.push_back(packet); } -bool ENetMultiplayerPeer::_parse_server_event(ENetConnection::EventType p_type, ENetConnection::Event &p_event) { - switch (p_type) { - case ENetConnection::EVENT_CONNECT: { - if (is_refusing_new_connections()) { - p_event.peer->reset(); - return false; - } - // Client joined with invalid ID, probably trying to exploit us. - if (p_event.data < 2 || peers.has((int)p_event.data)) { - p_event.peer->reset(); - return false; - } - int id = p_event.data; - p_event.peer->set_meta(SNAME("_net_id"), id); - peers[id] = p_event.peer; - - emit_signal(SNAME("peer_connected"), id); - return false; - } - case ENetConnection::EVENT_DISCONNECT: { - int id = p_event.peer->get_meta(SNAME("_net_id")); - if (!peers.has(id)) { - // Never fully connected. - return false; - } - - emit_signal(SNAME("peer_disconnected"), id); - peers.erase(id); - return false; - } - case ENetConnection::EVENT_RECEIVE: { - int32_t source = p_event.peer->get_meta(SNAME("_net_id")); - _store_packet(source, p_event); - return false; +void ENetMultiplayerPeer::_disconnect_inactive_peers() { + HashSet<int> to_drop; + for (const KeyValue<int, Ref<ENetPacketPeer>> &E : peers) { + if (E.value->is_active()) { + continue; } - default: - return true; + to_drop.insert(E.key); } -} - -bool ENetMultiplayerPeer::_parse_client_event(ENetConnection::EventType p_type, ENetConnection::Event &p_event) { - switch (p_type) { - case ENetConnection::EVENT_CONNECT: { - connection_status = CONNECTION_CONNECTED; - emit_signal(SNAME("peer_connected"), 1); - emit_signal(SNAME("connection_succeeded")); - return false; + for (const int &P : to_drop) { + peers.erase(P); + if (hosts.has(P)) { + hosts.erase(P); } - case ENetConnection::EVENT_DISCONNECT: { - if (connection_status == CONNECTION_CONNECTED) { - // Client just disconnected from server. - emit_signal(SNAME("server_disconnected")); - } else { - emit_signal(SNAME("connection_failed")); - } - close_connection(); - return true; + if (active_mode == MODE_CLIENT) { + ERR_CONTINUE(P != TARGET_PEER_SERVER); + emit_signal(SNAME("server_disconnected")); } - case ENetConnection::EVENT_RECEIVE: { - _store_packet(1, p_event); - return false; - } - default: - return true; - } -} - -bool ENetMultiplayerPeer::_parse_mesh_event(ENetConnection::EventType p_type, ENetConnection::Event &p_event, int p_peer_id) { - switch (p_type) { - case ENetConnection::EVENT_CONNECT: - p_event.peer->reset(); - return false; - case ENetConnection::EVENT_DISCONNECT: - if (peers.has(p_peer_id)) { - emit_signal(SNAME("peer_disconnected"), p_peer_id); - peers.erase(p_peer_id); - } - hosts.erase(p_peer_id); - return true; - case ENetConnection::EVENT_RECEIVE: { - _store_packet(p_peer_id, p_event); - return false; - } break; - default: - // Nothing to do - return true; + emit_signal(SNAME("peer_disconnected"), P); } } @@ -240,74 +172,96 @@ void ENetMultiplayerPeer::poll() { _pop_current_packet(); + _disconnect_inactive_peers(); + switch (active_mode) { case MODE_CLIENT: { - if (peers.has(1) && !peers[1]->is_active()) { - if (connection_status == CONNECTION_CONNECTED) { - // Client just disconnected from server. - emit_signal(SNAME("server_disconnected")); - } else { - emit_signal(SNAME("connection_failed")); - } - close_connection(); + if (!peers.has(1)) { + close(); return; } ENetConnection::Event event; ENetConnection::EventType ret = hosts[0]->service(0, event); - if (ret == ENetConnection::EVENT_ERROR) { - return; - } do { - if (_parse_client_event(ret, event)) { - return; + if (ret == ENetConnection::EVENT_CONNECT) { + connection_status = CONNECTION_CONNECTED; + emit_signal(SNAME("peer_connected"), 1); + emit_signal(SNAME("connection_succeeded")); + } else if (ret == ENetConnection::EVENT_DISCONNECT) { + if (connection_status == CONNECTION_CONNECTED) { + // Client just disconnected from server. + emit_signal(SNAME("server_disconnected")); + emit_signal(SNAME("peer_disconnected"), 1); + } else { + emit_signal(SNAME("connection_failed")); + } + close(); + } else if (ret == ENetConnection::EVENT_RECEIVE) { + _store_packet(1, event); + } else if (ret != ENetConnection::EVENT_NONE) { + close(); // Error. } - } while (hosts[0]->check_events(ret, event) > 0); + } while (hosts.has(0) && hosts[0]->check_events(ret, event) > 0); } break; case MODE_SERVER: { - for (const KeyValue<int, Ref<ENetPacketPeer>> &E : peers) { - if (!(E.value->is_active())) { - emit_signal(SNAME("peer_disconnected"), E.value->get_meta(SNAME("_net_id"))); - peers.erase(E.key); - } - } ENetConnection::Event event; ENetConnection::EventType ret = hosts[0]->service(0, event); - if (ret == ENetConnection::EVENT_ERROR) { - return; - } do { - if (_parse_server_event(ret, event)) { - return; + if (ret == ENetConnection::EVENT_CONNECT) { + if (is_refusing_new_connections()) { + event.peer->reset(); + continue; + } + // Client joined with invalid ID, probably trying to exploit us. + if (event.data < 2 || peers.has((int)event.data)) { + event.peer->reset(); + continue; + } + int id = event.data; + event.peer->set_meta(SNAME("_net_id"), id); + peers[id] = event.peer; + emit_signal(SNAME("peer_connected"), id); + } else if (ret == ENetConnection::EVENT_DISCONNECT) { + int id = event.peer->get_meta(SNAME("_net_id")); + if (!peers.has(id)) { + // Never fully connected. + continue; + } + emit_signal(SNAME("peer_disconnected"), id); + peers.erase(id); + } else if (ret == ENetConnection::EVENT_RECEIVE) { + int32_t source = event.peer->get_meta(SNAME("_net_id")); + _store_packet(source, event); + } else if (ret != ENetConnection::EVENT_NONE) { + close(); // Error } - } while (hosts[0]->check_events(ret, event) > 0); + } while (hosts.has(0) && hosts[0]->check_events(ret, event) > 0); } break; case MODE_MESH: { - for (const KeyValue<int, Ref<ENetPacketPeer>> &E : peers) { - if (!(E.value->is_active())) { - emit_signal(SNAME("peer_disconnected"), E.key); - peers.erase(E.key); - if (hosts.has(E.key)) { - hosts.erase(E.key); - } - } - } + HashSet<int> to_drop; for (KeyValue<int, Ref<ENetConnection>> &E : hosts) { ENetConnection::Event event; ENetConnection::EventType ret = E.value->service(0, event); - if (ret == ENetConnection::EVENT_ERROR) { - if (peers.has(E.key)) { - emit_signal(SNAME("peer_disconnected"), E.key); - peers.erase(E.key); - } - hosts.erase(E.key); - continue; - } do { - if (_parse_mesh_event(ret, event, E.key)) { + if (ret == ENetConnection::EVENT_CONNECT) { + event.peer->reset(); + } else if (ret == ENetConnection::EVENT_RECEIVE) { + _store_packet(E.key, event); + } else if (ret == ENetConnection::EVENT_NONE) { + break; // Keep polling the others. + } else { + to_drop.insert(E.key); // Error or disconnect. break; // Keep polling the others. } } while (E.value->check_events(ret, event) > 0); } + for (const int &P : to_drop) { + if (peers.has(P)) { + emit_signal(SNAME("peer_disconnected"), P); + peers.erase(P); + } + hosts.erase(P); + } } break; default: return; @@ -322,29 +276,41 @@ bool ENetMultiplayerPeer::is_server_relay_supported() const { return active_mode == MODE_SERVER || active_mode == MODE_CLIENT; } -void ENetMultiplayerPeer::close_connection(uint32_t wait_usec) { +void ENetMultiplayerPeer::disconnect_peer(int p_peer, bool p_force) { + ERR_FAIL_COND(!_is_active() || !peers.has(p_peer)); + peers[p_peer]->peer_disconnect(0); // Will be removed during next poll. + if (active_mode == MODE_CLIENT || active_mode == MODE_SERVER) { + hosts[0]->flush(); + } else { + ERR_FAIL_COND(!hosts.has(p_peer)); + hosts[p_peer]->flush(); + } + if (p_force) { + peers.erase(p_peer); + if (hosts.has(p_peer)) { + hosts.erase(p_peer); + } + if (active_mode == MODE_CLIENT) { + hosts.clear(); // Avoid flushing again. + close(); + } + } +} + +void ENetMultiplayerPeer::close() { if (!_is_active()) { return; } _pop_current_packet(); - bool peers_disconnected = false; for (KeyValue<int, Ref<ENetPacketPeer>> &E : peers) { if (E.value.is_valid() && E.value->get_state() == ENetPacketPeer::STATE_CONNECTED) { - E.value->peer_disconnect_now(unique_id); - peers_disconnected = true; + E.value->peer_disconnect_now(0); } } - - if (peers_disconnected) { - for (KeyValue<int, Ref<ENetConnection>> &E : hosts) { - E.value->flush(); - } - - if (wait_usec > 0) { - OS::get_singleton()->delay_usec(wait_usec); // Wait for disconnection packets to send - } + for (KeyValue<int, Ref<ENetConnection>> &E : hosts) { + E.value->flush(); } active_mode = MODE_NONE; @@ -516,7 +482,6 @@ void ENetMultiplayerPeer::_bind_methods() { ClassDB::bind_method(D_METHOD("create_client", "address", "port", "channel_count", "in_bandwidth", "out_bandwidth", "local_port"), &ENetMultiplayerPeer::create_client, DEFVAL(0), DEFVAL(0), DEFVAL(0), DEFVAL(0)); ClassDB::bind_method(D_METHOD("create_mesh", "unique_id"), &ENetMultiplayerPeer::create_mesh); ClassDB::bind_method(D_METHOD("add_mesh_peer", "peer_id", "host"), &ENetMultiplayerPeer::add_mesh_peer); - ClassDB::bind_method(D_METHOD("close_connection", "wait_usec"), &ENetMultiplayerPeer::close_connection, DEFVAL(100)); ClassDB::bind_method(D_METHOD("set_bind_ip", "ip"), &ENetMultiplayerPeer::set_bind_ip); ClassDB::bind_method(D_METHOD("get_host"), &ENetMultiplayerPeer::get_host); @@ -531,7 +496,7 @@ ENetMultiplayerPeer::ENetMultiplayerPeer() { ENetMultiplayerPeer::~ENetMultiplayerPeer() { if (_is_active()) { - close_connection(); + close(); } } diff --git a/modules/enet/enet_multiplayer_peer.h b/modules/enet/enet_multiplayer_peer.h index 85692e8eee..2665b69669 100644 --- a/modules/enet/enet_multiplayer_peer.h +++ b/modules/enet/enet_multiplayer_peer.h @@ -83,9 +83,7 @@ private: void _store_packet(int32_t p_source, ENetConnection::Event &p_event); void _pop_current_packet(); - bool _parse_server_event(ENetConnection::EventType p_event_type, ENetConnection::Event &p_event); - bool _parse_client_event(ENetConnection::EventType p_event_type, ENetConnection::Event &p_event); - bool _parse_mesh_event(ENetConnection::EventType p_event_type, ENetConnection::Event &p_event, int p_peer_id); + void _disconnect_inactive_peers(); void _destroy_unused(ENetPacket *p_packet); _FORCE_INLINE_ bool _is_active() const { return active_mode != MODE_NONE; } @@ -102,6 +100,9 @@ public: virtual int get_packet_channel() const override; virtual void poll() override; + virtual void close() override; + virtual void disconnect_peer(int p_peer, bool p_force = false) override; + virtual bool is_server() const override; virtual bool is_server_relay_supported() const override; @@ -122,10 +123,6 @@ public: Error create_mesh(int p_id); Error add_mesh_peer(int p_id, Ref<ENetConnection> p_host); - void close_connection(uint32_t wait_usec = 100); - - void disconnect_peer(int p_peer, bool now = false); - void set_bind_ip(const IPAddress &p_ip); Ref<ENetConnection> get_host() const; diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 1401e4b94b..704dda8045 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -985,21 +985,26 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class) { if (getter_function == nullptr) { push_error(vformat(R"(Getter "%s" not found.)", member.variable->getter_pointer->name), member.variable); - - } else if (getter_function->parameters.size() != 0 || getter_function->datatype.has_no_type()) { - push_error(vformat(R"(Function "%s" cannot be used as getter because of its signature.)", getter_function->identifier->name), member.variable); - - } else if (!is_type_compatible(member.variable->datatype, getter_function->datatype, true)) { - push_error(vformat(R"(Function with return type "%s" cannot be used as getter for a property of type "%s".)", getter_function->datatype.to_string(), member.variable->datatype.to_string()), member.variable); - } else { - has_valid_getter = true; + GDScriptParser::DataType return_datatype = getter_function->datatype; + if (getter_function->return_type != nullptr) { + return_datatype = getter_function->return_type->datatype; + return_datatype.is_meta_type = false; + } + + if (getter_function->parameters.size() != 0 || return_datatype.has_no_type()) { + push_error(vformat(R"(Function "%s" cannot be used as getter because of its signature.)", getter_function->identifier->name), member.variable); + } else if (!is_type_compatible(member.variable->datatype, return_datatype, true)) { + push_error(vformat(R"(Function with return type "%s" cannot be used as getter for a property of type "%s".)", return_datatype.to_string(), member.variable->datatype.to_string()), member.variable); + } else { + has_valid_getter = true; #ifdef DEBUG_ENABLED - if (member.variable->datatype.builtin_type == Variant::INT && getter_function->datatype.builtin_type == Variant::FLOAT) { - parser->push_warning(member.variable, GDScriptWarning::NARROWING_CONVERSION); - } + if (member.variable->datatype.builtin_type == Variant::INT && return_datatype.builtin_type == Variant::FLOAT) { + parser->push_warning(member.variable, GDScriptWarning::NARROWING_CONVERSION); + } #endif + } } } @@ -1377,7 +1382,7 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) { if (all_is_constant) { switch (args.size()) { case 1: - reduced = args[0]; + reduced = (int32_t)args[0]; break; case 2: reduced = Vector2i(args[0], args[1]); diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.gd b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.gd index c6645c2c34..809d0d28a9 100644 --- a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.gd +++ b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.gd @@ -69,6 +69,10 @@ func test(): value = Transform3D() print(value == null) + # Projection + value = Projection() + print(value == null) + # Color value = Color() print(value == null) diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.out b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.out index 639f6027b9..27423ab8e7 100644 --- a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.out +++ b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.out @@ -33,3 +33,4 @@ false false false false +false diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.gd b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.gd index ee622bf22f..f46afb0f18 100644 --- a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.gd +++ b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.gd @@ -69,6 +69,10 @@ func test(): value = Transform3D() print(value != null) + # Projection + value = Projection() + print(value != null) + # Color value = Color() print(value != null) diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.out b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.out index d1e332afba..a11c47854a 100644 --- a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.out +++ b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.out @@ -33,3 +33,4 @@ true true true true +true diff --git a/modules/gdscript/tests/scripts/runtime/features/range_optimized_in_for_has_int_iterator.gd b/modules/gdscript/tests/scripts/runtime/features/range_optimized_in_for_has_int_iterator.gd new file mode 100644 index 0000000000..e24137a20d --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/range_optimized_in_for_has_int_iterator.gd @@ -0,0 +1,60 @@ +func test(): + # All combinations of 1/2/3 arguments, each being int/float. + + for number in range(5): + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + for number in range(5.2): + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + + for number in range(1, 5): + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + for number in range(1, 5.2): + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + for number in range(1.2, 5): + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + for number in range(1.2, 5.2): + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + + for number in range(1, 5, 2): + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + for number in range(1, 5, 2.2): + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + for number in range(1, 5.2, 2): + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + for number in range(1, 5.2, 2.2): + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + for number in range(1.2, 5, 2): + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + for number in range(1.2, 5.2, 2): + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + for number in range(1.2, 5, 2.2): + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + for number in range(1.2, 5.2, 2.2): + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") diff --git a/modules/gdscript/tests/scripts/runtime/features/range_optimized_in_for_has_int_iterator.out b/modules/gdscript/tests/scripts/runtime/features/range_optimized_in_for_has_int_iterator.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/range_optimized_in_for_has_int_iterator.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/runtime/features/range_returns_ints.gd b/modules/gdscript/tests/scripts/runtime/features/range_returns_ints.gd new file mode 100644 index 0000000000..63c3b84305 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/range_returns_ints.gd @@ -0,0 +1,77 @@ +func test(): + # All combinations of 1/2/3 arguments, each being int/float. + # Store result in variable to ensure actual array is created (avoid `for` + `range` optimization). + + var result + + result = range(5) + for number in result: + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + result = range(5.2) + for number in result: + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + + result = range(1, 5) + for number in result: + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + result = range(1, 5.2) + for number in result: + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + result = range(1.2, 5) + for number in result: + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + result = range(1.2, 5.2) + for number in result: + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + + result = range(1, 5, 2) + for number in result: + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + result = range(1, 5, 2.2) + for number in result: + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + result = range(1, 5.2, 2) + for number in result: + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + result = range(1, 5.2, 2.2) + for number in result: + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + result = range(1.2, 5, 2) + for number in result: + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + result = range(1.2, 5.2, 2) + for number in result: + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + result = range(1.2, 5, 2.2) + for number in result: + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + result = range(1.2, 5.2, 2.2) + for number in result: + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") diff --git a/modules/gdscript/tests/scripts/runtime/features/range_returns_ints.out b/modules/gdscript/tests/scripts/runtime/features/range_returns_ints.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/range_returns_ints.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs index c4eb3b6ad0..5dd629aeb0 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs @@ -543,6 +543,30 @@ namespace Godot } } + public Quaternion(Vector3 arcFrom, Vector3 arcTo) + { + Vector3 c = arcFrom.Cross(arcTo); + real_t d = arcFrom.Dot(arcTo); + + if (d < -1.0f + Mathf.Epsilon) + { + x = 0f; + y = 1f; + z = 0f; + w = 0f; + } + else + { + real_t s = Mathf.Sqrt((1.0f + d) * 2.0f); + real_t rs = 1.0f / s; + + x = c.x * rs; + y = c.y * rs; + z = c.z * rs; + w = s * 0.5f; + } + } + /// <summary> /// Constructs a <see cref="Quaternion"/> that will perform a rotation specified by /// Euler angles (in the YXZ convention: when decomposing, first Z, then X, and Y last), diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index f310f46af9..155da3c67b 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -5072,6 +5072,8 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star p_sd->ascent = MAX(p_sd->ascent, get_hex_code_box_size(fs, gl.index).y); } else { gl.advance = get_hex_code_box_size(fs, gl.index).y; + gl.y_off = get_hex_code_box_size(fs, gl.index).y; + gl.x_off = -Math::round(get_hex_code_box_size(fs, gl.index).x * 0.5); p_sd->ascent = MAX(p_sd->ascent, Math::round(get_hex_code_box_size(fs, gl.index).x * 0.5)); p_sd->descent = MAX(p_sd->descent, Math::round(get_hex_code_box_size(fs, gl.index).x * 0.5)); } diff --git a/modules/tga/image_loader_tga.cpp b/modules/tga/image_loader_tga.cpp index a6fc650414..8adde3c5d3 100644 --- a/modules/tga/image_loader_tga.cpp +++ b/modules/tga/image_loader_tga.cpp @@ -284,14 +284,21 @@ Error ImageLoaderTGA::load_image(Ref<Image> p_image, Ref<FileAccess> f, BitField err = FAILED; } + uint64_t color_map_size; if (has_color_map) { if (tga_header.color_map_length > 256 || (tga_header.color_map_depth != 24) || tga_header.color_map_type != 1) { err = FAILED; } + color_map_size = tga_header.color_map_length * (tga_header.color_map_depth >> 3); } else { if (tga_header.color_map_type) { err = FAILED; } + color_map_size = 0; + } + + if ((src_image_len - f->get_position()) < (tga_header.id_length + color_map_size)) { + err = FAILED; // TGA data appears to be truncated (fewer bytes than expected). } if (tga_header.image_width <= 0 || tga_header.image_height <= 0) { @@ -308,7 +315,6 @@ Error ImageLoaderTGA::load_image(Ref<Image> p_image, Ref<FileAccess> f, BitField Vector<uint8_t> palette; if (has_color_map) { - size_t color_map_size = tga_header.color_map_length * (tga_header.color_map_depth >> 3); err = palette.resize(color_map_size); if (err == OK) { uint8_t *palette_w = palette.ptrw(); diff --git a/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml b/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml index 0b42c6ed35..91ee65e9bd 100644 --- a/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml +++ b/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml @@ -22,12 +22,6 @@ Three channels will be created for reliable, unreliable, and ordered transport. The value of [code]unreliable_lifetime[/code] will be passed to the [code]maxPacketLifetime[/code] option when creating unreliable and ordered channels (see [method WebRTCPeerConnection.create_data_channel]). </description> </method> - <method name="close"> - <return type="void" /> - <description> - Close all the add peer connections and channels, freeing all resources. - </description> - </method> <method name="create_client"> <return type="int" enum="Error" /> <param index="0" name="peer_id" type="int" /> diff --git a/modules/webrtc/webrtc_multiplayer_peer.cpp b/modules/webrtc/webrtc_multiplayer_peer.cpp index 5ea81d5a1b..163b17fa6f 100644 --- a/modules/webrtc/webrtc_multiplayer_peer.cpp +++ b/modules/webrtc/webrtc_multiplayer_peer.cpp @@ -42,7 +42,6 @@ void WebRTCMultiplayerPeer::_bind_methods() { ClassDB::bind_method(D_METHOD("has_peer", "peer_id"), &WebRTCMultiplayerPeer::has_peer); ClassDB::bind_method(D_METHOD("get_peer", "peer_id"), &WebRTCMultiplayerPeer::get_peer); ClassDB::bind_method(D_METHOD("get_peers"), &WebRTCMultiplayerPeer::get_peers); - ClassDB::bind_method(D_METHOD("close"), &WebRTCMultiplayerPeer::close); } void WebRTCMultiplayerPeer::set_target_peer(int p_peer_id) { @@ -352,6 +351,18 @@ void WebRTCMultiplayerPeer::remove_peer(int p_peer_id) { } } +void WebRTCMultiplayerPeer::disconnect_peer(int p_peer_id, bool p_force) { + ERR_FAIL_COND(!peer_map.has(p_peer_id)); + if (p_force) { + peer_map.erase(p_peer_id); + if (network_mode == MODE_CLIENT && p_peer_id == TARGET_PEER_SERVER) { + connection_status = CONNECTION_DISCONNECTED; + } + } else { + peer_map[p_peer_id]->connection->close(); // Will be removed during next poll. + } +} + Error WebRTCMultiplayerPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { // Peer not available if (next_packet_peer == 0 || !peer_map.has(next_packet_peer)) { diff --git a/modules/webrtc/webrtc_multiplayer_peer.h b/modules/webrtc/webrtc_multiplayer_peer.h index 3f608200fd..0556ef029c 100644 --- a/modules/webrtc/webrtc_multiplayer_peer.h +++ b/modules/webrtc/webrtc_multiplayer_peer.h @@ -98,28 +98,29 @@ public: bool has_peer(int p_peer_id); Dictionary get_peer(int p_peer_id); Dictionary get_peers(); - void close(); // PacketPeer - Error get_packet(const uint8_t **r_buffer, int &r_buffer_size) override; ///< buffer is GONE after next get_packet - Error put_packet(const uint8_t *p_buffer, int p_buffer_size) override; - int get_available_packet_count() const override; - int get_max_packet_size() const override; + virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size) override; ///< buffer is GONE after next get_packet + virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size) override; + virtual int get_available_packet_count() const override; + virtual int get_max_packet_size() const override; // MultiplayerPeer - void set_target_peer(int p_peer_id) override; + virtual void set_target_peer(int p_peer_id) override; - int get_unique_id() const override; - int get_packet_peer() const override; - int get_packet_channel() const override; - TransferMode get_packet_mode() const override; + virtual int get_unique_id() const override; + virtual int get_packet_peer() const override; + virtual int get_packet_channel() const override; + virtual TransferMode get_packet_mode() const override; - bool is_server() const override; - bool is_server_relay_supported() const override; + virtual bool is_server() const override; + virtual bool is_server_relay_supported() const override; - void poll() override; + virtual void poll() override; + virtual void close() override; + virtual void disconnect_peer(int p_peer_id, bool p_force = false) override; - ConnectionStatus get_connection_status() const override; + virtual ConnectionStatus get_connection_status() const override; }; #endif // WEBRTC_MULTIPLAYER_PEER_H diff --git a/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml b/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml index c4481b046b..7e896a0ca3 100644 --- a/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml +++ b/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml @@ -10,13 +10,6 @@ <tutorials> </tutorials> <methods> - <method name="close"> - <return type="void" /> - <description> - Closes this [MultiplayerPeer], resetting the state to [constant MultiplayerPeer.CONNECTION_CONNECTED]. - [b]Note:[/b] To make sure remote peers receive a clean close prefer disconnecting clients via [method disconnect_peer]. - </description> - </method> <method name="create_client"> <return type="int" enum="Error" /> <param index="0" name="url" type="String" /> @@ -37,15 +30,6 @@ Starts a new multiplayer server listening on the given [param port]. You can optionally specify a [param bind_address], and provide a [param tls_key] and [param tls_certificate] to use TLS. </description> </method> - <method name="disconnect_peer"> - <return type="void" /> - <param index="0" name="id" type="int" /> - <param index="1" name="code" type="int" default="1000" /> - <param index="2" name="reason" type="String" default="""" /> - <description> - Disconnects the peer identified by [code]id[/code] from the server. See [method WebSocketPeer.close] for more information. - </description> - </method> <method name="get_peer" qualifiers="const"> <return type="WebSocketPeer" /> <param index="0" name="peer_id" type="int" /> diff --git a/modules/websocket/websocket_multiplayer_peer.cpp b/modules/websocket/websocket_multiplayer_peer.cpp index 0202c5bac7..827c618e4e 100644 --- a/modules/websocket/websocket_multiplayer_peer.cpp +++ b/modules/websocket/websocket_multiplayer_peer.cpp @@ -75,12 +75,10 @@ void WebSocketMultiplayerPeer::_clear() { void WebSocketMultiplayerPeer::_bind_methods() { ClassDB::bind_method(D_METHOD("create_client", "url", "verify_tls", "tls_certificate"), &WebSocketMultiplayerPeer::create_client, DEFVAL(true), DEFVAL(Ref<X509Certificate>())); ClassDB::bind_method(D_METHOD("create_server", "port", "bind_address", "tls_key", "tls_certificate"), &WebSocketMultiplayerPeer::create_server, DEFVAL("*"), DEFVAL(Ref<CryptoKey>()), DEFVAL(Ref<X509Certificate>())); - ClassDB::bind_method(D_METHOD("close"), &WebSocketMultiplayerPeer::close); ClassDB::bind_method(D_METHOD("get_peer", "peer_id"), &WebSocketMultiplayerPeer::get_peer); ClassDB::bind_method(D_METHOD("get_peer_address", "id"), &WebSocketMultiplayerPeer::get_peer_address); ClassDB::bind_method(D_METHOD("get_peer_port", "id"), &WebSocketMultiplayerPeer::get_peer_port); - ClassDB::bind_method(D_METHOD("disconnect_peer", "id", "code", "reason"), &WebSocketMultiplayerPeer::disconnect_peer, DEFVAL(1000), DEFVAL("")); ClassDB::bind_method(D_METHOD("get_supported_protocols"), &WebSocketMultiplayerPeer::get_supported_protocols); ClassDB::bind_method(D_METHOD("set_supported_protocols", "protocols"), &WebSocketMultiplayerPeer::set_supported_protocols); @@ -488,9 +486,15 @@ int WebSocketMultiplayerPeer::get_peer_port(int p_peer_id) const { return peers_map[p_peer_id]->get_connected_port(); } -void WebSocketMultiplayerPeer::disconnect_peer(int p_peer_id, int p_code, String p_reason) { +void WebSocketMultiplayerPeer::disconnect_peer(int p_peer_id, bool p_force) { ERR_FAIL_COND(!peers_map.has(p_peer_id)); - peers_map[p_peer_id]->close(p_code, p_reason); + peers_map[p_peer_id]->close(); + if (p_force) { + peers_map.erase(p_peer_id); + if (!is_server()) { + _clear(); + } + } } void WebSocketMultiplayerPeer::close() { diff --git a/modules/websocket/websocket_multiplayer_peer.h b/modules/websocket/websocket_multiplayer_peer.h index ebe013a7bf..78a58162ab 100644 --- a/modules/websocket/websocket_multiplayer_peer.h +++ b/modules/websocket/websocket_multiplayer_peer.h @@ -102,6 +102,9 @@ public: virtual int get_max_packet_size() const override; virtual bool is_server() const override; virtual void poll() override; + virtual void close() override; + virtual void disconnect_peer(int p_peer_id, bool p_force = false) override; + virtual ConnectionStatus get_connection_status() const override; /* PacketPeer */ @@ -132,8 +135,6 @@ public: IPAddress get_peer_address(int p_peer_id) const; int get_peer_port(int p_peer_id) const; - void disconnect_peer(int p_peer_id, int p_code = 1000, String p_reason = ""); - void close(); void set_max_queued_packets(int p_max_queued_packets); int get_max_queued_packets() const; diff --git a/modules/zip/zip_packer.cpp b/modules/zip/zip_packer.cpp index c37fc0945e..e62700f191 100644 --- a/modules/zip/zip_packer.cpp +++ b/modules/zip/zip_packer.cpp @@ -50,7 +50,6 @@ Error ZIPPacker::close() { } Error ZIPPacker::start_file(String p_path) { - ERR_FAIL_COND_V_MSG(zf != NULL, FAILED, "ZIPPacker is already in use."); ERR_FAIL_COND_V_MSG(fa.is_null(), FAILED, "ZIPPacker must be opened before use."); zip_fileinfo zipfi; diff --git a/platform/ios/os_ios.mm b/platform/ios/os_ios.mm index a674498620..b6b94d2f5e 100644 --- a/platform/ios/os_ios.mm +++ b/platform/ios/os_ios.mm @@ -396,7 +396,14 @@ void OS_IOS::vibrate_handheld(int p_duration_ms) { } bool OS_IOS::_check_internal_feature_support(const String &p_feature) { - return p_feature == "mobile"; + if (p_feature == "system_fonts") { + return true; + } + if (p_feature == "mobile") { + return true; + } + + return false; } void OS_IOS::on_focus_out() { diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index 11b667fcef..db9b3ed77f 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -481,7 +481,16 @@ Error OS_LinuxBSD::shell_open(String p_uri) { } bool OS_LinuxBSD::_check_internal_feature_support(const String &p_feature) { - return p_feature == "pc"; +#ifdef FONTCONFIG_ENABLED + if (p_feature == "system_fonts") { + return font_config_initialized; + } +#endif + if (p_feature == "pc") { + return true; + } + + return false; } uint64_t OS_LinuxBSD::get_embedded_pck_offset() const { diff --git a/platform/macos/os_macos.mm b/platform/macos/os_macos.mm index 8ffb0abfdb..e620b058d3 100644 --- a/platform/macos/os_macos.mm +++ b/platform/macos/os_macos.mm @@ -499,7 +499,14 @@ String OS_MacOS::get_unique_id() const { } bool OS_MacOS::_check_internal_feature_support(const String &p_feature) { - return p_feature == "pc"; + if (p_feature == "system_fonts") { + return true; + } + if (p_feature == "pc") { + return true; + } + + return false; } void OS_MacOS::disable_crash_handler() { diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index d95a88fac1..36fd86c4f5 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -1181,7 +1181,14 @@ String OS_Windows::get_unique_id() const { } bool OS_Windows::_check_internal_feature_support(const String &p_feature) { - return p_feature == "pc"; + if (p_feature == "system_fonts") { + return true; + } + if (p_feature == "pc") { + return true; + } + + return false; } void OS_Windows::disable_crash_handler() { diff --git a/scene/2d/path_2d.cpp b/scene/2d/path_2d.cpp index c1044fdf5b..b5945a4562 100644 --- a/scene/2d/path_2d.cpp +++ b/scene/2d/path_2d.cpp @@ -175,51 +175,18 @@ void PathFollow2D::_update_transform() { if (path_length == 0) { return; } - Vector2 pos = c->sample_baked(progress, cubic); if (rotates) { - real_t ahead = progress + lookahead; - - if (loop && ahead >= path_length) { - // If our lookahead will loop, we need to check if the path is closed. - int point_count = c->get_point_count(); - if (point_count > 0) { - Vector2 start_point = c->get_point_position(0); - Vector2 end_point = c->get_point_position(point_count - 1); - if (start_point == end_point) { - // Since the path is closed we want to 'smooth off' - // the corner at the start/end. - // So we wrap the lookahead back round. - ahead = Math::fmod(ahead, path_length); - } - } - } - - Vector2 ahead_pos = c->sample_baked(ahead, cubic); - - Vector2 tangent_to_curve; - if (ahead_pos == pos) { - // This will happen at the end of non-looping or non-closed paths. - // We'll try a look behind instead, in order to get a meaningful angle. - tangent_to_curve = - (pos - c->sample_baked(progress - lookahead, cubic)).normalized(); - } else { - tangent_to_curve = (ahead_pos - pos).normalized(); - } - - Vector2 normal_of_curve = -tangent_to_curve.orthogonal(); - - pos += tangent_to_curve * h_offset; - pos += normal_of_curve * v_offset; - - set_rotation(tangent_to_curve.angle()); - + Transform2D xform = c->sample_baked_with_rotation(progress, cubic, loop, lookahead); + xform.translate_local(v_offset, h_offset); + set_rotation(xform[1].angle()); + set_position(xform[2]); } else { + Vector2 pos = c->sample_baked(progress, cubic); pos.x += h_offset; pos.y += v_offset; + set_position(pos); } - - set_position(pos); } void PathFollow2D::_notification(int p_what) { diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp index be6eab2178..d69953fee5 100644 --- a/scene/3d/sprite_3d.cpp +++ b/scene/3d/sprite_3d.cpp @@ -680,6 +680,7 @@ void Sprite3D::set_region_enabled(bool p_region) { region = p_region; _queue_redraw(); + notify_property_list_changed(); } bool Sprite3D::is_region_enabled() const { @@ -781,6 +782,10 @@ void Sprite3D::_validate_property(PropertyInfo &p_property) const { if (p_property.name == "frame_coords") { p_property.usage |= PROPERTY_USAGE_KEYING_INCREMENTS; } + + if (!region && (p_property.name == "region_rect")) { + p_property.usage = PROPERTY_USAGE_NO_EDITOR; + } } void Sprite3D::_bind_methods() { diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp index 552345e4fe..1082fc4d6d 100644 --- a/scene/gui/base_button.cpp +++ b/scene/gui/base_button.cpp @@ -127,6 +127,7 @@ void BaseButton::_notification(int p_what) { status.hovering = false; status.press_attempt = false; status.pressing_inside = false; + status.shortcut_press = false; } break; } } @@ -160,6 +161,7 @@ void BaseButton::on_action_event(Ref<InputEvent> p_event) { if (action_mode == ACTION_MODE_BUTTON_PRESS) { status.press_attempt = false; status.pressing_inside = false; + status.shortcut_press = false; } status.pressed = !status.pressed; _unpress_group(); @@ -185,6 +187,7 @@ void BaseButton::on_action_event(Ref<InputEvent> p_event) { } status.press_attempt = false; status.pressing_inside = false; + status.shortcut_press = false; emit_signal(SNAME("button_up")); } @@ -209,6 +212,7 @@ void BaseButton::set_disabled(bool p_disabled) { } status.press_attempt = false; status.pressing_inside = false; + status.shortcut_press = false; } queue_redraw(); } @@ -284,7 +288,7 @@ BaseButton::DrawMode BaseButton::get_draw_mode() const { pressing = status.pressed; } - if (pressing) { + if ((shortcut_feedback || !status.shortcut_press) && pressing) { return DRAW_PRESSED; } else { return DRAW_NORMAL; @@ -350,6 +354,7 @@ void BaseButton::shortcut_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (!is_disabled() && is_visible_in_tree() && !p_event->is_echo() && shortcut.is_valid() && shortcut->matches_event(p_event)) { + status.shortcut_press = true; on_action_event(p_event); accept_event(); } @@ -389,6 +394,14 @@ bool BaseButton::_was_pressed_by_mouse() const { return was_mouse_pressed; } +void BaseButton::set_shortcut_feedback(bool p_feedback) { + shortcut_feedback = p_feedback; +} + +bool BaseButton::is_shortcut_feedback() const { + return shortcut_feedback; +} + void BaseButton::_bind_methods() { ClassDB::bind_method(D_METHOD("set_pressed", "pressed"), &BaseButton::set_pressed); ClassDB::bind_method(D_METHOD("is_pressed"), &BaseButton::is_pressed); @@ -414,6 +427,9 @@ void BaseButton::_bind_methods() { ClassDB::bind_method(D_METHOD("set_button_group", "button_group"), &BaseButton::set_button_group); ClassDB::bind_method(D_METHOD("get_button_group"), &BaseButton::get_button_group); + ClassDB::bind_method(D_METHOD("set_shortcut_feedback", "enabled"), &BaseButton::set_shortcut_feedback); + ClassDB::bind_method(D_METHOD("is_shortcut_feedback"), &BaseButton::is_shortcut_feedback); + GDVIRTUAL_BIND(_pressed); GDVIRTUAL_BIND(_toggled, "button_pressed"); @@ -430,6 +446,7 @@ void BaseButton::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "button_mask", PROPERTY_HINT_FLAGS, "Mouse Left, Mouse Right, Mouse Middle"), "set_button_mask", "get_button_mask"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keep_pressed_outside"), "set_keep_pressed_outside", "is_keep_pressed_outside"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut", PROPERTY_HINT_RESOURCE_TYPE, "Shortcut"), "set_shortcut", "get_shortcut"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_feedback"), "set_shortcut_feedback", "is_shortcut_feedback"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "button_group", PROPERTY_HINT_RESOURCE_TYPE, "ButtonGroup"), "set_button_group", "get_button_group"); BIND_ENUM_CONSTANT(DRAW_NORMAL); diff --git a/scene/gui/base_button.h b/scene/gui/base_button.h index 7839239800..3acf535f54 100644 --- a/scene/gui/base_button.h +++ b/scene/gui/base_button.h @@ -53,6 +53,7 @@ private: bool keep_pressed_outside = false; Ref<Shortcut> shortcut; ObjectID shortcut_context; + bool shortcut_feedback = true; ActionMode action_mode = ACTION_MODE_BUTTON_RELEASE; struct Status { @@ -60,6 +61,7 @@ private: bool hovering = false; bool press_attempt = false; bool pressing_inside = false; + bool shortcut_press = false; bool disabled = false; @@ -131,6 +133,9 @@ public: void set_button_group(const Ref<ButtonGroup> &p_group); Ref<ButtonGroup> get_button_group() const; + void set_shortcut_feedback(bool p_feedback); + bool is_shortcut_feedback() const; + BaseButton(); ~BaseButton(); }; diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index f61fa29a33..5f8f25154c 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -138,7 +138,7 @@ void CodeEdit::_notification(int p_what) { code_completion_scroll_rect.position = code_completion_rect.position + Vector2(code_completion_rect.size.width, 0); code_completion_scroll_rect.size = Vector2(scroll_width, code_completion_rect.size.height); - code_completion_line_ofs = CLAMP(code_completion_current_selected - lines / 2, 0, code_completion_options_count - lines); + code_completion_line_ofs = CLAMP((code_completion_force_item_center < 0 ? code_completion_current_selected : code_completion_force_item_center) - lines / 2, 0, code_completion_options_count - lines); RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(code_completion_rect.position.x, code_completion_rect.position.y + (code_completion_current_selected - code_completion_line_ofs) * row_height), Size2(code_completion_rect.size.width, row_height)), code_completion_selected_color); for (int i = 0; i < lines; i++) { @@ -281,16 +281,22 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { case MouseButton::WHEEL_UP: { if (code_completion_current_selected > 0) { code_completion_current_selected--; + code_completion_force_item_center = -1; queue_redraw(); } } break; case MouseButton::WHEEL_DOWN: { if (code_completion_current_selected < code_completion_options.size() - 1) { code_completion_current_selected++; + code_completion_force_item_center = -1; queue_redraw(); } } break; case MouseButton::LEFT: { + if (code_completion_force_item_center == -1) { + code_completion_force_item_center = code_completion_current_selected; + } + code_completion_current_selected = CLAMP(code_completion_line_ofs + (mb->get_position().y - code_completion_rect.position.y) / get_line_height(), 0, code_completion_options.size() - 1); if (mb->is_double_click()) { confirm_code_completion(); @@ -300,6 +306,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { default: break; } + return; } else if (code_completion_active && code_completion_scroll_rect.has_point(mb->get_position())) { if (mb->get_button_index() != MouseButton::LEFT) { @@ -448,6 +455,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } else { code_completion_current_selected = code_completion_options.size() - 1; } + code_completion_force_item_center = -1; queue_redraw(); accept_event(); return; @@ -458,30 +466,35 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } else { code_completion_current_selected = 0; } + code_completion_force_item_center = -1; queue_redraw(); accept_event(); return; } if (k->is_action("ui_page_up", true)) { code_completion_current_selected = MAX(0, code_completion_current_selected - code_completion_max_lines); + code_completion_force_item_center = -1; queue_redraw(); accept_event(); return; } if (k->is_action("ui_page_down", true)) { code_completion_current_selected = MIN(code_completion_options.size() - 1, code_completion_current_selected + code_completion_max_lines); + code_completion_force_item_center = -1; queue_redraw(); accept_event(); return; } if (k->is_action("ui_home", true)) { code_completion_current_selected = 0; + code_completion_force_item_center = -1; queue_redraw(); accept_event(); return; } if (k->is_action("ui_end", true)) { code_completion_current_selected = code_completion_options.size() - 1; + code_completion_force_item_center = -1; queue_redraw(); accept_event(); return; @@ -1978,6 +1991,7 @@ void CodeEdit::set_code_completion_selected_index(int p_index) { } ERR_FAIL_INDEX(p_index, code_completion_options.size()); code_completion_current_selected = p_index; + code_completion_force_item_center = -1; queue_redraw(); } @@ -2808,6 +2822,7 @@ void CodeEdit::_update_scroll_selected_line(float p_mouse_y) { percent = CLAMP(percent, 0.0f, 1.0f); code_completion_current_selected = (int)(percent * (code_completion_options.size() - 1)); + code_completion_force_item_center = -1; } void CodeEdit::_filter_code_completion_candidates_impl() { @@ -2867,6 +2882,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() { code_completion_longest_line = MIN(max_width, code_completion_max_width * font_size); code_completion_current_selected = 0; + code_completion_force_item_center = -1; code_completion_active = true; queue_redraw(); return; @@ -3123,6 +3139,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() { code_completion_longest_line = MIN(max_width, code_completion_max_width * font_size); code_completion_current_selected = 0; + code_completion_force_item_center = -1; code_completion_active = true; queue_redraw(); } diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h index 09c7ef80bf..cbbc13480e 100644 --- a/scene/gui/code_edit.h +++ b/scene/gui/code_edit.h @@ -214,6 +214,7 @@ private: Vector<ScriptLanguage::CodeCompletionOption> code_completion_options; int code_completion_line_ofs = 0; int code_completion_current_selected = 0; + int code_completion_force_item_center = -1; int code_completion_longest_line = 0; Rect2i code_completion_rect; Rect2i code_completion_scroll_rect; diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index a6aa4707ff..ec34a8d26d 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -107,45 +107,65 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { double prev_v_scroll = v_scroll->get_value(); double prev_h_scroll = h_scroll->get_value(); + bool h_scroll_enabled = horizontal_scroll_mode != SCROLL_MODE_DISABLED; + bool v_scroll_enabled = vertical_scroll_mode != SCROLL_MODE_DISABLED; Ref<InputEventMouseButton> mb = p_gui_input; if (mb.is_valid()) { - if (mb->get_button_index() == MouseButton::WHEEL_UP && mb->is_pressed()) { - // only horizontal is enabled, scroll horizontally - if (h_scroll->is_visible() && (!v_scroll->is_visible() || mb->is_shift_pressed())) { - h_scroll->set_value(h_scroll->get_value() - h_scroll->get_page() / 8 * mb->get_factor()); - } else if (v_scroll->is_visible_in_tree()) { - v_scroll->set_value(v_scroll->get_value() - v_scroll->get_page() / 8 * mb->get_factor()); + if (mb->is_pressed()) { + bool scroll_value_modified = false; + + bool v_scroll_hidden = !v_scroll->is_visible() && vertical_scroll_mode != SCROLL_MODE_SHOW_NEVER; + if (mb->get_button_index() == MouseButton::WHEEL_UP) { + // By default, the vertical orientation takes precedence. This is an exception. + if ((h_scroll_enabled && mb->is_shift_pressed()) || v_scroll_hidden) { + h_scroll->set_value(prev_h_scroll - h_scroll->get_page() / 8 * mb->get_factor()); + scroll_value_modified = true; + } else if (v_scroll_enabled) { + v_scroll->set_value(prev_v_scroll - v_scroll->get_page() / 8 * mb->get_factor()); + scroll_value_modified = true; + } } - } - - if (mb->get_button_index() == MouseButton::WHEEL_DOWN && mb->is_pressed()) { - // only horizontal is enabled, scroll horizontally - if (h_scroll->is_visible() && (!v_scroll->is_visible() || mb->is_shift_pressed())) { - h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() / 8 * mb->get_factor()); - } else if (v_scroll->is_visible()) { - v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() / 8 * mb->get_factor()); + if (mb->get_button_index() == MouseButton::WHEEL_DOWN) { + if ((h_scroll_enabled && mb->is_shift_pressed()) || v_scroll_hidden) { + h_scroll->set_value(prev_h_scroll + h_scroll->get_page() / 8 * mb->get_factor()); + scroll_value_modified = true; + } else if (v_scroll_enabled) { + v_scroll->set_value(prev_v_scroll + v_scroll->get_page() / 8 * mb->get_factor()); + scroll_value_modified = true; + } } - } - if (mb->get_button_index() == MouseButton::WHEEL_LEFT && mb->is_pressed()) { - if (h_scroll->is_visible_in_tree()) { - h_scroll->set_value(h_scroll->get_value() - h_scroll->get_page() * mb->get_factor() / 8); + bool h_scroll_hidden = !h_scroll->is_visible() && horizontal_scroll_mode != SCROLL_MODE_SHOW_NEVER; + if (mb->get_button_index() == MouseButton::WHEEL_LEFT) { + // By default, the horizontal orientation takes precedence. This is an exception. + if ((v_scroll_enabled && mb->is_shift_pressed()) || h_scroll_hidden) { + v_scroll->set_value(prev_v_scroll - v_scroll->get_page() / 8 * mb->get_factor()); + scroll_value_modified = true; + } else if (h_scroll_enabled) { + h_scroll->set_value(prev_h_scroll - h_scroll->get_page() / 8 * mb->get_factor()); + scroll_value_modified = true; + } } - } - - if (mb->get_button_index() == MouseButton::WHEEL_RIGHT && mb->is_pressed()) { - if (h_scroll->is_visible_in_tree()) { - h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() * mb->get_factor() / 8); + if (mb->get_button_index() == MouseButton::WHEEL_RIGHT) { + if ((v_scroll_enabled && mb->is_shift_pressed()) || h_scroll_hidden) { + v_scroll->set_value(prev_v_scroll + v_scroll->get_page() / 8 * mb->get_factor()); + scroll_value_modified = true; + } else if (h_scroll_enabled) { + h_scroll->set_value(prev_h_scroll + h_scroll->get_page() / 8 * mb->get_factor()); + scroll_value_modified = true; + } } - } - if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) { - accept_event(); //accept event if scroll changed + if (scroll_value_modified && (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll)) { + accept_event(); // Accept event if scroll changed. + return; + } } - if (!DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id()))) { + bool screen_is_touchscreen = DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id())); + if (!screen_is_touchscreen) { return; } @@ -161,8 +181,8 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { drag_speed = Vector2(); drag_accum = Vector2(); last_drag_accum = Vector2(); - drag_from = Vector2(h_scroll->get_value(), v_scroll->get_value()); - drag_touching = DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id())); + drag_from = Vector2(prev_h_scroll, prev_v_scroll); + drag_touching = screen_is_touchscreen; drag_touching_deaccel = false; beyond_deadzone = false; time_since_motion = 0; @@ -180,6 +200,7 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { } } } + return; } Ref<InputEventMouseMotion> mm = p_gui_input; @@ -189,22 +210,22 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { Vector2 motion = mm->get_relative(); drag_accum -= motion; - if (beyond_deadzone || (horizontal_scroll_mode != SCROLL_MODE_DISABLED && Math::abs(drag_accum.x) > deadzone) || (vertical_scroll_mode != SCROLL_MODE_DISABLED && Math::abs(drag_accum.y) > deadzone)) { + if (beyond_deadzone || (h_scroll_enabled && Math::abs(drag_accum.x) > deadzone) || (v_scroll_enabled && Math::abs(drag_accum.y) > deadzone)) { if (!beyond_deadzone) { propagate_notification(NOTIFICATION_SCROLL_BEGIN); emit_signal(SNAME("scroll_started")); beyond_deadzone = true; - // resetting drag_accum here ensures smooth scrolling after reaching deadzone + // Resetting drag_accum here ensures smooth scrolling after reaching deadzone. drag_accum = -motion; } Vector2 diff = drag_from + drag_accum; - if (horizontal_scroll_mode != SCROLL_MODE_DISABLED) { + if (h_scroll_enabled) { h_scroll->set_value(diff.x); } else { drag_accum.x = 0; } - if (vertical_scroll_mode != SCROLL_MODE_DISABLED) { + if (v_scroll_enabled) { v_scroll->set_value(diff.y); } else { drag_accum.y = 0; @@ -212,20 +233,26 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { time_since_motion = 0; } } + + if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) { + accept_event(); // Accept event if scroll changed. + } + return; } Ref<InputEventPanGesture> pan_gesture = p_gui_input; if (pan_gesture.is_valid()) { - if (h_scroll->is_visible_in_tree()) { - h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() * pan_gesture->get_delta().x / 8); + if (h_scroll_enabled) { + h_scroll->set_value(prev_h_scroll + h_scroll->get_page() * pan_gesture->get_delta().x / 8); } - if (v_scroll->is_visible_in_tree()) { - v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() * pan_gesture->get_delta().y / 8); + if (v_scroll_enabled) { + v_scroll->set_value(prev_v_scroll + v_scroll->get_page() * pan_gesture->get_delta().y / 8); } - } - if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) { - accept_event(); //accept event if scroll changed + if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) { + accept_event(); // Accept event if scroll changed. + } + return; } } diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp index 0d3389c13d..3bf4e95e68 100644 --- a/scene/main/canvas_item.cpp +++ b/scene/main/canvas_item.cpp @@ -219,6 +219,7 @@ void CanvasItem::_enter_canvas() { } RenderingServer::get_singleton()->canvas_item_set_parent(canvas_item, canvas); + RenderingServer::get_singleton()->canvas_item_set_visibility_layer(canvas_item, visibility_layer); canvas_group = "root_canvas" + itos(canvas.get_id()); @@ -236,6 +237,7 @@ void CanvasItem::_enter_canvas() { canvas_layer = parent->canvas_layer; RenderingServer::get_singleton()->canvas_item_set_parent(canvas_item, parent->get_canvas_item()); RenderingServer::get_singleton()->canvas_item_set_draw_index(canvas_item, get_index()); + RenderingServer::get_singleton()->canvas_item_set_visibility_layer(canvas_item, visibility_layer); } pending_update = false; @@ -977,6 +979,11 @@ void CanvasItem::_bind_methods() { ClassDB::bind_method(D_METHOD("make_canvas_position_local", "screen_point"), &CanvasItem::make_canvas_position_local); ClassDB::bind_method(D_METHOD("make_input_local", "event"), &CanvasItem::make_input_local); + ClassDB::bind_method(D_METHOD("set_visibility_layer", "layer"), &CanvasItem::set_visibility_layer); + ClassDB::bind_method(D_METHOD("get_visibility_layer"), &CanvasItem::get_visibility_layer); + ClassDB::bind_method(D_METHOD("set_visibility_layer_bit", "layer", "enabled"), &CanvasItem::set_visibility_layer_bit); + ClassDB::bind_method(D_METHOD("get_visibility_layer_bit", "layer"), &CanvasItem::get_visibility_layer_bit); + ClassDB::bind_method(D_METHOD("set_texture_filter", "mode"), &CanvasItem::set_texture_filter); ClassDB::bind_method(D_METHOD("get_texture_filter"), &CanvasItem::get_texture_filter); @@ -996,6 +1003,7 @@ void CanvasItem::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "top_level"), "set_as_top_level", "is_set_as_top_level"); ADD_PROPERTY(PropertyInfo(Variant::INT, "clip_children", PROPERTY_HINT_ENUM, "Disabled,Clip Only,Clip + Draw"), "set_clip_children_mode", "get_clip_children_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "light_mask", PROPERTY_HINT_LAYERS_2D_RENDER), "set_light_mask", "get_light_mask"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "visibility_layer", PROPERTY_HINT_LAYERS_2D_RENDER), "set_visibility_layer", "get_visibility_layer"); ADD_GROUP("Texture", "texture_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_filter", PROPERTY_HINT_ENUM, "Inherit,Nearest,Linear,Nearest Mipmap,Linear Mipmap,Nearest Mipmap Anisotropic,Linear Mipmap Anisotropic"), "set_texture_filter", "get_texture_filter"); @@ -1094,6 +1102,29 @@ int CanvasItem::get_canvas_layer() const { } } +void CanvasItem::set_visibility_layer(uint32_t p_visibility_layer) { + visibility_layer = p_visibility_layer; + RenderingServer::get_singleton()->canvas_item_set_visibility_layer(canvas_item, p_visibility_layer); +} + +uint32_t CanvasItem::get_visibility_layer() const { + return visibility_layer; +} + +void CanvasItem::set_visibility_layer_bit(uint32_t p_visibility_layer, bool p_enable) { + ERR_FAIL_INDEX(p_visibility_layer, 32); + if (p_enable) { + set_visibility_layer(visibility_layer | (1 << p_visibility_layer)); + } else { + set_visibility_layer(visibility_layer & (~(1 << p_visibility_layer))); + } +} + +bool CanvasItem::get_visibility_layer_bit(uint32_t p_visibility_layer) const { + ERR_FAIL_INDEX_V(p_visibility_layer, 32, false); + return (visibility_layer & (1 << p_visibility_layer)); +} + void CanvasItem::_refresh_texture_filter_cache() { if (!is_inside_tree()) { return; diff --git a/scene/main/canvas_item.h b/scene/main/canvas_item.h index 565ea930ce..4e78a175dc 100644 --- a/scene/main/canvas_item.h +++ b/scene/main/canvas_item.h @@ -89,6 +89,7 @@ private: List<CanvasItem *>::Element *C = nullptr; int light_mask = 1; + uint32_t visibility_layer = 1; Window *window = nullptr; bool visible = true; @@ -223,6 +224,12 @@ public: void set_self_modulate(const Color &p_self_modulate); Color get_self_modulate() const; + void set_visibility_layer(uint32_t p_visibility_layer); + uint32_t get_visibility_layer() const; + + void set_visibility_layer_bit(uint32_t p_visibility_layer, bool p_enable); + bool get_visibility_layer_bit(uint32_t p_visibility_layer) const; + /* DRAWING API */ void draw_dashed_line(const Point2 &p_from, const Point2 &p_to, const Color &p_color, real_t p_width = 1.0, real_t p_dash = 2.0); diff --git a/scene/main/canvas_layer.cpp b/scene/main/canvas_layer.cpp index 214efe432b..be5788739b 100644 --- a/scene/main/canvas_layer.cpp +++ b/scene/main/canvas_layer.cpp @@ -38,6 +38,7 @@ void CanvasLayer::set_layer(int p_xform) { layer = p_xform; if (viewport.is_valid()) { RenderingServer::get_singleton()->viewport_set_canvas_stacking(viewport, canvas, layer, get_index()); + vp->_gui_set_root_order_dirty(); } } diff --git a/scene/main/multiplayer_peer.cpp b/scene/main/multiplayer_peer.cpp index 462dc1babb..b4e5b11abd 100644 --- a/scene/main/multiplayer_peer.cpp +++ b/scene/main/multiplayer_peer.cpp @@ -94,6 +94,8 @@ void MultiplayerPeer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_packet_mode"), &MultiplayerPeer::get_packet_mode); ClassDB::bind_method(D_METHOD("poll"), &MultiplayerPeer::poll); + ClassDB::bind_method(D_METHOD("close"), &MultiplayerPeer::close); + ClassDB::bind_method(D_METHOD("disconnect_peer", "peer", "force"), &MultiplayerPeer::disconnect_peer, DEFVAL(false)); ClassDB::bind_method(D_METHOD("get_connection_status"), &MultiplayerPeer::get_connection_status); ClassDB::bind_method(D_METHOD("get_unique_id"), &MultiplayerPeer::get_unique_id); @@ -213,6 +215,8 @@ void MultiplayerPeerExtension::_bind_methods() { GDVIRTUAL_BIND(_get_packet_peer); GDVIRTUAL_BIND(_is_server); GDVIRTUAL_BIND(_poll); + GDVIRTUAL_BIND(_close); + GDVIRTUAL_BIND(_disconnect_peer, "p_peer", "p_force"); GDVIRTUAL_BIND(_get_unique_id); GDVIRTUAL_BIND(_set_refuse_new_connections, "p_enable"); GDVIRTUAL_BIND(_is_refusing_new_connections); diff --git a/scene/main/multiplayer_peer.h b/scene/main/multiplayer_peer.h index 63ce66871e..4b5909538e 100644 --- a/scene/main/multiplayer_peer.h +++ b/scene/main/multiplayer_peer.h @@ -82,9 +82,12 @@ public: virtual TransferMode get_packet_mode() const = 0; virtual int get_packet_channel() const = 0; + virtual void disconnect_peer(int p_peer, bool p_force = false) = 0; + virtual bool is_server() const = 0; virtual void poll() = 0; + virtual void close() = 0; virtual int get_unique_id() const = 0; @@ -139,6 +142,8 @@ public: EXBIND0RC(int, get_packet_channel); EXBIND0RC(bool, is_server); EXBIND0(poll); + EXBIND0(close); + EXBIND2(disconnect_peer, int, bool); EXBIND0RC(int, get_unique_id); EXBIND0RC(ConnectionStatus, get_connection_status); }; diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 6af96a4147..2385e4f54a 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -362,6 +362,7 @@ void Viewport::_notification(int p_what) { current_canvas = find_world_2d()->get_canvas(); RenderingServer::get_singleton()->viewport_attach_canvas(viewport, current_canvas); + RenderingServer::get_singleton()->viewport_set_canvas_cull_mask(viewport, canvas_cull_mask); _update_audio_listener_2d(); #ifndef _3D_DISABLED RenderingServer::get_singleton()->viewport_set_scenario(viewport, find_world_3d()->get_scenario()); @@ -993,11 +994,6 @@ void Viewport::set_world_2d(const Ref<World2D> &p_world_2d) { return; } - if (parent && parent->find_world_2d() == p_world_2d) { - WARN_PRINT("Unable to use parent world_2d as world_2d"); - return; - } - if (is_inside_tree()) { RenderingServer::get_singleton()->viewport_remove_canvas(viewport, current_canvas); } @@ -3249,6 +3245,29 @@ Transform2D Viewport::get_screen_transform() const { return _get_input_pre_xform().affine_inverse() * get_final_transform(); } +void Viewport::set_canvas_cull_mask(uint32_t p_canvas_cull_mask) { + canvas_cull_mask = p_canvas_cull_mask; + RenderingServer::get_singleton()->viewport_set_canvas_cull_mask(viewport, canvas_cull_mask); +} + +uint32_t Viewport::get_canvas_cull_mask() const { + return canvas_cull_mask; +} + +void Viewport::set_canvas_cull_mask_bit(uint32_t p_layer, bool p_enable) { + ERR_FAIL_INDEX(p_layer, 32); + if (p_enable) { + set_canvas_cull_mask(canvas_cull_mask | (1 << p_layer)); + } else { + set_canvas_cull_mask(canvas_cull_mask & (~(1 << p_layer))); + } +} + +bool Viewport::get_canvas_cull_mask_bit(uint32_t p_layer) const { + ERR_FAIL_INDEX_V(p_layer, 32, false); + return (canvas_cull_mask & (1 << p_layer)); +} + #ifndef _3D_DISABLED AudioListener3D *Viewport::get_audio_listener_3d() const { return audio_listener_3d; @@ -3820,6 +3839,12 @@ void Viewport::_bind_methods() { ClassDB::bind_method(D_METHOD("set_embedding_subwindows", "enable"), &Viewport::set_embedding_subwindows); ClassDB::bind_method(D_METHOD("is_embedding_subwindows"), &Viewport::is_embedding_subwindows); + ClassDB::bind_method(D_METHOD("set_canvas_cull_mask", "mask"), &Viewport::set_canvas_cull_mask); + ClassDB::bind_method(D_METHOD("get_canvas_cull_mask"), &Viewport::get_canvas_cull_mask); + + ClassDB::bind_method(D_METHOD("set_canvas_cull_mask_bit", "layer", "enable"), &Viewport::set_canvas_cull_mask_bit); + ClassDB::bind_method(D_METHOD("get_canvas_cull_mask_bit", "layer"), &Viewport::get_canvas_cull_mask_bit); + ClassDB::bind_method(D_METHOD("set_default_canvas_item_texture_repeat", "mode"), &Viewport::set_default_canvas_item_texture_repeat); ClassDB::bind_method(D_METHOD("get_default_canvas_item_texture_repeat"), &Viewport::get_default_canvas_item_texture_repeat); @@ -3925,6 +3950,7 @@ void Viewport::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::INT, "positional_shadow_atlas_quad_3", PROPERTY_HINT_ENUM, "Disabled,1 Shadow,4 Shadows,16 Shadows,64 Shadows,256 Shadows,1024 Shadows"), "set_positional_shadow_atlas_quadrant_subdiv", "get_positional_shadow_atlas_quadrant_subdiv", 3); ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "canvas_transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_canvas_transform", "get_canvas_transform"); ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "global_canvas_transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_global_canvas_transform", "get_global_canvas_transform"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "canvas_cull_mask", PROPERTY_HINT_LAYERS_2D_RENDER), "set_canvas_cull_mask", "get_canvas_cull_mask"); ADD_SIGNAL(MethodInfo("size_changed")); ADD_SIGNAL(MethodInfo("gui_focus_changed", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Control"))); diff --git a/scene/main/viewport.h b/scene/main/viewport.h index 6f67649ea3..8911aea335 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -317,6 +317,8 @@ private: SDFOversize sdf_oversize = SDF_OVERSIZE_120_PERCENT; SDFScale sdf_scale = SDF_SCALE_50_PERCENT; + uint32_t canvas_cull_mask = 0xffffffff; // by default show everything + enum SubWindowDrag { SUB_WINDOW_DRAG_DISABLED, SUB_WINDOW_DRAG_MOVE, @@ -639,6 +641,12 @@ public: void pass_mouse_focus_to(Viewport *p_viewport, Control *p_control); + void set_canvas_cull_mask(uint32_t p_layers); + uint32_t get_canvas_cull_mask() const; + + void set_canvas_cull_mask_bit(uint32_t p_layer, bool p_enable); + bool get_canvas_cull_mask_bit(uint32_t p_layer) const; + virtual Transform2D get_screen_transform() const; #ifndef _3D_DISABLED diff --git a/scene/resources/curve.cpp b/scene/resources/curve.cpp index 49b78a091d..eda9af9dde 100644 --- a/scene/resources/curve.cpp +++ b/scene/resources/curve.cpp @@ -936,6 +936,46 @@ Vector2 Curve2D::sample_baked(real_t p_offset, bool p_cubic) const { } } +Transform2D Curve2D::sample_baked_with_rotation(real_t p_offset, bool p_cubic, bool p_loop, real_t p_lookahead) const { + real_t path_length = get_baked_length(); // Ensure baked. + ERR_FAIL_COND_V_MSG(path_length == 0, Transform2D(), "Length of Curve2D is 0."); + + Vector2 pos = sample_baked(p_offset, p_cubic); + + real_t ahead = p_offset + p_lookahead; + + if (p_loop && ahead >= path_length) { + // If our lookahead will loop, we need to check if the path is closed. + int point_count = get_point_count(); + if (point_count > 0) { + Vector2 start_point = get_point_position(0); + Vector2 end_point = get_point_position(point_count - 1); + if (start_point == end_point) { + // Since the path is closed we want to 'smooth off' + // the corner at the start/end. + // So we wrap the lookahead back round. + ahead = Math::fmod(ahead, path_length); + } + } + } + + Vector2 ahead_pos = sample_baked(ahead, p_cubic); + + Vector2 tangent_to_curve; + if (ahead_pos == pos) { + // This will happen at the end of non-looping or non-closed paths. + // We'll try a look behind instead, in order to get a meaningful angle. + tangent_to_curve = + (pos - sample_baked(p_offset - p_lookahead, p_cubic)).normalized(); + } else { + tangent_to_curve = (ahead_pos - pos).normalized(); + } + + Vector2 normal_of_curve = -tangent_to_curve.orthogonal(); + + return Transform2D(normal_of_curve, tangent_to_curve, pos); +} + PackedVector2Array Curve2D::get_baked_points() const { if (baked_cache_dirty) { _bake(); @@ -1184,6 +1224,7 @@ void Curve2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_baked_length"), &Curve2D::get_baked_length); ClassDB::bind_method(D_METHOD("sample_baked", "offset", "cubic"), &Curve2D::sample_baked, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("sample_baked_with_rotation", "offset", "cubic", "loop", "lookahead"), &Curve2D::sample_baked_with_rotation, DEFVAL(false), DEFVAL(true), DEFVAL(4.0)); ClassDB::bind_method(D_METHOD("get_baked_points"), &Curve2D::get_baked_points); ClassDB::bind_method(D_METHOD("get_closest_point", "to_point"), &Curve2D::get_closest_point); ClassDB::bind_method(D_METHOD("get_closest_offset", "to_point"), &Curve2D::get_closest_offset); diff --git a/scene/resources/curve.h b/scene/resources/curve.h index 88b6dda096..fa1d35aab1 100644 --- a/scene/resources/curve.h +++ b/scene/resources/curve.h @@ -216,6 +216,7 @@ public: real_t get_baked_length() const; Vector2 sample_baked(real_t p_offset, bool p_cubic = false) const; + Transform2D sample_baked_with_rotation(real_t p_offset, bool p_cubic = false, bool p_loop = true, real_t p_lookahead = 4.0) const; PackedVector2Array get_baked_points() const; //useful for going through Vector2 get_closest_point(const Vector2 &p_to_point) const; real_t get_closest_offset(const Vector2 &p_to_point) const; diff --git a/servers/rendering/dummy/rasterizer_canvas_dummy.h b/servers/rendering/dummy/rasterizer_canvas_dummy.h index 64c4cf5024..65c64e8115 100644 --- a/servers/rendering/dummy/rasterizer_canvas_dummy.h +++ b/servers/rendering/dummy/rasterizer_canvas_dummy.h @@ -39,7 +39,6 @@ public: void free_polygon(PolygonID p_polygon) override {} void canvas_render_items(RID p_to_render_target, Item *p_item_list, const Color &p_modulate, Light *p_light_list, Light *p_directional_list, const Transform2D &p_canvas_transform, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, bool &r_sdf_used) override {} - void canvas_debug_viewport_shadows(Light *p_lights_with_shadow) override {} RID light_create() override { return RID(); } void light_set_texture(RID p_rid, RID p_texture) override {} diff --git a/servers/rendering/renderer_canvas_cull.cpp b/servers/rendering/renderer_canvas_cull.cpp index ffd8f695c6..41d4ca8d5e 100644 --- a/servers/rendering/renderer_canvas_cull.cpp +++ b/servers/rendering/renderer_canvas_cull.cpp @@ -38,17 +38,17 @@ static const int z_range = RS::CANVAS_ITEM_Z_MAX - RS::CANVAS_ITEM_Z_MIN + 1; -void RendererCanvasCull::_render_canvas_item_tree(RID p_to_render_target, Canvas::ChildItem *p_child_items, int p_child_item_count, Item *p_canvas_item, const Transform2D &p_transform, const Rect2 &p_clip_rect, const Color &p_modulate, RendererCanvasRender::Light *p_lights, RendererCanvasRender::Light *p_directional_lights, RenderingServer::CanvasItemTextureFilter p_default_filter, RenderingServer::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel) { +void RendererCanvasCull::_render_canvas_item_tree(RID p_to_render_target, Canvas::ChildItem *p_child_items, int p_child_item_count, Item *p_canvas_item, const Transform2D &p_transform, const Rect2 &p_clip_rect, const Color &p_modulate, RendererCanvasRender::Light *p_lights, RendererCanvasRender::Light *p_directional_lights, RenderingServer::CanvasItemTextureFilter p_default_filter, RenderingServer::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, uint32_t canvas_cull_mask) { RENDER_TIMESTAMP("Cull CanvasItem Tree"); memset(z_list, 0, z_range * sizeof(RendererCanvasRender::Item *)); memset(z_last_list, 0, z_range * sizeof(RendererCanvasRender::Item *)); for (int i = 0; i < p_child_item_count; i++) { - _cull_canvas_item(p_child_items[i].item, p_transform, p_clip_rect, Color(1, 1, 1, 1), 0, z_list, z_last_list, nullptr, nullptr, true); + _cull_canvas_item(p_child_items[i].item, p_transform, p_clip_rect, Color(1, 1, 1, 1), 0, z_list, z_last_list, nullptr, nullptr, true, canvas_cull_mask); } if (p_canvas_item) { - _cull_canvas_item(p_canvas_item, p_transform, p_clip_rect, Color(1, 1, 1, 1), 0, z_list, z_last_list, nullptr, nullptr, true); + _cull_canvas_item(p_canvas_item, p_transform, p_clip_rect, Color(1, 1, 1, 1), 0, z_list, z_last_list, nullptr, nullptr, true, canvas_cull_mask); } RendererCanvasRender::Item *list = nullptr; @@ -223,13 +223,17 @@ void RendererCanvasCull::_attach_canvas_item_for_draw(RendererCanvasCull::Item * } } -void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2D &p_transform, const Rect2 &p_clip_rect, const Color &p_modulate, int p_z, RendererCanvasRender::Item **r_z_list, RendererCanvasRender::Item **r_z_last_list, Item *p_canvas_clip, Item *p_material_owner, bool allow_y_sort) { +void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2D &p_transform, const Rect2 &p_clip_rect, const Color &p_modulate, int p_z, RendererCanvasRender::Item **r_z_list, RendererCanvasRender::Item **r_z_last_list, Item *p_canvas_clip, Item *p_material_owner, bool allow_y_sort, uint32_t canvas_cull_mask) { Item *ci = p_canvas_item; if (!ci->visible) { return; } + if (!(ci->visibility_layer & canvas_cull_mask)) { + return; + } + if (ci->children_order_dirty) { ci->child_items.sort_custom<ItemIndexSort>(); ci->children_order_dirty = false; @@ -313,7 +317,7 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2 sorter.sort(child_items, child_item_count); for (i = 0; i < child_item_count; i++) { - _cull_canvas_item(child_items[i], xform * child_items[i]->ysort_xform, p_clip_rect, modulate, child_items[i]->ysort_parent_abs_z_index, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, (Item *)child_items[i]->material_owner, false); + _cull_canvas_item(child_items[i], xform * child_items[i]->ysort_xform, p_clip_rect, modulate, child_items[i]->ysort_parent_abs_z_index, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, (Item *)child_items[i]->material_owner, false, canvas_cull_mask); } } else { RendererCanvasRender::Item *canvas_group_from = nullptr; @@ -337,19 +341,19 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2 if (!child_items[i]->behind && !use_canvas_group) { continue; } - _cull_canvas_item(child_items[i], xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, true); + _cull_canvas_item(child_items[i], xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, true, canvas_cull_mask); } _attach_canvas_item_for_draw(ci, p_canvas_clip, r_z_list, r_z_last_list, xform, p_clip_rect, global_rect, modulate, p_z, p_material_owner, use_canvas_group, canvas_group_from, xform); for (int i = 0; i < child_item_count; i++) { if (child_items[i]->behind || use_canvas_group) { continue; } - _cull_canvas_item(child_items[i], xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, true); + _cull_canvas_item(child_items[i], xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, true, canvas_cull_mask); } } } -void RendererCanvasCull::render_canvas(RID p_render_target, Canvas *p_canvas, const Transform2D &p_transform, RendererCanvasRender::Light *p_lights, RendererCanvasRender::Light *p_directional_lights, const Rect2 &p_clip_rect, RenderingServer::CanvasItemTextureFilter p_default_filter, RenderingServer::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_transforms_to_pixel, bool p_snap_2d_vertices_to_pixel) { +void RendererCanvasCull::render_canvas(RID p_render_target, Canvas *p_canvas, const Transform2D &p_transform, RendererCanvasRender::Light *p_lights, RendererCanvasRender::Light *p_directional_lights, const Rect2 &p_clip_rect, RenderingServer::CanvasItemTextureFilter p_default_filter, RenderingServer::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_transforms_to_pixel, bool p_snap_2d_vertices_to_pixel, uint32_t canvas_cull_mask) { RENDER_TIMESTAMP("> Render Canvas"); sdf_used = false; @@ -372,26 +376,26 @@ void RendererCanvasCull::render_canvas(RID p_render_target, Canvas *p_canvas, co } if (!has_mirror) { - _render_canvas_item_tree(p_render_target, ci, l, nullptr, p_transform, p_clip_rect, p_canvas->modulate, p_lights, p_directional_lights, p_default_filter, p_default_repeat, p_snap_2d_vertices_to_pixel); + _render_canvas_item_tree(p_render_target, ci, l, nullptr, p_transform, p_clip_rect, p_canvas->modulate, p_lights, p_directional_lights, p_default_filter, p_default_repeat, p_snap_2d_vertices_to_pixel, canvas_cull_mask); } else { //used for parallaxlayer mirroring for (int i = 0; i < l; i++) { const Canvas::ChildItem &ci2 = p_canvas->child_items[i]; - _render_canvas_item_tree(p_render_target, nullptr, 0, ci2.item, p_transform, p_clip_rect, p_canvas->modulate, p_lights, p_directional_lights, p_default_filter, p_default_repeat, p_snap_2d_vertices_to_pixel); + _render_canvas_item_tree(p_render_target, nullptr, 0, ci2.item, p_transform, p_clip_rect, p_canvas->modulate, p_lights, p_directional_lights, p_default_filter, p_default_repeat, p_snap_2d_vertices_to_pixel, canvas_cull_mask); //mirroring (useful for scrolling backgrounds) if (ci2.mirror.x != 0) { Transform2D xform2 = p_transform * Transform2D(0, Vector2(ci2.mirror.x, 0)); - _render_canvas_item_tree(p_render_target, nullptr, 0, ci2.item, xform2, p_clip_rect, p_canvas->modulate, p_lights, p_directional_lights, p_default_filter, p_default_repeat, p_snap_2d_vertices_to_pixel); + _render_canvas_item_tree(p_render_target, nullptr, 0, ci2.item, xform2, p_clip_rect, p_canvas->modulate, p_lights, p_directional_lights, p_default_filter, p_default_repeat, p_snap_2d_vertices_to_pixel, canvas_cull_mask); } if (ci2.mirror.y != 0) { Transform2D xform2 = p_transform * Transform2D(0, Vector2(0, ci2.mirror.y)); - _render_canvas_item_tree(p_render_target, nullptr, 0, ci2.item, xform2, p_clip_rect, p_canvas->modulate, p_lights, p_directional_lights, p_default_filter, p_default_repeat, p_snap_2d_vertices_to_pixel); + _render_canvas_item_tree(p_render_target, nullptr, 0, ci2.item, xform2, p_clip_rect, p_canvas->modulate, p_lights, p_directional_lights, p_default_filter, p_default_repeat, p_snap_2d_vertices_to_pixel, canvas_cull_mask); } if (ci2.mirror.y != 0 && ci2.mirror.x != 0) { Transform2D xform2 = p_transform * Transform2D(0, ci2.mirror); - _render_canvas_item_tree(p_render_target, nullptr, 0, ci2.item, xform2, p_clip_rect, p_canvas->modulate, p_lights, p_directional_lights, p_default_filter, p_default_repeat, p_snap_2d_vertices_to_pixel); + _render_canvas_item_tree(p_render_target, nullptr, 0, ci2.item, xform2, p_clip_rect, p_canvas->modulate, p_lights, p_directional_lights, p_default_filter, p_default_repeat, p_snap_2d_vertices_to_pixel, canvas_cull_mask); } } } @@ -513,6 +517,20 @@ void RendererCanvasCull::canvas_item_set_transform(RID p_item, const Transform2D canvas_item->xform = p_transform; } +void RendererCanvasCull::canvas_item_set_visibility_layer(RID p_item, uint32_t p_visibility_layer) { + Item *canvas_item = canvas_item_owner.get_or_null(p_item); + ERR_FAIL_COND(!canvas_item); + + canvas_item->visibility_layer = p_visibility_layer; +} + +uint32_t RendererCanvasCull::canvas_item_get_visibility_layer(RID p_item) { + Item *canvas_item = canvas_item_owner.get_or_null(p_item); + if (!canvas_item) + return 0; + return canvas_item->visibility_layer; +} + void RendererCanvasCull::canvas_item_set_clip(RID p_item, bool p_clip) { Item *canvas_item = canvas_item_owner.get_or_null(p_item); ERR_FAIL_COND(!canvas_item); diff --git a/servers/rendering/renderer_canvas_cull.h b/servers/rendering/renderer_canvas_cull.h index 7a01fad9de..198e1020f0 100644 --- a/servers/rendering/renderer_canvas_cull.h +++ b/servers/rendering/renderer_canvas_cull.h @@ -54,6 +54,7 @@ public: Vector2 ysort_pos; int ysort_index; int ysort_parent_abs_z_index; // Absolute Z index of parent. Only populated and used when y-sorting. + uint32_t visibility_layer = 0xffffffff; Vector<Item *> child_items; @@ -179,14 +180,14 @@ public: _FORCE_INLINE_ void _attach_canvas_item_for_draw(Item *ci, Item *p_canvas_clip, RendererCanvasRender::Item **r_z_list, RendererCanvasRender::Item **r_z_last_list, const Transform2D &xform, const Rect2 &p_clip_rect, Rect2 global_rect, const Color &modulate, int p_z, RendererCanvasCull::Item *p_material_owner, bool p_use_canvas_group, RendererCanvasRender::Item *canvas_group_from, const Transform2D &p_xform); private: - void _render_canvas_item_tree(RID p_to_render_target, Canvas::ChildItem *p_child_items, int p_child_item_count, Item *p_canvas_item, const Transform2D &p_transform, const Rect2 &p_clip_rect, const Color &p_modulate, RendererCanvasRender::Light *p_lights, RendererCanvasRender::Light *p_directional_lights, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel); - void _cull_canvas_item(Item *p_canvas_item, const Transform2D &p_transform, const Rect2 &p_clip_rect, const Color &p_modulate, int p_z, RendererCanvasRender::Item **r_z_list, RendererCanvasRender::Item **r_z_last_list, Item *p_canvas_clip, Item *p_material_owner, bool allow_y_sort); + void _render_canvas_item_tree(RID p_to_render_target, Canvas::ChildItem *p_child_items, int p_child_item_count, Item *p_canvas_item, const Transform2D &p_transform, const Rect2 &p_clip_rect, const Color &p_modulate, RendererCanvasRender::Light *p_lights, RendererCanvasRender::Light *p_directional_lights, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, uint32_t canvas_cull_mask); + void _cull_canvas_item(Item *p_canvas_item, const Transform2D &p_transform, const Rect2 &p_clip_rect, const Color &p_modulate, int p_z, RendererCanvasRender::Item **r_z_list, RendererCanvasRender::Item **r_z_last_list, Item *p_canvas_clip, Item *p_material_owner, bool allow_y_sort, uint32_t canvas_cull_mask); RendererCanvasRender::Item **z_list; RendererCanvasRender::Item **z_last_list; public: - void render_canvas(RID p_render_target, Canvas *p_canvas, const Transform2D &p_transform, RendererCanvasRender::Light *p_lights, RendererCanvasRender::Light *p_directional_lights, const Rect2 &p_clip_rect, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_transforms_to_pixel, bool p_snap_2d_vertices_to_pixel); + void render_canvas(RID p_render_target, Canvas *p_canvas, const Transform2D &p_transform, RendererCanvasRender::Light *p_lights, RendererCanvasRender::Light *p_directional_lights, const Rect2 &p_clip_rect, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_transforms_to_pixel, bool p_snap_2d_vertices_to_pixel, uint32_t canvas_cull_mask); bool was_sdf_used(); @@ -206,6 +207,9 @@ public: void canvas_item_set_visible(RID p_item, bool p_visible); void canvas_item_set_light_mask(RID p_item, int p_mask); + void canvas_item_set_visibility_layer(RID p_item, uint32_t p_layer); + uint32_t canvas_item_get_visibility_layer(RID p_item); + void canvas_item_set_transform(RID p_item, const Transform2D &p_transform); void canvas_item_set_clip(RID p_item, bool p_clip); void canvas_item_set_distance_field_mode(RID p_item, bool p_enable); diff --git a/servers/rendering/renderer_canvas_render.h b/servers/rendering/renderer_canvas_render.h index 6791ed9626..375358a5e8 100644 --- a/servers/rendering/renderer_canvas_render.h +++ b/servers/rendering/renderer_canvas_render.h @@ -476,7 +476,6 @@ public: }; virtual void canvas_render_items(RID p_to_render_target, Item *p_item_list, const Color &p_modulate, Light *p_light_list, Light *p_directional_list, const Transform2D &p_canvas_transform, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, bool &r_sdf_used) = 0; - virtual void canvas_debug_viewport_shadows(Light *p_lights_with_shadow) = 0; struct LightOccluderInstance { bool enabled; diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp index dc0214e307..cbc5cc337c 100644 --- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp @@ -1914,11 +1914,12 @@ void RendererCanvasRenderRD::occluder_polygon_set_shape(RID p_occluder, const Ve } } - //if same buffer len is being set, just use BufferSubData to avoid a pipeline flush + //if same buffer len is being set, just use buffer_update to avoid a pipeline flush if (oc->vertex_array.is_null()) { //create from scratch //vertices + // TODO: geometry is always of length lc * 6 * sizeof(float), so in doubles builds this will receive half the data it needs oc->vertex_buffer = RD::get_singleton()->vertex_buffer_create(lc * 6 * sizeof(real_t), geometry); Vector<RID> buffer; diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.h b/servers/rendering/renderer_rd/renderer_canvas_render_rd.h index 4f1f77af5e..d1f3c9ec6a 100644 --- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.h +++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.h @@ -459,8 +459,6 @@ public: void canvas_render_items(RID p_to_render_target, Item *p_item_list, const Color &p_modulate, Light *p_light_list, Light *p_directional_light_list, const Transform2D &p_canvas_transform, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, bool &r_sdf_used); - void canvas_debug_viewport_shadows(Light *p_lights_with_shadow) {} - virtual void set_shadow_texture_size(int p_size); void set_time(double p_time); diff --git a/servers/rendering/renderer_viewport.cpp b/servers/rendering/renderer_viewport.cpp index 55653b5945..f887264696 100644 --- a/servers/rendering/renderer_viewport.cpp +++ b/servers/rendering/renderer_viewport.cpp @@ -530,7 +530,7 @@ void RendererViewport::_draw_viewport(Viewport *p_viewport) { ptr = ptr->filter_next_ptr; } - RSG::canvas->render_canvas(p_viewport->render_target, canvas, xform, canvas_lights, canvas_directional_lights, clip_rect, p_viewport->texture_filter, p_viewport->texture_repeat, p_viewport->snap_2d_transforms_to_pixel, p_viewport->snap_2d_vertices_to_pixel); + RSG::canvas->render_canvas(p_viewport->render_target, canvas, xform, canvas_lights, canvas_directional_lights, clip_rect, p_viewport->texture_filter, p_viewport->texture_repeat, p_viewport->snap_2d_transforms_to_pixel, p_viewport->snap_2d_vertices_to_pixel, p_viewport->canvas_cull_mask); if (RSG::canvas->was_sdf_used()) { p_viewport->sdf_active = true; } @@ -1353,6 +1353,12 @@ void RendererViewport::set_default_clear_color(const Color &p_color) { RSG::texture_storage->set_default_clear_color(p_color); } +void RendererViewport::viewport_set_canvas_cull_mask(RID p_viewport, uint32_t p_canvas_cull_mask) { + Viewport *viewport = viewport_owner.get_or_null(p_viewport); + ERR_FAIL_COND(!viewport); + viewport->canvas_cull_mask = p_canvas_cull_mask; +} + // Workaround for setting this on thread. void RendererViewport::call_set_vsync_mode(DisplayServer::VSyncMode p_mode, DisplayServer::WindowID p_window) { DisplayServer::get_singleton()->window_set_vsync_mode(p_mode, p_window); diff --git a/servers/rendering/renderer_viewport.h b/servers/rendering/renderer_viewport.h index 55058a30b8..5e0c090ec0 100644 --- a/servers/rendering/renderer_viewport.h +++ b/servers/rendering/renderer_viewport.h @@ -117,6 +117,8 @@ public: bool transparent_bg = false; + uint32_t canvas_cull_mask = 0xffffffff; + struct CanvasKey { int64_t stacking; RID canvas; @@ -249,6 +251,8 @@ public: void viewport_set_global_canvas_transform(RID p_viewport, const Transform2D &p_transform); void viewport_set_canvas_stacking(RID p_viewport, RID p_canvas, int p_layer, int p_sublayer); + void viewport_set_canvas_cull_mask(RID p_viewport, uint32_t p_canvas_cull_mask); + void viewport_set_positional_shadow_atlas_size(RID p_viewport, int p_size, bool p_16_bits = true); void viewport_set_positional_shadow_atlas_quadrant_subdivision(RID p_viewport, int p_quadrant, int p_subdiv); diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h index a68c7ddf2c..6d2d0d9906 100644 --- a/servers/rendering/rendering_server_default.h +++ b/servers/rendering/rendering_server_default.h @@ -610,6 +610,8 @@ public: FUNC2(viewport_set_disable_environment, RID, bool) FUNC2(viewport_set_disable_3d, RID, bool) + FUNC2(viewport_set_canvas_cull_mask, RID, uint32_t) + FUNC2(viewport_attach_camera, RID, RID) FUNC2(viewport_set_scenario, RID, RID) FUNC2(viewport_attach_canvas, RID, RID) @@ -828,6 +830,8 @@ public: FUNC2(canvas_item_set_visible, RID, bool) FUNC2(canvas_item_set_light_mask, RID, int) + FUNC2(canvas_item_set_visibility_layer, RID, uint32_t) + FUNC2(canvas_item_set_update_when_visible, RID, bool) FUNC2(canvas_item_set_transform, RID, const Transform2D &) diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp index 4d27c400f0..2f5846f520 100644 --- a/servers/rendering/shader_language.cpp +++ b/servers/rendering/shader_language.cpp @@ -1251,7 +1251,7 @@ bool ShaderLanguage::_find_identifier(const BlockNode *p_block, bool p_allow_rea if (is_shader_inc) { for (int i = 0; i < RenderingServer::SHADER_MAX; i++) { for (const KeyValue<StringName, FunctionInfo> &E : ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(i))) { - if ((current_function == E.key || E.key == "global") && E.value.built_ins.has(p_identifier)) { + if ((current_function == E.key || E.key == "global" || E.key == "constants") && E.value.built_ins.has(p_identifier)) { if (r_data_type) { *r_data_type = E.value.built_ins[p_identifier].type; } diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp index e1a3fe46be..cf30d377b7 100644 --- a/servers/rendering_server.cpp +++ b/servers/rendering_server.cpp @@ -2182,6 +2182,7 @@ void RenderingServer::_bind_methods() { ClassDB::bind_method(D_METHOD("viewport_set_parent_viewport", "viewport", "parent_viewport"), &RenderingServer::viewport_set_parent_viewport); ClassDB::bind_method(D_METHOD("viewport_attach_to_screen", "viewport", "rect", "screen"), &RenderingServer::viewport_attach_to_screen, DEFVAL(Rect2()), DEFVAL(DisplayServer::MAIN_WINDOW_ID)); ClassDB::bind_method(D_METHOD("viewport_set_render_direct_to_screen", "viewport", "enabled"), &RenderingServer::viewport_set_render_direct_to_screen); + ClassDB::bind_method(D_METHOD("viewport_set_canvas_cull_mask", "viewport", "canvas_cull_mask"), &RenderingServer::viewport_set_canvas_cull_mask); ClassDB::bind_method(D_METHOD("viewport_set_scaling_3d_mode", "viewport", "scaling_3d_mode"), &RenderingServer::viewport_set_scaling_3d_mode); ClassDB::bind_method(D_METHOD("viewport_set_scaling_3d_scale", "viewport", "scale"), &RenderingServer::viewport_set_scaling_3d_scale); @@ -2575,6 +2576,7 @@ void RenderingServer::_bind_methods() { ClassDB::bind_method(D_METHOD("canvas_item_set_default_texture_repeat", "item", "repeat"), &RenderingServer::canvas_item_set_default_texture_repeat); ClassDB::bind_method(D_METHOD("canvas_item_set_visible", "item", "visible"), &RenderingServer::canvas_item_set_visible); ClassDB::bind_method(D_METHOD("canvas_item_set_light_mask", "item", "mask"), &RenderingServer::canvas_item_set_light_mask); + ClassDB::bind_method(D_METHOD("canvas_item_set_visibility_layer", "item", "visibility_layer"), &RenderingServer::canvas_item_set_visibility_layer); ClassDB::bind_method(D_METHOD("canvas_item_set_transform", "item", "transform"), &RenderingServer::canvas_item_set_transform); ClassDB::bind_method(D_METHOD("canvas_item_set_clip", "item", "clip"), &RenderingServer::canvas_item_set_clip); ClassDB::bind_method(D_METHOD("canvas_item_set_distance_field_mode", "item", "enabled"), &RenderingServer::canvas_item_set_distance_field_mode); diff --git a/servers/rendering_server.h b/servers/rendering_server.h index 36779690de..97fafd1b14 100644 --- a/servers/rendering_server.h +++ b/servers/rendering_server.h @@ -807,6 +807,7 @@ public: virtual void viewport_set_size(RID p_viewport, int p_width, int p_height) = 0; virtual void viewport_set_active(RID p_viewport, bool p_active) = 0; virtual void viewport_set_parent_viewport(RID p_viewport, RID p_parent_viewport) = 0; + virtual void viewport_set_canvas_cull_mask(RID p_viewport, uint32_t p_canvas_cull_mask) = 0; virtual void viewport_attach_to_screen(RID p_viewport, const Rect2 &p_rect = Rect2(), DisplayServer::WindowID p_screen = DisplayServer::MAIN_WINDOW_ID) = 0; virtual void viewport_set_render_direct_to_screen(RID p_viewport, bool p_enable) = 0; @@ -1320,6 +1321,7 @@ public: virtual void canvas_item_set_custom_rect(RID p_item, bool p_custom_rect, const Rect2 &p_rect = Rect2()) = 0; virtual void canvas_item_set_modulate(RID p_item, const Color &p_color) = 0; virtual void canvas_item_set_self_modulate(RID p_item, const Color &p_color) = 0; + virtual void canvas_item_set_visibility_layer(RID p_item, uint32_t p_visibility_layer) = 0; virtual void canvas_item_set_draw_behind_parent(RID p_item, bool p_enable) = 0; |