diff options
author | George L. Albany <Megacake1234@gmail.com> | 2024-11-21 05:02:23 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-11-21 05:02:23 +0000 |
commit | c5b1645e60a59c0292c04bece3fdb0715a61afea (patch) | |
tree | b03c5b5de96e29ffb7e1b008912d21aba5629bc5 | |
parent | fd9045fe09e9bea691f0169c16d45cbebddb6bba (diff) | |
parent | 9857e4762b8d076259c4be863ba9f53df306d940 (diff) | |
download | redot-engine-c5b1645e60a59c0292c04bece3fdb0715a61afea.tar.gz |
Merge pull request #875 from Spartan322/merge/9e60984
Merge commit godotengine/godot@9e60984
54 files changed, 723 insertions, 220 deletions
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index 6e322a019e..f4810e4170 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -516,16 +516,19 @@ void ProjectSettings::_convert_to_last_version(int p_from_version) { } } } - if (p_from_version <= 5) { - // Converts the device in events from -1 (emulated events) to -3 (all events). + if (p_from_version == 5) { + // Converts the device in events from -3 to -1. + // -3 was introduced in GH-97707 as a way to prevent a clash in device IDs, but as reported in GH-99243, this leads to problems. + // -3 was used during dev-releases, so this conversion helps to revert such affected projects. + // This conversion doesn't affect any other projects, since -3 is not used otherwise. for (KeyValue<StringName, ProjectSettings::VariantContainer> &E : props) { if (String(E.key).begins_with("input/")) { Dictionary action = E.value.variant; Array events = action["events"]; for (int i = 0; i < events.size(); i++) { Ref<InputEvent> ev = events[i]; - if (ev.is_valid() && ev->get_device() == -1) { // -1 was the previous value (GH-97707). - ev->set_device(InputEvent::DEVICE_ID_ALL_DEVICES); + if (ev.is_valid() && ev->get_device() == -3) { + ev->set_device(-1); } } } diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp index 2dc2735fc5..101b76eb95 100644 --- a/core/extension/gdextension.cpp +++ b/core/extension/gdextension.cpp @@ -156,7 +156,7 @@ public: } virtual bool is_vararg() const override { - return false; + return vararg; } #ifdef TOOLS_ENABLED diff --git a/core/input/input_event.cpp b/core/input/input_event.cpp index e580ea185d..2871ef24dd 100644 --- a/core/input/input_event.cpp +++ b/core/input/input_event.cpp @@ -37,6 +37,9 @@ #include "core/os/keyboard.h" #include "core/os/os.h" +const int InputEvent::DEVICE_ID_EMULATION = -1; +const int InputEvent::DEVICE_ID_INTERNAL = -2; + void InputEvent::set_device(int p_device) { device = p_device; emit_changed(); diff --git a/core/input/input_event.h b/core/input/input_event.h index b6faac3f54..e7cd43a042 100644 --- a/core/input/input_event.h +++ b/core/input/input_event.h @@ -64,9 +64,8 @@ protected: static void _bind_methods(); public: - inline static constexpr int DEVICE_ID_EMULATION = -1; - inline static constexpr int DEVICE_ID_INTERNAL = -2; - inline static constexpr int DEVICE_ID_ALL_DEVICES = -3; // Signify that a given Action can be triggered by any device. + static const int DEVICE_ID_EMULATION; + static const int DEVICE_ID_INTERNAL; void set_device(int p_device); int get_device() const; diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp index dad21911a9..757468b822 100644 --- a/core/input/input_map.cpp +++ b/core/input/input_map.cpp @@ -41,6 +41,8 @@ InputMap *InputMap::singleton = nullptr; +int InputMap::ALL_DEVICES = -1; + void InputMap::_bind_methods() { ClassDB::bind_method(D_METHOD("has_action", "action"), &InputMap::has_action); ClassDB::bind_method(D_METHOD("get_actions"), &InputMap::_get_actions); @@ -163,7 +165,7 @@ List<Ref<InputEvent>>::Element *InputMap::_find_event(Action &p_action, const Re int i = 0; for (List<Ref<InputEvent>>::Element *E = p_action.inputs.front(); E; E = E->next()) { int device = E->get()->get_device(); - if (device == InputEvent::DEVICE_ID_ALL_DEVICES || device == p_event->get_device()) { + if (device == ALL_DEVICES || device == p_event->get_device()) { if (E->get()->action_match(p_event, p_exact_match, p_action.deadzone, r_pressed, r_strength, r_raw_strength)) { if (r_event_index) { *r_event_index = i; diff --git a/core/input/input_map.h b/core/input/input_map.h index 319ce98ebd..492bf0e9c7 100644 --- a/core/input/input_map.h +++ b/core/input/input_map.h @@ -45,6 +45,11 @@ class InputMap : public Object { GDCLASS(InputMap, Object); public: + /** + * A special value used to signify that a given Action can be triggered by any device + */ + static int ALL_DEVICES; + struct Action { int id; float deadzone; diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index 85781a9591..bd276010bf 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -297,13 +297,13 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin load_paths_stack.push_back(original_path); // Try all loaders and pick the first match for the type hint - bool loader_found = false; + bool found = false; Ref<Resource> res; for (int i = 0; i < loader_count; i++) { if (!loader[i]->recognize_path(p_path, p_type_hint)) { continue; } - loader_found = true; + found = true; res = loader[i]->load(p_path, original_path, r_error, p_use_sub_threads, r_progress, p_cache_mode); if (!res.is_null()) { break; @@ -318,24 +318,15 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin return res; } - if (!loader_found) { - if (r_error) { - *r_error = ERR_FILE_UNRECOGNIZED; - } - ERR_FAIL_V_MSG(Ref<Resource>(), vformat("No loader found for resource: %s (expected type: %s)", p_path, p_type_hint)); - } + ERR_FAIL_COND_V_MSG(found, Ref<Resource>(), + vformat("Failed loading resource: %s. Make sure resources have been imported by opening the project in the editor at least once.", p_path)); #ifdef TOOLS_ENABLED Ref<FileAccess> file_check = FileAccess::create(FileAccess::ACCESS_RESOURCES); - if (!file_check->file_exists(p_path)) { - if (r_error) { - *r_error = ERR_FILE_NOT_FOUND; - } - ERR_FAIL_V_MSG(Ref<Resource>(), vformat("Resource file not found: %s (expected type: %s)", p_path, p_type_hint)); - } + ERR_FAIL_COND_V_MSG(!file_check->file_exists(p_path), Ref<Resource>(), vformat("Resource file not found: %s (expected type: %s)", p_path, p_type_hint)); #endif - ERR_FAIL_V_MSG(Ref<Resource>(), vformat("Failed loading resource: %s. Make sure resources have been imported by opening the project in the editor at least once.", p_path)); + ERR_FAIL_V_MSG(Ref<Resource>(), vformat("No loader found for resource: %s (expected type: %s)", p_path, p_type_hint)); } // This implementation must allow re-entrancy for a task that started awaiting in a deeper stack frame. diff --git a/doc/classes/EditorSpinSlider.xml b/doc/classes/EditorSpinSlider.xml index 60f68d67fc..b4b1534017 100644 --- a/doc/classes/EditorSpinSlider.xml +++ b/doc/classes/EditorSpinSlider.xml @@ -40,6 +40,11 @@ Emitted when the spinner/slider is ungrabbed. </description> </signal> + <signal name="updown_pressed"> + <description> + Emitted when the updown button is pressed. + </description> + </signal> <signal name="value_focus_entered"> <description> Emitted when the value form gains focus. diff --git a/doc/classes/FileAccess.xml b/doc/classes/FileAccess.xml index 79c3f0c3f1..f397b5d7ee 100644 --- a/doc/classes/FileAccess.xml +++ b/doc/classes/FileAccess.xml @@ -4,7 +4,7 @@ Provides methods for file reading and writing operations. </brief_description> <description> - This class can be used to permanently store data in the user device's file system and to read from it. This is useful for store game save data or player configuration files. + This class can be used to permanently store data in the user device's file system and to read from it. This is useful for storing game save data or player configuration files. Here's a sample on how to write and read from a file: [codeblocks] [gdscript] diff --git a/drivers/vulkan/rendering_device_driver_vulkan.cpp b/drivers/vulkan/rendering_device_driver_vulkan.cpp index 38c4f4ae07..161fe4eac4 100644 --- a/drivers/vulkan/rendering_device_driver_vulkan.cpp +++ b/drivers/vulkan/rendering_device_driver_vulkan.cpp @@ -2394,7 +2394,9 @@ RDD::CommandQueueFamilyID RenderingDeviceDriverVulkan::command_queue_family_get( } } - ERR_FAIL_COND_V_MSG(picked_family_index >= queue_family_properties.size(), CommandQueueFamilyID(), "A queue family with the requested bits could not be found."); + if (picked_family_index >= queue_family_properties.size()) { + return CommandQueueFamilyID(); + } // Since 0 is a valid index and we use 0 as the error case, we make the index start from 1 instead. return CommandQueueFamilyID(picked_family_index + 1); diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 045812bf0b..d3e79dbe04 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -2193,7 +2193,7 @@ void AnimationTrackEdit::_notification(int p_what) { offset = offset * scale + limit; Color marker_color = animation->get_marker_color(marker); marker_color.a = 0.2; - draw_line(Point2(offset, 0), Point2(offset, get_size().height), marker_color); + draw_line(Point2(offset, 0), Point2(offset, get_size().height), marker_color, Math::round(EDSCALE)); } } } @@ -3649,7 +3649,7 @@ void AnimationTrackEditGroup::_notification(int p_what) { offset = offset * scale + limit; Color marker_color = editor->get_current_animation()->get_marker_color(marker); marker_color.a = 0.2; - draw_line(Point2(offset, 0), Point2(offset, get_size().height), marker_color); + draw_line(Point2(offset, 0), Point2(offset, get_size().height), marker_color, Math::round(EDSCALE)); } } } @@ -7366,7 +7366,6 @@ void AnimationTrackEditor::_update_snap_unit() { } if (timeline->is_using_fps()) { - _clear_selection(true); // Needs to recreate a spinbox of the KeyEdit. snap_unit = 1.0 / step->get_value(); } else { if (fps_compat->is_pressed()) { @@ -7985,6 +7984,11 @@ AnimationTrackEditor::~AnimationTrackEditor() { // AnimationTrackKeyEditEditorPlugin. +void AnimationTrackKeyEditEditor::_time_edit_spun() { + _time_edit_entered(); + _time_edit_exited(); +} + void AnimationTrackKeyEditEditor::_time_edit_entered() { int key = animation->track_find_key(track, key_ofs, Animation::FIND_MODE_APPROX); if (key == -1) { @@ -8012,7 +8016,7 @@ void AnimationTrackKeyEditEditor::_time_edit_exited() { int existing = animation->track_find_key(track, new_time, Animation::FIND_MODE_APPROX); EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Animation Change Keyframe Time"), UndoRedo::MERGE_ENDS); + undo_redo->create_action(TTR("Animation Change Keyframe Time")); if (existing != -1) { undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", track, animation->track_get_key_time(track, existing)); @@ -8059,6 +8063,7 @@ AnimationTrackKeyEditEditor::AnimationTrackKeyEditEditor(Ref<Animation> p_animat spinner->set_min(0); spinner->set_allow_greater(true); spinner->set_allow_lesser(true); + add_child(spinner); if (use_fps) { spinner->set_step(FPS_DECIMAL); @@ -8067,14 +8072,13 @@ AnimationTrackKeyEditEditor::AnimationTrackKeyEditEditor(Ref<Animation> p_animat fps = 1.0 / fps; } spinner->set_value(key_ofs * fps); + spinner->connect("updown_pressed", callable_mp(this, &AnimationTrackKeyEditEditor::_time_edit_spun), CONNECT_DEFERRED); } else { spinner->set_step(SECOND_DECIMAL); spinner->set_value(key_ofs); spinner->set_max(animation->get_length()); } - add_child(spinner); - spinner->connect("grabbed", callable_mp(this, &AnimationTrackKeyEditEditor::_time_edit_entered), CONNECT_DEFERRED); spinner->connect("ungrabbed", callable_mp(this, &AnimationTrackKeyEditEditor::_time_edit_exited), CONNECT_DEFERRED); spinner->connect("value_focus_entered", callable_mp(this, &AnimationTrackKeyEditEditor::_time_edit_entered), CONNECT_DEFERRED); @@ -8309,9 +8313,6 @@ void AnimationMarkerEdit::_notification(int p_what) { Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label")); Color color = get_theme_color(SceneStringName(font_color), SNAME("Label")); - int hsep = get_theme_constant(SNAME("h_separation"), SNAME("ItemList")); - Color linecolor = color; - linecolor.a = 0.2; // SECTION PREVIEW // @@ -8370,31 +8371,21 @@ void AnimationMarkerEdit::_notification(int p_what) { draw_key(name, scale, int(offset), is_selected, limit, limit_end); - const int font_size = 16; + const int font_size = 12 * EDSCALE; Size2 string_size = font->get_string_size(name, HORIZONTAL_ALIGNMENT_LEFT, -1.0, font_size); if (int(offset) <= limit_end && int(offset) >= limit && should_show_all_marker_names) { float bottom = get_size().height + string_size.y - font->get_descent(font_size); float extrusion = MAX(0, offset + string_size.x - limit_end); // How much the string would extrude outside limit_end if unadjusted. Color marker_color = animation->get_marker_color(name); - draw_string(font, Point2(offset - extrusion, bottom), name, HORIZONTAL_ALIGNMENT_LEFT, -1.0, font_size, marker_color); - draw_string_outline(font, Point2(offset - extrusion, bottom), name, HORIZONTAL_ALIGNMENT_LEFT, -1.0, font_size, 1, color); + float margin = 4 * EDSCALE; + Point2 pos = Point2(offset - extrusion + margin, bottom + margin); + draw_string(font, pos, name, HORIZONTAL_ALIGNMENT_LEFT, -1.0, font_size, marker_color); + draw_string_outline(font, pos, name, HORIZONTAL_ALIGNMENT_LEFT, -1.0, font_size, 1, color); } } } draw_fg(limit, get_size().width - timeline->get_buttons_width()); - - // BUTTONS // - - { - int ofs = get_size().width - timeline->get_buttons_width(); - - draw_line(Point2(ofs, 0), Point2(ofs, get_size().height), linecolor, Math::round(EDSCALE)); - - ofs += hsep; - } - - draw_line(Vector2(0, get_size().height), get_size(), linecolor, Math::round(EDSCALE)); } break; case NOTIFICATION_MOUSE_ENTER: @@ -9195,9 +9186,6 @@ void AnimationMultiMarkerKeyEdit::_get_property_list(List<PropertyInfo> *p_list) // AnimationMarkerKeyEditEditorPlugin -void AnimationMarkerKeyEditEditor::_time_edit_entered() { -} - void AnimationMarkerKeyEditEditor::_time_edit_exited() { real_t new_time = spinner->get_value(); @@ -9216,7 +9204,7 @@ void AnimationMarkerKeyEditEditor::_time_edit_exited() { } EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Animation Change Marker Time"), UndoRedo::MERGE_ENDS); + undo_redo->create_action(TTR("Animation Change Marker Time")); Color color = animation->get_marker_color(marker_name); undo_redo->add_do_method(animation.ptr(), "add_marker", marker_name, new_time); @@ -9257,6 +9245,7 @@ AnimationMarkerKeyEditEditor::AnimationMarkerKeyEditEditor(Ref<Animation> p_anim spinner->set_min(0); spinner->set_allow_greater(true); spinner->set_allow_lesser(true); + add_child(spinner); float time = animation->get_marker_time(marker_name); @@ -9267,17 +9256,14 @@ AnimationMarkerKeyEditEditor::AnimationMarkerKeyEditEditor(Ref<Animation> p_anim fps = 1.0 / fps; } spinner->set_value(time * fps); + spinner->connect("updown_pressed", callable_mp(this, &AnimationMarkerKeyEditEditor::_time_edit_exited), CONNECT_DEFERRED); } else { spinner->set_step(SECOND_DECIMAL); spinner->set_value(time); spinner->set_max(animation->get_length()); } - add_child(spinner); - - spinner->connect("grabbed", callable_mp(this, &AnimationMarkerKeyEditEditor::_time_edit_entered), CONNECT_DEFERRED); spinner->connect("ungrabbed", callable_mp(this, &AnimationMarkerKeyEditEditor::_time_edit_exited), CONNECT_DEFERRED); - spinner->connect("value_focus_entered", callable_mp(this, &AnimationMarkerKeyEditEditor::_time_edit_entered), CONNECT_DEFERRED); spinner->connect("value_focus_exited", callable_mp(this, &AnimationMarkerKeyEditEditor::_time_edit_exited), CONNECT_DEFERRED); } diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h index b3533d2f75..e0a3c7197a 100644 --- a/editor/animation_track_editor.h +++ b/editor/animation_track_editor.h @@ -979,6 +979,7 @@ class AnimationTrackKeyEditEditor : public EditorProperty { Variant value; } key_data_cache; + void _time_edit_spun(); void _time_edit_entered(); void _time_edit_exited(); @@ -998,7 +999,6 @@ class AnimationMarkerKeyEditEditor : public EditorProperty { EditorSpinSlider *spinner = nullptr; - void _time_edit_entered(); void _time_edit_exited(); public: diff --git a/editor/event_listener_line_edit.cpp b/editor/event_listener_line_edit.cpp index b6c5c65c21..a930f353a3 100644 --- a/editor/event_listener_line_edit.cpp +++ b/editor/event_listener_line_edit.cpp @@ -123,7 +123,7 @@ String EventListenerLineEdit::get_event_text(const Ref<InputEvent> &p_event, boo } String EventListenerLineEdit::get_device_string(int p_device) { - if (p_device == InputEvent::DEVICE_ID_ALL_DEVICES) { + if (p_device == InputMap::ALL_DEVICES) { return TTR("All Devices"); } return TTR("Device") + " " + itos(p_device); diff --git a/editor/export/project_export.cpp b/editor/export/project_export.cpp index e8024c456f..e1a9a5a331 100644 --- a/editor/export/project_export.cpp +++ b/editor/export/project_export.cpp @@ -1208,6 +1208,9 @@ void ProjectExportDialog::_export_pck_zip_selected(const String &p_path) { bool export_debug = fd_option.get(TTR("Export With Debug"), true); bool export_as_patch = fd_option.get(TTR("Export As Patch"), true); + EditorSettings::get_singleton()->set_project_metadata("export_options", "export_debug", export_debug); + EditorSettings::get_singleton()->set_project_metadata("export_options", "export_as_patch", export_as_patch); + if (p_path.ends_with(".zip")) { if (export_as_patch) { platform->export_zip_patch(current, export_debug, p_path); @@ -1307,6 +1310,8 @@ void ProjectExportDialog::_export_project_to_path(const String &p_path) { Dictionary fd_option = export_project->get_selected_options(); bool export_debug = fd_option.get(TTR("Export With Debug"), true); + EditorSettings::get_singleton()->set_project_metadata("export_options", "export_debug", export_debug); + Error err = platform->export_project(current, export_debug, current->get_export_path(), 0); result_dialog_log->clear(); if (err != ERR_SKIP) { @@ -1776,9 +1781,9 @@ ProjectExportDialog::ProjectExportDialog() { export_project->connect("file_selected", callable_mp(this, &ProjectExportDialog::_export_project_to_path)); export_project->get_line_edit()->connect(SceneStringName(text_changed), callable_mp(this, &ProjectExportDialog::_validate_export_path)); - export_project->add_option(TTR("Export With Debug"), Vector<String>(), true); - export_pck_zip->add_option(TTR("Export With Debug"), Vector<String>(), true); - export_pck_zip->add_option(TTR("Export As Patch"), Vector<String>(), true); + export_project->add_option(TTR("Export With Debug"), Vector<String>(), EditorSettings::get_singleton()->get_project_metadata("export_options", "export_debug", true)); + export_pck_zip->add_option(TTR("Export With Debug"), Vector<String>(), EditorSettings::get_singleton()->get_project_metadata("export_options", "export_debug", true)); + export_pck_zip->add_option(TTR("Export As Patch"), Vector<String>(), EditorSettings::get_singleton()->get_project_metadata("export_options", "export_as_patch", true)); set_hide_on_ok(false); diff --git a/editor/gui/editor_spin_slider.cpp b/editor/gui/editor_spin_slider.cpp index 54fde9b119..1065783f0c 100644 --- a/editor/gui/editor_spin_slider.cpp +++ b/editor/gui/editor_spin_slider.cpp @@ -69,6 +69,7 @@ void EditorSpinSlider::gui_input(const Ref<InputEvent> &p_event) { } else { set_value(get_value() - get_step()); } + emit_signal("updown_pressed"); return; } _grab_start(); @@ -698,6 +699,7 @@ void EditorSpinSlider::_bind_methods() { ADD_SIGNAL(MethodInfo("grabbed")); ADD_SIGNAL(MethodInfo("ungrabbed")); + ADD_SIGNAL(MethodInfo("updown_pressed")); ADD_SIGNAL(MethodInfo("value_focus_entered")); ADD_SIGNAL(MethodInfo("value_focus_exited")); diff --git a/editor/input_event_configuration_dialog.cpp b/editor/input_event_configuration_dialog.cpp index 9c36e2be54..c98f288f06 100644 --- a/editor/input_event_configuration_dialog.cpp +++ b/editor/input_event_configuration_dialog.cpp @@ -553,18 +553,18 @@ void InputEventConfigurationDialog::_input_list_item_selected() { } void InputEventConfigurationDialog::_device_selection_changed(int p_option_button_index) { - // Option index 0 corresponds to "All Devices" (value of -3). - // Otherwise subtract 1 as option index 1 corresponds to device 0, etc... - event->set_device(p_option_button_index == 0 ? InputEvent::DEVICE_ID_ALL_DEVICES : p_option_button_index - 1); + // Subtract 1 as option index 0 corresponds to "All Devices" (value of -1) + // and option index 1 corresponds to device 0, etc... + event->set_device(p_option_button_index - 1); event_as_text->set_text(EventListenerLineEdit::get_event_text(event, true)); } void InputEventConfigurationDialog::_set_current_device(int p_device) { - device_id_option->select(p_device == InputEvent::DEVICE_ID_ALL_DEVICES ? 0 : p_device + 1); + device_id_option->select(p_device + 1); } int InputEventConfigurationDialog::_get_current_device() const { - return device_id_option->get_selected() == 0 ? InputEvent::DEVICE_ID_ALL_DEVICES : device_id_option->get_selected() - 1; + return device_id_option->get_selected() - 1; } void InputEventConfigurationDialog::_notification(int p_what) { @@ -707,12 +707,11 @@ InputEventConfigurationDialog::InputEventConfigurationDialog() { device_id_option = memnew(OptionButton); device_id_option->set_h_size_flags(Control::SIZE_EXPAND_FILL); - device_id_option->add_item(EventListenerLineEdit::get_device_string(InputEvent::DEVICE_ID_ALL_DEVICES)); - for (int i = 0; i < 8; i++) { + for (int i = -1; i < 8; i++) { device_id_option->add_item(EventListenerLineEdit::get_device_string(i)); } device_id_option->connect(SceneStringName(item_selected), callable_mp(this, &InputEventConfigurationDialog::_device_selection_changed)); - _set_current_device(InputEvent::DEVICE_ID_ALL_DEVICES); + _set_current_device(InputMap::ALL_DEVICES); device_container->add_child(device_id_option); device_container->hide(); diff --git a/editor/plugins/lightmap_gi_editor_plugin.cpp b/editor/plugins/lightmap_gi_editor_plugin.cpp index 7f68ad212c..af2653c8b8 100644 --- a/editor/plugins/lightmap_gi_editor_plugin.cpp +++ b/editor/plugins/lightmap_gi_editor_plugin.cpp @@ -95,10 +95,14 @@ void LightmapGIEditorPlugin::_bake_select_file(const String &p_file) { file_dialog->popup_file_dialog(); } break; case LightmapGI::BAKE_ERROR_NO_MESHES: { - EditorNode::get_singleton()->show_warning(TTR("No meshes to bake. Make sure they contain an UV2 channel and that the 'Bake Light' flag is on.")); + EditorNode::get_singleton()->show_warning( + TTR("No meshes with lightmapping support to bake. Make sure they contain UV2 data and their Global Illumination property is set to Static.") + + String::utf8("\n\n• ") + TTR("To import a scene with lightmapping support, set Meshes > Light Baking to Static Lightmaps in the Import dock.") + + String::utf8("\n• ") + TTR("To enable lightmapping support on a primitive mesh, edit the PrimitiveMesh resource in the inspector and check Add UV2.") + + String::utf8("\n• ") + TTR("To enable lightmapping support on a CSG mesh, select the root CSG node and choose CSG > Bake Mesh Instance at the top of the 3D editor viewport.\nSelect the generated MeshInstance3D node and choose Mesh > Unwrap UV2 for Lightmap/AO at the top of the 3D editor viewport.")); } break; case LightmapGI::BAKE_ERROR_CANT_CREATE_IMAGE: { - EditorNode::get_singleton()->show_warning(TTR("Failed creating lightmap images, make sure path is writable.")); + EditorNode::get_singleton()->show_warning(TTR("Failed creating lightmap images. Make sure the lightmap destination path is writable.")); } break; case LightmapGI::BAKE_ERROR_NO_SCENE_ROOT: { EditorNode::get_singleton()->show_warning(TTR("No editor scene root found.")); @@ -110,7 +114,7 @@ void LightmapGIEditorPlugin::_bake_select_file(const String &p_file) { EditorNode::get_singleton()->show_warning(TTR("Maximum texture size is too small for the lightmap images.\nWhile this can be fixed by increasing the maximum texture size, it is recommended you split the scene into more objects instead.")); } break; case LightmapGI::BAKE_ERROR_LIGHTMAP_TOO_SMALL: { - EditorNode::get_singleton()->show_warning(TTR("Failed creating lightmap images. Make sure all meshes selected to bake have `lightmap_size_hint` value set high enough, and `texel_scale` value of LightmapGI is not too low.")); + EditorNode::get_singleton()->show_warning(TTR("Failed creating lightmap images. Make sure all meshes to bake have the Lightmap Size Hint property set high enough, and the LightmapGI's Texel Scale value is not too low.")); } break; case LightmapGI::BAKE_ERROR_ATLAS_TOO_SMALL: { EditorNode::get_singleton()->show_warning(TTR("Failed fitting a lightmap image into an atlas. This should never happen and should be reported.")); diff --git a/modules/gridmap/editor/grid_map_editor_plugin.cpp b/modules/gridmap/editor/grid_map_editor_plugin.cpp index bf2a544e59..328d79d979 100644 --- a/modules/gridmap/editor/grid_map_editor_plugin.cpp +++ b/modules/gridmap/editor/grid_map_editor_plugin.cpp @@ -1353,7 +1353,6 @@ GridMapEditor::GridMapEditor() { callable_mp(this, &GridMapEditor::_on_tool_mode_changed).unbind(1)); mode_buttons->add_child(select_mode_button); viewport_shortcut_buttons.push_back(select_mode_button); - select_mode_button->set_pressed(true); erase_mode_button = memnew(Button); erase_mode_button->set_theme_type_variation("FlatButton"); @@ -1726,6 +1725,10 @@ bool GridMapEditorPlugin::handles(Object *p_object) const { void GridMapEditorPlugin::make_visible(bool p_visible) { if (p_visible) { + BaseButton *button = grid_map_editor->mode_buttons_group->get_pressed_button(); + if (button == nullptr) { + grid_map_editor->select_mode_button->set_pressed(true); + } grid_map_editor->_on_tool_mode_changed(); panel_button->show(); EditorNode::get_bottom_panel()->make_item_visible(grid_map_editor); diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ExportDiagnosticsTests.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ExportDiagnosticsTests.cs index 6425c723dd..fe511d67ef 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ExportDiagnosticsTests.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ExportDiagnosticsTests.cs @@ -74,4 +74,31 @@ public class ExportDiagnosticsTests } ); } + + [Fact] + public async void ExportToolButtonInNonToolClass() + { + await CSharpSourceGeneratorVerifier<ScriptPropertiesGenerator>.Verify( + new string[] { "ExportDiagnostics_GD0108.cs" }, + new string[] { "ExportDiagnostics_GD0108_ScriptProperties.generated.cs" } + ); + } + + [Fact] + public async void ExportAndExportToolButtonOnSameMember() + { + await CSharpSourceGeneratorVerifier<ScriptPropertiesGenerator>.Verify( + new string[] { "ExportDiagnostics_GD0109.cs" }, + new string[] { "ExportDiagnostics_GD0109_ScriptProperties.generated.cs" } + ); + } + + [Fact] + public async void ExportToolButtonOnNonCallable() + { + await CSharpSourceGeneratorVerifier<ScriptPropertiesGenerator>.Verify( + new string[] { "ExportDiagnostics_GD0110.cs" }, + new string[] { "ExportDiagnostics_GD0110_ScriptProperties.generated.cs" } + ); + } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertiesGeneratorTests.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertiesGeneratorTests.cs index 724fb164e0..9693cba9ce 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertiesGeneratorTests.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertiesGeneratorTests.cs @@ -66,4 +66,13 @@ public class ScriptPropertiesGeneratorTests "AbstractGenericNode(Of T)_ScriptProperties.generated.cs" ); } + + [Fact] + public async void ExportedButtons() + { + await CSharpSourceGeneratorVerifier<ScriptPropertiesGenerator>.Verify( + "ExportedToolButtons.cs", + "ExportedToolButtons_ScriptProperties.generated.cs" + ); + } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0108_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0108_ScriptProperties.generated.cs new file mode 100644 index 0000000000..fedec5e3e1 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0108_ScriptProperties.generated.cs @@ -0,0 +1,48 @@ +using Godot; +using Godot.NativeInterop; + +partial class ExportDiagnostics_GD0108 +{ +#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword + /// <summary> + /// Cached StringNames for the properties and fields contained in this class, for fast lookup. + /// </summary> + public new class PropertyName : global::Godot.Node.PropertyName { + /// <summary> + /// Cached name for the 'MyButton' field. + /// </summary> + public new static readonly global::Godot.StringName @MyButton = "MyButton"; + } + /// <inheritdoc/> + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + protected override bool SetGodotClassPropertyValue(in godot_string_name name, in godot_variant value) + { + if (name == PropertyName.@MyButton) { + this.@MyButton = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Callable>(value); + return true; + } + return base.SetGodotClassPropertyValue(name, value); + } + /// <inheritdoc/> + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value) + { + if (name == PropertyName.@MyButton) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.@MyButton); + return true; + } + return base.GetGodotClassPropertyValue(name, out value); + } + /// <summary> + /// Get the property information for all the properties declared in this class. + /// This method is used by Redot to register the available properties in the editor. + /// Do not call this method. + /// </summary> + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal new static global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo> GetGodotPropertyList() + { + var properties = new global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>(); + return properties; + } +#pragma warning restore CS0109 +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0109_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0109_ScriptProperties.generated.cs new file mode 100644 index 0000000000..f202d0e4f8 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0109_ScriptProperties.generated.cs @@ -0,0 +1,48 @@ +using Godot; +using Godot.NativeInterop; + +partial class ExportDiagnostics_GD0109 +{ +#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword + /// <summary> + /// Cached StringNames for the properties and fields contained in this class, for fast lookup. + /// </summary> + public new class PropertyName : global::Godot.Node.PropertyName { + /// <summary> + /// Cached name for the 'MyButton' field. + /// </summary> + public new static readonly global::Godot.StringName @MyButton = "MyButton"; + } + /// <inheritdoc/> + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + protected override bool SetGodotClassPropertyValue(in godot_string_name name, in godot_variant value) + { + if (name == PropertyName.@MyButton) { + this.@MyButton = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Callable>(value); + return true; + } + return base.SetGodotClassPropertyValue(name, value); + } + /// <inheritdoc/> + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value) + { + if (name == PropertyName.@MyButton) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.@MyButton); + return true; + } + return base.GetGodotClassPropertyValue(name, out value); + } + /// <summary> + /// Get the property information for all the properties declared in this class. + /// This method is used by Redot to register the available properties in the editor. + /// Do not call this method. + /// </summary> + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal new static global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo> GetGodotPropertyList() + { + var properties = new global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>(); + return properties; + } +#pragma warning restore CS0109 +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0110_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0110_ScriptProperties.generated.cs new file mode 100644 index 0000000000..bd537d1db6 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0110_ScriptProperties.generated.cs @@ -0,0 +1,48 @@ +using Godot; +using Godot.NativeInterop; + +partial class ExportDiagnostics_GD0110 +{ +#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword + /// <summary> + /// Cached StringNames for the properties and fields contained in this class, for fast lookup. + /// </summary> + public new class PropertyName : global::Godot.Node.PropertyName { + /// <summary> + /// Cached name for the 'MyButton' field. + /// </summary> + public new static readonly global::Godot.StringName @MyButton = "MyButton"; + } + /// <inheritdoc/> + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + protected override bool SetGodotClassPropertyValue(in godot_string_name name, in godot_variant value) + { + if (name == PropertyName.@MyButton) { + this.@MyButton = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value); + return true; + } + return base.SetGodotClassPropertyValue(name, value); + } + /// <inheritdoc/> + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value) + { + if (name == PropertyName.@MyButton) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.@MyButton); + return true; + } + return base.GetGodotClassPropertyValue(name, out value); + } + /// <summary> + /// Get the property information for all the properties declared in this class. + /// This method is used by Redot to register the available properties in the editor. + /// Do not call this method. + /// </summary> + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal new static global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo> GetGodotPropertyList() + { + var properties = new global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>(); + return properties; + } +#pragma warning restore CS0109 +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedToolButtons_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedToolButtons_ScriptProperties.generated.cs new file mode 100644 index 0000000000..d896bc76e2 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedToolButtons_ScriptProperties.generated.cs @@ -0,0 +1,48 @@ +using Godot; +using Godot.NativeInterop; + +partial class ExportedToolButtons +{ +#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword + /// <summary> + /// Cached StringNames for the properties and fields contained in this class, for fast lookup. + /// </summary> + public new class PropertyName : global::Godot.GodotObject.PropertyName { + /// <summary> + /// Cached name for the 'MyButton1' property. + /// </summary> + public new static readonly global::Godot.StringName @MyButton1 = "MyButton1"; + /// <summary> + /// Cached name for the 'MyButton2' property. + /// </summary> + public new static readonly global::Godot.StringName @MyButton2 = "MyButton2"; + } + /// <inheritdoc/> + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value) + { + if (name == PropertyName.@MyButton1) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.@MyButton1); + return true; + } + if (name == PropertyName.@MyButton2) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.@MyButton2); + return true; + } + return base.GetGodotClassPropertyValue(name, out value); + } + /// <summary> + /// Get the property information for all the properties declared in this class. + /// This method is used by Redot to register the available properties in the editor. + /// Do not call this method. + /// </summary> + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal new static global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo> GetGodotPropertyList() + { + var properties = new global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>(); + properties.Add(new(type: (global::Godot.Variant.Type)25, name: PropertyName.@MyButton1, hint: (global::Godot.PropertyHint)39, hintString: "Click me!", usage: (global::Godot.PropertyUsageFlags)4, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)25, name: PropertyName.@MyButton2, hint: (global::Godot.PropertyHint)39, hintString: "Click me!,ColorRect", usage: (global::Godot.PropertyUsageFlags)4, exported: true)); + return properties; + } +#pragma warning restore CS0109 +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0108.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0108.cs new file mode 100644 index 0000000000..6fde2e5957 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0108.cs @@ -0,0 +1,8 @@ +using Godot; +using Godot.Collections; + +public partial class ExportDiagnostics_GD0108 : Node +{ + [ExportToolButton("")] + public Callable {|GD0108:MyButton|}; +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0109.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0109.cs new file mode 100644 index 0000000000..80a04cd641 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0109.cs @@ -0,0 +1,9 @@ +using Godot; +using Godot.Collections; + +[Tool] +public partial class ExportDiagnostics_GD0109 : Node +{ + [Export, ExportToolButton("")] + public Callable {|GD0109:MyButton|}; +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0110.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0110.cs new file mode 100644 index 0000000000..586bf6c19e --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0110.cs @@ -0,0 +1,9 @@ +using Godot; +using Godot.Collections; + +[Tool] +public partial class ExportDiagnostics_GD0110 : Node +{ + [ExportToolButton("")] + public string {|GD0110:MyButton|}; +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportedToolButtons.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportedToolButtons.cs new file mode 100644 index 0000000000..91c14387c5 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportedToolButtons.cs @@ -0,0 +1,12 @@ +using Godot; +using System; + +[Tool] +public partial class ExportedToolButtons : GodotObject +{ + [ExportToolButton("Click me!")] + public Callable MyButton1 => Callable.From(() => { GD.Print("Clicked MyButton1!"); }); + + [ExportToolButton("Click me!", Icon = "ColorRect")] + public Callable MyButton2 => Callable.From(() => { GD.Print("Clicked MyButton2!"); }); +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/AnalyzerReleases.Unshipped.md b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/AnalyzerReleases.Unshipped.md index 7286853f7a..90c8491177 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/AnalyzerReleases.Unshipped.md +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/AnalyzerReleases.Unshipped.md @@ -3,3 +3,6 @@ Rule ID | Category | Severity | Notes --------|----------|----------|-------------------- GD0003 | Usage | Error | ScriptPathAttributeGenerator, [Documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/diagnostics/GD0003.html) +GD0108 | Usage | Error | ScriptPropertiesGenerator, [Documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/diagnostics/GD0108.html) +GD0109 | Usage | Error | ScriptPropertiesGenerator, [Documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/diagnostics/GD0109.html) +GD0110 | Usage | Error | ScriptPropertiesGenerator, [Documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/diagnostics/GD0110.html) diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs index ad7962e7df..8da0818e42 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs @@ -107,6 +107,36 @@ namespace Godot.SourceGenerators "Types not derived from Node should not export Node members. Node export is only supported in Node-derived classes.", helpLinkUri: string.Format(_helpLinkFormat, "GD0107")); + public static readonly DiagnosticDescriptor OnlyToolClassesShouldUseExportToolButtonRule = + new DiagnosticDescriptor(id: "GD0108", + title: "The exported tool button is not in a tool class", + messageFormat: "The exported tool button '{0}' is not in a tool class", + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + "The exported tool button is not in a tool class. Annotate the class with the '[Tool]' attribute, or remove the '[ExportToolButton]' attribute.", + helpLinkUri: string.Format(_helpLinkFormat, "GD0108")); + + public static readonly DiagnosticDescriptor ExportToolButtonShouldNotBeUsedWithExportRule = + new DiagnosticDescriptor(id: "GD0109", + title: "The '[ExportToolButton]' attribute cannot be used with another '[Export]' attribute", + messageFormat: "The '[ExportToolButton]' attribute cannot be used with another '[Export]' attribute on '{0}'", + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + "The '[ExportToolButton]' attribute cannot be used with the '[Export]' attribute. Remove one of the attributes.", + helpLinkUri: string.Format(_helpLinkFormat, "GD0109")); + + public static readonly DiagnosticDescriptor ExportToolButtonIsNotCallableRule = + new DiagnosticDescriptor(id: "GD0110", + title: "The exported tool button is not a Callable", + messageFormat: "The exported tool button '{0}' is not a Callable", + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + "The exported tool button is not a Callable. The '[ExportToolButton]' attribute is only supported on members of type Callable.", + helpLinkUri: string.Format(_helpLinkFormat, "GD0110")); + public static readonly DiagnosticDescriptor SignalDelegateMissingSuffixRule = new DiagnosticDescriptor(id: "GD0201", title: "The name of the delegate must end with 'EventHandler'", diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs index 62fa7b0a36..46c446169a 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs @@ -287,6 +287,12 @@ namespace Godot.SourceGenerators public static bool IsGodotGlobalClassAttribute(this INamedTypeSymbol symbol) => symbol.FullQualifiedNameOmitGlobal() == GodotClasses.GlobalClassAttr; + public static bool IsGodotExportToolButtonAttribute(this INamedTypeSymbol symbol) + => symbol.FullQualifiedNameOmitGlobal() == GodotClasses.ExportToolButtonAttr; + + public static bool IsGodotToolAttribute(this INamedTypeSymbol symbol) + => symbol.FullQualifiedNameOmitGlobal() == GodotClasses.ToolAttr; + public static bool IsSystemFlagsAttribute(this INamedTypeSymbol symbol) => symbol.FullQualifiedNameOmitGlobal() == GodotClasses.SystemFlagsAttr; diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs index af39a54b0b..f6de09e25b 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs @@ -9,10 +9,12 @@ namespace Godot.SourceGenerators public const string ExportCategoryAttr = "Godot.ExportCategoryAttribute"; public const string ExportGroupAttr = "Godot.ExportGroupAttribute"; public const string ExportSubgroupAttr = "Godot.ExportSubgroupAttribute"; + public const string ExportToolButtonAttr = "Godot.ExportToolButtonAttribute"; public const string SignalAttr = "Godot.SignalAttribute"; public const string MustBeVariantAttr = "Godot.MustBeVariantAttribute"; public const string GodotClassNameAttr = "Godot.GodotClassNameAttribute"; public const string GlobalClassAttr = "Godot.GlobalClassAttribute"; + public const string ToolAttr = "Godot.ToolAttribute"; public const string SystemFlagsAttr = "System.FlagsAttribute"; } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs index bb4c4824e7..d77ebc3cec 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs @@ -89,7 +89,8 @@ namespace Godot.SourceGenerators Password = 36, LayersAvoidance = 37, DictionaryType = 38, - Max = 39 + ToolButton = 39, + Max = 40 } [Flags] diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs index cc80c03c74..9e88e81920 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs @@ -69,6 +69,7 @@ namespace Godot.SourceGenerators bool hasNamespace = classNs.Length != 0; bool isInnerClass = symbol.ContainingType != null; + bool isToolClass = symbol.GetAttributes().Any(a => a.AttributeClass?.IsGodotToolAttribute() ?? false); string uniqueHint = symbol.FullQualifiedNameOmitGlobal().SanitizeQualifiedNameForUniqueHint() + "_ScriptProperties.generated"; @@ -277,6 +278,16 @@ namespace Godot.SourceGenerators if (propertyInfo == null) continue; + if (propertyInfo.Value.Hint == PropertyHint.ToolButton && !isToolClass) + { + context.ReportDiagnostic(Diagnostic.Create( + Common.OnlyToolClassesShouldUseExportToolButtonRule, + member.Symbol.Locations.FirstLocationWithSourceTreeOrDefault(), + member.Symbol.ToDisplayString() + )); + continue; + } + AppendPropertyInfo(source, propertyInfo.Value); } @@ -418,6 +429,19 @@ namespace Godot.SourceGenerators var exportAttr = memberSymbol.GetAttributes() .FirstOrDefault(a => a.AttributeClass?.IsGodotExportAttribute() ?? false); + var exportToolButtonAttr = memberSymbol.GetAttributes() + .FirstOrDefault(a => a.AttributeClass?.IsGodotExportToolButtonAttribute() ?? false); + + if (exportAttr != null && exportToolButtonAttr != null) + { + context.ReportDiagnostic(Diagnostic.Create( + Common.ExportToolButtonShouldNotBeUsedWithExportRule, + memberSymbol.Locations.FirstLocationWithSourceTreeOrDefault(), + memberSymbol.ToDisplayString() + )); + return null; + } + var propertySymbol = memberSymbol as IPropertySymbol; var fieldSymbol = memberSymbol as IFieldSymbol; @@ -431,19 +455,56 @@ namespace Godot.SourceGenerators } } + if (exportToolButtonAttr != null && propertySymbol != null && propertySymbol.GetMethod == null) + { + context.ReportDiagnostic(Diagnostic.Create( + Common.ExportedPropertyIsWriteOnlyRule, + propertySymbol.Locations.FirstLocationWithSourceTreeOrDefault(), + propertySymbol.ToDisplayString() + )); + return null; + } + var memberType = propertySymbol?.Type ?? fieldSymbol!.Type; var memberVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(marshalType)!.Value; string memberName = memberSymbol.Name; + string? hintString = null; + + if (exportToolButtonAttr != null) + { + if (memberVariantType != VariantType.Callable) + { + context.ReportDiagnostic(Diagnostic.Create( + Common.ExportToolButtonIsNotCallableRule, + memberSymbol.Locations.FirstLocationWithSourceTreeOrDefault(), + memberSymbol.ToDisplayString() + )); + return null; + } + + hintString = exportToolButtonAttr.ConstructorArguments[0].Value?.ToString() ?? ""; + foreach (var namedArgument in exportToolButtonAttr.NamedArguments) + { + if (namedArgument is { Key: "Icon", Value.Value: string { Length: > 0 } }) + { + hintString += $",{namedArgument.Value.Value}"; + } + } + + return new PropertyInfo(memberVariantType, memberName, PropertyHint.ToolButton, + hintString: hintString, PropertyUsageFlags.Editor, exported: true); + } + if (exportAttr == null) { return new PropertyInfo(memberVariantType, memberName, PropertyHint.None, - hintString: null, PropertyUsageFlags.ScriptVariable, exported: false); + hintString: hintString, PropertyUsageFlags.ScriptVariable, exported: false); } if (!TryGetMemberExportHint(typeCache, memberType, exportAttr, memberVariantType, - isTypeArgument: false, out var hint, out var hintString)) + isTypeArgument: false, out var hint, out hintString)) { var constructorArguments = exportAttr.ConstructorArguments; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportToolButtonAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportToolButtonAttribute.cs new file mode 100644 index 0000000000..87a9e628f9 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportToolButtonAttribute.cs @@ -0,0 +1,33 @@ +using System; + +#nullable enable + +namespace Godot +{ + /// <summary> + /// Exports the annotated <see cref="Callable"/> as a clickable button. + /// </summary> + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public sealed class ExportToolButtonAttribute : Attribute + { + /// <summary> + /// The label of the button. + /// </summary> + public string Text { get; } + + /// <summary> + /// If defined, used to fetch an icon for the button via <see cref="Control.GetThemeIcon"/>, + /// from the <code>EditorIcons</code> theme type. + /// </summary> + public string? Icon { get; init; } + + /// <summary> + /// Exports the annotated <see cref="Callable"/> as a clickable button. + /// </summary> + /// <param name="text">The label of the button.</param> + public ExportToolButtonAttribute(string text) + { + Text = text; + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj index 3a3134d160..5aa68559d8 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj @@ -48,6 +48,7 @@ <!-- Sources --> <ItemGroup> <Compile Include="Core\Aabb.cs" /> + <Compile Include="Core\Attributes\ExportToolButtonAttribute.cs" /> <Compile Include="Core\Bridge\GodotSerializationInfo.cs" /> <Compile Include="Core\Bridge\MethodInfo.cs" /> <Compile Include="Core\Callable.generics.cs" /> diff --git a/modules/multiplayer/multiplayer_spawner.cpp b/modules/multiplayer/multiplayer_spawner.cpp index 946f89405b..87a1f5eb89 100644 --- a/modules/multiplayer/multiplayer_spawner.cpp +++ b/modules/multiplayer/multiplayer_spawner.cpp @@ -99,7 +99,13 @@ PackedStringArray MultiplayerSpawner::get_configuration_warnings() const { void MultiplayerSpawner::add_spawnable_scene(const String &p_path) { SpawnableScene sc; - sc.path = p_path; + if (p_path.begins_with("uid://")) { + sc.uid = p_path; + sc.path = ResourceUID::uid_to_path(p_path); + } else { + sc.uid = ResourceUID::path_to_uid(p_path); + sc.path = p_path; + } if (Engine::get_singleton()->is_editor_hint()) { ERR_FAIL_COND(!ResourceLoader::exists(p_path)); } @@ -141,7 +147,7 @@ Vector<String> MultiplayerSpawner::_get_spawnable_scenes() const { Vector<String> ss; ss.resize(spawnable_scenes.size()); for (int i = 0; i < ss.size(); i++) { - ss.write[i] = spawnable_scenes[i].path; + ss.write[i] = spawnable_scenes[i].uid; } return ss; } diff --git a/modules/multiplayer/multiplayer_spawner.h b/modules/multiplayer/multiplayer_spawner.h index a7665be790..fa9b59b1f8 100644 --- a/modules/multiplayer/multiplayer_spawner.h +++ b/modules/multiplayer/multiplayer_spawner.h @@ -51,6 +51,7 @@ public: private: struct SpawnableScene { String path; + String uid; Ref<PackedScene> cache; }; diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 3424df2969..9bbe299f7d 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -792,9 +792,10 @@ Error EditorExportPlatformAndroid::save_apk_so(void *p_userdata, const SharedObj Error EditorExportPlatformAndroid::save_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key, uint64_t p_seed) { APKExportData *ed = static_cast<APKExportData *>(p_userdata); - String dst_path = p_path.replace_first("res://", "assets/"); + const String path = ResourceUID::ensure_path(p_path); + const String dst_path = path.replace_first("res://", "assets/"); - store_in_apk(ed, dst_path, p_data, _should_compress_asset(p_path, p_data) ? Z_DEFLATED : 0); + store_in_apk(ed, dst_path, p_data, _should_compress_asset(path, p_data) ? Z_DEFLATED : 0); return OK; } diff --git a/platform/android/export/gradle_export_util.cpp b/platform/android/export/gradle_export_util.cpp index 7cf55c62d1..585ce1f238 100644 --- a/platform/android/export/gradle_export_util.cpp +++ b/platform/android/export/gradle_export_util.cpp @@ -173,8 +173,9 @@ Error store_string_at_path(const String &p_path, const String &p_data) { // This method will be called ONLY when gradle build is enabled. Error rename_and_store_file_in_gradle_project(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key, uint64_t p_seed) { CustomExportData *export_data = static_cast<CustomExportData *>(p_userdata); - String dst_path = p_path.replace_first("res://", export_data->assets_directory + "/"); - print_verbose("Saving project files from " + p_path + " into " + dst_path); + const String path = ResourceUID::ensure_path(p_path); + const String dst_path = path.replace_first("res://", export_data->assets_directory + "/"); + print_verbose("Saving project files from " + path + " into " + dst_path); Error err = store_file_at_path(dst_path, p_data); return err; } diff --git a/scene/3d/mesh_instance_3d.cpp b/scene/3d/mesh_instance_3d.cpp index eaa4cd7343..67654bf40c 100644 --- a/scene/3d/mesh_instance_3d.cpp +++ b/scene/3d/mesh_instance_3d.cpp @@ -89,17 +89,9 @@ bool MeshInstance3D::_get(const StringName &p_name, Variant &r_ret) const { } void MeshInstance3D::_get_property_list(List<PropertyInfo> *p_list) const { - List<String> ls; - for (const KeyValue<StringName, int> &E : blend_shape_properties) { - ls.push_back(E.key); - } - - ls.sort(); - - for (const String &E : ls) { - p_list->push_back(PropertyInfo(Variant::FLOAT, E, PROPERTY_HINT_RANGE, "-1,1,0.00001")); + for (uint32_t i = 0; i < blend_shape_tracks.size(); i++) { + p_list->push_back(PropertyInfo(Variant::FLOAT, vformat("blend_shapes/%s", String(mesh->get_blend_shape_name(i))), PROPERTY_HINT_RANGE, "-1,1,0.00001")); } - if (mesh.is_valid()) { for (int i = 0; i < mesh->get_surface_count(); i++) { p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("%s/%d", PNAME("surface_material_override"), i), PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial", PROPERTY_USAGE_DEFAULT)); @@ -144,6 +136,7 @@ int MeshInstance3D::get_blend_shape_count() const { } return mesh->get_blend_shape_count(); } + int MeshInstance3D::find_blend_shape_by_name(const StringName &p_name) { if (mesh.is_null()) { return -1; @@ -155,11 +148,13 @@ int MeshInstance3D::find_blend_shape_by_name(const StringName &p_name) { } return -1; } + float MeshInstance3D::get_blend_shape_value(int p_blend_shape) const { ERR_FAIL_COND_V(mesh.is_null(), 0.0); ERR_FAIL_INDEX_V(p_blend_shape, (int)blend_shape_tracks.size(), 0); return blend_shape_tracks[p_blend_shape]; } + void MeshInstance3D::set_blend_shape_value(int p_blend_shape, float p_value) { ERR_FAIL_COND(mesh.is_null()); ERR_FAIL_INDEX(p_blend_shape, (int)blend_shape_tracks.size()); diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp index 895f9907ad..4e75cc2c45 100644 --- a/scene/gui/spin_box.cpp +++ b/scene/gui/spin_box.cpp @@ -547,6 +547,12 @@ void SpinBox::_set_step_no_signal(double p_step) { set_block_signals(false); } +void SpinBox::_validate_property(PropertyInfo &p_property) const { + if (p_property.name == "exp_edit") { + p_property.usage = PROPERTY_USAGE_NONE; + } +} + void SpinBox::_bind_methods() { ClassDB::bind_method(D_METHOD("set_horizontal_alignment", "alignment"), &SpinBox::set_horizontal_alignment); ClassDB::bind_method(D_METHOD("get_horizontal_alignment"), &SpinBox::get_horizontal_alignment); diff --git a/scene/gui/spin_box.h b/scene/gui/spin_box.h index 17efa331cf..60f93a0f19 100644 --- a/scene/gui/spin_box.h +++ b/scene/gui/spin_box.h @@ -141,6 +141,7 @@ class SpinBox : public Range { protected: virtual void gui_input(const Ref<InputEvent> &p_event) override; void _value_changed(double p_value) override; + void _validate_property(PropertyInfo &p_property) const; void _notification(int p_what); static void _bind_methods(); diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 8a48b99040..dabd1dbe07 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -1938,21 +1938,19 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } } - // If the tooltip timer isn't running, start it. - // Otherwise, only reset the timer if the mouse has moved more than 5 pixels. - if (!is_tooltip_shown && over->can_process() && - (gui.tooltip_timer.is_null() || - Math::is_zero_approx(gui.tooltip_timer->get_time_left()) || - mm->get_relative().length() > 5.0)) { - if (gui.tooltip_timer.is_valid()) { - gui.tooltip_timer->release_connections(); - gui.tooltip_timer = Ref<SceneTreeTimer>(); + // Reset the timer if the mouse has moved more than 5 pixels or has entered a new control. + if (!is_tooltip_shown && over->can_process()) { + Vector2 new_tooltip_pos = over->get_screen_transform().xform(pos); + if (over != gui.tooltip_control || gui.tooltip_pos.distance_squared_to(new_tooltip_pos) > 25) { + if (gui.tooltip_timer.is_valid()) { + gui.tooltip_timer->release_connections(); + } + gui.tooltip_control = over; + gui.tooltip_pos = new_tooltip_pos; + gui.tooltip_timer = get_tree()->create_timer(gui.tooltip_delay); + gui.tooltip_timer->set_ignore_time_scale(true); + gui.tooltip_timer->connect("timeout", callable_mp(this, &Viewport::_gui_show_tooltip)); } - gui.tooltip_control = over; - gui.tooltip_pos = over->get_screen_transform().xform(pos); - gui.tooltip_timer = get_tree()->create_timer(gui.tooltip_delay); - gui.tooltip_timer->set_ignore_time_scale(true); - gui.tooltip_timer->connect("timeout", callable_mp(this, &Viewport::_gui_show_tooltip)); } } diff --git a/servers/rendering/renderer_rd/environment/sky.cpp b/servers/rendering/renderer_rd/environment/sky.cpp index e71e3f6efb..0ecc0f39f5 100644 --- a/servers/rendering/renderer_rd/environment/sky.cpp +++ b/servers/rendering/renderer_rd/environment/sky.cpp @@ -974,26 +974,26 @@ SkyRD::~SkyRD() { } } -void SkyRD::setup_sky(RID p_env, Ref<RenderSceneBuffersRD> p_render_buffers, const PagedArray<RID> &p_lights, RID p_camera_attributes, uint32_t p_view_count, const Projection *p_view_projections, const Vector3 *p_view_eye_offsets, const Transform3D &p_cam_transform, const Projection &p_cam_projection, const Size2i p_screen_size, Vector2 p_jitter, RendererSceneRenderRD *p_scene_render) { +void SkyRD::setup_sky(const RenderDataRD *p_render_data, const Size2i p_screen_size) { RendererRD::LightStorage *light_storage = RendererRD::LightStorage::get_singleton(); RendererRD::MaterialStorage *material_storage = RendererRD::MaterialStorage::get_singleton(); - ERR_FAIL_COND(p_env.is_null()); + ERR_FAIL_COND(p_render_data->environment.is_null()); - ERR_FAIL_COND(p_render_buffers.is_null()); + ERR_FAIL_COND(p_render_data->render_buffers.is_null()); // make sure we support our view count - ERR_FAIL_COND(p_view_count == 0); - ERR_FAIL_COND(p_view_count > RendererSceneRender::MAX_RENDER_VIEWS); + ERR_FAIL_COND(p_render_data->scene_data->view_count == 0); + ERR_FAIL_COND(p_render_data->scene_data->view_count > RendererSceneRender::MAX_RENDER_VIEWS); SkyMaterialData *material = nullptr; - Sky *sky = get_sky(RendererSceneRenderRD::get_singleton()->environment_get_sky(p_env)); + Sky *sky = get_sky(RendererSceneRenderRD::get_singleton()->environment_get_sky(p_render_data->environment)); RID sky_material; SkyShaderData *shader_data = nullptr; if (sky) { - sky_material = sky_get_material(RendererSceneRenderRD::get_singleton()->environment_get_sky(p_env)); + sky_material = sky_get_material(RendererSceneRenderRD::get_singleton()->environment_get_sky(p_render_data->environment)); if (sky_material.is_valid()) { material = static_cast<SkyMaterialData *>(material_storage->material_get_data(sky_material, RendererRD::MaterialStorage::SHADER_TYPE_SKY)); @@ -1027,8 +1027,8 @@ void SkyRD::setup_sky(RID p_env, Ref<RenderSceneBuffersRD> p_render_buffers, con update_dirty_skys(); } - if (shader_data->uses_time && p_scene_render->time - sky->prev_time > 0.00001) { - sky->prev_time = p_scene_render->time; + if (shader_data->uses_time && p_render_data->scene_data->time - sky->prev_time > 0.00001) { + sky->prev_time = p_render_data->scene_data->time; sky->reflection.dirty = true; RenderingServerDefault::redraw_request(); } @@ -1043,29 +1043,30 @@ void SkyRD::setup_sky(RID p_env, Ref<RenderSceneBuffersRD> p_render_buffers, con sky->reflection.dirty = true; } - if (!p_cam_transform.origin.is_equal_approx(sky->prev_position) && shader_data->uses_position) { - sky->prev_position = p_cam_transform.origin; + if (!p_render_data->scene_data->cam_transform.origin.is_equal_approx(sky->prev_position) && shader_data->uses_position) { + sky->prev_position = p_render_data->scene_data->cam_transform.origin; sky->reflection.dirty = true; } } sky_scene_state.ubo.directional_light_count = 0; if (shader_data->uses_light) { + const PagedArray<RID> &lights = *p_render_data->lights; // Run through the list of lights in the scene and pick out the Directional Lights. // This can't be done in RenderSceneRenderRD::_setup lights because that needs to be called // after the depth prepass, but this runs before the depth prepass. - for (int i = 0; i < (int)p_lights.size(); i++) { - if (!light_storage->owns_light_instance(p_lights[i])) { + for (int i = 0; i < (int)lights.size(); i++) { + if (!light_storage->owns_light_instance(lights[i])) { continue; } - RID base = light_storage->light_instance_get_base_light(p_lights[i]); + RID base = light_storage->light_instance_get_base_light(lights[i]); ERR_CONTINUE(base.is_null()); RS::LightType type = light_storage->light_get_type(base); if (type == RS::LIGHT_DIRECTIONAL && light_storage->light_directional_get_sky_mode(base) != RS::LIGHT_DIRECTIONAL_SKY_MODE_LIGHT_ONLY) { SkyDirectionalLightData &sky_light_data = sky_scene_state.directional_lights[sky_scene_state.ubo.directional_light_count]; - Transform3D light_transform = light_storage->light_instance_get_base_transform(p_lights[i]); + Transform3D light_transform = light_storage->light_instance_get_base_transform(lights[i]); Vector3 world_direction = light_transform.basis.xform(Vector3(0, 0, 1)).normalized(); sky_light_data.direction[0] = world_direction.x; @@ -1075,12 +1076,12 @@ void SkyRD::setup_sky(RID p_env, Ref<RenderSceneBuffersRD> p_render_buffers, con float sign = light_storage->light_is_negative(base) ? -1 : 1; sky_light_data.energy = sign * light_storage->light_get_param(base, RS::LIGHT_PARAM_ENERGY); - if (p_scene_render->is_using_physical_light_units()) { + if (RendererSceneRenderRD::get_singleton()->is_using_physical_light_units()) { sky_light_data.energy *= light_storage->light_get_param(base, RS::LIGHT_PARAM_INTENSITY); } - if (p_camera_attributes.is_valid()) { - sky_light_data.energy *= RSG::camera_attributes->camera_attributes_get_exposure_normalization_factor(p_camera_attributes); + if (p_render_data->camera_attributes.is_valid()) { + sky_light_data.energy *= RSG::camera_attributes->camera_attributes_get_exposure_normalization_factor(p_render_data->camera_attributes); } Color linear_col = light_storage->light_get_color(base).srgb_to_linear(); @@ -1151,43 +1152,48 @@ void SkyRD::setup_sky(RID p_env, Ref<RenderSceneBuffersRD> p_render_buffers, con // Setup fog variables. sky_scene_state.ubo.volumetric_fog_enabled = false; - if (p_render_buffers.is_valid()) { - if (p_render_buffers->has_custom_data(RB_SCOPE_FOG)) { - Ref<RendererRD::Fog::VolumetricFog> fog = p_render_buffers->get_custom_data(RB_SCOPE_FOG); - sky_scene_state.ubo.volumetric_fog_enabled = true; - - float fog_end = fog->length; - if (fog_end > 0.0) { - sky_scene_state.ubo.volumetric_fog_inv_length = 1.0 / fog_end; - } else { - sky_scene_state.ubo.volumetric_fog_inv_length = 1.0; - } + if (p_render_data->render_buffers->has_custom_data(RB_SCOPE_FOG)) { + Ref<RendererRD::Fog::VolumetricFog> fog = p_render_data->render_buffers->get_custom_data(RB_SCOPE_FOG); + sky_scene_state.ubo.volumetric_fog_enabled = true; - float fog_detail_spread = fog->spread; // Reverse lookup. - if (fog_detail_spread > 0.0) { - sky_scene_state.ubo.volumetric_fog_detail_spread = 1.0 / fog_detail_spread; - } else { - sky_scene_state.ubo.volumetric_fog_detail_spread = 1.0; - } + float fog_end = fog->length; + if (fog_end > 0.0) { + sky_scene_state.ubo.volumetric_fog_inv_length = 1.0 / fog_end; + } else { + sky_scene_state.ubo.volumetric_fog_inv_length = 1.0; + } - sky_scene_state.fog_uniform_set = fog->sky_uniform_set; + float fog_detail_spread = fog->spread; // Reverse lookup. + if (fog_detail_spread > 0.0) { + sky_scene_state.ubo.volumetric_fog_detail_spread = 1.0 / fog_detail_spread; + } else { + sky_scene_state.ubo.volumetric_fog_detail_spread = 1.0; } + + sky_scene_state.fog_uniform_set = fog->sky_uniform_set; } + sky_scene_state.view_count = p_render_data->scene_data->view_count; + sky_scene_state.cam_transform = p_render_data->scene_data->cam_transform; + Projection correction; - correction.set_depth_correction(false, true); - correction.add_jitter_offset(p_jitter); + correction.set_depth_correction(p_render_data->scene_data->flip_y, true); + correction.add_jitter_offset(p_render_data->scene_data->taa_jitter); + + Projection projection = p_render_data->scene_data->cam_projection; + if (p_render_data->scene_data->cam_frustum) { + // We don't use a full projection matrix for the sky, this is enough to make up for it. + projection[2].y = -projection[2].y; + } - sky_scene_state.view_count = p_view_count; - sky_scene_state.cam_transform = p_cam_transform; - sky_scene_state.cam_projection = correction * p_cam_projection; // We only use this when rendering a single view. + sky_scene_state.cam_projection = correction * projection; // Our info in our UBO is only used if we're rendering stereo. - for (uint32_t i = 0; i < p_view_count; i++) { - Projection view_inv_projection = (correction * p_view_projections[i]).inverse(); - if (p_view_count > 1) { + for (uint32_t i = 0; i < p_render_data->scene_data->view_count; i++) { + Projection view_inv_projection = (correction * p_render_data->scene_data->view_projection[i]).inverse(); + if (p_render_data->scene_data->view_count > 1) { // Reprojection is used when we need to have things in combined space. - RendererRD::MaterialStorage::store_camera(p_cam_projection * view_inv_projection, sky_scene_state.ubo.combined_reprojection[i]); + RendererRD::MaterialStorage::store_camera(p_render_data->scene_data->cam_projection * view_inv_projection, sky_scene_state.ubo.combined_reprojection[i]); } else { // This is unused so just reset to identity. Projection ident; @@ -1195,25 +1201,25 @@ void SkyRD::setup_sky(RID p_env, Ref<RenderSceneBuffersRD> p_render_buffers, con } RendererRD::MaterialStorage::store_camera(view_inv_projection, sky_scene_state.ubo.view_inv_projections[i]); - sky_scene_state.ubo.view_eye_offsets[i][0] = p_view_eye_offsets[i].x; - sky_scene_state.ubo.view_eye_offsets[i][1] = p_view_eye_offsets[i].y; - sky_scene_state.ubo.view_eye_offsets[i][2] = p_view_eye_offsets[i].z; + sky_scene_state.ubo.view_eye_offsets[i][0] = p_render_data->scene_data->view_eye_offset[i].x; + sky_scene_state.ubo.view_eye_offsets[i][1] = p_render_data->scene_data->view_eye_offset[i].y; + sky_scene_state.ubo.view_eye_offsets[i][2] = p_render_data->scene_data->view_eye_offset[i].z; sky_scene_state.ubo.view_eye_offsets[i][3] = 0.0; } - sky_scene_state.ubo.z_far = p_view_projections[0].get_z_far(); // Should be the same for all projection. - sky_scene_state.ubo.fog_enabled = RendererSceneRenderRD::get_singleton()->environment_get_fog_enabled(p_env); - sky_scene_state.ubo.fog_density = RendererSceneRenderRD::get_singleton()->environment_get_fog_density(p_env); - sky_scene_state.ubo.fog_aerial_perspective = RendererSceneRenderRD::get_singleton()->environment_get_fog_aerial_perspective(p_env); - Color fog_color = RendererSceneRenderRD::get_singleton()->environment_get_fog_light_color(p_env).srgb_to_linear(); - float fog_energy = RendererSceneRenderRD::get_singleton()->environment_get_fog_light_energy(p_env); + sky_scene_state.ubo.z_far = p_render_data->scene_data->view_projection[0].get_z_far(); // Should be the same for all projection. + sky_scene_state.ubo.fog_enabled = RendererSceneRenderRD::get_singleton()->environment_get_fog_enabled(p_render_data->environment); + sky_scene_state.ubo.fog_density = RendererSceneRenderRD::get_singleton()->environment_get_fog_density(p_render_data->environment); + sky_scene_state.ubo.fog_aerial_perspective = RendererSceneRenderRD::get_singleton()->environment_get_fog_aerial_perspective(p_render_data->environment); + Color fog_color = RendererSceneRenderRD::get_singleton()->environment_get_fog_light_color(p_render_data->environment).srgb_to_linear(); + float fog_energy = RendererSceneRenderRD::get_singleton()->environment_get_fog_light_energy(p_render_data->environment); sky_scene_state.ubo.fog_light_color[0] = fog_color.r * fog_energy; sky_scene_state.ubo.fog_light_color[1] = fog_color.g * fog_energy; sky_scene_state.ubo.fog_light_color[2] = fog_color.b * fog_energy; - sky_scene_state.ubo.fog_sun_scatter = RendererSceneRenderRD::get_singleton()->environment_get_fog_sun_scatter(p_env); + sky_scene_state.ubo.fog_sun_scatter = RendererSceneRenderRD::get_singleton()->environment_get_fog_sun_scatter(p_render_data->environment); - sky_scene_state.ubo.fog_sky_affect = RendererSceneRenderRD::get_singleton()->environment_get_fog_sky_affect(p_env); - sky_scene_state.ubo.volumetric_fog_sky_affect = RendererSceneRenderRD::get_singleton()->environment_get_volumetric_fog_sky_affect(p_env); + sky_scene_state.ubo.fog_sky_affect = RendererSceneRenderRD::get_singleton()->environment_get_fog_sky_affect(p_render_data->environment); + sky_scene_state.ubo.volumetric_fog_sky_affect = RendererSceneRenderRD::get_singleton()->environment_get_volumetric_fog_sky_affect(p_render_data->environment); RD::get_singleton()->buffer_update(sky_scene_state.uniform_buffer, 0, sizeof(SkySceneState::UBO), &sky_scene_state.ubo); } @@ -1294,7 +1300,7 @@ void SkyRD::update_radiance_buffers(Ref<RenderSceneBuffersRD> p_render_buffers, Projection cm; cm.set_perspective(90, 1, 0.01, 10.0); Projection correction; - correction.set_depth_correction(true); + correction.set_depth_correction(false); cm = correction * cm; // Note, we ignore environment_get_sky_orientation here as this is applied when we do our lookup in our scene shader. diff --git a/servers/rendering/renderer_rd/environment/sky.h b/servers/rendering/renderer_rd/environment/sky.h index c74144734e..608afa1852 100644 --- a/servers/rendering/renderer_rd/environment/sky.h +++ b/servers/rendering/renderer_rd/environment/sky.h @@ -38,6 +38,7 @@ #include "servers/rendering/renderer_rd/pipeline_cache_rd.h" #include "servers/rendering/renderer_rd/shaders/environment/sky.glsl.gen.h" #include "servers/rendering/renderer_rd/storage_rd/material_storage.h" +#include "servers/rendering/renderer_rd/storage_rd/render_data_rd.h" #include "servers/rendering/renderer_scene_render.h" #include "servers/rendering/rendering_device.h" #include "servers/rendering/shader_compiler.h" @@ -296,7 +297,7 @@ public: void set_texture_format(RD::DataFormat p_texture_format); ~SkyRD(); - void setup_sky(RID p_env, Ref<RenderSceneBuffersRD> p_render_buffers, const PagedArray<RID> &p_lights, RID p_camera_attributes, uint32_t p_view_count, const Projection *p_view_projections, const Vector3 *p_view_eye_offsets, const Transform3D &p_cam_transform, const Projection &p_cam_projection, const Size2i p_screen_size, Vector2 p_jitter, RendererSceneRenderRD *p_scene_render); + void setup_sky(const RenderDataRD *p_render_data, const Size2i p_screen_size); void update_radiance_buffers(Ref<RenderSceneBuffersRD> p_render_buffers, RID p_env, const Vector3 &p_global_pos, double p_time, float p_luminance_multiplier = 1.0); void update_res_buffers(Ref<RenderSceneBuffersRD> p_render_buffers, RID p_env, double p_time, float p_luminance_multiplier = 1.0); void draw_sky(RD::DrawListID p_draw_list, Ref<RenderSceneBuffersRD> p_render_buffers, RID p_env, RID p_fb, double p_time, float p_luminance_multiplier = 1.0); diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp index 73c2b8aa39..0eb99feaac 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp @@ -1959,22 +1959,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co RD::get_singleton()->draw_command_begin_label("Setup Sky"); // Setup our sky render information for this frame/viewport - if (is_reflection_probe) { - Vector3 eye_offset; - Projection correction; - correction.set_depth_correction(true); - Projection projection = correction * p_render_data->scene_data->cam_projection; - - sky.setup_sky(p_render_data->environment, rb, *p_render_data->lights, p_render_data->camera_attributes, 1, &projection, &eye_offset, p_render_data->scene_data->cam_transform, projection, screen_size, Vector2(0.0f, 0.0f), this); - } else { - Projection projection = p_render_data->scene_data->cam_projection; - if (p_render_data->scene_data->cam_frustum) { - // Sky is drawn upside down, the frustum offset doesn't know the image is upside down so needs a flip. - projection[2].y = -projection[2].y; - } - - sky.setup_sky(p_render_data->environment, rb, *p_render_data->lights, p_render_data->camera_attributes, p_render_data->scene_data->view_count, &projection, p_render_data->scene_data->view_eye_offset, p_render_data->scene_data->cam_transform, projection, screen_size, p_render_data->scene_data->taa_jitter, this); - } + sky.setup_sky(p_render_data, screen_size); sky_energy_multiplier *= bg_energy_multiplier; diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp index d7e4d31e43..18a673e15a 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp @@ -974,23 +974,7 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color RENDER_TIMESTAMP("Setup Sky"); RD::get_singleton()->draw_command_begin_label("Setup Sky"); - // Setup our sky render information for this frame/viewport - if (is_reflection_probe) { - Vector3 eye_offset; - Projection correction; - correction.set_depth_correction(true); - Projection projection = correction * p_render_data->scene_data->cam_projection; - - sky.setup_sky(p_render_data->environment, p_render_data->render_buffers, *p_render_data->lights, p_render_data->camera_attributes, 1, &projection, &eye_offset, p_render_data->scene_data->cam_transform, projection, screen_size, Vector2(0.0f, 0.0f), this); - } else { - Projection projection = p_render_data->scene_data->cam_projection; - if (p_render_data->scene_data->cam_frustum) { - // Sky is drawn upside down, the frustum offset doesn't know the image is upside down so needs a flip. - projection[2].y = -projection[2].y; - } - - sky.setup_sky(p_render_data->environment, p_render_data->render_buffers, *p_render_data->lights, p_render_data->camera_attributes, p_render_data->scene_data->view_count, &projection, p_render_data->scene_data->view_eye_offset, p_render_data->scene_data->cam_transform, projection, screen_size, p_render_data->scene_data->taa_jitter, this); - } + sky.setup_sky(p_render_data, screen_size); sky_energy_multiplier *= bg_energy_multiplier; diff --git a/servers/rendering/renderer_rd/shaders/environment/sky.glsl b/servers/rendering/renderer_rd/shaders/environment/sky.glsl index cc1c40cad1..7bb2e0a539 100644 --- a/servers/rendering/renderer_rd/shaders/environment/sky.glsl +++ b/servers/rendering/renderer_rd/shaders/environment/sky.glsl @@ -189,7 +189,7 @@ void main() { vec3 cube_normal; #ifdef USE_MULTIVIEW // In multiview our projection matrices will contain positional and rotational offsets that we need to properly unproject. - vec4 unproject = vec4(uv_interp.x, -uv_interp.y, 0.0, 1.0); // unproject at the far plane + vec4 unproject = vec4(uv_interp.x, uv_interp.y, 0.0, 1.0); // unproject at the far plane vec4 unprojected = sky_scene_data.view_inv_projections[ViewIndex] * unproject; cube_normal = unprojected.xyz / unprojected.w; @@ -198,7 +198,7 @@ void main() { #else cube_normal.z = -1.0; cube_normal.x = (cube_normal.z * (-uv_interp.x - params.projection.x)) / params.projection.y; - cube_normal.y = -(cube_normal.z * (-uv_interp.y - params.projection.z)) / params.projection.w; + cube_normal.y = -(cube_normal.z * (uv_interp.y - params.projection.z)) / params.projection.w; #endif cube_normal = mat3(params.orientation) * cube_normal; cube_normal = normalize(cube_normal); diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp index f564ca64a5..cd0c23564c 100644 --- a/servers/rendering/rendering_device.cpp +++ b/servers/rendering/rendering_device.cpp @@ -948,6 +948,7 @@ RID RenderingDevice::texture_create_shared(const TextureView &p_view, RID p_with RDG::ResourceTracker *tracker = RDG::resource_tracker_create(); tracker->texture_driver_id = texture.shared_fallback->texture; + tracker->texture_size = Size2i(texture.width, texture.height); tracker->texture_subresources = texture.barrier_range(); tracker->texture_usage = alias_format.usage_bits; tracker->reference_count = 1; @@ -1127,6 +1128,7 @@ RID RenderingDevice::texture_create_shared_from_slice(const TextureView &p_view, RDG::ResourceTracker *tracker = RDG::resource_tracker_create(); tracker->texture_driver_id = texture.shared_fallback->texture; + tracker->texture_size = Size2i(texture.width, texture.height); tracker->texture_subresources = slice_range; tracker->texture_usage = slice_format.usage_bits; tracker->reference_count = 1; @@ -5417,6 +5419,7 @@ bool RenderingDevice::_texture_make_mutable(Texture *p_texture, RID p_texture_id draw_tracker = RDG::resource_tracker_create(); draw_tracker->parent = owner_texture->draw_tracker; draw_tracker->texture_driver_id = p_texture->driver_id; + draw_tracker->texture_size = Size2i(p_texture->width, p_texture->height); draw_tracker->texture_subresources = p_texture->barrier_range(); draw_tracker->texture_usage = p_texture->usage_flags; draw_tracker->texture_slice_or_dirty_rect = p_texture->slice_rect; @@ -5439,6 +5442,7 @@ bool RenderingDevice::_texture_make_mutable(Texture *p_texture, RID p_texture_id // Regular texture. p_texture->draw_tracker = RDG::resource_tracker_create(); p_texture->draw_tracker->texture_driver_id = p_texture->driver_id; + p_texture->draw_tracker->texture_size = Size2i(p_texture->width, p_texture->height); p_texture->draw_tracker->texture_subresources = p_texture->barrier_range(); p_texture->draw_tracker->texture_usage = p_texture->usage_flags; p_texture->draw_tracker->reference_count = 1; diff --git a/servers/rendering/rendering_device_graph.cpp b/servers/rendering/rendering_device_graph.cpp index bf0530b565..f7d4f3e31f 100644 --- a/servers/rendering/rendering_device_graph.cpp +++ b/servers/rendering/rendering_device_graph.cpp @@ -142,7 +142,7 @@ RDD::BarrierAccessBits RenderingDeviceGraph::_usage_to_access_bits(ResourceUsage #endif } -bool RenderingDeviceGraph::_check_command_intersection(ResourceTracker *p_resource_tracker, int32_t p_previous_command_index, int32_t p_command_index, bool &r_intersection_partial_coverage) const { +bool RenderingDeviceGraph::_check_command_intersection(ResourceTracker *p_resource_tracker, int32_t p_previous_command_index, int32_t p_command_index) const { if (p_resource_tracker->usage != RESOURCE_USAGE_ATTACHMENT_COLOR_READ_WRITE && p_resource_tracker->usage != RESOURCE_USAGE_ATTACHMENT_DEPTH_STENCIL_READ_WRITE) { // We don't check possible intersections for usages that aren't consecutive color or depth writes. return true; @@ -157,15 +157,27 @@ bool RenderingDeviceGraph::_check_command_intersection(ResourceTracker *p_resour return true; } - if (!r_intersection_partial_coverage) { - // Indicate if this draw list only partially covers the region of the previous draw list. - r_intersection_partial_coverage = !current_draw_list_command.region.encloses(previous_draw_list_command.region); - } - // We check if the region used by both draw lists have an intersection. return previous_draw_list_command.region.intersects(current_draw_list_command.region); } +bool RenderingDeviceGraph::_check_command_partial_coverage(ResourceTracker *p_resource_tracker, int32_t p_command_index) const { + if (p_resource_tracker->usage != RESOURCE_USAGE_ATTACHMENT_COLOR_READ_WRITE && p_resource_tracker->usage != RESOURCE_USAGE_ATTACHMENT_DEPTH_STENCIL_READ_WRITE) { + // We don't check for partial coverage in usages that aren't attachment writes. + return true; + } + + const uint32_t command_data_offset = command_data_offsets[p_command_index]; + const RecordedDrawListCommand &draw_list_command = *reinterpret_cast<const RecordedDrawListCommand *>(&command_data[command_data_offset]); + if (draw_list_command.type != RecordedCommand::TYPE_DRAW_LIST) { + // We don't check for partial coverage on commands that aren't draw lists. + return false; + } + + Rect2i texture_region(Point2i(0, 0), p_resource_tracker->texture_size); + return !draw_list_command.region.encloses(texture_region); +} + int32_t RenderingDeviceGraph::_add_to_command_list(int32_t p_command_index, int32_t p_list_index) { DEV_ASSERT(p_command_index < int32_t(command_count)); DEV_ASSERT(p_list_index < int32_t(command_list_nodes.size())); @@ -201,7 +213,7 @@ int32_t RenderingDeviceGraph::_add_to_slice_read_list(int32_t p_command_index, R return next_index; } -int32_t RenderingDeviceGraph::_add_to_write_list(int32_t p_command_index, Rect2i p_subresources, int32_t p_list_index) { +int32_t RenderingDeviceGraph::_add_to_write_list(int32_t p_command_index, Rect2i p_subresources, int32_t p_list_index, bool p_partial_coverage) { DEV_ASSERT(p_command_index < int32_t(command_count)); DEV_ASSERT(p_list_index < int32_t(write_slice_list_nodes.size())); @@ -212,6 +224,7 @@ int32_t RenderingDeviceGraph::_add_to_write_list(int32_t p_command_index, Rect2i new_node.command_index = p_command_index; new_node.next_list_index = p_list_index; new_node.subresources = p_subresources; + new_node.partial_coverage = p_partial_coverage; return next_index; } @@ -478,7 +491,7 @@ void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_tr resource_tracker->usage = new_resource_usage; } - bool intersection_partial_coverage = false; + bool write_usage_has_partial_coverage = !different_usage && _check_command_partial_coverage(resource_tracker, p_command_index); if (search_tracker->write_command_or_list_index >= 0) { if (search_tracker->write_command_list_enabled) { // Make this command adjacent to any commands that wrote to this resource and intersect with the slice if it applies. @@ -490,11 +503,11 @@ void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_tr if (!resource_has_parent || search_tracker_rect.intersects(write_list_node.subresources)) { if (write_list_node.command_index == p_command_index) { ERR_FAIL_COND_MSG(!resource_has_parent, "Command can't have itself as a dependency."); - } else if (_check_command_intersection(resource_tracker, write_list_node.command_index, p_command_index, intersection_partial_coverage)) { + } else if (!write_list_node.partial_coverage || _check_command_intersection(resource_tracker, write_list_node.command_index, p_command_index)) { // Command is dependent on this command. Add this command to the adjacency list of the write command. _add_adjacent_command(write_list_node.command_index, p_command_index, r_command); - if (resource_has_parent && write_usage && search_tracker_rect.encloses(write_list_node.subresources)) { + if (resource_has_parent && write_usage && search_tracker_rect.encloses(write_list_node.subresources) && !write_usage_has_partial_coverage) { // Eliminate redundant writes from the list. if (previous_write_list_index >= 0) { RecordedSliceListNode &previous_list_node = write_slice_list_nodes[previous_write_list_index]; @@ -516,22 +529,23 @@ void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_tr // The index is just the latest command index that wrote to the resource. if (search_tracker->write_command_or_list_index == p_command_index) { ERR_FAIL_MSG("Command can't have itself as a dependency."); - } else if (_check_command_intersection(resource_tracker, search_tracker->write_command_or_list_index, p_command_index, intersection_partial_coverage)) { + } else if (_check_command_intersection(resource_tracker, search_tracker->write_command_or_list_index, p_command_index)) { _add_adjacent_command(search_tracker->write_command_or_list_index, p_command_index, r_command); } } } if (write_usage) { - if (resource_has_parent || intersection_partial_coverage) { + bool use_write_list = resource_has_parent || write_usage_has_partial_coverage; + if (use_write_list) { if (!search_tracker->write_command_list_enabled && search_tracker->write_command_or_list_index >= 0) { // Write command list was not being used but there was a write command recorded. Add a new node with the entire parent resource's subresources and the recorded command index to the list. const RDD::TextureSubresourceRange &tracker_subresources = search_tracker->texture_subresources; Rect2i tracker_rect(tracker_subresources.base_mipmap, tracker_subresources.base_layer, tracker_subresources.mipmap_count, tracker_subresources.layer_count); - search_tracker->write_command_or_list_index = _add_to_write_list(search_tracker->write_command_or_list_index, tracker_rect, -1); + search_tracker->write_command_or_list_index = _add_to_write_list(search_tracker->write_command_or_list_index, tracker_rect, -1, false); } - search_tracker->write_command_or_list_index = _add_to_write_list(p_command_index, search_tracker_rect, search_tracker->write_command_or_list_index); + search_tracker->write_command_or_list_index = _add_to_write_list(p_command_index, search_tracker_rect, search_tracker->write_command_or_list_index, write_usage_has_partial_coverage); search_tracker->write_command_list_enabled = true; } else { search_tracker->write_command_or_list_index = p_command_index; @@ -556,7 +570,7 @@ void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_tr read_full_command_list_index = read_full_next_index; } - if (!resource_has_parent) { + if (!use_write_list) { // Clear the full list if this resource is not a slice. search_tracker->read_full_command_list_index = -1; } @@ -566,7 +580,7 @@ void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_tr int32_t read_slice_command_list_index = search_tracker->read_slice_command_list_index; while (read_slice_command_list_index >= 0) { const RecordedSliceListNode &read_list_node = read_slice_list_nodes[read_slice_command_list_index]; - if (!resource_has_parent || search_tracker_rect.encloses(read_list_node.subresources)) { + if (!use_write_list || search_tracker_rect.encloses(read_list_node.subresources)) { if (previous_slice_command_list_index >= 0) { // Erase this element and connect the previous one to the next element. read_slice_list_nodes[previous_slice_command_list_index].next_list_index = read_list_node.next_list_index; diff --git a/servers/rendering/rendering_device_graph.h b/servers/rendering/rendering_device_graph.h index 222bff8f95..3cb43b3931 100644 --- a/servers/rendering/rendering_device_graph.h +++ b/servers/rendering/rendering_device_graph.h @@ -169,6 +169,7 @@ public: RDD::BufferID buffer_driver_id; RDD::TextureID texture_driver_id; RDD::TextureSubresourceRange texture_subresources; + Size2i texture_size; uint32_t texture_usage = 0; int32_t texture_slice_command_index = -1; ResourceTracker *parent = nullptr; @@ -273,6 +274,7 @@ private: int32_t command_index = -1; int32_t next_list_index = -1; Rect2i subresources; + bool partial_coverage = false; }; struct RecordedBufferClearCommand : RecordedCommand { @@ -651,11 +653,12 @@ private: static bool _is_write_usage(ResourceUsage p_usage); static RDD::TextureLayout _usage_to_image_layout(ResourceUsage p_usage); static RDD::BarrierAccessBits _usage_to_access_bits(ResourceUsage p_usage); - bool _check_command_intersection(ResourceTracker *p_resource_tracker, int32_t p_previous_command_index, int32_t p_command_index, bool &r_intersection_partial_coverage) const; + bool _check_command_intersection(ResourceTracker *p_resource_tracker, int32_t p_previous_command_index, int32_t p_command_index) const; + bool _check_command_partial_coverage(ResourceTracker *p_resource_tracker, int32_t p_command_index) const; int32_t _add_to_command_list(int32_t p_command_index, int32_t p_list_index); void _add_adjacent_command(int32_t p_previous_command_index, int32_t p_command_index, RecordedCommand *r_command); int32_t _add_to_slice_read_list(int32_t p_command_index, Rect2i p_subresources, int32_t p_list_index); - int32_t _add_to_write_list(int32_t p_command_index, Rect2i p_subresources, int32_t p_list_index); + int32_t _add_to_write_list(int32_t p_command_index, Rect2i p_subresources, int32_t p_list_index, bool p_partial_coverage); RecordedCommand *_allocate_command(uint32_t p_command_size, int32_t &r_command_index); DrawListInstruction *_allocate_draw_list_instruction(uint32_t p_instruction_size); ComputeListInstruction *_allocate_compute_list_instruction(uint32_t p_instruction_size); diff --git a/tests/scene/test_fontfile.h b/tests/scene/test_fontfile.h new file mode 100644 index 0000000000..ecdcd71d47 --- /dev/null +++ b/tests/scene/test_fontfile.h @@ -0,0 +1,84 @@ +/**************************************************************************/ +/* test_fontfile.h */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef TEST_FONTFILE_H +#define TEST_FONTFILE_H + +#include "modules/modules_enabled.gen.h" + +#include "scene/resources/font.h" +#include "tests/test_macros.h" + +namespace TestFontfile { + +TEST_CASE("[FontFile] Create font file and check data") { + // Create test instance. + Ref<FontFile> font_file; + font_file.instantiate(); + +#ifdef MODULE_FREETYPE_ENABLED + // Try to load non-existent files. + ERR_PRINT_OFF + CHECK(font_file->load_dynamic_font("") == OK); + CHECK_MESSAGE(font_file->get_data().is_empty() == true, "Invalid fontfile should not be loaded."); + + CHECK(font_file->load_dynamic_font("thirdparty/fonts/nofonthasthisname.woff2") == OK); + CHECK_MESSAGE(font_file->get_data().is_empty() == true, "Invalid fontfile should not be loaded."); + ERR_PRINT_ON + + // Load a valid file. + CHECK(font_file->load_dynamic_font("thirdparty/fonts/NotoSans_Regular.woff2") == OK); + + // Check fontfile data. + CHECK_MESSAGE(font_file->get_data().is_empty() == false, "Fontfile should have been loaded."); + CHECK_MESSAGE(font_file->get_font_name() == "Noto Sans", "Loaded correct font name."); + CHECK_MESSAGE(font_file->get_font_style_name() == "Regular", "Loaded correct font style."); + CHECK_MESSAGE(font_file->get_data().size() == 148480llu, "Whole fontfile was loaded."); + + // Valid glyphs. + CHECK_MESSAGE(font_file->get_glyph_index(2, 'a', 0) != 0, "Glyph index for 'a' is valid."); + CHECK_MESSAGE(font_file->get_glyph_index(2, 'b', 0) != 0, "Glyph index for 'b' is valid."); + CHECK_MESSAGE(font_file->get_glyph_index(2, 0x0103, 0) != 0, "Glyph index for 'latin small letter a with breve' is valid."); + CHECK_MESSAGE(font_file->get_glyph_index(2, 0x03a8, 0) != 0, "Glyph index for 'Greek psi' is valid."); + CHECK_MESSAGE(font_file->get_glyph_index(2, 0x0416, 0) != 0, "Glyph index for 'Cyrillic zhe' is valid."); + CHECK_MESSAGE(font_file->get_glyph_index(2, '&', 0) != 0, "Glyph index for '&' is valid."); + + // Invalid glyphs. + CHECK_MESSAGE(font_file->get_glyph_index(2, 0x4416, 0) == 0, "Glyph index is invalid."); + CHECK_MESSAGE(font_file->get_glyph_index(2, 0x5555, 0) == 0, "Glyph index is invalid."); + CHECK_MESSAGE(font_file->get_glyph_index(2, 0x2901, 0) == 0, "Glyph index is invalid."); +#endif +} + +} // namespace TestFontfile + +#endif // TEST_FONTFILE_H diff --git a/tests/test_main.cpp b/tests/test_main.cpp index 6c3cca6d54..e86f17cef5 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -119,6 +119,7 @@ #include "tests/scene/test_curve.h" #include "tests/scene/test_curve_2d.h" #include "tests/scene/test_curve_3d.h" +#include "tests/scene/test_fontfile.h" #include "tests/scene/test_gradient.h" #include "tests/scene/test_gradient_texture.h" #include "tests/scene/test_image_texture.h" |