summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/config/engine.cpp4
-rw-r--r--core/config/engine.h2
-rw-r--r--core/core_bind.cpp12
-rw-r--r--core/core_bind.h1
-rw-r--r--core/extension/gdextension.cpp13
-rw-r--r--doc/classes/Geometry3D.xml7
-rw-r--r--doc/classes/Node.xml8
-rw-r--r--drivers/gles3/storage/material_storage.cpp10
-rw-r--r--drivers/vulkan/rendering_device_vulkan.cpp58
-rw-r--r--drivers/vulkan/rendering_device_vulkan.h1
-rw-r--r--drivers/vulkan/vulkan_context.cpp40
-rw-r--r--main/main.cpp3
-rw-r--r--modules/glslang/register_types.cpp30
-rw-r--r--modules/mono/editor/bindings_generator.cpp81
-rw-r--r--modules/mono/editor/bindings_generator.h8
-rw-r--r--modules/text_server_adv/text_server_adv.cpp8
-rw-r--r--modules/text_server_adv/thorvg_svg_in_ot.cpp15
-rw-r--r--modules/text_server_adv/thorvg_svg_in_ot.h14
-rw-r--r--platform/android/doc_classes/EditorExportPlatformAndroid.xml4
-rw-r--r--platform/android/export/export_plugin.cpp1
-rw-r--r--platform/android/export/gradle_export_util.cpp8
-rw-r--r--platform/android/java/app/AndroidManifest.xml1
-rw-r--r--platform/android/java/editor/src/main/AndroidManifest.xml1
-rw-r--r--scene/2d/canvas_modulate.cpp67
-rw-r--r--scene/2d/canvas_modulate.h8
-rw-r--r--scene/gui/rich_text_label.cpp13
-rw-r--r--scene/gui/scroll_bar.cpp8
-rw-r--r--scene/main/viewport.cpp4
-rw-r--r--scene/main/viewport.h5
-rw-r--r--scene/main/window.cpp38
-rw-r--r--scene/main/window.h4
-rw-r--r--scene/resources/particle_process_material.cpp6
-rw-r--r--servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp6
-rw-r--r--servers/rendering/renderer_rd/renderer_canvas_render_rd.h2
-rw-r--r--servers/rendering/renderer_rd/storage_rd/material_storage.cpp10
-rw-r--r--servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp9
-rw-r--r--servers/rendering/renderer_rd/storage_rd/texture_storage.cpp4
-rw-r--r--servers/rendering/renderer_rd/storage_rd/texture_storage.h2
-rw-r--r--servers/rendering/rendering_device.h1
-rw-r--r--tests/scene/test_window.h96
-rw-r--r--tests/test_main.cpp1
41 files changed, 454 insertions, 160 deletions
diff --git a/core/config/engine.cpp b/core/config/engine.cpp
index 7fdea7d1aa..6727c58fd1 100644
--- a/core/config/engine.cpp
+++ b/core/config/engine.cpp
@@ -239,6 +239,10 @@ bool Engine::is_validation_layers_enabled() const {
return use_validation_layers;
}
+bool Engine::is_generate_spirv_debug_info_enabled() const {
+ return generate_spirv_debug_info;
+}
+
void Engine::set_print_error_messages(bool p_enabled) {
CoreGlobals::print_error_enabled = p_enabled;
}
diff --git a/core/config/engine.h b/core/config/engine.h
index 5ea653ba6c..73d40d50ae 100644
--- a/core/config/engine.h
+++ b/core/config/engine.h
@@ -67,6 +67,7 @@ private:
double _physics_interpolation_fraction = 0.0f;
bool abort_on_gpu_errors = false;
bool use_validation_layers = false;
+ bool generate_spirv_debug_info = false;
int32_t gpu_idx = -1;
uint64_t _process_frames = 0;
@@ -156,6 +157,7 @@ public:
bool is_abort_on_gpu_errors_enabled() const;
bool is_validation_layers_enabled() const;
+ bool is_generate_spirv_debug_info_enabled() const;
int32_t get_gpu_index() const;
Engine();
diff --git a/core/core_bind.cpp b/core/core_bind.cpp
index 4e220d0839..05fe393a2f 100644
--- a/core/core_bind.cpp
+++ b/core/core_bind.cpp
@@ -929,6 +929,17 @@ Geometry3D *Geometry3D::get_singleton() {
return singleton;
}
+Vector<Vector3> Geometry3D::compute_convex_mesh_points(const TypedArray<Plane> &p_planes) {
+ Vector<Plane> planes_vec;
+ int size = p_planes.size();
+ planes_vec.resize(size);
+ for (int i = 0; i < size; ++i) {
+ planes_vec.set(i, p_planes[i]);
+ }
+ Variant ret = ::Geometry3D::compute_convex_mesh_points(planes_vec.ptr(), size);
+ return ret;
+}
+
TypedArray<Plane> Geometry3D::build_box_planes(const Vector3 &p_extents) {
Variant ret = ::Geometry3D::build_box_planes(p_extents);
return ret;
@@ -1029,6 +1040,7 @@ Vector<Vector3> Geometry3D::clip_polygon(const Vector<Vector3> &p_points, const
}
void Geometry3D::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("compute_convex_mesh_points", "planes"), &Geometry3D::compute_convex_mesh_points);
ClassDB::bind_method(D_METHOD("build_box_planes", "extents"), &Geometry3D::build_box_planes);
ClassDB::bind_method(D_METHOD("build_cylinder_planes", "radius", "height", "sides", "axis"), &Geometry3D::build_cylinder_planes, DEFVAL(Vector3::AXIS_Z));
ClassDB::bind_method(D_METHOD("build_capsule_planes", "radius", "height", "sides", "lats", "axis"), &Geometry3D::build_capsule_planes, DEFVAL(Vector3::AXIS_Z));
diff --git a/core/core_bind.h b/core/core_bind.h
index 1cbbcdd251..5f51b64eb7 100644
--- a/core/core_bind.h
+++ b/core/core_bind.h
@@ -320,6 +320,7 @@ protected:
public:
static Geometry3D *get_singleton();
+ Vector<Vector3> compute_convex_mesh_points(const TypedArray<Plane> &p_planes);
TypedArray<Plane> build_box_planes(const Vector3 &p_extents);
TypedArray<Plane> build_cylinder_planes(float p_radius, float p_height, int p_sides, Vector3::Axis p_axis = Vector3::AXIS_Z);
TypedArray<Plane> build_capsule_planes(float p_radius, float p_height, int p_sides, int p_lats, Vector3::Axis p_axis = Vector3::AXIS_Z);
diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp
index 67b55db3db..9a735f5aa6 100644
--- a/core/extension/gdextension.cpp
+++ b/core/extension/gdextension.cpp
@@ -607,12 +607,13 @@ Ref<Resource> GDExtensionResourceLoader::load(const String &p_path, const String
}
bool compatible = true;
- if (VERSION_MAJOR < compatibility_minimum[0]) {
- compatible = false;
- } else if (VERSION_MINOR < compatibility_minimum[1]) {
- compatible = false;
- } else if (VERSION_PATCH < compatibility_minimum[2]) {
- compatible = false;
+ // Check version lexicographically.
+ if (VERSION_MAJOR != compatibility_minimum[0]) {
+ compatible = VERSION_MAJOR > compatibility_minimum[0];
+ } else if (VERSION_MINOR != compatibility_minimum[1]) {
+ compatible = VERSION_MINOR > compatibility_minimum[1];
+ } else {
+ compatible = VERSION_PATCH >= compatibility_minimum[2];
}
if (!compatible) {
if (r_error) {
diff --git a/doc/classes/Geometry3D.xml b/doc/classes/Geometry3D.xml
index 85b8965faf..a85d17d925 100644
--- a/doc/classes/Geometry3D.xml
+++ b/doc/classes/Geometry3D.xml
@@ -45,6 +45,13 @@
Clips the polygon defined by the points in [param points] against the [param plane] and returns the points of the clipped polygon.
</description>
</method>
+ <method name="compute_convex_mesh_points">
+ <return type="PackedVector3Array" />
+ <param index="0" name="planes" type="Plane[]" />
+ <description>
+ Calculates and returns all the vertex points of a convex shape defined by an array of [param planes].
+ </description>
+ </method>
<method name="get_closest_point_to_segment">
<return type="Vector3" />
<param index="0" name="point" type="Vector3" />
diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml
index 49ab3918bb..7510d6bb64 100644
--- a/doc/classes/Node.xml
+++ b/doc/classes/Node.xml
@@ -1022,12 +1022,12 @@
Notification received right after the scene with the node is saved in the editor. This notification is only sent in the Godot editor and will not occur in exported projects.
</constant>
<constant name="NOTIFICATION_WM_MOUSE_ENTER" value="1002">
- Notification received from the OS when the mouse enters the game window.
- Implemented on desktop and web platforms.
+ Notification received when the mouse enters the window.
+ Implemented for embedded windows and on desktop and web platforms.
</constant>
<constant name="NOTIFICATION_WM_MOUSE_EXIT" value="1003">
- Notification received from the OS when the mouse leaves the game window.
- Implemented on desktop and web platforms.
+ Notification received when the mouse leaves the window.
+ Implemented for embedded windows and on desktop and web platforms.
</constant>
<constant name="NOTIFICATION_WM_WINDOW_FOCUS_IN" value="1004">
Notification received when the node's parent [Window] is focused. This may be a change of focus between two windows of the same engine instance, or from the OS desktop or a third-party application to a window of the game (in which case [constant NOTIFICATION_APPLICATION_FOCUS_IN] is also emitted).
diff --git a/drivers/gles3/storage/material_storage.cpp b/drivers/gles3/storage/material_storage.cpp
index aa6319f0ef..a36004209b 100644
--- a/drivers/gles3/storage/material_storage.cpp
+++ b/drivers/gles3/storage/material_storage.cpp
@@ -1957,14 +1957,16 @@ void MaterialStorage::global_shader_parameters_load_settings(bool p_load_texture
if (gvtype >= RS::GLOBAL_VAR_TYPE_SAMPLER2D) {
//textire
if (!p_load_textures) {
- value = RID();
continue;
}
String path = value;
- Ref<Resource> resource = ResourceLoader::load(path);
- ERR_CONTINUE(resource.is_null());
- value = resource;
+ if (path.is_empty()) {
+ value = RID();
+ } else {
+ Ref<Resource> resource = ResourceLoader::load(path);
+ value = resource;
+ }
}
if (global_shader_uniforms.variables.has(name)) {
diff --git a/drivers/vulkan/rendering_device_vulkan.cpp b/drivers/vulkan/rendering_device_vulkan.cpp
index 1ed6839dd7..a55abe6a82 100644
--- a/drivers/vulkan/rendering_device_vulkan.cpp
+++ b/drivers/vulkan/rendering_device_vulkan.cpp
@@ -5905,6 +5905,64 @@ void RenderingDeviceVulkan::uniform_set_set_invalidation_callback(RID p_uniform_
us->invalidated_callback_userdata = p_userdata;
}
+Error RenderingDeviceVulkan::buffer_copy(RID p_src_buffer, RID p_dst_buffer, uint32_t p_src_offset, uint32_t p_dst_offset, uint32_t p_size, BitField<BarrierMask> p_post_barrier) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V_MSG(draw_list, ERR_INVALID_PARAMETER,
+ "Copying buffers is forbidden during creation of a draw list");
+ ERR_FAIL_COND_V_MSG(compute_list, ERR_INVALID_PARAMETER,
+ "Copying buffers is forbidden during creation of a compute list");
+
+ // This method assumes the barriers have been pushed prior to being called, therefore no barriers are pushed
+ // for the source or destination buffers before performing the copy. These masks are effectively ignored.
+ VkPipelineShaderStageCreateFlags src_stage_mask = 0;
+ VkAccessFlags src_access_mask = 0;
+ Buffer *src_buffer = _get_buffer_from_owner(p_src_buffer, src_stage_mask, src_access_mask, BARRIER_MASK_NO_BARRIER);
+ if (!src_buffer) {
+ ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Source buffer argument is not a valid buffer of any type.");
+ }
+
+ VkPipelineStageFlags dst_stage_mask = 0;
+ VkAccessFlags dst_access = 0;
+ if (p_post_barrier.has_flag(BARRIER_MASK_TRANSFER)) {
+ // If the post barrier mask defines it, we indicate the destination buffer will require a barrier with these flags set
+ // after the copy command is queued.
+ dst_stage_mask = VK_PIPELINE_STAGE_TRANSFER_BIT;
+ dst_access = VK_ACCESS_TRANSFER_WRITE_BIT;
+ }
+
+ Buffer *dst_buffer = _get_buffer_from_owner(p_dst_buffer, dst_stage_mask, dst_access, p_post_barrier);
+ if (!dst_buffer) {
+ ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Destination buffer argument is not a valid buffer of any type.");
+ }
+
+ // Validate the copy's dimensions for both buffers.
+ ERR_FAIL_COND_V_MSG((p_size + p_src_offset) > src_buffer->size, ERR_INVALID_PARAMETER, "Size is larger than the source buffer.");
+ ERR_FAIL_COND_V_MSG((p_size + p_dst_offset) > dst_buffer->size, ERR_INVALID_PARAMETER, "Size is larger than the destination buffer.");
+
+ // Perform the copy.
+ VkBufferCopy region;
+ region.srcOffset = p_src_offset;
+ region.dstOffset = p_dst_offset;
+ region.size = p_size;
+ vkCmdCopyBuffer(frames[frame].draw_command_buffer, src_buffer->buffer, dst_buffer->buffer, 1, &region);
+
+#ifdef FORCE_FULL_BARRIER
+ _full_barrier(true);
+#else
+ if (dst_stage_mask == 0) {
+ dst_stage_mask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+ }
+
+ // As indicated by the post barrier mask, push a new barrier.
+ if (p_post_barrier != RD::BARRIER_MASK_NO_BARRIER) {
+ _buffer_memory_barrier(dst_buffer->buffer, p_dst_offset, p_size, VK_PIPELINE_STAGE_TRANSFER_BIT, dst_stage_mask, VK_ACCESS_TRANSFER_WRITE_BIT, dst_access, true);
+ }
+#endif
+
+ return OK;
+}
+
Error RenderingDeviceVulkan::buffer_update(RID p_buffer, uint32_t p_offset, uint32_t p_size, const void *p_data, BitField<BarrierMask> p_post_barrier) {
_THREAD_SAFE_METHOD_
diff --git a/drivers/vulkan/rendering_device_vulkan.h b/drivers/vulkan/rendering_device_vulkan.h
index fd832312ac..edff19a70c 100644
--- a/drivers/vulkan/rendering_device_vulkan.h
+++ b/drivers/vulkan/rendering_device_vulkan.h
@@ -1152,6 +1152,7 @@ public:
virtual bool uniform_set_is_valid(RID p_uniform_set);
virtual void uniform_set_set_invalidation_callback(RID p_uniform_set, InvalidationCallback p_callback, void *p_userdata);
+ virtual Error buffer_copy(RID p_src_buffer, RID p_dst_buffer, uint32_t p_src_offset, uint32_t p_dst_offset, uint32_t p_size, BitField<BarrierMask> p_post_barrier = BARRIER_MASK_ALL_BARRIERS);
virtual Error buffer_update(RID p_buffer, uint32_t p_offset, uint32_t p_size, const void *p_data, BitField<BarrierMask> p_post_barrier = BARRIER_MASK_ALL_BARRIERS); // Works for any buffer.
virtual Error buffer_clear(RID p_buffer, uint32_t p_offset, uint32_t p_size, BitField<BarrierMask> p_post_barrier = BARRIER_MASK_ALL_BARRIERS);
virtual Vector<uint8_t> buffer_get_data(RID p_buffer, uint32_t p_offset = 0, uint32_t p_size = 0);
diff --git a/drivers/vulkan/vulkan_context.cpp b/drivers/vulkan/vulkan_context.cpp
index c167caeb7c..c1632907eb 100644
--- a/drivers/vulkan/vulkan_context.cpp
+++ b/drivers/vulkan/vulkan_context.cpp
@@ -504,6 +504,10 @@ Error VulkanContext::_initialize_device_extensions() {
register_requested_device_extension(VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME, false);
register_requested_device_extension(VK_KHR_MAINTENANCE_2_EXTENSION_NAME, false);
+ if (Engine::get_singleton()->is_generate_spirv_debug_info_enabled()) {
+ register_requested_device_extension(VK_KHR_SHADER_NON_SEMANTIC_INFO_EXTENSION_NAME, true);
+ }
+
// TODO consider the following extensions:
// - VK_KHR_spirv_1_4
// - VK_KHR_swapchain_mutable_format
@@ -1700,17 +1704,6 @@ Error VulkanContext::_window_create(DisplayServer::WindowID p_window_id, Display
Error err = _update_swap_chain(&window);
ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);
- VkSemaphoreCreateInfo semaphoreCreateInfo = {
- /*sType*/ VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
- /*pNext*/ nullptr,
- /*flags*/ 0,
- };
-
- for (uint32_t i = 0; i < FRAME_LAG; i++) {
- VkResult vkerr = vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &window.image_acquired_semaphores[i]);
- ERR_FAIL_COND_V(vkerr, ERR_CANT_CREATE);
- }
-
windows[p_window_id] = window;
return OK;
}
@@ -1760,9 +1753,6 @@ VkFramebuffer VulkanContext::window_get_framebuffer(DisplayServer::WindowID p_wi
void VulkanContext::window_destroy(DisplayServer::WindowID p_window_id) {
ERR_FAIL_COND(!windows.has(p_window_id));
_clean_up_swap_chain(&windows[p_window_id]);
- for (uint32_t i = 0; i < FRAME_LAG; i++) {
- vkDestroySemaphore(device, windows[p_window_id].image_acquired_semaphores[i], nullptr);
- }
vkDestroySurfaceKHR(inst, windows[p_window_id].surface, nullptr);
windows.erase(p_window_id);
@@ -1792,6 +1782,17 @@ Error VulkanContext::_clean_up_swap_chain(Window *window) {
if (separate_present_queue) {
vkDestroyCommandPool(device, window->present_cmd_pool, nullptr);
}
+
+ for (uint32_t i = 0; i < FRAME_LAG; i++) {
+ // Destroy the semaphores now (we'll re-create it later if we have to).
+ // We must do this because the semaphore cannot be reused if it's in a signaled state
+ // (which happens if vkAcquireNextImageKHR returned VK_ERROR_OUT_OF_DATE_KHR or VK_SUBOPTIMAL_KHR)
+ // The only way to reset it would be to present the swapchain... the one we just destroyed.
+ // And the API has no way to "unsignal" the semaphore.
+ vkDestroySemaphore(device, window->image_acquired_semaphores[i], nullptr);
+ window->image_acquired_semaphores[i] = 0;
+ }
+
return OK;
}
@@ -2175,6 +2176,17 @@ Error VulkanContext::_update_swap_chain(Window *window) {
// Reset current buffer.
window->current_buffer = 0;
+ VkSemaphoreCreateInfo semaphoreCreateInfo = {
+ /*sType*/ VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
+ /*pNext*/ nullptr,
+ /*flags*/ 0,
+ };
+
+ for (uint32_t i = 0; i < FRAME_LAG; i++) {
+ VkResult vkerr = vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &window->image_acquired_semaphores[i]);
+ ERR_FAIL_COND_V(vkerr, ERR_CANT_CREATE);
+ }
+
return OK;
}
diff --git a/main/main.cpp b/main/main.cpp
index 220afda5de..f79a71474d 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -458,6 +458,7 @@ void Main::print_help(const char *p_binary) {
#if DEBUG_ENABLED
OS::get_singleton()->print(" --gpu-abort Abort on graphics API usage errors (usually validation layer errors). May help see the problem if your system freezes.\n");
#endif
+ OS::get_singleton()->print(" --generate-spirv-debug-info Generate SPIR-V debug information. This allows source-level shader debugging with RenderDoc.\n");
OS::get_singleton()->print(" --remote-debug <uri> Remote debug (<protocol>://<host/IP>[:<port>], e.g. tcp://127.0.0.1:6007).\n");
OS::get_singleton()->print(" --single-threaded-scene Scene tree runs in single-threaded mode. Sub-thread groups are disabled and run on the main thread.\n");
#if defined(DEBUG_ENABLED)
@@ -1019,6 +1020,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
} else if (I->get() == "--gpu-abort") {
Engine::singleton->abort_on_gpu_errors = true;
#endif
+ } else if (I->get() == "--generate-spirv-debug-info") {
+ Engine::singleton->generate_spirv_debug_info = true;
} else if (I->get() == "--tablet-driver") {
if (I->next()) {
tablet_driver = I->next()->get();
diff --git a/modules/glslang/register_types.cpp b/modules/glslang/register_types.cpp
index 622910761d..2b070c24b8 100644
--- a/modules/glslang/register_types.cpp
+++ b/modules/glslang/register_types.cpp
@@ -32,6 +32,7 @@
#include "glslang_resource_limits.h"
+#include "core/config/engine.h"
#include "servers/rendering/rendering_device.h"
#include <glslang/Include/Types.h>
@@ -56,7 +57,6 @@ static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage
glslang::EShTargetClientVersion ClientVersion = glslang::EShTargetVulkan_1_2;
glslang::EShTargetLanguageVersion TargetVersion = glslang::EShTargetSpv_1_5;
- glslang::TShader::ForbidIncluder includer;
if (capabilities->device_family == RenderingDevice::DeviceFamily::DEVICE_VULKAN) {
if (capabilities->version_major == 1 && capabilities->version_minor == 0) {
@@ -127,23 +127,10 @@ static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage
}
EShMessages messages = (EShMessages)(EShMsgSpvRules | EShMsgVulkanRules);
- const int DefaultVersion = 100;
- std::string pre_processed_code;
-
- //preprocess
- if (!shader.preprocess(&DefaultTBuiltInResource, DefaultVersion, ENoProfile, false, false, messages, &pre_processed_code, includer)) {
- if (r_error) {
- (*r_error) = "Failed pre-process:\n";
- (*r_error) += shader.getInfoLog();
- (*r_error) += "\n";
- (*r_error) += shader.getInfoDebugLog();
- }
-
- return ret;
+ if (Engine::get_singleton()->is_generate_spirv_debug_info_enabled()) {
+ messages = (EShMessages)(messages | EShMsgDebugInfo);
}
- //set back..
- cs_strings = pre_processed_code.c_str();
- shader.setStrings(&cs_strings, 1);
+ const int DefaultVersion = 100;
//parse
if (!shader.parse(&DefaultTBuiltInResource, DefaultVersion, false, messages)) {
@@ -174,6 +161,13 @@ static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage
std::vector<uint32_t> SpirV;
spv::SpvBuildLogger logger;
glslang::SpvOptions spvOptions;
+
+ if (Engine::get_singleton()->is_generate_spirv_debug_info_enabled()) {
+ spvOptions.generateDebugInfo = true;
+ spvOptions.emitNonSemanticShaderDebugInfo = true;
+ spvOptions.emitNonSemanticShaderDebugSource = true;
+ }
+
glslang::GlslangToSpv(*program.getIntermediate(stages[p_stage]), SpirV, &logger, &spvOptions);
ret.resize(SpirV.size() * sizeof(uint32_t));
@@ -188,7 +182,7 @@ static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage
static String _get_cache_key_function_glsl(const RenderingDevice *p_render_device) {
const RD::Capabilities *capabilities = p_render_device->get_device_capabilities();
String version;
- version = "SpirVGen=" + itos(glslang::GetSpirvGeneratorVersion()) + ", major=" + itos(capabilities->version_major) + ", minor=" + itos(capabilities->version_minor) + " , subgroup_size=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_SIZE)) + " , subgroup_ops=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_OPERATIONS)) + " , subgroup_in_shaders=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_IN_SHADERS));
+ version = "SpirVGen=" + itos(glslang::GetSpirvGeneratorVersion()) + ", major=" + itos(capabilities->version_major) + ", minor=" + itos(capabilities->version_minor) + " , subgroup_size=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_SIZE)) + " , subgroup_ops=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_OPERATIONS)) + " , subgroup_in_shaders=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_IN_SHADERS)) + " , debug=" + itos(Engine::get_singleton()->is_generate_spirv_debug_info_enabled());
return version;
}
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index 342c94cbd9..006aca6c73 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -148,7 +148,7 @@ static String fix_doc_description(const String &p_bbcode) {
.strip_edges();
}
-String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterface *p_itype) {
+String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterface *p_itype, bool p_is_signal) {
// Based on the version in EditorHelp
if (p_bbcode.is_empty()) {
@@ -305,11 +305,11 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
_append_xml_enum(xml_output, target_itype, target_cname, link_target, link_target_parts);
} else if (link_tag == "constant") {
_append_xml_constant(xml_output, target_itype, target_cname, link_target, link_target_parts);
+ } else if (link_tag == "param") {
+ _append_xml_param(xml_output, link_target, p_is_signal);
} else if (link_tag == "theme_item") {
// We do not declare theme_items in any way in C#, so there is nothing to reference
_append_xml_undeclared(xml_output, link_target);
- } else if (link_tag == "param") {
- _append_xml_undeclared(xml_output, snake_to_camel_case(link_target, false));
}
pos = brk_end + 1;
@@ -684,7 +684,7 @@ void BindingsGenerator::_append_xml_constant(StringBuilder &p_xml_output, const
p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");
p_xml_output.append(p_target_itype->proxy_name);
p_xml_output.append(".");
- p_xml_output.append(target_ienum->cname);
+ p_xml_output.append(target_ienum->proxy_name);
p_xml_output.append(".");
p_xml_output.append(target_iconst->proxy_name);
p_xml_output.append("\"/>");
@@ -725,7 +725,7 @@ void BindingsGenerator::_append_xml_constant_in_global_scope(StringBuilder &p_xm
if (target_iconst) {
p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");
- p_xml_output.append(target_ienum->cname);
+ p_xml_output.append(target_ienum->proxy_name);
p_xml_output.append(".");
p_xml_output.append(target_iconst->proxy_name);
p_xml_output.append("\"/>");
@@ -736,6 +736,21 @@ void BindingsGenerator::_append_xml_constant_in_global_scope(StringBuilder &p_xm
}
}
+void BindingsGenerator::_append_xml_param(StringBuilder &p_xml_output, const String &p_link_target, bool p_is_signal) {
+ const String link_target = snake_to_camel_case(p_link_target);
+
+ if (!p_is_signal) {
+ p_xml_output.append("<paramref name=\"");
+ p_xml_output.append(link_target);
+ p_xml_output.append("\"/>");
+ } else {
+ // Documentation in C# is added to an event, not the delegate itself;
+ // as such, we treat these parameters as codeblocks instead.
+ // See: https://github.com/godotengine/godot/pull/65529
+ _append_xml_undeclared(p_xml_output, link_target);
+ }
+}
+
void BindingsGenerator::_append_xml_undeclared(StringBuilder &p_xml_output, const String &p_link_target) {
p_xml_output.append("<c>");
p_xml_output.append(p_link_target);
@@ -981,7 +996,7 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) {
for (const EnumInterface &ienum : global_enums) {
CRASH_COND(ienum.constants.is_empty());
- String enum_proxy_name = ienum.cname.operator String();
+ String enum_proxy_name = ienum.proxy_name;
bool enum_in_static_class = false;
@@ -995,7 +1010,7 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) {
_log("Declaring global enum '%s' inside struct '%s'\n", enum_proxy_name.utf8().get_data(), enum_class_name.utf8().get_data());
p_output.append("\npublic partial struct ");
- p_output.append(pascal_to_pascal_case(enum_class_name));
+ p_output.append(enum_class_name);
p_output.append("\n" OPEN_BLOCK);
}
@@ -1004,7 +1019,7 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) {
}
p_output.append("\npublic enum ");
- p_output.append(pascal_to_pascal_case(enum_proxy_name));
+ p_output.append(enum_proxy_name);
p_output.append(" : long");
p_output.append("\n" OPEN_BLOCK);
@@ -1469,7 +1484,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
}
output.append(MEMBER_BEGIN "public enum ");
- output.append(pascal_to_pascal_case(ienum.cname.operator String()));
+ output.append(ienum.proxy_name);
output.append(" : long");
output.append(MEMBER_BEGIN OPEN_BLOCK);
@@ -2330,31 +2345,31 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf
// Generate signal
{
- p_output.append(MEMBER_BEGIN "/// <summary>\n");
- p_output.append(INDENT1 "/// ");
- p_output.append("Represents the method that handles the ");
- p_output.append("<see cref=\"" BINDINGS_NAMESPACE "." + p_itype.proxy_name + "." + p_isignal.proxy_name + "\"/>");
- p_output.append(" event of a ");
- p_output.append("<see cref=\"" BINDINGS_NAMESPACE "." + p_itype.proxy_name + "\"/>");
- p_output.append(" class.\n");
- p_output.append(INDENT1 "/// </summary>");
-
- if (p_isignal.is_deprecated) {
- if (p_isignal.deprecation_message.is_empty()) {
- WARN_PRINT("An empty deprecation message is discouraged. Signal: '" + p_isignal.proxy_name + "'.");
- }
-
- p_output.append(MEMBER_BEGIN "[Obsolete(\"");
- p_output.append(p_isignal.deprecation_message);
- p_output.append("\")]");
- }
-
bool is_parameterless = p_isignal.arguments.size() == 0;
// Delegate name is [SignalName]EventHandler
String delegate_name = is_parameterless ? "Action" : p_isignal.proxy_name + "EventHandler";
if (!is_parameterless) {
+ p_output.append(MEMBER_BEGIN "/// <summary>\n");
+ p_output.append(INDENT1 "/// ");
+ p_output.append("Represents the method that handles the ");
+ p_output.append("<see cref=\"" BINDINGS_NAMESPACE "." + p_itype.proxy_name + "." + p_isignal.proxy_name + "\"/>");
+ p_output.append(" event of a ");
+ p_output.append("<see cref=\"" BINDINGS_NAMESPACE "." + p_itype.proxy_name + "\"/>");
+ p_output.append(" class.\n");
+ p_output.append(INDENT1 "/// </summary>");
+
+ if (p_isignal.is_deprecated) {
+ if (p_isignal.deprecation_message.is_empty()) {
+ WARN_PRINT("An empty deprecation message is discouraged. Signal: '" + p_isignal.proxy_name + "'.");
+ }
+
+ p_output.append(MEMBER_BEGIN "[Obsolete(\"");
+ p_output.append(p_isignal.deprecation_message);
+ p_output.append("\")]");
+ }
+
// Generate delegate
p_output.append(MEMBER_BEGIN "public delegate void ");
p_output.append(delegate_name);
@@ -2390,7 +2405,7 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf
}
if (p_isignal.method_doc && p_isignal.method_doc->description.size()) {
- String xml_summary = bbcode_to_xml(fix_doc_description(p_isignal.method_doc->description), &p_itype);
+ String xml_summary = bbcode_to_xml(fix_doc_description(p_isignal.method_doc->description), &p_itype, true);
Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();
if (summary_lines.size()) {
@@ -3316,8 +3331,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
enum_proxy_name += "Enum";
enum_proxy_cname = StringName(enum_proxy_name);
}
- EnumInterface ienum(enum_proxy_cname);
- ienum.is_flags = E.value.is_bitfield;
+ EnumInterface ienum(enum_proxy_cname, enum_proxy_name, E.value.is_bitfield);
const List<StringName> &enum_constants = E.value.constants;
for (const StringName &constant_cname : enum_constants) {
String constant_name = constant_cname.operator String();
@@ -3950,8 +3964,7 @@ void BindingsGenerator::_populate_global_constants() {
iconstant.const_doc = const_doc;
if (enum_name != StringName()) {
- EnumInterface ienum(enum_name);
- ienum.is_flags = CoreConstants::is_global_constant_bitfield(i);
+ EnumInterface ienum(enum_name, pascal_to_pascal_case(enum_name.operator String()), CoreConstants::is_global_constant_bitfield(i));
List<EnumInterface>::Element *enum_match = global_enums.find(ienum);
if (enum_match) {
enum_match->get().constants.push_back(iconstant);
@@ -3969,7 +3982,7 @@ void BindingsGenerator::_populate_global_constants() {
enum_itype.is_enum = true;
enum_itype.name = ienum.cname.operator String();
enum_itype.cname = ienum.cname;
- enum_itype.proxy_name = pascal_to_pascal_case(enum_itype.name);
+ enum_itype.proxy_name = ienum.proxy_name;
TypeInterface::postsetup_enum_type(enum_itype);
enum_types.insert(enum_itype.cname, enum_itype);
diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h
index d4c7a59e74..6118576bb6 100644
--- a/modules/mono/editor/bindings_generator.h
+++ b/modules/mono/editor/bindings_generator.h
@@ -60,6 +60,7 @@ class BindingsGenerator {
struct EnumInterface {
StringName cname;
+ String proxy_name;
List<ConstantInterface> constants;
bool is_flags = false;
@@ -69,8 +70,10 @@ class BindingsGenerator {
EnumInterface() {}
- EnumInterface(const StringName &p_cname) {
+ EnumInterface(const StringName &p_cname, const String &p_proxy_name, bool p_is_flags) {
cname = p_cname;
+ proxy_name = p_proxy_name;
+ is_flags = p_is_flags;
}
};
@@ -751,7 +754,7 @@ class BindingsGenerator {
return p_type->name;
}
- String bbcode_to_xml(const String &p_bbcode, const TypeInterface *p_itype);
+ String bbcode_to_xml(const String &p_bbcode, const TypeInterface *p_itype, bool p_is_signal = false);
void _append_xml_method(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts);
void _append_xml_member(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts);
@@ -759,6 +762,7 @@ class BindingsGenerator {
void _append_xml_enum(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts);
void _append_xml_constant(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts);
void _append_xml_constant_in_global_scope(StringBuilder &p_xml_output, const String &p_target_cname, const String &p_link_target);
+ void _append_xml_param(StringBuilder &p_xml_output, const String &p_link_target, bool p_is_signal);
void _append_xml_undeclared(StringBuilder &p_xml_output, const String &p_link_target);
int _determine_enum_prefix(const EnumInterface &p_ienum);
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index 043a33ab35..2a502d081e 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -2113,12 +2113,10 @@ Dictionary TextServerAdvanced::_font_get_ot_name_strings(const RID &p_font_rid)
name = vformat("unknown_%d", names[i].name_id);
} break;
}
+ String text;
unsigned int text_size = hb_ot_name_get_utf32(hb_face, names[i].name_id, names[i].language, nullptr, nullptr) + 1;
- // @todo After godot-cpp#1141 is fixed, use text.resize() and write directly to text.wptr() instead of using a temporary buffer.
- char32_t *buffer = memnew_arr(char32_t, text_size);
- hb_ot_name_get_utf32(hb_face, names[i].name_id, names[i].language, &text_size, (uint32_t *)buffer);
- String text(buffer);
- memdelete_arr(buffer);
+ text.resize(text_size);
+ hb_ot_name_get_utf32(hb_face, names[i].name_id, names[i].language, &text_size, (uint32_t *)text.ptrw());
if (!text.is_empty()) {
Dictionary &id_string = names_for_lang[String(hb_language_to_string(names[i].language))];
id_string[name] = text;
diff --git a/modules/text_server_adv/thorvg_svg_in_ot.cpp b/modules/text_server_adv/thorvg_svg_in_ot.cpp
index 2f48f1564c..951ca15503 100644
--- a/modules/text_server_adv/thorvg_svg_in_ot.cpp
+++ b/modules/text_server_adv/thorvg_svg_in_ot.cpp
@@ -155,21 +155,10 @@ FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Poin
}
String xml_code_str = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"" + rtos(min_x) + " " + rtos(min_y) + " " + rtos(new_w) + " " + rtos(new_h) + "\">" + xml_body;
-#ifndef GDEXTENSION
gl_state.xml_code = xml_code_str.utf8();
-#else
- CharString xml_code = xml_code_str.utf8();
- gl_state.xml_code_length = xml_code.length();
- gl_state.xml_code = memnew_arr(char, gl_state.xml_code_length);
- memcpy(gl_state.xml_code, xml_code.get_data(), gl_state.xml_code_length);
-#endif
picture = tvg::Picture::gen();
-#ifndef GDEXTENSION
result = picture->load(gl_state.xml_code.get_data(), gl_state.xml_code.length(), "svg+xml", false);
-#else
- result = picture->load(gl_state.xml_code, gl_state.xml_code_length, "svg+xml", false);
-#endif
if (result != tvg::Result::Success) {
ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to load SVG document (glyph metrics).");
}
@@ -257,11 +246,7 @@ FT_Error tvg_svg_in_ot_render(FT_GlyphSlot p_slot, FT_Pointer *p_state) {
ERR_FAIL_COND_V_MSG(!gl_state.ready, FT_Err_Invalid_SVG_Document, "SVG glyph not ready.");
std::unique_ptr<tvg::Picture> picture = tvg::Picture::gen();
-#ifndef GDEXTENSION
tvg::Result res = picture->load(gl_state.xml_code.get_data(), gl_state.xml_code.length(), "svg+xml", false);
-#else
- tvg::Result res = picture->load(gl_state.xml_code, gl_state.xml_code_length, "svg+xml", false);
-#endif
if (res != tvg::Result::Success) {
ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to load SVG document (glyph rendering).");
}
diff --git a/modules/text_server_adv/thorvg_svg_in_ot.h b/modules/text_server_adv/thorvg_svg_in_ot.h
index 5e79c8e444..034fffb5e6 100644
--- a/modules/text_server_adv/thorvg_svg_in_ot.h
+++ b/modules/text_server_adv/thorvg_svg_in_ot.h
@@ -66,22 +66,8 @@ struct GL_State {
float y = 0;
float w = 0;
float h = 0;
-#ifndef GDEXTENSION
CharString xml_code;
-#else
- // @todo After godot-cpp#1142 is fixed, use CharString just like when compiled as a Godot module.
- char *xml_code = nullptr;
- int xml_code_length = 0;
-#endif
tvg::Matrix m;
-
-#ifdef GDEXTENSION
- ~GL_State() {
- if (xml_code) {
- memdelete_arr(xml_code);
- }
- }
-#endif
};
struct TVG_State {
diff --git a/platform/android/doc_classes/EditorExportPlatformAndroid.xml b/platform/android/doc_classes/EditorExportPlatformAndroid.xml
index c0c57cafbe..d270980d72 100644
--- a/platform/android/doc_classes/EditorExportPlatformAndroid.xml
+++ b/platform/android/doc_classes/EditorExportPlatformAndroid.xml
@@ -110,6 +110,10 @@
<member name="package/show_in_android_tv" type="bool" setter="" getter="">
If [code]true[/code], this app will show in Android TV launcher UI.
</member>
+ <member name="package/show_in_app_library" type="bool" setter="" getter="">
+ If [code]true[/code], this app will show in the device's app library.
+ [b]Note:[/b] This is [code]true[/code] by default.
+ </member>
<member name="package/signed" type="bool" setter="" getter="">
If [code]true[/code], package signing is enabled.
</member>
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index 6f0341d75c..20aaed1e3e 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -1838,6 +1838,7 @@ void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_optio
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "package/retain_data_on_uninstall"), false));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "package/exclude_from_recents"), false));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "package/show_in_android_tv"), false));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "package/show_in_app_library"), true));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "package/show_as_launcher_app"), false));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, launcher_icon_option, PROPERTY_HINT_FILE, "*.png"), ""));
diff --git a/platform/android/export/gradle_export_util.cpp b/platform/android/export/gradle_export_util.cpp
index d0d0c34bb4..0915009235 100644
--- a/platform/android/export/gradle_export_util.cpp
+++ b/platform/android/export/gradle_export_util.cpp
@@ -269,7 +269,12 @@ String _get_activity_tag(const Ref<EditorExportPlatform> &p_export_platform, con
manifest_activity_text += " <intent-filter>\n"
" <action android:name=\"android.intent.action.MAIN\" />\n"
- " <category android:name=\"android.intent.category.LAUNCHER\" />\n";
+ " <category android:name=\"android.intent.category.DEFAULT\" />\n";
+
+ bool show_in_app_library = p_preset->get("package/show_in_app_library");
+ if (show_in_app_library) {
+ manifest_activity_text += " <category android:name=\"android.intent.category.LAUNCHER\" />\n";
+ }
bool uses_leanback_category = p_preset->get("package/show_in_android_tv");
if (uses_leanback_category) {
@@ -279,7 +284,6 @@ String _get_activity_tag(const Ref<EditorExportPlatform> &p_export_platform, con
bool uses_home_category = p_preset->get("package/show_as_launcher_app");
if (uses_home_category) {
manifest_activity_text += " <category android:name=\"android.intent.category.HOME\" />\n";
- manifest_activity_text += " <category android:name=\"android.intent.category.DEFAULT\" />\n";
}
manifest_activity_text += " </intent-filter>\n";
diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml
index 542ab51660..56d403a263 100644
--- a/platform/android/java/app/AndroidManifest.xml
+++ b/platform/android/java/app/AndroidManifest.xml
@@ -45,6 +45,7 @@
<intent-filter>
<action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
diff --git a/platform/android/java/editor/src/main/AndroidManifest.xml b/platform/android/java/editor/src/main/AndroidManifest.xml
index c7b2c8ad67..cb89d6e1b0 100644
--- a/platform/android/java/editor/src/main/AndroidManifest.xml
+++ b/platform/android/java/editor/src/main/AndroidManifest.xml
@@ -45,6 +45,7 @@
<intent-filter>
<action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
diff --git a/scene/2d/canvas_modulate.cpp b/scene/2d/canvas_modulate.cpp
index 7dd2e75f09..2c5c6a1a16 100644
--- a/scene/2d/canvas_modulate.cpp
+++ b/scene/2d/canvas_modulate.cpp
@@ -30,32 +30,67 @@
#include "canvas_modulate.h"
+void CanvasModulate::_on_in_canvas_visibility_changed(bool p_new_visibility) {
+ RID canvas = get_canvas();
+ StringName group_name = "_canvas_modulate_" + itos(canvas.get_id());
+
+ ERR_FAIL_COND_MSG(p_new_visibility == is_in_group(group_name), vformat("CanvasModulate becoming %s in the canvas already %s in the modulate group. Buggy logic, please report.", p_new_visibility ? "visible" : "invisible", p_new_visibility ? "was" : "was not"));
+
+ if (p_new_visibility) {
+ bool has_active_canvas_modulate = get_tree()->has_group(group_name); // Group would be removed if empty; otherwise one CanvasModulate within must be active.
+ add_to_group(group_name);
+ if (!has_active_canvas_modulate) {
+ is_active = true;
+ RS::get_singleton()->canvas_set_modulate(canvas, color);
+ }
+ } else {
+ remove_from_group(group_name);
+ if (is_active) {
+ is_active = false;
+ CanvasModulate *new_active = Object::cast_to<CanvasModulate>(get_tree()->get_first_node_in_group(group_name));
+ if (new_active) {
+ new_active->is_active = true;
+ RS::get_singleton()->canvas_set_modulate(canvas, new_active->color);
+ } else {
+ RS::get_singleton()->canvas_set_modulate(canvas, Color(1, 1, 1, 1));
+ }
+ }
+ }
+
+ update_configuration_warnings();
+}
+
void CanvasModulate::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_CANVAS: {
- if (is_visible_in_tree()) {
- RS::get_singleton()->canvas_set_modulate(get_canvas(), color);
- add_to_group("_canvas_modulate_" + itos(get_canvas().get_id()));
+ is_in_canvas = true;
+ bool visible_in_tree = is_visible_in_tree();
+ if (visible_in_tree) {
+ _on_in_canvas_visibility_changed(true);
}
+ was_visible_in_tree = visible_in_tree;
} break;
case NOTIFICATION_EXIT_CANVAS: {
- if (is_visible_in_tree()) {
- RS::get_singleton()->canvas_set_modulate(get_canvas(), Color(1, 1, 1, 1));
- remove_from_group("_canvas_modulate_" + itos(get_canvas().get_id()));
+ is_in_canvas = false;
+ if (was_visible_in_tree) {
+ _on_in_canvas_visibility_changed(false);
}
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
- if (is_visible_in_tree()) {
- RS::get_singleton()->canvas_set_modulate(get_canvas(), color);
- add_to_group("_canvas_modulate_" + itos(get_canvas().get_id()));
- } else {
- RS::get_singleton()->canvas_set_modulate(get_canvas(), Color(1, 1, 1, 1));
- remove_from_group("_canvas_modulate_" + itos(get_canvas().get_id()));
+ if (!is_in_canvas) {
+ return;
}
- update_configuration_warnings();
+ bool visible_in_tree = is_visible_in_tree();
+ if (visible_in_tree == was_visible_in_tree) {
+ return;
+ }
+
+ _on_in_canvas_visibility_changed(visible_in_tree);
+
+ was_visible_in_tree = visible_in_tree;
} break;
}
}
@@ -69,7 +104,7 @@ void CanvasModulate::_bind_methods() {
void CanvasModulate::set_color(const Color &p_color) {
color = p_color;
- if (is_visible_in_tree()) {
+ if (is_active) {
RS::get_singleton()->canvas_set_modulate(get_canvas(), color);
}
}
@@ -81,12 +116,12 @@ Color CanvasModulate::get_color() const {
PackedStringArray CanvasModulate::get_configuration_warnings() const {
PackedStringArray warnings = Node::get_configuration_warnings();
- if (is_visible_in_tree() && is_inside_tree()) {
+ if (is_in_canvas && is_visible_in_tree()) {
List<Node *> nodes;
get_tree()->get_nodes_in_group("_canvas_modulate_" + itos(get_canvas().get_id()), &nodes);
if (nodes.size() > 1) {
- warnings.push_back(RTR("Only one visible CanvasModulate is allowed per scene (or set of instantiated scenes). The first created one will work, while the rest will be ignored."));
+ warnings.push_back(RTR("Only one visible CanvasModulate is allowed per canvas.\nWhen there are more than one, only one of them will be active. Which one is undefined."));
}
}
diff --git a/scene/2d/canvas_modulate.h b/scene/2d/canvas_modulate.h
index 3b11cf71f1..08ded52e23 100644
--- a/scene/2d/canvas_modulate.h
+++ b/scene/2d/canvas_modulate.h
@@ -38,6 +38,14 @@ class CanvasModulate : public Node2D {
Color color = Color(1, 1, 1, 1);
+ // CanvasModulate is in canvas-specific modulate group when both in canvas and visible in tree.
+ // Exactly one CanvasModulate in each such non-empty group is active.
+ bool is_in_canvas = false;
+ bool was_visible_in_tree = false; // Relevant only when in canvas.
+ bool is_active = false;
+
+ void _on_in_canvas_visibility_changed(bool p_new_visibility);
+
protected:
void _notification(int p_what);
static void _bind_methods();
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index 3a0fb6d89c..1e7e376fc8 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -2745,6 +2745,9 @@ void RichTextLabel::_thread_function(void *p_userdata) {
void RichTextLabel::_thread_end() {
set_physics_process_internal(false);
+ if (!scroll_visible) {
+ vscroll->hide();
+ }
if (is_visible_in_tree()) {
queue_redraw();
}
@@ -2814,7 +2817,6 @@ _FORCE_INLINE_ float RichTextLabel::_update_scroll_exceeds(float p_total_height,
} else {
scroll_visible = false;
scroll_w = 0;
- vscroll->hide();
}
main->first_resized_line.store(0);
@@ -2862,6 +2864,9 @@ bool RichTextLabel::_validate_line_caches() {
if (main->first_resized_line.load() == (int)main->lines.size()) {
vscroll->set_value(old_scroll);
validating.store(false);
+ if (!scroll_visible) {
+ vscroll->hide();
+ }
return true;
}
@@ -2881,6 +2886,9 @@ bool RichTextLabel::_validate_line_caches() {
update_minimum_size();
}
validating.store(false);
+ if (!scroll_visible) {
+ vscroll->hide();
+ }
return true;
}
validating.store(false);
@@ -2896,6 +2904,9 @@ bool RichTextLabel::_validate_line_caches() {
updating.store(true);
_process_line_caches();
updating.store(false);
+ if (!scroll_visible) {
+ vscroll->hide();
+ }
queue_redraw();
return true;
}
diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp
index fcf9302953..06b3882d25 100644
--- a/scene/gui/scroll_bar.cpp
+++ b/scene/gui/scroll_bar.cpp
@@ -428,6 +428,14 @@ void ScrollBar::_notification(int p_what) {
}
} break;
+ case NOTIFICATION_VISIBILITY_CHANGED: {
+ if (!is_visible()) {
+ incr_active = false;
+ decr_active = false;
+ drag.active = false;
+ }
+ } break;
+
case NOTIFICATION_MOUSE_EXIT: {
highlight = HIGHLIGHT_NONE;
queue_redraw();
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index 2d3aa66f2c..180efaaa60 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -2955,7 +2955,7 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) {
void Viewport::_update_mouse_over() {
// Update gui.mouse_over and gui.subwindow_over in all Viewports.
- // Send necessary mouse_enter/mouse_exit signals and the NOTIFICATION_VP_MOUSE_ENTER/NOTIFICATION_VP_MOUSE_EXIT notifications for every Viewport in the SceneTree.
+ // Send necessary mouse_enter/mouse_exit signals and the MOUSE_ENTER/MOUSE_EXIT notifications for every Viewport in the SceneTree.
if (is_attached_in_viewport()) {
// Execute this function only, when it is processed by a native Window or a SubViewport, that has no SubViewportContainer as parent.
@@ -3009,7 +3009,7 @@ void Viewport::_update_mouse_over(Vector2 p_pos) {
}
gui.subwindow_over = sw;
if (!sw->is_input_disabled()) {
- sw->notification(NOTIFICATION_VP_MOUSE_ENTER);
+ sw->_propagate_window_notification(sw, NOTIFICATION_WM_MOUSE_ENTER);
}
}
if (!sw->is_input_disabled()) {
diff --git a/scene/main/viewport.h b/scene/main/viewport.h
index 331ce98cdd..1e107ea99c 100644
--- a/scene/main/viewport.h
+++ b/scene/main/viewport.h
@@ -468,7 +468,8 @@ private:
SubWindowResize _sub_window_get_resize_margin(Window *p_subwindow, const Point2 &p_point);
void _update_mouse_over();
- void _update_mouse_over(Vector2 p_pos);
+ virtual void _update_mouse_over(Vector2 p_pos);
+ virtual void _mouse_leave_viewport();
virtual bool _can_consume_input_events() const { return true; }
uint64_t event_count = 0;
@@ -482,8 +483,6 @@ protected:
Size2i _get_size_2d_override() const;
bool _is_size_allocated() const;
- void _mouse_leave_viewport();
-
void _notification(int p_what);
void _process_picking();
static void _bind_methods();
diff --git a/scene/main/window.cpp b/scene/main/window.cpp
index 0b95406b94..1af279d94c 100644
--- a/scene/main/window.cpp
+++ b/scene/main/window.cpp
@@ -679,7 +679,7 @@ void Window::_event_callback(DisplayServer::WindowEvent p_event) {
}
_propagate_window_notification(this, NOTIFICATION_WM_MOUSE_ENTER);
root->gui.windowmanager_window_over = this;
- notification(NOTIFICATION_VP_MOUSE_ENTER);
+ mouse_in_window = true;
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CURSOR_SHAPE)) {
DisplayServer::get_singleton()->cursor_set_shape(DisplayServer::CURSOR_ARROW); //restore cursor shape
}
@@ -692,6 +692,7 @@ void Window::_event_callback(DisplayServer::WindowEvent p_event) {
#endif // DEV_ENABLED
return;
}
+ mouse_in_window = false;
root->gui.windowmanager_window_over->_mouse_leave_viewport();
root->gui.windowmanager_window_over = nullptr;
_propagate_window_notification(this, NOTIFICATION_WM_MOUSE_EXIT);
@@ -2552,6 +2553,41 @@ bool Window::is_attached_in_viewport() const {
return get_embedder();
}
+void Window::_update_mouse_over(Vector2 p_pos) {
+ if (!mouse_in_window) {
+ if (is_embedded()) {
+ mouse_in_window = true;
+ _propagate_window_notification(this, NOTIFICATION_WM_MOUSE_ENTER);
+ } else {
+ // Prevent update based on delayed InputEvents from DisplayServer.
+ return;
+ }
+ }
+
+ bool new_in = get_visible_rect().has_point(p_pos);
+ if (new_in == gui.mouse_in_viewport) {
+ if (new_in) {
+ Viewport::_update_mouse_over(p_pos);
+ }
+ return;
+ }
+
+ if (new_in) {
+ notification(NOTIFICATION_VP_MOUSE_ENTER);
+ Viewport::_update_mouse_over(p_pos);
+ } else {
+ Viewport::_mouse_leave_viewport();
+ }
+}
+
+void Window::_mouse_leave_viewport() {
+ Viewport::_mouse_leave_viewport();
+ if (is_embedded()) {
+ mouse_in_window = false;
+ _propagate_window_notification(this, NOTIFICATION_WM_MOUSE_EXIT);
+ }
+}
+
void Window::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_title", "title"), &Window::set_title);
ClassDB::bind_method(D_METHOD("get_title"), &Window::get_title);
diff --git a/scene/main/window.h b/scene/main/window.h
index d5b8cd4ead..c387ffa92a 100644
--- a/scene/main/window.h
+++ b/scene/main/window.h
@@ -203,6 +203,10 @@ private:
void _event_callback(DisplayServer::WindowEvent p_event);
virtual bool _can_consume_input_events() const override;
+ bool mouse_in_window = false;
+ void _update_mouse_over(Vector2 p_pos) override;
+ void _mouse_leave_viewport() override;
+
Ref<Shortcut> debugger_stop_shortcut;
protected:
diff --git a/scene/resources/particle_process_material.cpp b/scene/resources/particle_process_material.cpp
index 745c71626c..72d38ec8ce 100644
--- a/scene/resources/particle_process_material.cpp
+++ b/scene/resources/particle_process_material.cpp
@@ -683,11 +683,13 @@ void ParticleProcessMaterial::_update_shader() {
code += " pos.z = 0.0;\n";
}
code += " // apply linear acceleration\n";
- code += " force += length(VELOCITY) > 0.0 ? normalize(VELOCITY) * tex_linear_accel * mix(linear_accel_min, linear_accel_max, rand_from_seed(alt_seed)) : vec3(0.0);\n";
+ code += " float linear_accel_rand = rand_from_seed(alt_seed);\n";
+ code += " force += length(VELOCITY) > 0.0 ? normalize(VELOCITY) * tex_linear_accel * mix(linear_accel_min, linear_accel_max, linear_accel_rand) : vec3(0.0);\n";
code += " // apply radial acceleration\n";
code += " vec3 org = EMISSION_TRANSFORM[3].xyz;\n";
code += " vec3 diff = pos - org;\n";
- code += " force += length(diff) > 0.0 ? normalize(diff) * tex_radial_accel * mix(radial_accel_min, radial_accel_max, rand_from_seed(alt_seed)) : vec3(0.0);\n";
+ code += " float radial_accel_rand = rand_from_seed(alt_seed);\n";
+ code += " force += length(diff) > 0.0 ? normalize(diff) * tex_radial_accel * mix(radial_accel_min, radial_accel_max, radial_accel_rand) : vec3(0.0);\n";
code += " // apply tangential acceleration;\n";
code += " float tangent_accel_val = tex_tangent_accel * mix(tangent_accel_min, tangent_accel_max, rand_from_seed(alt_seed));\n";
if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) {
diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
index 773ea9098a..0ee76e0aa4 100644
--- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
@@ -348,7 +348,7 @@ void RendererCanvasRenderRD::free_polygon(PolygonID p_polygon) {
////////////////////
-void RendererCanvasRenderRD::_bind_canvas_texture(RD::DrawListID p_draw_list, RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, RID &r_last_texture, PushConstant &push_constant, Size2 &r_texpixel_size) {
+void RendererCanvasRenderRD::_bind_canvas_texture(RD::DrawListID p_draw_list, RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, RID &r_last_texture, PushConstant &push_constant, Size2 &r_texpixel_size, bool p_texture_is_data) {
if (p_texture == RID()) {
p_texture = default_canvas_texture;
}
@@ -363,7 +363,7 @@ void RendererCanvasRenderRD::_bind_canvas_texture(RD::DrawListID p_draw_list, RI
bool use_normal;
bool use_specular;
- bool success = RendererRD::TextureStorage::get_singleton()->canvas_texture_get_uniform_set(p_texture, p_base_filter, p_base_repeat, shader.default_version_rd_shader, CANVAS_TEXTURE_UNIFORM_SET, bool(push_constant.flags & FLAGS_CONVERT_ATTRIBUTES_TO_LINEAR), uniform_set, size, specular_shininess, use_normal, use_specular);
+ bool success = RendererRD::TextureStorage::get_singleton()->canvas_texture_get_uniform_set(p_texture, p_base_filter, p_base_repeat, shader.default_version_rd_shader, CANVAS_TEXTURE_UNIFORM_SET, bool(push_constant.flags & FLAGS_CONVERT_ATTRIBUTES_TO_LINEAR), uniform_set, size, specular_shininess, use_normal, use_specular, p_texture_is_data);
//something odd happened
if (!success) {
_bind_canvas_texture(p_draw_list, default_canvas_texture, p_base_filter, p_base_repeat, r_last_texture, push_constant, r_texpixel_size);
@@ -507,7 +507,7 @@ void RendererCanvasRenderRD::_render_item(RD::DrawListID p_draw_list, RID p_rend
//bind textures
- _bind_canvas_texture(p_draw_list, rect->texture, current_filter, current_repeat, last_texture, push_constant, texpixel_size);
+ _bind_canvas_texture(p_draw_list, rect->texture, current_filter, current_repeat, last_texture, push_constant, texpixel_size, bool(rect->flags & CANVAS_RECT_MSDF));
Rect2 src_rect;
Rect2 dst_rect;
diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.h b/servers/rendering/renderer_rd/renderer_canvas_render_rd.h
index af8736a445..4c8cbd1c9f 100644
--- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.h
+++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.h
@@ -418,7 +418,7 @@ class RendererCanvasRenderRD : public RendererCanvasRender {
RID _create_base_uniform_set(RID p_to_render_target, bool p_backbuffer);
- inline void _bind_canvas_texture(RD::DrawListID p_draw_list, RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, RID &r_last_texture, PushConstant &push_constant, Size2 &r_texpixel_size); //recursive, so regular inline used instead.
+ inline void _bind_canvas_texture(RD::DrawListID p_draw_list, RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, RID &r_last_texture, PushConstant &push_constant, Size2 &r_texpixel_size, bool p_texture_is_data = false); //recursive, so regular inline used instead.
void _render_item(RenderingDevice::DrawListID p_draw_list, RID p_render_target, const Item *p_item, RenderingDevice::FramebufferFormatID p_framebuffer_format, const Transform2D &p_canvas_transform_inverse, Item *&current_clip, Light *p_lights, PipelineVariants *p_pipeline_variants, bool &r_sdf_used);
void _render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer = false);
diff --git a/servers/rendering/renderer_rd/storage_rd/material_storage.cpp b/servers/rendering/renderer_rd/storage_rd/material_storage.cpp
index a96893570e..fda341bbc9 100644
--- a/servers/rendering/renderer_rd/storage_rd/material_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/material_storage.cpp
@@ -1821,14 +1821,16 @@ void MaterialStorage::global_shader_parameters_load_settings(bool p_load_texture
if (gvtype >= RS::GLOBAL_VAR_TYPE_SAMPLER2D) {
//textire
if (!p_load_textures) {
- value = RID();
continue;
}
String path = value;
- Ref<Resource> resource = ResourceLoader::load(path);
- ERR_CONTINUE(resource.is_null());
- value = resource;
+ if (path.is_empty()) {
+ value = RID();
+ } else {
+ Ref<Resource> resource = ResourceLoader::load(path);
+ value = resource;
+ }
}
if (global_shader_uniforms.variables.has(name)) {
diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
index 6888af7af9..56f2ea0b0c 100644
--- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
@@ -1309,13 +1309,10 @@ void MeshStorage::_multimesh_enable_motion_vectors(MultiMesh *multimesh) {
RID new_buffer = RD::get_singleton()->storage_buffer_create(new_buffer_size);
if (multimesh->buffer_set && multimesh->data_cache.is_empty()) {
- // If the buffer was set but there's no data cached in the CPU, we must download it from the GPU and
- // upload it because RD does not provide a way to copy the buffer directly yet.
+ // If the buffer was set but there's no data cached in the CPU, we copy the buffer directly on the GPU.
RD::get_singleton()->barrier();
- Vector<uint8_t> buffer_data = RD::get_singleton()->buffer_get_data(multimesh->buffer);
- ERR_FAIL_COND(buffer_data.size() != int(buffer_size));
- RD::get_singleton()->buffer_update(new_buffer, 0, buffer_size, buffer_data.ptr(), RD::BARRIER_MASK_NO_BARRIER);
- RD::get_singleton()->buffer_update(new_buffer, buffer_size, buffer_size, buffer_data.ptr());
+ RD::get_singleton()->buffer_copy(multimesh->buffer, new_buffer, 0, 0, buffer_size, RD::BARRIER_MASK_NO_BARRIER);
+ RD::get_singleton()->buffer_copy(multimesh->buffer, new_buffer, 0, buffer_size, buffer_size);
} else if (!multimesh->data_cache.is_empty()) {
// Simply upload the data cached in the CPU, which should already be doubled in size.
ERR_FAIL_COND(multimesh->data_cache.size() * sizeof(float) != size_t(new_buffer_size));
diff --git a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp
index f009318d24..286a9528fc 100644
--- a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp
@@ -643,7 +643,7 @@ void TextureStorage::canvas_texture_set_texture_repeat(RID p_canvas_texture, RS:
ct->clear_sets();
}
-bool TextureStorage::canvas_texture_get_uniform_set(RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, RID p_base_shader, int p_base_set, bool p_use_srgb, RID &r_uniform_set, Size2i &r_size, Color &r_specular_shininess, bool &r_use_normal, bool &r_use_specular) {
+bool TextureStorage::canvas_texture_get_uniform_set(RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, RID p_base_shader, int p_base_set, bool p_use_srgb, RID &r_uniform_set, Size2i &r_size, Color &r_specular_shininess, bool &r_use_normal, bool &r_use_specular, bool p_texture_is_data) {
MaterialStorage *material_storage = MaterialStorage::get_singleton();
CanvasTexture *ct = nullptr;
@@ -690,7 +690,7 @@ bool TextureStorage::canvas_texture_get_uniform_set(RID p_texture, RS::CanvasIte
u.append_id(texture_rd_get_default(DEFAULT_RD_TEXTURE_WHITE));
ct->size_cache = Size2i(1, 1);
} else {
- u.append_id(t->rd_texture_srgb.is_valid() && (p_use_srgb) ? t->rd_texture_srgb : t->rd_texture);
+ u.append_id(t->rd_texture_srgb.is_valid() && p_use_srgb && !p_texture_is_data ? t->rd_texture_srgb : t->rd_texture);
ct->size_cache = Size2i(t->width_2d, t->height_2d);
if (t->render_target) {
t->render_target->was_used = true;
diff --git a/servers/rendering/renderer_rd/storage_rd/texture_storage.h b/servers/rendering/renderer_rd/storage_rd/texture_storage.h
index 2137c5f383..276c8c4ce2 100644
--- a/servers/rendering/renderer_rd/storage_rd/texture_storage.h
+++ b/servers/rendering/renderer_rd/storage_rd/texture_storage.h
@@ -475,7 +475,7 @@ public:
virtual void canvas_texture_set_texture_filter(RID p_item, RS::CanvasItemTextureFilter p_filter) override;
virtual void canvas_texture_set_texture_repeat(RID p_item, RS::CanvasItemTextureRepeat p_repeat) override;
- bool canvas_texture_get_uniform_set(RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, RID p_base_shader, int p_base_set, bool p_use_srgb, RID &r_uniform_set, Size2i &r_size, Color &r_specular_shininess, bool &r_use_normal, bool &r_use_specular);
+ bool canvas_texture_get_uniform_set(RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, RID p_base_shader, int p_base_set, bool p_use_srgb, RID &r_uniform_set, Size2i &r_size, Color &r_specular_shininess, bool &r_use_normal, bool &r_use_specular, bool p_texture_is_data);
/* Texture API */
diff --git a/servers/rendering/rendering_device.h b/servers/rendering/rendering_device.h
index 30d9b1c7b7..1ade1b25c4 100644
--- a/servers/rendering/rendering_device.h
+++ b/servers/rendering/rendering_device.h
@@ -840,6 +840,7 @@ public:
virtual bool uniform_set_is_valid(RID p_uniform_set) = 0;
virtual void uniform_set_set_invalidation_callback(RID p_uniform_set, InvalidationCallback p_callback, void *p_userdata) = 0;
+ virtual Error buffer_copy(RID p_src_buffer, RID p_dst_buffer, uint32_t p_src_offset, uint32_t p_dst_offset, uint32_t p_size, BitField<BarrierMask> p_post_barrier = BARRIER_MASK_ALL_BARRIERS) = 0;
virtual Error buffer_update(RID p_buffer, uint32_t p_offset, uint32_t p_size, const void *p_data, BitField<BarrierMask> p_post_barrier = BARRIER_MASK_ALL_BARRIERS) = 0;
virtual Error buffer_clear(RID p_buffer, uint32_t p_offset, uint32_t p_size, BitField<BarrierMask> p_post_barrier = BARRIER_MASK_ALL_BARRIERS) = 0;
virtual Vector<uint8_t> buffer_get_data(RID p_buffer, uint32_t p_offset = 0, uint32_t p_size = 0) = 0; // This causes stall, only use to retrieve large buffers for saving.
diff --git a/tests/scene/test_window.h b/tests/scene/test_window.h
new file mode 100644
index 0000000000..e0c55101de
--- /dev/null
+++ b/tests/scene/test_window.h
@@ -0,0 +1,96 @@
+/**************************************************************************/
+/* test_window.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef TEST_WINDOW_H
+#define TEST_WINDOW_H
+
+#include "scene/gui/control.h"
+#include "scene/main/window.h"
+
+#include "tests/test_macros.h"
+
+namespace TestWindow {
+
+class NotificationControl : public Control {
+ GDCLASS(NotificationControl, Control);
+
+protected:
+ void _notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_MOUSE_ENTER: {
+ mouse_over = true;
+ } break;
+
+ case NOTIFICATION_MOUSE_EXIT: {
+ mouse_over = false;
+ } break;
+ }
+ }
+
+public:
+ bool mouse_over = false;
+};
+
+TEST_CASE("[SceneTree][Window]") {
+ Window *root = SceneTree::get_singleton()->get_root();
+
+ SUBCASE("Control-mouse-over within Window-black bars should not happen") {
+ Window *w = memnew(Window);
+ root->add_child(w);
+ w->set_size(Size2i(400, 200));
+ w->set_position(Size2i(0, 0));
+ w->set_content_scale_size(Size2i(200, 200));
+ w->set_content_scale_mode(Window::CONTENT_SCALE_MODE_CANVAS_ITEMS);
+ w->set_content_scale_aspect(Window::CONTENT_SCALE_ASPECT_KEEP);
+ NotificationControl *c = memnew(NotificationControl);
+ w->add_child(c);
+ c->set_size(Size2i(100, 100));
+ c->set_position(Size2i(-50, -50));
+
+ CHECK_FALSE(c->mouse_over);
+ SEND_GUI_MOUSE_MOTION_EVENT(Point2i(110, 10), MouseButtonMask::NONE, Key::NONE);
+ CHECK(c->mouse_over);
+ SEND_GUI_MOUSE_MOTION_EVENT(Point2i(90, 10), MouseButtonMask::NONE, Key::NONE);
+ CHECK_FALSE(c->mouse_over); // GH-80011
+
+ /* TODO:
+ SEND_GUI_MOUSE_BUTTON_EVENT(Point2i(90, 10), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(Point2i(90, 10), MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+ CHECK(Control was not pressed);
+ */
+
+ memdelete(c);
+ memdelete(w);
+ }
+}
+
+} // namespace TestWindow
+
+#endif // TEST_WINDOW_H
diff --git a/tests/test_main.cpp b/tests/test_main.cpp
index 5ca03a20af..f1e348345b 100644
--- a/tests/test_main.cpp
+++ b/tests/test_main.cpp
@@ -115,6 +115,7 @@
#include "tests/scene/test_theme.h"
#include "tests/scene/test_viewport.h"
#include "tests/scene/test_visual_shader.h"
+#include "tests/scene/test_window.h"
#include "tests/servers/rendering/test_shader_preprocessor.h"
#include "tests/servers/test_navigation_server_2d.h"
#include "tests/servers/test_navigation_server_3d.h"