diff options
83 files changed, 2131 insertions, 561 deletions
diff --git a/core/error/error_macros.cpp b/core/error/error_macros.cpp index 8376c0aaf8..813ee7684f 100644 --- a/core/error/error_macros.cpp +++ b/core/error/error_macros.cpp @@ -34,6 +34,12 @@ #include "core/os/os.h" #include "core/string/ustring.h" +// Optional physics interpolation warnings try to include the path to the relevant node. +#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED) +#include "core/config/project_settings.h" +#include "scene/main/node.h" +#endif + static ErrorHandlerList *error_handler_list = nullptr; void add_error_handler(ErrorHandlerList *p_handler) { @@ -128,3 +134,48 @@ void _err_print_index_error(const char *p_function, const char *p_file, int p_li void _err_flush_stdout() { fflush(stdout); } + +// Prevent error spam by limiting the warnings to a certain frequency. +void _physics_interpolation_warning(const char *p_function, const char *p_file, int p_line, ObjectID p_id, const char *p_warn_string) { +#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED) + const uint32_t warn_max = 2048; + const uint32_t warn_timeout_seconds = 15; + + static uint32_t warn_count = warn_max; + static uint32_t warn_timeout = warn_timeout_seconds; + + uint32_t time_now = UINT32_MAX; + + if (warn_count) { + warn_count--; + } + + if (!warn_count) { + time_now = OS::get_singleton()->get_ticks_msec() / 1000; + } + + if ((warn_count == 0) && (time_now >= warn_timeout)) { + warn_count = warn_max; + warn_timeout = time_now + warn_timeout_seconds; + + if (GLOBAL_GET("debug/settings/physics_interpolation/enable_warnings")) { + // UINT64_MAX means unused. + if (p_id.operator uint64_t() == UINT64_MAX) { + _err_print_error(p_function, p_file, p_line, "[Physics interpolation] " + String(p_warn_string) + " (possibly benign).", false, ERR_HANDLER_WARNING); + } else { + String node_name; + if (p_id.is_valid()) { + Node *node = Object::cast_to<Node>(ObjectDB::get_instance(p_id)); + if (node && node->is_inside_tree()) { + node_name = "\"" + String(node->get_path()) + "\""; + } else { + node_name = "\"unknown\""; + } + } + + _err_print_error(p_function, p_file, p_line, "[Physics interpolation] " + String(p_warn_string) + ": " + node_name + " (possibly benign).", false, ERR_HANDLER_WARNING); + } + } + } +#endif +} diff --git a/core/error/error_macros.h b/core/error/error_macros.h index ab7dbcbd44..d31adb72be 100644 --- a/core/error/error_macros.h +++ b/core/error/error_macros.h @@ -31,6 +31,7 @@ #ifndef ERROR_MACROS_H #define ERROR_MACROS_H +#include "core/object/object_id.h" #include "core/typedefs.h" #include <atomic> // We'd normally use safe_refcount.h, but that would cause circular includes. @@ -71,6 +72,8 @@ void _err_print_index_error(const char *p_function, const char *p_file, int p_li void _err_print_index_error(const char *p_function, const char *p_file, int p_line, int64_t p_index, int64_t p_size, const char *p_index_str, const char *p_size_str, const String &p_message, bool p_editor_notify = false, bool fatal = false); void _err_flush_stdout(); +void _physics_interpolation_warning(const char *p_function, const char *p_file, int p_line, ObjectID p_id, const char *p_warn_string); + #ifdef __GNUC__ //#define FUNCTION_STR __PRETTY_FUNCTION__ - too annoying #define FUNCTION_STR __FUNCTION__ @@ -832,4 +835,14 @@ void _err_flush_stdout(); #define DEV_CHECK_ONCE(m_cond) #endif +/** + * Physics Interpolation warnings. + * These are spam protection warnings. + */ +#define PHYSICS_INTERPOLATION_NODE_WARNING(m_object_id, m_string) \ + _physics_interpolation_warning(FUNCTION_STR, __FILE__, __LINE__, m_object_id, m_string) + +#define PHYSICS_INTERPOLATION_WARNING(m_string) \ + _physics_interpolation_warning(FUNCTION_STR, __FILE__, __LINE__, UINT64_MAX, m_string) + #endif // ERROR_MACROS_H diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp index 8e2366fc95..cb6832ea39 100644 --- a/core/extension/gdextension.cpp +++ b/core/extension/gdextension.cpp @@ -781,23 +781,14 @@ Error GDExtension::open_library(const String &p_path, const String &p_entry_symb } } - String actual_lib_path; OS::GDExtensionData data = { true, // also_set_library_path - &actual_lib_path, // r_resolved_path + &library_path, // r_resolved_path Engine::get_singleton()->is_editor_hint(), // generate_temp_files &abs_dependencies_paths, // library_dependencies }; Error err = OS::get_singleton()->open_dynamic_library(abs_path, library, &data); - if (actual_lib_path.get_file() != abs_path.get_file()) { - // If temporary files are generated, let's change the library path to point at the original, - // because that's what we want to check to see if it's changed. - library_path = actual_lib_path.get_base_dir().path_join(p_path.get_file()); - } else { - library_path = actual_lib_path; - } - ERR_FAIL_COND_V_MSG(err == ERR_FILE_NOT_FOUND, err, "GDExtension dynamic library not found: " + abs_path); ERR_FAIL_COND_V_MSG(err != OK, err, "Can't open GDExtension dynamic library: " + abs_path); diff --git a/core/io/image.cpp b/core/io/image.cpp index d0598e4dc6..b35d405662 100644 --- a/core/io/image.cpp +++ b/core/io/image.cpp @@ -501,6 +501,38 @@ static void _convert(int p_width, int p_height, const uint8_t *p_src, uint8_t *p } } +template <typename T, uint32_t read_channels, uint32_t write_channels, T def_zero, T def_one> +static void _convert_fast(int p_width, int p_height, const T *p_src, T *p_dst) { + uint32_t dst_count = 0; + uint32_t src_count = 0; + + const int resolution = p_width * p_height; + + for (int i = 0; i < resolution; i++) { + memcpy(p_dst + dst_count, p_src + src_count, MIN(read_channels, write_channels) * sizeof(T)); + + if constexpr (write_channels > read_channels) { + const T def_value[4] = { def_zero, def_zero, def_zero, def_one }; + memcpy(p_dst + dst_count + read_channels, &def_value[read_channels], (write_channels - read_channels) * sizeof(T)); + } + + dst_count += write_channels; + src_count += read_channels; + } +} + +static bool _are_formats_compatible(Image::Format p_format0, Image::Format p_format1) { + if (p_format0 <= Image::FORMAT_RGBA8 && p_format1 <= Image::FORMAT_RGBA8) { + return true; + } else if (p_format0 <= Image::FORMAT_RGBAH && p_format0 >= Image::FORMAT_RH && p_format1 <= Image::FORMAT_RGBAH && p_format1 >= Image::FORMAT_RH) { + return true; + } else if (p_format0 <= Image::FORMAT_RGBAF && p_format0 >= Image::FORMAT_RF && p_format1 <= Image::FORMAT_RGBAF && p_format1 >= Image::FORMAT_RF) { + return true; + } + + return false; +} + void Image::convert(Format p_new_format) { ERR_FAIL_INDEX_MSG(p_new_format, FORMAT_MAX, "The Image format specified (" + itos(p_new_format) + ") is out of range. See Image's Format enum."); if (data.size() == 0) { @@ -517,7 +549,7 @@ void Image::convert(Format p_new_format) { if (Image::is_format_compressed(format) || Image::is_format_compressed(p_new_format)) { ERR_FAIL_MSG("Cannot convert to <-> from compressed formats. Use compress() and decompress() instead."); - } else if (format > FORMAT_RGBA8 || p_new_format > FORMAT_RGBA8) { + } else if (!_are_formats_compatible(format, p_new_format)) { //use put/set pixel which is slower but works with non byte formats Image new_img(width, height, mipmaps, p_new_format); @@ -648,6 +680,78 @@ void Image::convert(Format p_new_format) { case FORMAT_RGBA8 | (FORMAT_RGB8 << 8): _convert<3, true, 3, false, false, false>(mip_width, mip_height, rptr, wptr); break; + case FORMAT_RH | (FORMAT_RGH << 8): + _convert_fast<uint16_t, 1, 2, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RH | (FORMAT_RGBH << 8): + _convert_fast<uint16_t, 1, 3, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RH | (FORMAT_RGBAH << 8): + _convert_fast<uint16_t, 1, 4, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RGH | (FORMAT_RH << 8): + _convert_fast<uint16_t, 2, 1, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RGH | (FORMAT_RGBH << 8): + _convert_fast<uint16_t, 2, 3, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RGH | (FORMAT_RGBAH << 8): + _convert_fast<uint16_t, 2, 4, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RGBH | (FORMAT_RH << 8): + _convert_fast<uint16_t, 3, 1, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RGBH | (FORMAT_RGH << 8): + _convert_fast<uint16_t, 3, 2, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RGBH | (FORMAT_RGBAH << 8): + _convert_fast<uint16_t, 3, 4, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RGBAH | (FORMAT_RH << 8): + _convert_fast<uint16_t, 4, 1, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RGBAH | (FORMAT_RGH << 8): + _convert_fast<uint16_t, 4, 2, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RGBAH | (FORMAT_RGBH << 8): + _convert_fast<uint16_t, 4, 3, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RF | (FORMAT_RGF << 8): + _convert_fast<uint32_t, 1, 2, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; + case FORMAT_RF | (FORMAT_RGBF << 8): + _convert_fast<uint32_t, 1, 3, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; + case FORMAT_RF | (FORMAT_RGBAF << 8): + _convert_fast<uint32_t, 1, 4, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; + case FORMAT_RGF | (FORMAT_RF << 8): + _convert_fast<uint32_t, 2, 1, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; + case FORMAT_RGF | (FORMAT_RGBF << 8): + _convert_fast<uint32_t, 2, 3, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; + case FORMAT_RGF | (FORMAT_RGBAF << 8): + _convert_fast<uint32_t, 2, 4, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; + case FORMAT_RGBF | (FORMAT_RF << 8): + _convert_fast<uint32_t, 3, 1, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; + case FORMAT_RGBF | (FORMAT_RGF << 8): + _convert_fast<uint32_t, 3, 2, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; + case FORMAT_RGBF | (FORMAT_RGBAF << 8): + _convert_fast<uint32_t, 3, 4, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; + case FORMAT_RGBAF | (FORMAT_RF << 8): + _convert_fast<uint32_t, 4, 1, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; + case FORMAT_RGBAF | (FORMAT_RGF << 8): + _convert_fast<uint32_t, 4, 2, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; + case FORMAT_RGBAF | (FORMAT_RGBF << 8): + _convert_fast<uint32_t, 4, 3, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; } } diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index 928bb95de3..e727dfa56d 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -583,13 +583,7 @@ ResourceLoader::ThreadLoadStatus ResourceLoader::load_threaded_get_status(const } String local_path = _validate_local_path(p_path); - if (!thread_load_tasks.has(local_path)) { -#ifdef DEV_ENABLED - CRASH_NOW(); -#endif - // On non-dev, be defensive and at least avoid crashing (at this point at least). - return THREAD_LOAD_INVALID_RESOURCE; - } + ERR_FAIL_COND_V_MSG(!thread_load_tasks.has(local_path), THREAD_LOAD_INVALID_RESOURCE, "Bug in ResourceLoader logic, please report."); ThreadLoadTask &load_task = thread_load_tasks[local_path]; status = load_task.status; @@ -678,14 +672,10 @@ Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro if (!p_load_token.local_path.is_empty()) { if (!thread_load_tasks.has(p_load_token.local_path)) { -#ifdef DEV_ENABLED - CRASH_NOW(); -#endif - // On non-dev, be defensive and at least avoid crashing (at this point at least). if (r_error) { *r_error = ERR_BUG; } - return Ref<Resource>(); + ERR_FAIL_V_MSG(Ref<Resource>(), "Bug in ResourceLoader logic, please report."); } ThreadLoadTask &load_task = thread_load_tasks[p_load_token.local_path]; diff --git a/core/math/color.h b/core/math/color.h index e17b8c9fd7..70fad78acb 100644 --- a/core/math/color.h +++ b/core/math/color.h @@ -129,33 +129,46 @@ struct [[nodiscard]] Color { } _FORCE_INLINE_ uint32_t to_rgbe9995() const { - const float pow2to9 = 512.0f; - const float B = 15.0f; - const float N = 9.0f; - - float sharedexp = 65408.000f; // Result of: ((pow2to9 - 1.0f) / pow2to9) * powf(2.0f, 31.0f - 15.0f) - - float cRed = MAX(0.0f, MIN(sharedexp, r)); - float cGreen = MAX(0.0f, MIN(sharedexp, g)); - float cBlue = MAX(0.0f, MIN(sharedexp, b)); - - float cMax = MAX(cRed, MAX(cGreen, cBlue)); - - float expp = MAX(-B - 1.0f, floor(Math::log(cMax) / (real_t)Math_LN2)) + 1.0f + B; - - float sMax = (float)floor((cMax / Math::pow(2.0f, expp - B - N)) + 0.5f); - - float exps = expp + 1.0f; - - if (0.0f <= sMax && sMax < pow2to9) { - exps = expp; - } - - float sRed = Math::floor((cRed / pow(2.0f, exps - B - N)) + 0.5f); - float sGreen = Math::floor((cGreen / pow(2.0f, exps - B - N)) + 0.5f); - float sBlue = Math::floor((cBlue / pow(2.0f, exps - B - N)) + 0.5f); - - return (uint32_t(Math::fast_ftoi(sRed)) & 0x1FF) | ((uint32_t(Math::fast_ftoi(sGreen)) & 0x1FF) << 9) | ((uint32_t(Math::fast_ftoi(sBlue)) & 0x1FF) << 18) | ((uint32_t(Math::fast_ftoi(exps)) & 0x1F) << 27); + // https://github.com/microsoft/DirectX-Graphics-Samples/blob/v10.0.19041.0/MiniEngine/Core/Color.cpp + static const float kMaxVal = float(0x1FF << 7); + static const float kMinVal = float(1.f / (1 << 16)); + + // Clamp RGB to [0, 1.FF*2^16] + const float _r = CLAMP(r, 0.0f, kMaxVal); + const float _g = CLAMP(g, 0.0f, kMaxVal); + const float _b = CLAMP(b, 0.0f, kMaxVal); + + // Compute the maximum channel, no less than 1.0*2^-15 + const float MaxChannel = MAX(MAX(_r, _g), MAX(_b, kMinVal)); + + // Take the exponent of the maximum channel (rounding up the 9th bit) and + // add 15 to it. When added to the channels, it causes the implicit '1.0' + // bit and the first 8 mantissa bits to be shifted down to the low 9 bits + // of the mantissa, rounding the truncated bits. + union { + float f; + int32_t i; + } R, G, B, E; + + E.f = MaxChannel; + E.i += 0x07804000; // Add 15 to the exponent and 0x4000 to the mantissa + E.i &= 0x7F800000; // Zero the mantissa + + // This shifts the 9-bit values we need into the lowest bits, rounding as + // needed. Note that if the channel has a smaller exponent than the max + // channel, it will shift even more. This is intentional. + R.f = _r + E.f; + G.f = _g + E.f; + B.f = _b + E.f; + + // Convert the Bias to the correct exponent in the upper 5 bits. + E.i <<= 4; + E.i += 0x10000000; + + // Combine the fields. RGB floats have unwanted data in the upper 9 + // bits. Only red needs to mask them off because green and blue shift + // it out to the left. + return E.i | (B.i << 18) | (G.i << 9) | (R.i & 511); } _FORCE_INLINE_ Color blend(const Color &p_over) const { diff --git a/core/math/math_funcs.h b/core/math/math_funcs.h index 3060f31970..fd53ed28fd 100644 --- a/core/math/math_funcs.h +++ b/core/math/math_funcs.h @@ -447,14 +447,22 @@ public: static _ALWAYS_INLINE_ double smoothstep(double p_from, double p_to, double p_s) { if (is_equal_approx(p_from, p_to)) { - return p_from; + if (likely(p_from <= p_to)) { + return p_s <= p_from ? 0.0 : 1.0; + } else { + return p_s <= p_to ? 1.0 : 0.0; + } } double s = CLAMP((p_s - p_from) / (p_to - p_from), 0.0, 1.0); return s * s * (3.0 - 2.0 * s); } static _ALWAYS_INLINE_ float smoothstep(float p_from, float p_to, float p_s) { if (is_equal_approx(p_from, p_to)) { - return p_from; + if (likely(p_from <= p_to)) { + return p_s <= p_from ? 0.0f : 1.0f; + } else { + return p_s <= p_to ? 1.0f : 0.0f; + } } float s = CLAMP((p_s - p_from) / (p_to - p_from), 0.0f, 1.0f); return s * s * (3.0f - 2.0f * s); diff --git a/core/math/transform_interpolator.cpp b/core/math/transform_interpolator.cpp index 6a564b0ca7..1cd35b3d1a 100644 --- a/core/math/transform_interpolator.cpp +++ b/core/math/transform_interpolator.cpp @@ -31,6 +31,7 @@ #include "transform_interpolator.h" #include "core/math/transform_2d.h" +#include "core/math/transform_3d.h" void TransformInterpolator::interpolate_transform_2d(const Transform2D &p_prev, const Transform2D &p_curr, Transform2D &r_result, real_t p_fraction) { // Special case for physics interpolation, if flipping, don't interpolate basis. @@ -44,3 +45,340 @@ void TransformInterpolator::interpolate_transform_2d(const Transform2D &p_prev, r_result = p_prev.interpolate_with(p_curr, p_fraction); } + +void TransformInterpolator::interpolate_transform_3d(const Transform3D &p_prev, const Transform3D &p_curr, Transform3D &r_result, real_t p_fraction) { + r_result.origin = p_prev.origin + ((p_curr.origin - p_prev.origin) * p_fraction); + interpolate_basis(p_prev.basis, p_curr.basis, r_result.basis, p_fraction); +} + +void TransformInterpolator::interpolate_basis(const Basis &p_prev, const Basis &p_curr, Basis &r_result, real_t p_fraction) { + Method method = find_method(p_prev, p_curr); + interpolate_basis_via_method(p_prev, p_curr, r_result, p_fraction, method); +} + +void TransformInterpolator::interpolate_transform_3d_via_method(const Transform3D &p_prev, const Transform3D &p_curr, Transform3D &r_result, real_t p_fraction, Method p_method) { + r_result.origin = p_prev.origin + ((p_curr.origin - p_prev.origin) * p_fraction); + interpolate_basis_via_method(p_prev.basis, p_curr.basis, r_result.basis, p_fraction, p_method); +} + +void TransformInterpolator::interpolate_basis_via_method(const Basis &p_prev, const Basis &p_curr, Basis &r_result, real_t p_fraction, Method p_method) { + switch (p_method) { + default: { + interpolate_basis_linear(p_prev, p_curr, r_result, p_fraction); + } break; + case INTERP_SLERP: { + r_result = _basis_slerp_unchecked(p_prev, p_curr, p_fraction); + } break; + case INTERP_SCALED_SLERP: { + interpolate_basis_scaled_slerp(p_prev, p_curr, r_result, p_fraction); + } break; + } +} + +Quaternion TransformInterpolator::_basis_to_quat_unchecked(const Basis &p_basis) { + Basis m = p_basis; + real_t trace = m.rows[0][0] + m.rows[1][1] + m.rows[2][2]; + real_t temp[4]; + + if (trace > 0.0) { + real_t s = Math::sqrt(trace + 1.0f); + temp[3] = (s * 0.5f); + s = 0.5f / s; + + temp[0] = ((m.rows[2][1] - m.rows[1][2]) * s); + temp[1] = ((m.rows[0][2] - m.rows[2][0]) * s); + temp[2] = ((m.rows[1][0] - m.rows[0][1]) * s); + } else { + int i = m.rows[0][0] < m.rows[1][1] + ? (m.rows[1][1] < m.rows[2][2] ? 2 : 1) + : (m.rows[0][0] < m.rows[2][2] ? 2 : 0); + int j = (i + 1) % 3; + int k = (i + 2) % 3; + + real_t s = Math::sqrt(m.rows[i][i] - m.rows[j][j] - m.rows[k][k] + 1.0f); + temp[i] = s * 0.5f; + s = 0.5f / s; + + temp[3] = (m.rows[k][j] - m.rows[j][k]) * s; + temp[j] = (m.rows[j][i] + m.rows[i][j]) * s; + temp[k] = (m.rows[k][i] + m.rows[i][k]) * s; + } + + return Quaternion(temp[0], temp[1], temp[2], temp[3]); +} + +Quaternion TransformInterpolator::_quat_slerp_unchecked(const Quaternion &p_from, const Quaternion &p_to, real_t p_fraction) { + Quaternion to1; + real_t omega, cosom, sinom, scale0, scale1; + + // Calculate cosine. + cosom = p_from.dot(p_to); + + // Adjust signs (if necessary) + if (cosom < 0.0f) { + cosom = -cosom; + to1.x = -p_to.x; + to1.y = -p_to.y; + to1.z = -p_to.z; + to1.w = -p_to.w; + } else { + to1.x = p_to.x; + to1.y = p_to.y; + to1.z = p_to.z; + to1.w = p_to.w; + } + + // Calculate coefficients. + + // This check could possibly be removed as we dealt with this + // case in the find_method() function, but is left for safety, it probably + // isn't a bottleneck. + if ((1.0f - cosom) > (real_t)CMP_EPSILON) { + // standard case (slerp) + omega = Math::acos(cosom); + sinom = Math::sin(omega); + scale0 = Math::sin((1.0f - p_fraction) * omega) / sinom; + scale1 = Math::sin(p_fraction * omega) / sinom; + } else { + // "from" and "to" quaternions are very close + // ... so we can do a linear interpolation + scale0 = 1.0f - p_fraction; + scale1 = p_fraction; + } + // Calculate final values. + return Quaternion( + scale0 * p_from.x + scale1 * to1.x, + scale0 * p_from.y + scale1 * to1.y, + scale0 * p_from.z + scale1 * to1.z, + scale0 * p_from.w + scale1 * to1.w); +} + +Basis TransformInterpolator::_basis_slerp_unchecked(Basis p_from, Basis p_to, real_t p_fraction) { + Quaternion from = _basis_to_quat_unchecked(p_from); + Quaternion to = _basis_to_quat_unchecked(p_to); + + Basis b(_quat_slerp_unchecked(from, to, p_fraction)); + return b; +} + +void TransformInterpolator::interpolate_basis_scaled_slerp(Basis p_prev, Basis p_curr, Basis &r_result, real_t p_fraction) { + // Normalize both and find lengths. + Vector3 lengths_prev = _basis_orthonormalize(p_prev); + Vector3 lengths_curr = _basis_orthonormalize(p_curr); + + r_result = _basis_slerp_unchecked(p_prev, p_curr, p_fraction); + + // Now the result is unit length basis, we need to scale. + Vector3 lengths_lerped = lengths_prev + ((lengths_curr - lengths_prev) * p_fraction); + + // Keep a note that the column / row order of the basis is weird, + // so keep an eye for bugs with this. + r_result[0] *= lengths_lerped; + r_result[1] *= lengths_lerped; + r_result[2] *= lengths_lerped; +} + +void TransformInterpolator::interpolate_basis_linear(const Basis &p_prev, const Basis &p_curr, Basis &r_result, real_t p_fraction) { + // Interpolate basis. + r_result = p_prev.lerp(p_curr, p_fraction); + + // It turns out we need to guard against zero scale basis. + // This is kind of silly, as we should probably fix the bugs elsewhere in Godot that can't deal with + // zero scale, but until that time... + for (int n = 0; n < 3; n++) { + Vector3 &axis = r_result[n]; + + // Not ok, this could cause errors due to bugs elsewhere, + // so we will bodge set this to a small value. + const real_t smallest = 0.0001f; + const real_t smallest_squared = smallest * smallest; + if (axis.length_squared() < smallest_squared) { + // Setting a different component to the smallest + // helps prevent the situation where all the axes are pointing in the same direction, + // which could be a problem for e.g. cross products... + axis[n] = smallest; + } + } +} + +// Returns length. +real_t TransformInterpolator::_vec3_normalize(Vector3 &p_vec) { + real_t lengthsq = p_vec.length_squared(); + if (lengthsq == 0.0f) { + p_vec.x = p_vec.y = p_vec.z = 0.0f; + return 0.0f; + } + real_t length = Math::sqrt(lengthsq); + p_vec.x /= length; + p_vec.y /= length; + p_vec.z /= length; + return length; +} + +// Returns lengths. +Vector3 TransformInterpolator::_basis_orthonormalize(Basis &r_basis) { + // Gram-Schmidt Process. + + Vector3 x = r_basis.get_column(0); + Vector3 y = r_basis.get_column(1); + Vector3 z = r_basis.get_column(2); + + Vector3 lengths; + + lengths.x = _vec3_normalize(x); + y = (y - x * (x.dot(y))); + lengths.y = _vec3_normalize(y); + z = (z - x * (x.dot(z)) - y * (y.dot(z))); + lengths.z = _vec3_normalize(z); + + r_basis.set_column(0, x); + r_basis.set_column(1, y); + r_basis.set_column(2, z); + + return lengths; +} + +TransformInterpolator::Method TransformInterpolator::_test_basis(Basis p_basis, bool r_needed_normalize, Quaternion &r_quat) { + // Axis lengths. + Vector3 al = Vector3(p_basis.get_column(0).length_squared(), + p_basis.get_column(1).length_squared(), + p_basis.get_column(2).length_squared()); + + // Non unit scale? + if (r_needed_normalize || !_vec3_is_equal_approx(al, Vector3(1.0, 1.0, 1.0), (real_t)0.001f)) { + // If the basis is not normalized (at least approximately), it will fail the checks needed for slerp. + // So we try to detect a scaled (but not sheared) basis, which we *can* slerp by normalizing first, + // and lerping the scales separately. + + // If any of the axes are really small, it is unlikely to be a valid rotation, or is scaled too small to deal with float error. + const real_t sl_epsilon = 0.00001f; + if ((al.x < sl_epsilon) || + (al.y < sl_epsilon) || + (al.z < sl_epsilon)) { + return INTERP_LERP; + } + + // Normalize the basis. + Basis norm_basis = p_basis; + + al.x = Math::sqrt(al.x); + al.y = Math::sqrt(al.y); + al.z = Math::sqrt(al.z); + + norm_basis.set_column(0, norm_basis.get_column(0) / al.x); + norm_basis.set_column(1, norm_basis.get_column(1) / al.y); + norm_basis.set_column(2, norm_basis.get_column(2) / al.z); + + // This doesn't appear necessary, as the later checks will catch it. + // if (!_basis_is_orthogonal_any_scale(norm_basis)) { + // return INTERP_LERP; + // } + + p_basis = norm_basis; + + // Orthonormalize not necessary as normal normalization(!) works if the + // axes are orthonormal. + // p_basis.orthonormalize(); + + // If we needed to normalize one of the two bases, we will need to normalize both, + // regardless of whether the 2nd needs it, just to make sure it takes the path to return + // INTERP_SCALED_LERP on the 2nd call of _test_basis. + r_needed_normalize = true; + } + + // Apply less stringent tests than the built in slerp, the standard Godot slerp + // is too susceptible to float error to be useful. + real_t det = p_basis.determinant(); + if (!Math::is_equal_approx(det, 1, (real_t)0.01f)) { + return INTERP_LERP; + } + + if (!_basis_is_orthogonal(p_basis)) { + return INTERP_LERP; + } + + // TODO: This could possibly be less stringent too, check this. + r_quat = _basis_to_quat_unchecked(p_basis); + if (!r_quat.is_normalized()) { + return INTERP_LERP; + } + + return r_needed_normalize ? INTERP_SCALED_SLERP : INTERP_SLERP; +} + +// This check doesn't seem to be needed but is preserved in case of bugs. +bool TransformInterpolator::_basis_is_orthogonal_any_scale(const Basis &p_basis) { + Vector3 cross = p_basis.get_column(0).cross(p_basis.get_column(1)); + real_t l = _vec3_normalize(cross); + // Too small numbers, revert to lerp. + if (l < 0.001f) { + return false; + } + + const real_t epsilon = 0.9995f; + + real_t dot = cross.dot(p_basis.get_column(2)); + if (dot < epsilon) { + return false; + } + + cross = p_basis.get_column(1).cross(p_basis.get_column(2)); + l = _vec3_normalize(cross); + // Too small numbers, revert to lerp. + if (l < 0.001f) { + return false; + } + + dot = cross.dot(p_basis.get_column(0)); + if (dot < epsilon) { + return false; + } + + return true; +} + +bool TransformInterpolator::_basis_is_orthogonal(const Basis &p_basis, real_t p_epsilon) { + Basis identity; + Basis m = p_basis * p_basis.transposed(); + + // Less stringent tests than the standard Godot slerp. + if (!_vec3_is_equal_approx(m[0], identity[0], p_epsilon) || !_vec3_is_equal_approx(m[1], identity[1], p_epsilon) || !_vec3_is_equal_approx(m[2], identity[2], p_epsilon)) { + return false; + } + return true; +} + +real_t TransformInterpolator::checksum_transform_3d(const Transform3D &p_transform) { + // just a really basic checksum, this can probably be improved + real_t sum = _vec3_sum(p_transform.origin); + sum -= _vec3_sum(p_transform.basis.rows[0]); + sum += _vec3_sum(p_transform.basis.rows[1]); + sum -= _vec3_sum(p_transform.basis.rows[2]); + return sum; +} + +TransformInterpolator::Method TransformInterpolator::find_method(const Basis &p_a, const Basis &p_b) { + bool needed_normalize = false; + + Quaternion q0; + Method method = _test_basis(p_a, needed_normalize, q0); + if (method == INTERP_LERP) { + return method; + } + + Quaternion q1; + method = _test_basis(p_b, needed_normalize, q1); + if (method == INTERP_LERP) { + return method; + } + + // Are they close together? + // Apply the same test that will revert to lerp as is present in the slerp routine. + // Calculate cosine. + real_t cosom = Math::abs(q0.dot(q1)); + if ((1.0f - cosom) <= (real_t)CMP_EPSILON) { + return INTERP_LERP; + } + + return method; +} diff --git a/core/math/transform_interpolator.h b/core/math/transform_interpolator.h index a9bce2bd7f..cc556707e4 100644 --- a/core/math/transform_interpolator.h +++ b/core/math/transform_interpolator.h @@ -32,15 +32,64 @@ #define TRANSFORM_INTERPOLATOR_H #include "core/math/math_defs.h" +#include "core/math/vector3.h" + +// Keep all the functions for fixed timestep interpolation together. +// There are two stages involved: +// Finding a method, for determining the interpolation method between two +// keyframes (which are physics ticks). +// And applying that pre-determined method. + +// Pre-determining the method makes sense because it is expensive and often +// several frames may occur between each physics tick, which will make it cheaper +// than performing every frame. struct Transform2D; +struct Transform3D; +struct Basis; +struct Quaternion; class TransformInterpolator { +public: + enum Method { + INTERP_LERP, + INTERP_SLERP, + INTERP_SCALED_SLERP, + }; + private: - static bool _sign(real_t p_val) { return p_val >= 0; } + _FORCE_INLINE_ static bool _sign(real_t p_val) { return p_val >= 0; } + static real_t _vec3_sum(const Vector3 &p_pt) { return p_pt.x + p_pt.y + p_pt.z; } + static real_t _vec3_normalize(Vector3 &p_vec); + _FORCE_INLINE_ static bool _vec3_is_equal_approx(const Vector3 &p_a, const Vector3 &p_b, real_t p_tolerance) { + return Math::is_equal_approx(p_a.x, p_b.x, p_tolerance) && Math::is_equal_approx(p_a.y, p_b.y, p_tolerance) && Math::is_equal_approx(p_a.z, p_b.z, p_tolerance); + } + static Vector3 _basis_orthonormalize(Basis &r_basis); + static Method _test_basis(Basis p_basis, bool r_needed_normalize, Quaternion &r_quat); + static Basis _basis_slerp_unchecked(Basis p_from, Basis p_to, real_t p_fraction); + static Quaternion _quat_slerp_unchecked(const Quaternion &p_from, const Quaternion &p_to, real_t p_fraction); + static Quaternion _basis_to_quat_unchecked(const Basis &p_basis); + static bool _basis_is_orthogonal(const Basis &p_basis, real_t p_epsilon = 0.01f); + static bool _basis_is_orthogonal_any_scale(const Basis &p_basis); + + static void interpolate_basis_linear(const Basis &p_prev, const Basis &p_curr, Basis &r_result, real_t p_fraction); + static void interpolate_basis_scaled_slerp(Basis p_prev, Basis p_curr, Basis &r_result, real_t p_fraction); public: static void interpolate_transform_2d(const Transform2D &p_prev, const Transform2D &p_curr, Transform2D &r_result, real_t p_fraction); + + // Generic functions, use when you don't know what method should be used, e.g. from GDScript. + // These will be slower. + static void interpolate_transform_3d(const Transform3D &p_prev, const Transform3D &p_curr, Transform3D &r_result, real_t p_fraction); + static void interpolate_basis(const Basis &p_prev, const Basis &p_curr, Basis &r_result, real_t p_fraction); + + // Optimized function when you know ahead of time the method. + static void interpolate_transform_3d_via_method(const Transform3D &p_prev, const Transform3D &p_curr, Transform3D &r_result, real_t p_fraction, Method p_method); + static void interpolate_basis_via_method(const Basis &p_prev, const Basis &p_curr, Basis &r_result, real_t p_fraction, Method p_method); + + static real_t checksum_transform_3d(const Transform3D &p_transform); + + static Method find_method(const Basis &p_a, const Basis &p_b); }; #endif // TRANSFORM_INTERPOLATOR_H diff --git a/core/os/main_loop.h b/core/os/main_loop.h index e48541d074..9c22cbaf3c 100644 --- a/core/os/main_loop.h +++ b/core/os/main_loop.h @@ -64,6 +64,7 @@ public: virtual void initialize(); virtual void iteration_prepare() {} virtual bool physics_process(double p_time); + virtual void iteration_end() {} virtual bool process(double p_time); virtual void finalize(); diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index 3d37e17ef8..cf19a1d48f 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -1639,13 +1639,43 @@ Vector<int> String::split_ints_mk(const Vector<String> &p_splitters, bool p_allo } String String::join(const Vector<String> &parts) const { + if (parts.is_empty()) { + return String(); + } else if (parts.size() == 1) { + return parts[0]; + } + + const int this_length = length(); + + int new_size = (parts.size() - 1) * this_length; + for (const String &part : parts) { + new_size += part.length(); + } + new_size += 1; + String ret; - for (int i = 0; i < parts.size(); ++i) { - if (i > 0) { - ret += *this; + ret.resize(new_size); + char32_t *ret_ptrw = ret.ptrw(); + const char32_t *this_ptr = ptr(); + + bool first = true; + for (const String &part : parts) { + if (first) { + first = false; + } else if (this_length) { + memcpy(ret_ptrw, this_ptr, this_length * sizeof(char32_t)); + ret_ptrw += this_length; + } + + const int part_length = part.length(); + if (part_length) { + memcpy(ret_ptrw, part.ptr(), part_length * sizeof(char32_t)); + ret_ptrw += part_length; } - ret += parts[i]; } + + *ret_ptrw = 0; + return ret; } @@ -3149,7 +3179,7 @@ Vector<uint8_t> String::sha256_buffer() const { } String String::insert(int p_at_pos, const String &p_string) const { - if (p_at_pos < 0) { + if (p_string.is_empty() || p_at_pos < 0) { return *this; } @@ -3157,17 +3187,27 @@ String String::insert(int p_at_pos, const String &p_string) const { p_at_pos = length(); } - String pre; + String ret; + ret.resize(length() + p_string.length() + 1); + char32_t *ret_ptrw = ret.ptrw(); + const char32_t *this_ptr = ptr(); + if (p_at_pos > 0) { - pre = substr(0, p_at_pos); + memcpy(ret_ptrw, this_ptr, p_at_pos * sizeof(char32_t)); + ret_ptrw += p_at_pos; } - String post; + memcpy(ret_ptrw, p_string.ptr(), p_string.length() * sizeof(char32_t)); + ret_ptrw += p_string.length(); + if (p_at_pos < length()) { - post = substr(p_at_pos, length() - p_at_pos); + memcpy(ret_ptrw, this_ptr + p_at_pos, (length() - p_at_pos) * sizeof(char32_t)); + ret_ptrw += length() - p_at_pos; } - return pre + p_string + post; + *ret_ptrw = 0; + + return ret; } String String::erase(int p_pos, int p_chars) const { @@ -5321,6 +5361,11 @@ String String::lpad(int min_length, const String &character) const { // "fish %s %d pie" % ["frog", 12] // In case of an error, the string returned is the error description and "error" is true. String String::sprintf(const Array &values, bool *error) const { + static const String ZERO("0"); + static const String SPACE(" "); + static const String MINUS("-"); + static const String PLUS("+"); + String formatted; char32_t *self = (char32_t *)get_data(); bool in_format = false; @@ -5343,7 +5388,7 @@ String String::sprintf(const Array &values, bool *error) const { if (in_format) { // We have % - let's see what else we get. switch (c) { case '%': { // Replace %% with % - formatted += chr(c); + formatted += c; in_format = false; break; } @@ -5393,7 +5438,7 @@ String String::sprintf(const Array &values, bool *error) const { // Padding. int pad_chars_count = (negative || show_sign) ? min_chars - 1 : min_chars; - String pad_char = pad_with_zeros ? String("0") : String(" "); + const String &pad_char = pad_with_zeros ? ZERO : SPACE; if (left_justified) { str = str.rpad(pad_chars_count, pad_char); } else { @@ -5402,7 +5447,7 @@ String String::sprintf(const Array &values, bool *error) const { // Sign. if (show_sign || negative) { - String sign_char = negative ? "-" : "+"; + const String &sign_char = negative ? MINUS : PLUS; if (left_justified) { str = str.insert(0, sign_char); } else { @@ -5439,7 +5484,7 @@ String String::sprintf(const Array &values, bool *error) const { // Padding. Leave room for sign later if required. int pad_chars_count = (is_negative || show_sign) ? min_chars - 1 : min_chars; - String pad_char = (pad_with_zeros && is_finite) ? String("0") : String(" "); // Never pad NaN or inf with zeros + const String &pad_char = (pad_with_zeros && is_finite) ? ZERO : SPACE; // Never pad NaN or inf with zeros if (left_justified) { str = str.rpad(pad_chars_count, pad_char); } else { @@ -5448,7 +5493,7 @@ String String::sprintf(const Array &values, bool *error) const { // Add sign if needed. if (show_sign || is_negative) { - String sign_char = is_negative ? "-" : "+"; + const String &sign_char = is_negative ? MINUS : PLUS; if (left_justified) { str = str.insert(0, sign_char); } else { @@ -5501,7 +5546,7 @@ String String::sprintf(const Array &values, bool *error) const { // Padding. Leave room for sign later if required. int pad_chars_count = val < 0 ? min_chars - 1 : min_chars; - String pad_char = (pad_with_zeros && is_finite) ? String("0") : String(" "); // Never pad NaN or inf with zeros + const String &pad_char = (pad_with_zeros && is_finite) ? ZERO : SPACE; // Never pad NaN or inf with zeros if (left_justified) { number_str = number_str.rpad(pad_chars_count, pad_char); } else { @@ -5511,9 +5556,9 @@ String String::sprintf(const Array &values, bool *error) const { // Add sign if needed. if (val < 0) { if (left_justified) { - number_str = number_str.insert(0, "-"); + number_str = number_str.insert(0, MINUS); } else { - number_str = number_str.insert(pad_with_zeros ? 0 : number_str.length() - initial_len, "-"); + number_str = number_str.insert(pad_with_zeros ? 0 : number_str.length() - initial_len, MINUS); } } @@ -5678,7 +5723,7 @@ String String::sprintf(const Array &values, bool *error) const { in_decimals = false; break; default: - formatted += chr(c); + formatted += c; } } } diff --git a/core/templates/cowdata.h b/core/templates/cowdata.h index f22ae1f1d3..fedcfaec3b 100644 --- a/core/templates/cowdata.h +++ b/core/templates/cowdata.h @@ -160,7 +160,7 @@ private: return *out; } - void _unref(void *p_data); + void _unref(); void _ref(const CowData *p_from); void _ref(const CowData &p_from); USize _copy_on_write(); @@ -222,12 +222,15 @@ public: } Error insert(Size p_pos, const T &p_val) { - ERR_FAIL_INDEX_V(p_pos, size() + 1, ERR_INVALID_PARAMETER); - resize(size() + 1); - for (Size i = (size() - 1); i > p_pos; i--) { - set(i, get(i - 1)); + Size new_size = size() + 1; + ERR_FAIL_INDEX_V(p_pos, new_size, ERR_INVALID_PARAMETER); + Error err = resize(new_size); + ERR_FAIL_COND_V(err, err); + T *p = ptrw(); + for (Size i = new_size - 1; i > p_pos; i--) { + p[i] = p[i - 1]; } - set(p_pos, p_val); + p[p_pos] = p_val; return OK; } @@ -242,30 +245,29 @@ public: }; template <typename T> -void CowData<T>::_unref(void *p_data) { - if (!p_data) { +void CowData<T>::_unref() { + if (!_ptr) { return; } SafeNumeric<USize> *refc = _get_refcount(); - if (refc->decrement() > 0) { return; // still in use } // clean up if constexpr (!std::is_trivially_destructible_v<T>) { - USize *count = _get_size(); - T *data = (T *)(count + 1); + USize current_size = *_get_size(); - for (USize i = 0; i < *count; ++i) { + for (USize i = 0; i < current_size; ++i) { // call destructors - data[i].~T(); + T *t = &_ptr[i]; + t->~T(); } } // free mem - Memory::free_static(((uint8_t *)p_data) - DATA_OFFSET, false); + Memory::free_static(((uint8_t *)_ptr) - DATA_OFFSET, false); } template <typename T> @@ -300,7 +302,7 @@ typename CowData<T>::USize CowData<T>::_copy_on_write() { } } - _unref(_ptr); + _unref(); _ptr = _data_ptr; rc = 1; @@ -321,7 +323,7 @@ Error CowData<T>::resize(Size p_size) { if (p_size == 0) { // wants to clean up - _unref(_ptr); + _unref(); _ptr = nullptr; return OK; } @@ -460,7 +462,7 @@ void CowData<T>::_ref(const CowData &p_from) { return; // self assign, do nothing. } - _unref(_ptr); + _unref(); _ptr = nullptr; if (!p_from._ptr) { @@ -474,7 +476,7 @@ void CowData<T>::_ref(const CowData &p_from) { template <typename T> CowData<T>::~CowData() { - _unref(_ptr); + _unref(); } #if defined(__GNUC__) && !defined(__clang__) diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index 2cd3a51722..339bbb71dd 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -1248,8 +1248,9 @@ <param index="1" name="to" type="float" /> <param index="2" name="x" type="float" /> <description> - Returns the result of smoothly interpolating the value of [param x] between [code]0[/code] and [code]1[/code], based on the where [param x] lies with respect to the edges [param from] and [param to]. - The return value is [code]0[/code] if [code]x <= from[/code], and [code]1[/code] if [code]x >= to[/code]. If [param x] lies between [param from] and [param to], the returned value follows an S-shaped curve that maps [param x] between [code]0[/code] and [code]1[/code]. + Returns a smooth cubic Hermite interpolation between [code]0[/code] and [code]1[/code]. + For positive ranges (when [code]from <= to[/code]) the return value is [code]0[/code] when [code]x <= from[/code], and [code]1[/code] when [code]x >= to[/code]. If [param x] lies between [param from] and [param to], the return value follows an S-shaped curve that smoothly transitions from [code]0[/code] to [code]1[/code]. + For negative ranges (when [code]from > to[/code]) the function is mirrored and returns [code]1[/code] when [code]x <= to[/code] and [code]0[/code] when [code]x >= from[/code]. This S-shaped curve is the cubic Hermite interpolator, given by [code]f(y) = 3*y^2 - 2*y^3[/code] where [code]y = (x-from) / (to-from)[/code]. [codeblock] smoothstep(0, 2, -5.0) # Returns 0.0 @@ -1259,6 +1260,7 @@ [/codeblock] Compared to [method ease] with a curve value of [code]-1.6521[/code], [method smoothstep] returns the smoothest possible curve with no sudden changes in the derivative. If you need to perform more advanced transitions, use [Tween] or [AnimationPlayer]. [url=https://raw.githubusercontent.com/godotengine/godot-docs/master/img/smoothstep_ease_comparison.png]Comparison between smoothstep() and ease(x, -1.6521) return values[/url] + [url=https://raw.githubusercontent.com/godotengine/godot-docs/master/img/smoothstep_range.webp]Smoothstep() return values with positive, zero, and negative ranges[/url] </description> </method> <method name="snapped"> diff --git a/doc/classes/Node3D.xml b/doc/classes/Node3D.xml index 125c7ef3ee..ae13af4b82 100644 --- a/doc/classes/Node3D.xml +++ b/doc/classes/Node3D.xml @@ -46,6 +46,14 @@ Returns all the gizmos attached to this [Node3D]. </description> </method> + <method name="get_global_transform_interpolated"> + <return type="Transform3D" /> + <description> + When using physics interpolation, there will be circumstances in which you want to know the interpolated (displayed) transform of a node rather than the standard transform (which may only be accurate to the most recent physics tick). + This is particularly important for frame-based operations that take place in [method Node._process], rather than [method Node._physics_process]. Examples include [Camera3D]s focusing on a node, or finding where to fire lasers from on a frame rather than physics tick. + [b]Note:[/b] This function creates an interpolation pump on the [Node3D] the first time it is called, which can respond to physics interpolation resets. If you get problems with "streaking" when initially following a [Node3D], be sure to call [method get_global_transform_interpolated] at least once [i]before[/i] resetting the [Node3D] physics interpolation. + </description> + </method> <method name="get_parent_node_3d" qualifiers="const"> <return type="Node3D" /> <description> diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index b20b374382..ff2c0bbfd9 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -630,6 +630,10 @@ <member name="debug/settings/gdscript/max_call_stack" type="int" setter="" getter="" default="1024"> Maximum call stack allowed for debugging GDScript. </member> + <member name="debug/settings/physics_interpolation/enable_warnings" type="bool" setter="" getter="" default="true"> + If [code]true[/code], enables warnings which can help pinpoint where nodes are being incorrectly updated, which will result in incorrect interpolation and visual glitches. + When a node is being interpolated, it is essential that the transform is set during [method Node._physics_process] (during a physics tick) rather than [method Node._process] (during a frame). + </member> <member name="debug/settings/profiler/max_functions" type="int" setter="" getter="" default="16384"> Maximum number of functions per frame allowed when profiling. </member> @@ -2322,7 +2326,8 @@ </member> <member name="physics/common/physics_jitter_fix" type="float" setter="" getter="" default="0.5"> Controls how much physics ticks are synchronized with real time. For 0 or less, the ticks are synchronized. Such values are recommended for network games, where clock synchronization matters. Higher values cause higher deviation of in-game clock and real clock, but allows smoothing out framerate jitters. The default value of 0.5 should be good enough for most; values above 2 could cause the game to react to dropped frames with a noticeable delay and are not recommended. - [b]Note:[/b] When using a physics interpolation solution (such as enabling [member physics/common/physics_interpolation] or using a custom solution), the physics jitter fix should be disabled by setting [member physics/common/physics_jitter_fix] to [code]0.0[/code]. + [b]Note:[/b] Jitter fix is automatically disabled at runtime when [member physics/common/physics_interpolation] is enabled. + [b]Note:[/b] When using a custom physics interpolation solution, the physics jitter fix should be disabled by setting [member physics/common/physics_jitter_fix] to [code]0.0[/code]. [b]Note:[/b] This property is only read when the project starts. To change the physics jitter fix at runtime, set [member Engine.physics_jitter_fix] instead. </member> <member name="physics/common/physics_ticks_per_second" type="int" setter="" getter="" default="60"> diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml index 3c9f0fc7af..d86b82b72a 100644 --- a/doc/classes/RenderingServer.xml +++ b/doc/classes/RenderingServer.xml @@ -1855,6 +1855,14 @@ Sets the visibility range values for the given geometry instance. Equivalent to [member GeometryInstance3D.visibility_range_begin] and related properties. </description> </method> + <method name="instance_reset_physics_interpolation"> + <return type="void" /> + <param index="0" name="instance" type="RID" /> + <description> + Prevents physics interpolation for the current physics tick. + This is useful when moving an instance to a new location, to give an instantaneous change rather than interpolation from the previous location. + </description> + </method> <method name="instance_set_base"> <return type="void" /> <param index="0" name="instance" type="RID" /> @@ -1896,6 +1904,14 @@ If [code]true[/code], ignores both frustum and occlusion culling on the specified 3D geometry instance. This is not the same as [member GeometryInstance3D.ignore_occlusion_culling], which only ignores occlusion culling and leaves frustum culling intact. </description> </method> + <method name="instance_set_interpolated"> + <return type="void" /> + <param index="0" name="instance" type="RID" /> + <param index="1" name="interpolated" type="bool" /> + <description> + Turns on and off physics interpolation for the instance. + </description> + </method> <method name="instance_set_layer_mask"> <return type="void" /> <param index="0" name="instance" type="RID" /> diff --git a/doc/classes/ResourceImporterOBJ.xml b/doc/classes/ResourceImporterOBJ.xml index 55043a311c..a63dddb0e8 100644 --- a/doc/classes/ResourceImporterOBJ.xml +++ b/doc/classes/ResourceImporterOBJ.xml @@ -21,9 +21,6 @@ <member name="offset_mesh" type="Vector3" setter="" getter="" default="Vector3(0, 0, 0)"> Offsets the mesh's data by the specified value. This can be used to work around misaligned meshes without having to modify the source file. </member> - <member name="optimize_mesh" type="bool" setter="" getter="" default="true"> - Unused parameter. This currently has no effect. - </member> <member name="scale_mesh" type="Vector3" setter="" getter="" default="Vector3(1, 1, 1)"> Scales the mesh's data by the specified value. This can be used to work around misscaled meshes without having to modify the source file. </member> diff --git a/doc/classes/ShapeCast2D.xml b/doc/classes/ShapeCast2D.xml index 576bd62cc3..385e3a9285 100644 --- a/doc/classes/ShapeCast2D.xml +++ b/doc/classes/ShapeCast2D.xml @@ -139,7 +139,7 @@ <member name="collision_mask" type="int" setter="set_collision_mask" getter="get_collision_mask" default="1"> The shape's collision mask. Only objects in at least one collision layer enabled in the mask will be detected. </member> - <member name="collision_result" type="Array" setter="" getter="_get_collision_result" default="[]"> + <member name="collision_result" type="Array" setter="" getter="get_collision_result" default="[]"> Returns the complete collision information from the collision sweep. The data returned is the same as in the [method PhysicsDirectSpaceState2D.get_rest_info] method. </member> <member name="enabled" type="bool" setter="set_enabled" getter="is_enabled" default="true"> diff --git a/doc/classes/ShapeCast3D.xml b/doc/classes/ShapeCast3D.xml index 2c6efe2ebe..f70cf169df 100644 --- a/doc/classes/ShapeCast3D.xml +++ b/doc/classes/ShapeCast3D.xml @@ -146,7 +146,7 @@ <member name="collision_mask" type="int" setter="set_collision_mask" getter="get_collision_mask" default="1"> The shape's collision mask. Only objects in at least one collision layer enabled in the mask will be detected. See [url=$DOCS_URL/tutorials/physics/physics_introduction.html#collision-layers-and-masks]Collision layers and masks[/url] in the documentation for more information. </member> - <member name="collision_result" type="Array" setter="" getter="_get_collision_result" default="[]"> + <member name="collision_result" type="Array" setter="" getter="get_collision_result" default="[]"> Returns the complete collision information from the collision sweep. The data returned is the same as in the [method PhysicsDirectSpaceState3D.get_rest_info] method. </member> <member name="debug_shape_custom_color" type="Color" setter="set_debug_shape_custom_color" getter="get_debug_shape_custom_color" default="Color(0, 0, 0, 1)"> diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml index b288ee7ff6..f57185ae87 100644 --- a/doc/classes/Viewport.xml +++ b/doc/classes/Viewport.xml @@ -301,6 +301,7 @@ <member name="own_world_3d" type="bool" setter="set_use_own_world_3d" getter="is_using_own_world_3d" default="false"> If [code]true[/code], the viewport will use a unique copy of the [World3D] defined in [member world_3d]. </member> + <member name="physics_interpolation_mode" type="int" setter="set_physics_interpolation_mode" getter="get_physics_interpolation_mode" overrides="Node" enum="Node.PhysicsInterpolationMode" default="1" /> <member name="physics_object_picking" type="bool" setter="set_physics_object_picking" getter="get_physics_object_picking" default="false"> If [code]true[/code], the objects rendered by viewport become subjects of mouse picking process. [b]Note:[/b] The number of simultaneously pickable objects is limited to 64 and they are selected in a non-deterministic order, which can be different in each picking process. diff --git a/drivers/d3d12/rendering_device_driver_d3d12.cpp b/drivers/d3d12/rendering_device_driver_d3d12.cpp index a33fc977c6..38caff648e 100644 --- a/drivers/d3d12/rendering_device_driver_d3d12.cpp +++ b/drivers/d3d12/rendering_device_driver_d3d12.cpp @@ -2143,33 +2143,59 @@ void RenderingDeviceDriverD3D12::command_pipeline_barrier(CommandBufferID p_cmd_ for (uint32_t i = 0; i < p_texture_barriers.size(); i++) { const TextureBarrier &texture_barrier_rd = p_texture_barriers[i]; const TextureInfo *texture_info = (const TextureInfo *)(texture_barrier_rd.texture.id); + if (texture_info->main_texture) { + texture_info = texture_info->main_texture; + } _rd_stages_and_access_to_d3d12(p_src_stages, texture_barrier_rd.prev_layout, texture_barrier_rd.src_access, texture_barrier_d3d12.SyncBefore, texture_barrier_d3d12.AccessBefore); _rd_stages_and_access_to_d3d12(p_dst_stages, texture_barrier_rd.next_layout, texture_barrier_rd.dst_access, texture_barrier_d3d12.SyncAfter, texture_barrier_d3d12.AccessAfter); texture_barrier_d3d12.LayoutBefore = _rd_texture_layout_to_d3d12_barrier_layout(texture_barrier_rd.prev_layout); texture_barrier_d3d12.LayoutAfter = _rd_texture_layout_to_d3d12_barrier_layout(texture_barrier_rd.next_layout); texture_barrier_d3d12.pResource = texture_info->resource; - texture_barrier_d3d12.Subresources.IndexOrFirstMipLevel = texture_barrier_rd.subresources.base_mipmap; - texture_barrier_d3d12.Subresources.NumMipLevels = texture_barrier_rd.subresources.mipmap_count; - texture_barrier_d3d12.Subresources.FirstArraySlice = texture_barrier_rd.subresources.base_layer; - texture_barrier_d3d12.Subresources.NumArraySlices = texture_barrier_rd.subresources.layer_count; - texture_barrier_d3d12.Subresources.FirstPlane = _compute_plane_slice(texture_info->format, texture_barrier_rd.subresources.aspect); - texture_barrier_d3d12.Subresources.NumPlanes = format_get_plane_count(texture_info->format); + if (texture_barrier_rd.subresources.mipmap_count == texture_info->mipmaps && texture_barrier_rd.subresources.layer_count == texture_info->layers) { + // So, all resources. Then, let's be explicit about it so D3D12 doesn't think + // we are dealing with a subset of subresources. + texture_barrier_d3d12.Subresources.IndexOrFirstMipLevel = 0xffffffff; + texture_barrier_d3d12.Subresources.NumMipLevels = 0; + // Because NumMipLevels == 0, all the other fields are ignored by D3D12. + } else { + texture_barrier_d3d12.Subresources.IndexOrFirstMipLevel = texture_barrier_rd.subresources.base_mipmap; + texture_barrier_d3d12.Subresources.NumMipLevels = texture_barrier_rd.subresources.mipmap_count; + texture_barrier_d3d12.Subresources.FirstArraySlice = texture_barrier_rd.subresources.base_layer; + texture_barrier_d3d12.Subresources.NumArraySlices = texture_barrier_rd.subresources.layer_count; + texture_barrier_d3d12.Subresources.FirstPlane = _compute_plane_slice(texture_info->format, texture_barrier_rd.subresources.aspect); + texture_barrier_d3d12.Subresources.NumPlanes = format_get_plane_count(texture_info->format); + } texture_barrier_d3d12.Flags = (texture_barrier_rd.prev_layout == RDD::TEXTURE_LAYOUT_UNDEFINED) ? D3D12_TEXTURE_BARRIER_FLAG_DISCARD : D3D12_TEXTURE_BARRIER_FLAG_NONE; texture_barriers.push_back(texture_barrier_d3d12); } // Define the barrier groups and execute. + D3D12_BARRIER_GROUP barrier_groups[3] = {}; - barrier_groups[0].Type = D3D12_BARRIER_TYPE_GLOBAL; - barrier_groups[1].Type = D3D12_BARRIER_TYPE_BUFFER; - barrier_groups[2].Type = D3D12_BARRIER_TYPE_TEXTURE; - barrier_groups[0].NumBarriers = global_barriers.size(); - barrier_groups[1].NumBarriers = buffer_barriers.size(); - barrier_groups[2].NumBarriers = texture_barriers.size(); - barrier_groups[0].pGlobalBarriers = global_barriers.ptr(); - barrier_groups[1].pBufferBarriers = buffer_barriers.ptr(); - barrier_groups[2].pTextureBarriers = texture_barriers.ptr(); - cmd_list_7->Barrier(ARRAY_SIZE(barrier_groups), barrier_groups); + uint32_t barrier_groups_count = 0; + + if (!global_barriers.is_empty()) { + D3D12_BARRIER_GROUP &barrier_group = barrier_groups[barrier_groups_count++]; + barrier_group.Type = D3D12_BARRIER_TYPE_GLOBAL; + barrier_group.NumBarriers = global_barriers.size(); + barrier_group.pGlobalBarriers = global_barriers.ptr(); + } + + if (!buffer_barriers.is_empty()) { + D3D12_BARRIER_GROUP &barrier_group = barrier_groups[barrier_groups_count++]; + barrier_group.Type = D3D12_BARRIER_TYPE_BUFFER; + barrier_group.NumBarriers = buffer_barriers.size(); + barrier_group.pBufferBarriers = buffer_barriers.ptr(); + } + + if (!texture_barriers.is_empty()) { + D3D12_BARRIER_GROUP &barrier_group = barrier_groups[barrier_groups_count++]; + barrier_group.Type = D3D12_BARRIER_TYPE_TEXTURE; + barrier_group.NumBarriers = texture_barriers.size(); + barrier_group.pTextureBarriers = texture_barriers.ptr(); + } + + cmd_list_7->Barrier(barrier_groups_count, barrier_groups); } /****************/ diff --git a/drivers/gles3/rasterizer_gles3.cpp b/drivers/gles3/rasterizer_gles3.cpp index 37e7256d76..19ef3d416c 100644 --- a/drivers/gles3/rasterizer_gles3.cpp +++ b/drivers/gles3/rasterizer_gles3.cpp @@ -62,6 +62,10 @@ #define _EXT_DEBUG_SEVERITY_LOW_ARB 0x9148 #define _EXT_DEBUG_OUTPUT 0x92E0 +#ifndef GL_FRAMEBUFFER_SRGB +#define GL_FRAMEBUFFER_SRGB 0x8DB9 +#endif + #ifndef GLAPIENTRY #if defined(WINDOWS_ENABLED) #define GLAPIENTRY APIENTRY @@ -345,6 +349,9 @@ RasterizerGLES3::RasterizerGLES3() { } } + // Disable OpenGL linear to sRGB conversion, because Godot will always do this conversion itself. + glDisable(GL_FRAMEBUFFER_SRGB); + // OpenGL needs to be initialized before initializing the Rasterizers config = memnew(GLES3::Config); utilities = memnew(GLES3::Utilities); diff --git a/drivers/gles3/shader_gles3.cpp b/drivers/gles3/shader_gles3.cpp index 4a15ed827a..5a0f394db0 100644 --- a/drivers/gles3/shader_gles3.cpp +++ b/drivers/gles3/shader_gles3.cpp @@ -698,7 +698,8 @@ void ShaderGLES3::_clear_version(Version *p_version) { void ShaderGLES3::_initialize_version(Version *p_version) { ERR_FAIL_COND(p_version->variants.size() > 0); - if (shader_cache_dir_valid && _load_from_cache(p_version)) { + bool use_cache = shader_cache_dir_valid && !(feedback_count > 0 && GLES3::Config::get_singleton()->disable_transform_feedback_shader_cache); + if (use_cache && _load_from_cache(p_version)) { return; } p_version->variants.reserve(variant_count); @@ -709,7 +710,7 @@ void ShaderGLES3::_initialize_version(Version *p_version) { _compile_specialization(spec, i, p_version, specialization_default_mask); p_version->variants[i].insert(specialization_default_mask, spec); } - if (shader_cache_dir_valid) { + if (use_cache) { _save_to_cache(p_version); } } diff --git a/drivers/gles3/shaders/skeleton.glsl b/drivers/gles3/shaders/skeleton.glsl index aad856a5a2..66befbc3b2 100644 --- a/drivers/gles3/shaders/skeleton.glsl +++ b/drivers/gles3/shaders/skeleton.glsl @@ -59,7 +59,7 @@ layout(location = 10) in highp uvec4 in_bone_attrib; layout(location = 11) in mediump vec4 in_weight_attrib; #endif -uniform mediump sampler2D skeleton_texture; // texunit:0 +uniform highp sampler2D skeleton_texture; // texunit:0 #endif /* clang-format on */ diff --git a/drivers/gles3/storage/config.cpp b/drivers/gles3/storage/config.cpp index a28b050bf8..2b3c19dbb8 100644 --- a/drivers/gles3/storage/config.cpp +++ b/drivers/gles3/storage/config.cpp @@ -218,6 +218,8 @@ Config::Config() { //https://github.com/godotengine/godot/issues/92662#issuecomment-2161199477 //disable_particles_workaround = false; } + } else if (rendering_device_name == "PowerVR Rogue GE8320") { + disable_transform_feedback_shader_cache = true; } } diff --git a/drivers/gles3/storage/config.h b/drivers/gles3/storage/config.h index 0c9f9bc275..ff72fc5b58 100644 --- a/drivers/gles3/storage/config.h +++ b/drivers/gles3/storage/config.h @@ -96,6 +96,9 @@ public: bool disable_particles_workaround = false; // set to 'true' to disable 'GPUParticles' bool flip_xy_workaround = false; + // PowerVR GE 8320 workaround + bool disable_transform_feedback_shader_cache = false; + #ifdef ANDROID_ENABLED PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC eglFramebufferTextureMultiviewOVR = nullptr; PFNGLTEXSTORAGE3DMULTISAMPLEPROC eglTexStorage3DMultisample = nullptr; diff --git a/drivers/unix/file_access_unix.cpp b/drivers/unix/file_access_unix.cpp index 210507c2c6..ea8b42b2e4 100644 --- a/drivers/unix/file_access_unix.cpp +++ b/drivers/unix/file_access_unix.cpp @@ -383,7 +383,7 @@ uint64_t FileAccessUnix::_get_modified_time(const String &p_file) { if (!err) { return status.st_mtime; } else { - print_verbose("Failed to get modified time for: " + p_file + ""); + WARN_PRINT("Failed to get modified time for: " + p_file); return 0; } } diff --git a/editor/import/3d/resource_importer_obj.cpp b/editor/import/3d/resource_importer_obj.cpp index 2fbf564ae8..f3770adcb7 100644 --- a/editor/import/3d/resource_importer_obj.cpp +++ b/editor/import/3d/resource_importer_obj.cpp @@ -202,7 +202,7 @@ static Error _parse_material_library(const String &p_path, HashMap<String, Ref<S return OK; } -static Error _parse_obj(const String &p_path, List<Ref<ImporterMesh>> &r_meshes, bool p_single_mesh, bool p_generate_tangents, bool p_optimize, Vector3 p_scale_mesh, Vector3 p_offset_mesh, bool p_disable_compression, List<String> *r_missing_deps) { +static Error _parse_obj(const String &p_path, List<Ref<ImporterMesh>> &r_meshes, bool p_single_mesh, bool p_generate_tangents, Vector3 p_scale_mesh, Vector3 p_offset_mesh, bool p_disable_compression, List<String> *r_missing_deps) { Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ); ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, vformat("Couldn't open OBJ file '%s', it may not exist or not be readable.", p_path)); @@ -512,7 +512,7 @@ static Error _parse_obj(const String &p_path, List<Ref<ImporterMesh>> &r_meshes, Node *EditorOBJImporter::import_scene(const String &p_path, uint32_t p_flags, const HashMap<StringName, Variant> &p_options, List<String> *r_missing_deps, Error *r_err) { List<Ref<ImporterMesh>> meshes; - Error err = _parse_obj(p_path, meshes, false, p_flags & IMPORT_GENERATE_TANGENT_ARRAYS, false, Vector3(1, 1, 1), Vector3(0, 0, 0), p_flags & IMPORT_FORCE_DISABLE_MESH_COMPRESSION, r_missing_deps); + Error err = _parse_obj(p_path, meshes, false, p_flags & IMPORT_GENERATE_TANGENT_ARRAYS, Vector3(1, 1, 1), Vector3(0, 0, 0), p_flags & IMPORT_FORCE_DISABLE_MESH_COMPRESSION, r_missing_deps); if (err != OK) { if (r_err) { @@ -583,7 +583,6 @@ void ResourceImporterOBJ::get_import_options(const String &p_path, List<ImportOp r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "generate_tangents"), true)); r_options->push_back(ImportOption(PropertyInfo(Variant::VECTOR3, "scale_mesh"), Vector3(1, 1, 1))); r_options->push_back(ImportOption(PropertyInfo(Variant::VECTOR3, "offset_mesh"), Vector3(0, 0, 0))); - r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "optimize_mesh"), true)); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "force_disable_mesh_compression"), false)); } @@ -594,7 +593,7 @@ bool ResourceImporterOBJ::get_option_visibility(const String &p_path, const Stri Error ResourceImporterOBJ::import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) { List<Ref<ImporterMesh>> meshes; - Error err = _parse_obj(p_source_file, meshes, true, p_options["generate_tangents"], p_options["optimize_mesh"], p_options["scale_mesh"], p_options["offset_mesh"], p_options["force_disable_mesh_compression"], nullptr); + Error err = _parse_obj(p_source_file, meshes, true, p_options["generate_tangents"], p_options["scale_mesh"], p_options["offset_mesh"], p_options["force_disable_mesh_compression"], nullptr); ERR_FAIL_COND_V(err != OK, err); ERR_FAIL_COND_V(meshes.size() != 1, ERR_BUG); diff --git a/editor/import/3d/resource_importer_scene.cpp b/editor/import/3d/resource_importer_scene.cpp index df4026195c..27b2af8f77 100644 --- a/editor/import/3d/resource_importer_scene.cpp +++ b/editor/import/3d/resource_importer_scene.cpp @@ -2013,6 +2013,7 @@ void ResourceImporterScene::get_internal_import_options(InternalImportCategory p r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "generate/lods", PROPERTY_HINT_ENUM, "Default,Enable,Disable"), 0)); r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "lods/normal_split_angle", PROPERTY_HINT_RANGE, "0,180,0.1,degrees"), 25.0f)); r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "lods/normal_merge_angle", PROPERTY_HINT_RANGE, "0,180,0.1,degrees"), 60.0f)); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "lods/raycast_normals", PROPERTY_HINT_NONE, ""), false)); } break; case INTERNAL_IMPORT_CATEGORY_MATERIAL: { r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "use_external/enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false)); @@ -2440,6 +2441,7 @@ Node *ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_ bool generate_lods = p_generate_lods; float split_angle = 25.0f; float merge_angle = 60.0f; + bool raycast_normals = false; bool create_shadow_meshes = p_create_shadow_meshes; bool bake_lightmaps = p_light_bake_mode == LIGHT_BAKE_STATIC_LIGHTMAPS; String save_to_file; @@ -2494,6 +2496,10 @@ Node *ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_ merge_angle = mesh_settings["lods/normal_merge_angle"]; } + if (mesh_settings.has("lods/raycast_normals")) { + raycast_normals = mesh_settings["lods/raycast_normals"]; + } + if (bool(mesh_settings.get("save_to_file/enabled", false))) { save_to_file = mesh_settings.get("save_to_file/path", String()); if (!save_to_file.is_resource_file()) { @@ -2540,7 +2546,7 @@ Node *ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_ if (generate_lods) { Array skin_pose_transform_array = _get_skinned_pose_transforms(src_mesh_node); - src_mesh_node->get_mesh()->generate_lods(merge_angle, split_angle, skin_pose_transform_array); + src_mesh_node->get_mesh()->generate_lods(merge_angle, split_angle, skin_pose_transform_array, raycast_normals); } if (create_shadow_meshes) { diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 59a4ac8075..80d2e85830 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -5001,14 +5001,24 @@ void Node3DEditorViewport::update_transform(bool p_shift) { } break; case TRANSFORM_ROTATE: { - Plane plane = Plane(_get_camera_normal(), _edit.center); + Plane plane; + if (camera->get_projection() == Camera3D::PROJECTION_PERSPECTIVE) { + Vector3 cam_to_obj = _edit.center - _get_camera_position(); + if (!cam_to_obj.is_zero_approx()) { + plane = Plane(cam_to_obj.normalized(), _edit.center); + } else { + plane = Plane(_get_camera_normal(), _edit.center); + } + } else { + plane = Plane(_get_camera_normal(), _edit.center); + } Vector3 local_axis; Vector3 global_axis; switch (_edit.plane) { case TRANSFORM_VIEW: // local_axis unused - global_axis = _get_camera_normal(); + global_axis = plane.normal; break; case TRANSFORM_X_AXIS: local_axis = Vector3(1, 0, 0); @@ -5039,7 +5049,7 @@ void Node3DEditorViewport::update_transform(bool p_shift) { break; } - static const float orthogonal_threshold = Math::cos(Math::deg_to_rad(87.0f)); + static const float orthogonal_threshold = Math::cos(Math::deg_to_rad(85.0f)); bool axis_is_orthogonal = ABS(plane.normal.dot(global_axis)) < orthogonal_threshold; double angle = 0.0f; @@ -7289,9 +7299,15 @@ void Node3DEditor::_init_grid() { bool orthogonal = camera->get_projection() == Camera3D::PROJECTION_ORTHOGONAL; - Vector<Color> grid_colors[3]; - Vector<Vector3> grid_points[3]; - Vector<Vector3> grid_normals[3]; + static LocalVector<Color> grid_colors[3]; + static LocalVector<Vector3> grid_points[3]; + static LocalVector<Vector3> grid_normals[3]; + + for (uint32_t n = 0; n < 3; n++) { + grid_colors[n].clear(); + grid_points[n].clear(); + grid_normals[n].clear(); + } Color primary_grid_color = EDITOR_GET("editors/3d/primary_grid_color"); Color secondary_grid_color = EDITOR_GET("editors/3d/secondary_grid_color"); @@ -7367,10 +7383,9 @@ void Node3DEditor::_init_grid() { grid_mat[c]->set_shader_parameter("grid_size", grid_fade_size); grid_mat[c]->set_shader_parameter("orthogonal", orthogonal); - // Cache these so we don't have to re-access memory. - Vector<Vector3> &ref_grid = grid_points[c]; - Vector<Vector3> &ref_grid_normals = grid_normals[c]; - Vector<Color> &ref_grid_colors = grid_colors[c]; + LocalVector<Vector3> &ref_grid = grid_points[c]; + LocalVector<Vector3> &ref_grid_normals = grid_normals[c]; + LocalVector<Color> &ref_grid_colors = grid_colors[c]; // Count our elements same as code below it. int expected_size = 0; @@ -7415,12 +7430,12 @@ void Node3DEditor::_init_grid() { line_end[a] = position_a; line_bgn[b] = bgn_b; line_end[b] = end_b; - ref_grid.set(idx, line_bgn); - ref_grid.set(idx + 1, line_end); - ref_grid_colors.set(idx, line_color); - ref_grid_colors.set(idx + 1, line_color); - ref_grid_normals.set(idx, normal); - ref_grid_normals.set(idx + 1, normal); + ref_grid[idx] = line_bgn; + ref_grid[idx + 1] = line_end; + ref_grid_colors[idx] = line_color; + ref_grid_colors[idx + 1] = line_color; + ref_grid_normals[idx] = normal; + ref_grid_normals[idx + 1] = normal; idx += 2; } @@ -7431,12 +7446,12 @@ void Node3DEditor::_init_grid() { line_end[b] = position_b; line_bgn[a] = bgn_a; line_end[a] = end_a; - ref_grid.set(idx, line_bgn); - ref_grid.set(idx + 1, line_end); - ref_grid_colors.set(idx, line_color); - ref_grid_colors.set(idx + 1, line_color); - ref_grid_normals.set(idx, normal); - ref_grid_normals.set(idx + 1, normal); + ref_grid[idx] = line_bgn; + ref_grid[idx + 1] = line_end; + ref_grid_colors[idx] = line_color; + ref_grid_colors[idx + 1] = line_color; + ref_grid_normals[idx] = normal; + ref_grid_normals[idx + 1] = normal; idx += 2; } } @@ -7445,9 +7460,9 @@ void Node3DEditor::_init_grid() { grid[c] = RenderingServer::get_singleton()->mesh_create(); Array d; d.resize(RS::ARRAY_MAX); - d[RenderingServer::ARRAY_VERTEX] = grid_points[c]; - d[RenderingServer::ARRAY_COLOR] = grid_colors[c]; - d[RenderingServer::ARRAY_NORMAL] = grid_normals[c]; + d[RenderingServer::ARRAY_VERTEX] = (Vector<Vector3>)grid_points[c]; + d[RenderingServer::ARRAY_COLOR] = (Vector<Color>)grid_colors[c]; + d[RenderingServer::ARRAY_NORMAL] = (Vector<Vector3>)grid_normals[c]; RenderingServer::get_singleton()->mesh_add_surface_from_arrays(grid[c], RenderingServer::PRIMITIVE_LINES, d); RenderingServer::get_singleton()->mesh_surface_set_material(grid[c], 0, grid_mat[c]->get_rid()); grid_instance[c] = RenderingServer::get_singleton()->instance_create2(grid[c], get_tree()->get_root()->get_world_3d()->get_scenario()); diff --git a/main/main.cpp b/main/main.cpp index 105b18cdda..d0b58d9abd 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -2456,6 +2456,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph GLOBAL_DEF("debug/settings/stdout/print_fps", false); GLOBAL_DEF("debug/settings/stdout/print_gpu_profile", false); GLOBAL_DEF("debug/settings/stdout/verbose_stdout", false); + GLOBAL_DEF("debug/settings/physics_interpolation/enable_warnings", true); if (!OS::get_singleton()->_verbose_stdout) { // Not manually overridden. OS::get_singleton()->_verbose_stdout = GLOBAL_GET("debug/settings/stdout/verbose_stdout"); @@ -4125,16 +4126,16 @@ bool Main::iteration() { uint64_t physics_begin = OS::get_singleton()->get_ticks_usec(); -#ifndef _3D_DISABLED - PhysicsServer3D::get_singleton()->sync(); - PhysicsServer3D::get_singleton()->flush_queries(); -#endif // _3D_DISABLED - // Prepare the fixed timestep interpolated nodes BEFORE they are updated // by the physics server, otherwise the current and previous transforms // may be the same, and no interpolation takes place. OS::get_singleton()->get_main_loop()->iteration_prepare(); +#ifndef _3D_DISABLED + PhysicsServer3D::get_singleton()->sync(); + PhysicsServer3D::get_singleton()->flush_queries(); +#endif // _3D_DISABLED + PhysicsServer2D::get_singleton()->sync(); PhysicsServer2D::get_singleton()->flush_queries(); @@ -4144,6 +4145,7 @@ bool Main::iteration() { #endif // _3D_DISABLED PhysicsServer2D::get_singleton()->end_sync(); + Engine::get_singleton()->_in_physics = false; exit = true; break; } @@ -4167,6 +4169,8 @@ bool Main::iteration() { message_queue->flush(); + OS::get_singleton()->get_main_loop()->iteration_end(); + physics_process_ticks = MAX(physics_process_ticks, OS::get_singleton()->get_ticks_usec() - physics_begin); // keep the largest one for reference physics_process_max = MAX(OS::get_singleton()->get_ticks_usec() - physics_begin, physics_process_max); diff --git a/main/main_timer_sync.cpp b/main/main_timer_sync.cpp index d358d9fa93..569930d427 100644 --- a/main/main_timer_sync.cpp +++ b/main/main_timer_sync.cpp @@ -299,17 +299,6 @@ int64_t MainTimerSync::DeltaSmoother::smooth_delta(int64_t p_delta) { // before advance_core considers changing the physics_steps return from // the typical values as defined by typical_physics_steps double MainTimerSync::get_physics_jitter_fix() { - // Turn off jitter fix when using fixed timestep interpolation. - // Note this shouldn't be on UNTIL 3d interpolation is implemented, - // otherwise we will get people making 3d games with the physics_interpolation - // set to on getting jitter fix disabled unexpectedly. -#if 0 - if (Engine::get_singleton()->is_physics_interpolation_enabled()) { - // Would be better to write a simple bypass for jitter fix but this will do to get started. - return 0.0; - } -#endif - return Engine::get_singleton()->get_physics_jitter_fix(); } diff --git a/misc/extension_api_validation/4.3-stable.expected b/misc/extension_api_validation/4.3-stable.expected index a2112d71f2..24c7702090 100644 --- a/misc/extension_api_validation/4.3-stable.expected +++ b/misc/extension_api_validation/4.3-stable.expected @@ -7,3 +7,10 @@ should instead be used to justify these changes and describe how users should wo Add new entries at the end of the file. ## Changes between 4.3-stable and 4.4-stable + +GH-95374 +-------- +Validate extension JSON: Error: Field 'classes/ShapeCast2D/properties/collision_result': getter changed value in new API, from "_get_collision_result" to &"get_collision_result". +Validate extension JSON: Error: Field 'classes/ShapeCast3D/properties/collision_result': getter changed value in new API, from "_get_collision_result" to &"get_collision_result". + +These getters have been renamed to expose them. GDExtension language bindings couldn't have exposed these properties before. diff --git a/modules/hdr/image_loader_hdr.cpp b/modules/hdr/image_loader_hdr.cpp index c49c62a08b..ba59bb25ee 100644 --- a/modules/hdr/image_loader_hdr.cpp +++ b/modules/hdr/image_loader_hdr.cpp @@ -68,9 +68,11 @@ Error ImageLoaderHDR::load_image(Ref<Image> p_image, Ref<FileAccess> f, BitField imgdata.resize(height * width * (int)sizeof(uint32_t)); { - uint8_t *w = imgdata.ptrw(); + uint8_t *ptr = imgdata.ptrw(); - uint8_t *ptr = (uint8_t *)w; + Vector<uint8_t> temp_read_data; + temp_read_data.resize(128); + uint8_t *temp_read_ptr = temp_read_data.ptrw(); if (width < 8 || width >= 32768) { // Read flat data @@ -113,8 +115,9 @@ Error ImageLoaderHDR::load_image(Ref<Image> p_image, Ref<FileAccess> f, BitField } } else { // Dump + f->get_buffer(temp_read_ptr, count); for (int z = 0; z < count; ++z) { - ptr[(j * width + i++) * 4 + k] = f->get_8(); + ptr[(j * width + i++) * 4 + k] = temp_read_ptr[z]; } } } @@ -122,20 +125,27 @@ Error ImageLoaderHDR::load_image(Ref<Image> p_image, Ref<FileAccess> f, BitField } } + const bool force_linear = p_flags & FLAG_FORCE_LINEAR; + //convert for (int i = 0; i < width * height; i++) { - float exp = pow(2.0f, ptr[3] - 128.0f); + int e = ptr[3] - 128; + + if (force_linear || (e < -15 || e > 15)) { + float exp = pow(2.0f, e); + Color c(ptr[0] * exp / 255.0, ptr[1] * exp / 255.0, ptr[2] * exp / 255.0); - Color c( - ptr[0] * exp / 255.0, - ptr[1] * exp / 255.0, - ptr[2] * exp / 255.0); + if (force_linear) { + c = c.srgb_to_linear(); + } - if (p_flags & FLAG_FORCE_LINEAR) { - c = c.srgb_to_linear(); + *(uint32_t *)ptr = c.to_rgbe9995(); + } else { + // https://github.com/george-steel/rgbe-rs/blob/e7cc33b7f42b4eb3272c166dac75385e48687c92/src/types.rs#L123-L129 + uint32_t e5 = (uint32_t)(e + 15); + *(uint32_t *)ptr = ((e5 << 27) | ((uint32_t)ptr[2] << 19) | ((uint32_t)ptr[1] << 10) | ((uint32_t)ptr[0] << 1)); } - *(uint32_t *)ptr = c.to_rgbe9995(); ptr += 4; } } diff --git a/modules/hdr/image_loader_hdr.h b/modules/hdr/image_loader_hdr.h index 9821db059e..0a8e91fb9e 100644 --- a/modules/hdr/image_loader_hdr.h +++ b/modules/hdr/image_loader_hdr.h @@ -37,6 +37,7 @@ class ImageLoaderHDR : public ImageFormatLoader { public: virtual Error load_image(Ref<Image> p_image, Ref<FileAccess> f, BitField<ImageFormatLoader::LoaderFlags> p_flags, float p_scale); virtual void get_recognized_extensions(List<String> *p_extensions) const; + ImageLoaderHDR(); }; diff --git a/modules/openxr/extensions/platform/openxr_opengl_extension.cpp b/modules/openxr/extensions/platform/openxr_opengl_extension.cpp index d92084a220..de4a9e4b8e 100644 --- a/modules/openxr/extensions/platform/openxr_opengl_extension.cpp +++ b/modules/openxr/extensions/platform/openxr_opengl_extension.cpp @@ -56,11 +56,6 @@ // feature off. // See: https://registry.khronos.org/OpenGL/extensions/EXT/EXT_sRGB_write_control.txt -// On OpenGLES this is not defined in our standard headers.. -#ifndef GL_FRAMEBUFFER_SRGB -#define GL_FRAMEBUFFER_SRGB 0x8DB9 -#endif - HashMap<String, bool *> OpenXROpenGLExtension::get_requested_extensions() { HashMap<String, bool *> request_extensions; @@ -196,23 +191,6 @@ void OpenXROpenGLExtension::get_usable_depth_formats(Vector<int64_t> &p_usable_d p_usable_depth_formats.push_back(GL_DEPTH_COMPONENT24); } -void OpenXROpenGLExtension::on_pre_draw_viewport(RID p_render_target) { - if (srgb_ext_is_available) { - hw_linear_to_srgb_is_enabled = glIsEnabled(GL_FRAMEBUFFER_SRGB); - if (hw_linear_to_srgb_is_enabled) { - // Disable this. - glDisable(GL_FRAMEBUFFER_SRGB); - } - } -} - -void OpenXROpenGLExtension::on_post_draw_viewport(RID p_render_target) { - if (srgb_ext_is_available && hw_linear_to_srgb_is_enabled) { - // Re-enable this. - glEnable(GL_FRAMEBUFFER_SRGB); - } -} - bool OpenXROpenGLExtension::get_swapchain_image_data(XrSwapchain p_swapchain, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, void **r_swapchain_graphics_data) { GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton(); ERR_FAIL_NULL_V(texture_storage, false); diff --git a/modules/openxr/extensions/platform/openxr_opengl_extension.h b/modules/openxr/extensions/platform/openxr_opengl_extension.h index a3052d3f53..8da3ca48f4 100644 --- a/modules/openxr/extensions/platform/openxr_opengl_extension.h +++ b/modules/openxr/extensions/platform/openxr_opengl_extension.h @@ -49,9 +49,6 @@ public: virtual void on_instance_created(const XrInstance p_instance) override; virtual void *set_session_create_and_get_next_pointer(void *p_next_pointer) override; - virtual void on_pre_draw_viewport(RID p_render_target) override; - virtual void on_post_draw_viewport(RID p_render_target) override; - virtual void get_usable_swapchain_formats(Vector<int64_t> &p_usable_swap_chains) override; virtual void get_usable_depth_formats(Vector<int64_t> &p_usable_swap_chains) override; virtual String get_swapchain_format_name(int64_t p_swapchain_format) const override; @@ -76,9 +73,6 @@ private: Vector<RID> texture_rids; }; - bool srgb_ext_is_available = true; - bool hw_linear_to_srgb_is_enabled = false; - bool check_graphics_api_support(XrVersion p_desired_version); #ifdef ANDROID_ENABLED diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 5169b9417f..689360aef6 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -3270,18 +3270,17 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP } List<String> copy_args; - String copy_command; - if (export_format == EXPORT_FORMAT_AAB) { - copy_command = vformat("copyAndRename%sAab", build_type); - } else if (export_format == EXPORT_FORMAT_APK) { - copy_command = vformat("copyAndRename%sApk", build_type); - } - + String copy_command = "copyAndRenameBinary"; copy_args.push_back(copy_command); copy_args.push_back("-p"); // argument to specify the start directory. copy_args.push_back(build_path); // start directory. + copy_args.push_back("-Pexport_build_type=" + build_type.to_lower()); + + String export_format_arg = export_format == EXPORT_FORMAT_AAB ? "aab" : "apk"; + copy_args.push_back("-Pexport_format=" + export_format_arg); + String export_filename = p_path.get_file(); String export_path = p_path.get_base_dir(); if (export_path.is_relative_path()) { diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle index 01d5d9ef92..05b4f379b3 100644 --- a/platform/android/java/app/build.gradle +++ b/platform/android/java/app/build.gradle @@ -211,70 +211,24 @@ android { } } -task copyAndRenameDebugApk(type: Copy) { +task copyAndRenameBinary(type: Copy) { // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files // and directories. Otherwise this check may cause permissions access failures on Windows // machines. doNotTrackState("No need for up-to-date checks for the copy-and-rename operation") - from "$buildDir/outputs/apk/debug/android_debug.apk" - into getExportPath() - rename "android_debug.apk", getExportFilename() -} + String exportPath = getExportPath() + String exportFilename = getExportFilename() + String exportBuildType = getExportBuildType() + String exportFormat = getExportFormat() -task copyAndRenameDevApk(type: Copy) { - // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files - // and directories. Otherwise this check may cause permissions access failures on Windows - // machines. - doNotTrackState("No need for up-to-date checks for the copy-and-rename operation") - - from "$buildDir/outputs/apk/dev/android_dev.apk" - into getExportPath() - rename "android_dev.apk", getExportFilename() -} - -task copyAndRenameReleaseApk(type: Copy) { - // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files - // and directories. Otherwise this check may cause permissions access failures on Windows - // machines. - doNotTrackState("No need for up-to-date checks for the copy-and-rename operation") - - from "$buildDir/outputs/apk/release/android_release.apk" - into getExportPath() - rename "android_release.apk", getExportFilename() -} - -task copyAndRenameDebugAab(type: Copy) { - // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files - // and directories. Otherwise this check may cause permissions access failures on Windows - // machines. - doNotTrackState("No need for up-to-date checks for the copy-and-rename operation") - - from "$buildDir/outputs/bundle/debug/build-debug.aab" - into getExportPath() - rename "build-debug.aab", getExportFilename() -} - -task copyAndRenameDevAab(type: Copy) { - // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files - // and directories. Otherwise this check may cause permissions access failures on Windows - // machines. - doNotTrackState("No need for up-to-date checks for the copy-and-rename operation") - - from "$buildDir/outputs/bundle/dev/build-dev.aab" - into getExportPath() - rename "build-dev.aab", getExportFilename() -} - -task copyAndRenameReleaseAab(type: Copy) { - // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files - // and directories. Otherwise this check may cause permissions access failures on Windows - // machines. - doNotTrackState("No need for up-to-date checks for the copy-and-rename operation") + boolean isAab = exportFormat == "aab" + String sourceFilepath = isAab ? "$buildDir/outputs/bundle/$exportBuildType/build-${exportBuildType}.aab" : "$buildDir/outputs/apk/$exportBuildType/android_${exportBuildType}.apk" + String sourceFilename = isAab ? "build-${exportBuildType}.aab" : "android_${exportBuildType}.apk" - from "$buildDir/outputs/bundle/release/build-release.aab" - into getExportPath() - rename "build-release.aab", getExportFilename() + from sourceFilepath + into exportPath + rename sourceFilename, exportFilename } /** diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle index eb9ad9de05..611a9c4a40 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -224,6 +224,22 @@ ext.getExportFilename = { return exportFilename } +ext.getExportBuildType = { + String exportBuildType = project.hasProperty("export_build_type") ? project.property("export_build_type") : "" + if (exportBuildType == null || exportBuildType.isEmpty()) { + exportBuildType = "debug" + } + return exportBuildType +} + +ext.getExportFormat = { + String exportFormat = project.hasProperty("export_format") ? project.property("export_format") : "" + if (exportFormat == null || exportFormat.isEmpty()) { + exportFormat = "apk" + } + return exportFormat +} + /** * Parse the project properties for the 'plugins_maven_repos' property and return the list * of maven repos. diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle index f5555289fd..771bda6948 100644 --- a/platform/android/java/build.gradle +++ b/platform/android/java/build.gradle @@ -35,116 +35,17 @@ ext { // `./gradlew generateGodotTemplates` build command instead after running the `scons` command(s). // The {selectedAbis} values must be from the {supportedAbis} values. selectedAbis = ["arm64"] -} -def rootDir = "../../.." -def binDir = "$rootDir/bin/" -def androidEditorBuildsDir = "$binDir/android_editor_builds/" + rootDir = "../../.." + binDir = "$rootDir/bin/" + androidEditorBuildsDir = "$binDir/android_editor_builds/" +} def getSconsTaskName(String flavor, String buildType, String abi) { return "compileGodotNativeLibs" + flavor.capitalize() + buildType.capitalize() + abi.capitalize() } /** - * Copy the generated 'android_debug.apk' binary template into the Godot bin directory. - * Depends on the app build task to ensure the binary is generated prior to copying. - */ -task copyDebugBinaryToBin(type: Copy) { - dependsOn ':app:assembleDebug' - from('app/build/outputs/apk/debug') - into(binDir) - include('android_debug.apk') -} - -/** - * Copy the generated 'android_dev.apk' binary template into the Godot bin directory. - * Depends on the app build task to ensure the binary is generated prior to copying. - */ -task copyDevBinaryToBin(type: Copy) { - dependsOn ':app:assembleDev' - from('app/build/outputs/apk/dev') - into(binDir) - include('android_dev.apk') -} - -/** - * Copy the generated 'android_release.apk' binary template into the Godot bin directory. - * Depends on the app build task to ensure the binary is generated prior to copying. - */ -task copyReleaseBinaryToBin(type: Copy) { - dependsOn ':app:assembleRelease' - from('app/build/outputs/apk/release') - into(binDir) - include('android_release.apk') -} - -/** - * Copy the Godot android library archive debug file into the app module debug libs directory. - * Depends on the library build task to ensure the AAR file is generated prior to copying. - */ -task copyDebugAARToAppModule(type: Copy) { - dependsOn ':lib:assembleTemplateDebug' - from('lib/build/outputs/aar') - into('app/libs/debug') - include('godot-lib.template_debug.aar') -} - -/** - * Copy the Godot android library archive debug file into the root bin directory. - * Depends on the library build task to ensure the AAR file is generated prior to copying. - */ -task copyDebugAARToBin(type: Copy) { - dependsOn ':lib:assembleTemplateDebug' - from('lib/build/outputs/aar') - into(binDir) - include('godot-lib.template_debug.aar') -} - -/** - * Copy the Godot android library archive dev file into the app module dev libs directory. - * Depends on the library build task to ensure the AAR file is generated prior to copying. - */ -task copyDevAARToAppModule(type: Copy) { - dependsOn ':lib:assembleTemplateDev' - from('lib/build/outputs/aar') - into('app/libs/dev') - include('godot-lib.template_debug.dev.aar') -} - -/** - * Copy the Godot android library archive dev file into the root bin directory. - * Depends on the library build task to ensure the AAR file is generated prior to copying. - */ -task copyDevAARToBin(type: Copy) { - dependsOn ':lib:assembleTemplateDev' - from('lib/build/outputs/aar') - into(binDir) - include('godot-lib.template_debug.dev.aar') -} - -/** - * Copy the Godot android library archive release file into the app module release libs directory. - * Depends on the library build task to ensure the AAR file is generated prior to copying. - */ -task copyReleaseAARToAppModule(type: Copy) { - dependsOn ':lib:assembleTemplateRelease' - from('lib/build/outputs/aar') - into('app/libs/release') - include('godot-lib.template_release.aar') -} - -/** - * Copy the Godot android library archive release file into the root bin directory. - * Depends on the library build task to ensure the AAR file is generated prior to copying. - */ -task copyReleaseAARToBin(type: Copy) { - dependsOn ':lib:assembleTemplateRelease' - from('lib/build/outputs/aar') - into(binDir) - include('godot-lib.template_release.aar') -} - -/** * Generate Godot gradle build template by zipping the source files from the app directory, as well * as the AAR files generated by 'copyDebugAAR', 'copyDevAAR' and 'copyReleaseAAR'. * The zip file also includes some gradle tools to enable gradle builds from the Godot Editor. @@ -197,7 +98,7 @@ def generateBuildTasks(String flavor = "template") { throw new GradleException("Invalid build flavor: $flavor") } - def tasks = [] + def buildTasks = [] // Only build the apks and aar files for which we have native shared libraries unless we intend // to run the scons build tasks. @@ -206,72 +107,93 @@ def generateBuildTasks(String flavor = "template") { String libsDir = isTemplate ? "lib/libs/" : "lib/libs/tools/" for (String target : supportedFlavorsBuildTypes[flavor]) { File targetLibs = new File(libsDir + target) + + String targetSuffix = target + if (target == "dev") { + targetSuffix = "debug.dev" + } + if (!excludeSconsBuildTasks || (targetLibs != null && targetLibs.isDirectory() && targetLibs.listFiles() != null && targetLibs.listFiles().length > 0)) { + String capitalizedTarget = target.capitalize() if (isTemplate) { - // Copy the generated aar library files to the build directory. - tasks += "copy${capitalizedTarget}AARToAppModule" - // Copy the generated aar library files to the bin directory. - tasks += "copy${capitalizedTarget}AARToBin" - // Copy the prebuilt binary templates to the bin directory. - tasks += "copy${capitalizedTarget}BinaryToBin" + // Copy the Godot android library archive file into the app module libs directory. + // Depends on the library build task to ensure the AAR file is generated prior to copying. + String copyAARTaskName = "copy${capitalizedTarget}AARToAppModule" + if (tasks.findByName(copyAARTaskName) != null) { + buildTasks += tasks.getByName(copyAARTaskName) + } else { + buildTasks += tasks.create(name: copyAARTaskName, type: Copy) { + dependsOn ":lib:assembleTemplate${capitalizedTarget}" + from('lib/build/outputs/aar') + include("godot-lib.template_${targetSuffix}.aar") + into("app/libs/${target}") + } + } + + // Copy the Godot android library archive file into the root bin directory. + // Depends on the library build task to ensure the AAR file is generated prior to copying. + String copyAARToBinTaskName = "copy${capitalizedTarget}AARToBin" + if (tasks.findByName(copyAARToBinTaskName) != null) { + buildTasks += tasks.getByName(copyAARToBinTaskName) + } else { + buildTasks += tasks.create(name: copyAARToBinTaskName, type: Copy) { + dependsOn ":lib:assembleTemplate${capitalizedTarget}" + from('lib/build/outputs/aar') + include("godot-lib.template_${targetSuffix}.aar") + into(binDir) + } + } + + // Copy the generated binary template into the Godot bin directory. + // Depends on the app build task to ensure the binary is generated prior to copying. + String copyBinaryTaskName = "copy${capitalizedTarget}BinaryToBin" + if (tasks.findByName(copyBinaryTaskName) != null) { + buildTasks += tasks.getByName(copyBinaryTaskName) + } else { + buildTasks += tasks.create(name: copyBinaryTaskName, type: Copy) { + dependsOn ":app:assemble${capitalizedTarget}" + from("app/build/outputs/apk/${target}") + into(binDir) + include("android_${target}.apk") + } + } } else { // Copy the generated editor apk to the bin directory. - tasks += "copyEditor${capitalizedTarget}ApkToBin" + String copyEditorApkTaskName = "copyEditor${capitalizedTarget}ApkToBin" + if (tasks.findByName(copyEditorApkTaskName) != null) { + buildTasks += tasks.getByName(copyEditorApkTaskName) + } else { + buildTasks += tasks.create(name: copyEditorApkTaskName, type: Copy) { + dependsOn ":editor:assemble${capitalizedTarget}" + from("editor/build/outputs/apk/${target}") + into(androidEditorBuildsDir) + include("android_editor-${target}*.apk") + } + } + // Copy the generated editor aab to the bin directory. - tasks += "copyEditor${capitalizedTarget}AabToBin" + String copyEditorAabTaskName = "copyEditor${capitalizedTarget}AabToBin" + if (tasks.findByName(copyEditorAabTaskName) != null) { + buildTasks += tasks.getByName(copyEditorAabTaskName) + } else { + buildTasks += tasks.create(name: copyEditorAabTaskName, type: Copy) { + dependsOn ":editor:bundle${capitalizedTarget}" + from("editor/build/outputs/bundle/${target}") + into(androidEditorBuildsDir) + include("android_editor-${target}*.aab") + } + } } } else { logger.lifecycle("No native shared libs for target $target. Skipping build.") } } - return tasks -} - -task copyEditorReleaseApkToBin(type: Copy) { - dependsOn ':editor:assembleRelease' - from('editor/build/outputs/apk/release') - into(androidEditorBuildsDir) - include('android_editor-release*.apk') -} - -task copyEditorReleaseAabToBin(type: Copy) { - dependsOn ':editor:bundleRelease' - from('editor/build/outputs/bundle/release') - into(androidEditorBuildsDir) - include('android_editor-release*.aab') -} - -task copyEditorDebugApkToBin(type: Copy) { - dependsOn ':editor:assembleDebug' - from('editor/build/outputs/apk/debug') - into(androidEditorBuildsDir) - include('android_editor-debug.apk') -} - -task copyEditorDebugAabToBin(type: Copy) { - dependsOn ':editor:bundleDebug' - from('editor/build/outputs/bundle/debug') - into(androidEditorBuildsDir) - include('android_editor-debug.aab') -} - -task copyEditorDevApkToBin(type: Copy) { - dependsOn ':editor:assembleDev' - from('editor/build/outputs/apk/dev') - into(androidEditorBuildsDir) - include('android_editor-dev.apk') -} - -task copyEditorDevAabToBin(type: Copy) { - dependsOn ':editor:bundleDev' - from('editor/build/outputs/bundle/dev') - into(androidEditorBuildsDir) - include('android_editor-dev.aab') + return buildTasks } /** diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 9025f53f42..40b265785f 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -379,6 +379,8 @@ Error OS_Windows::open_dynamic_library(const String &p_path, void *&p_library_ha //this code exists so gdextension can load .dll files from within the executable path path = get_executable_path().get_base_dir().path_join(p_path.get_file()); } + // Path to load from may be different from original if we make copies. + String load_path = path; ERR_FAIL_COND_V(!FileAccess::exists(path), ERR_FILE_NOT_FOUND); @@ -387,25 +389,22 @@ Error OS_Windows::open_dynamic_library(const String &p_path, void *&p_library_ha if (p_data != nullptr && p_data->generate_temp_files) { // Copy the file to the same directory as the original with a prefix in the name. // This is so relative path to dependencies are satisfied. - String copy_path = path.get_base_dir().path_join("~" + path.get_file()); + load_path = path.get_base_dir().path_join("~" + path.get_file()); // If there's a left-over copy (possibly from a crash) then delete it first. - if (FileAccess::exists(copy_path)) { - DirAccess::remove_absolute(copy_path); + if (FileAccess::exists(load_path)) { + DirAccess::remove_absolute(load_path); } - Error copy_err = DirAccess::copy_absolute(path, copy_path); + Error copy_err = DirAccess::copy_absolute(path, load_path); if (copy_err) { ERR_PRINT("Error copying library: " + path); return ERR_CANT_CREATE; } - FileAccess::set_hidden_attribute(copy_path, true); + FileAccess::set_hidden_attribute(load_path, true); - // Save the copied path so it can be deleted later. - path = copy_path; - - Error pdb_err = WindowsUtils::copy_and_rename_pdb(path); + Error pdb_err = WindowsUtils::copy_and_rename_pdb(load_path); if (pdb_err != OK && pdb_err != ERR_SKIP) { WARN_PRINT(vformat("Failed to rename the PDB file. The original PDB file for '%s' will be loaded.", path)); } @@ -421,21 +420,21 @@ Error OS_Windows::open_dynamic_library(const String &p_path, void *&p_library_ha DLL_DIRECTORY_COOKIE cookie = nullptr; if (p_data != nullptr && p_data->also_set_library_path && has_dll_directory_api) { - cookie = add_dll_directory((LPCWSTR)(path.get_base_dir().utf16().get_data())); + cookie = add_dll_directory((LPCWSTR)(load_path.get_base_dir().utf16().get_data())); } - p_library_handle = (void *)LoadLibraryExW((LPCWSTR)(path.utf16().get_data()), nullptr, (p_data != nullptr && p_data->also_set_library_path && has_dll_directory_api) ? LOAD_LIBRARY_SEARCH_DEFAULT_DIRS : 0); + p_library_handle = (void *)LoadLibraryExW((LPCWSTR)(load_path.utf16().get_data()), nullptr, (p_data != nullptr && p_data->also_set_library_path && has_dll_directory_api) ? LOAD_LIBRARY_SEARCH_DEFAULT_DIRS : 0); if (!p_library_handle) { if (p_data != nullptr && p_data->generate_temp_files) { - DirAccess::remove_absolute(path); + DirAccess::remove_absolute(load_path); } #ifdef DEBUG_ENABLED DWORD err_code = GetLastError(); - HashSet<String> checekd_libs; + HashSet<String> checked_libs; HashSet<String> missing_libs; - debug_dynamic_library_check_dependencies(path, path, checekd_libs, missing_libs); + debug_dynamic_library_check_dependencies(load_path, load_path, checked_libs, missing_libs); if (!missing_libs.is_empty()) { String missing; for (const String &E : missing_libs) { @@ -464,7 +463,8 @@ Error OS_Windows::open_dynamic_library(const String &p_path, void *&p_library_ha } if (p_data != nullptr && p_data->generate_temp_files) { - temp_libraries[p_library_handle] = path; + // Save the copied path so it can be deleted later. + temp_libraries[p_library_handle] = load_path; } return OK; diff --git a/scene/2d/gpu_particles_2d.cpp b/scene/2d/gpu_particles_2d.cpp index 1d3f1ceada..bfbdb49f22 100644 --- a/scene/2d/gpu_particles_2d.cpp +++ b/scene/2d/gpu_particles_2d.cpp @@ -688,6 +688,7 @@ void GPUParticles2D::_notification(int p_what) { RS::get_singleton()->particles_set_speed_scale(particles, 0); } set_process_internal(true); + set_physics_process_internal(true); previous_position = get_global_position(); } break; @@ -711,15 +712,6 @@ void GPUParticles2D::_notification(int p_what) { } break; case NOTIFICATION_INTERNAL_PROCESS: { - const Vector3 velocity = Vector3((get_global_position() - previous_position).x, (get_global_position() - previous_position).y, 0.0) / - get_process_delta_time(); - - if (velocity != previous_velocity) { - RS::get_singleton()->particles_set_emitter_velocity(particles, velocity); - previous_velocity = velocity; - } - previous_position = get_global_position(); - if (one_shot) { time += get_process_delta_time(); if (time > emission_time) { @@ -739,6 +731,19 @@ void GPUParticles2D::_notification(int p_what) { } } } break; + + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + // Update velocity in physics process, so that velocity calculations remain correct + // if the physics tick rate is lower than the rendered framerate (especially without physics interpolation). + const Vector3 velocity = Vector3((get_global_position() - previous_position).x, (get_global_position() - previous_position).y, 0.0) / + get_physics_process_delta_time(); + + if (velocity != previous_velocity) { + RS::get_singleton()->particles_set_emitter_velocity(particles, velocity); + previous_velocity = velocity; + } + previous_position = get_global_position(); + } break; } } diff --git a/scene/2d/physics/shape_cast_2d.cpp b/scene/2d/physics/shape_cast_2d.cpp index 00be84b622..b92978bcad 100644 --- a/scene/2d/physics/shape_cast_2d.cpp +++ b/scene/2d/physics/shape_cast_2d.cpp @@ -382,7 +382,7 @@ bool ShapeCast2D::is_collide_with_bodies_enabled() const { return collide_with_bodies; } -Array ShapeCast2D::_get_collision_result() const { +Array ShapeCast2D::get_collision_result() const { Array ret; for (int i = 0; i < result.size(); ++i) { @@ -464,7 +464,7 @@ void ShapeCast2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_collide_with_bodies", "enable"), &ShapeCast2D::set_collide_with_bodies); ClassDB::bind_method(D_METHOD("is_collide_with_bodies_enabled"), &ShapeCast2D::is_collide_with_bodies_enabled); - ClassDB::bind_method(D_METHOD("_get_collision_result"), &ShapeCast2D::_get_collision_result); + ClassDB::bind_method(D_METHOD("get_collision_result"), &ShapeCast2D::get_collision_result); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shape", PROPERTY_HINT_RESOURCE_TYPE, "Shape2D"), "set_shape", "get_shape"); @@ -473,7 +473,7 @@ void ShapeCast2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "margin", PROPERTY_HINT_RANGE, "0,100,0.01,suffix:px"), "set_margin", "get_margin"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_results"), "set_max_results", "get_max_results"); ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_collision_mask", "get_collision_mask"); - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "collision_result", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "", "_get_collision_result"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "collision_result", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "", "get_collision_result"); ADD_GROUP("Collide With", "collide_with"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collide_with_areas", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collide_with_areas", "is_collide_with_areas_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collide_with_bodies", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collide_with_bodies", "is_collide_with_bodies_enabled"); diff --git a/scene/2d/physics/shape_cast_2d.h b/scene/2d/physics/shape_cast_2d.h index 6b8fd5b798..d866dd4edb 100644 --- a/scene/2d/physics/shape_cast_2d.h +++ b/scene/2d/physics/shape_cast_2d.h @@ -60,7 +60,6 @@ class ShapeCast2D : public Node2D { real_t collision_safe_fraction = 1.0; real_t collision_unsafe_fraction = 1.0; - Array _get_collision_result() const; void _shape_changed(); protected: @@ -102,6 +101,7 @@ public: void force_shapecast_update(); bool is_colliding() const; + Array get_collision_result() const; int get_collision_count() const; Object *get_collider(int p_idx) const; RID get_collider_rid(int p_idx) const; diff --git a/scene/3d/camera_3d.cpp b/scene/3d/camera_3d.cpp index 8515aacba7..c70fa3ca2e 100644 --- a/scene/3d/camera_3d.cpp +++ b/scene/3d/camera_3d.cpp @@ -31,7 +31,9 @@ #include "camera_3d.h" #include "core/math/projection.h" +#include "core/math/transform_interpolator.h" #include "scene/main/viewport.h" +#include "servers/rendering/rendering_server_constants.h" void Camera3D::_update_audio_listener_state() { } @@ -88,7 +90,16 @@ void Camera3D::_update_camera() { return; } - RenderingServer::get_singleton()->camera_set_transform(camera, get_camera_transform()); + if (!is_physics_interpolated_and_enabled()) { + RenderingServer::get_singleton()->camera_set_transform(camera, get_camera_transform()); + } else { + // Ideally we shouldn't be moving a physics interpolated camera within a frame, + // because it will break smooth interpolation, but it may occur on e.g. level load. + if (!Engine::get_singleton()->is_in_physics_frame() && camera.is_valid()) { + _physics_interpolation_ensure_transform_calculated(true); + RenderingServer::get_singleton()->camera_set_transform(camera, _interpolation_data.camera_xform_interpolated); + } + } if (is_part_of_edited_scene() || !is_current()) { return; @@ -97,6 +108,64 @@ void Camera3D::_update_camera() { get_viewport()->_camera_3d_transform_changed_notify(); } +void Camera3D::_physics_interpolated_changed() { + _update_process_mode(); +} + +void Camera3D::_physics_interpolation_ensure_data_flipped() { + // The curr -> previous update can either occur + // on the INTERNAL_PHYSICS_PROCESS OR + // on NOTIFICATION_TRANSFORM_CHANGED, + // if NOTIFICATION_TRANSFORM_CHANGED takes place + // earlier than INTERNAL_PHYSICS_PROCESS on a tick. + // This is to ensure that the data keeps flowing, but the new data + // doesn't overwrite before prev has been set. + + // Keep the data flowing. + uint64_t tick = Engine::get_singleton()->get_physics_frames(); + if (_interpolation_data.last_update_physics_tick != tick) { + _interpolation_data.xform_prev = _interpolation_data.xform_curr; + _interpolation_data.last_update_physics_tick = tick; + physics_interpolation_flip_data(); + } +} + +void Camera3D::_physics_interpolation_ensure_transform_calculated(bool p_force) const { + DEV_CHECK_ONCE(!Engine::get_singleton()->is_in_physics_frame()); + + InterpolationData &id = _interpolation_data; + uint64_t frame = Engine::get_singleton()->get_frames_drawn(); + + if (id.last_update_frame != frame || p_force) { + id.last_update_frame = frame; + + TransformInterpolator::interpolate_transform_3d(id.xform_prev, id.xform_curr, id.xform_interpolated, Engine::get_singleton()->get_physics_interpolation_fraction()); + + Transform3D &tr = id.camera_xform_interpolated; + tr = _get_adjusted_camera_transform(id.xform_interpolated); + } +} + +void Camera3D::set_desired_process_modes(bool p_process_internal, bool p_physics_process_internal) { + _desired_process_internal = p_process_internal; + _desired_physics_process_internal = p_physics_process_internal; + _update_process_mode(); +} + +void Camera3D::_update_process_mode() { + bool process = _desired_process_internal; + bool physics_process = _desired_physics_process_internal; + + if (is_physics_interpolated_and_enabled()) { + if (is_current()) { + process = true; + physics_process = true; + } + } + set_process_internal(process); + set_physics_process_internal(physics_process); +} + void Camera3D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_WORLD: { @@ -118,11 +187,58 @@ void Camera3D::_notification(int p_what) { #endif } break; + case NOTIFICATION_INTERNAL_PROCESS: { + if (is_physics_interpolated_and_enabled() && camera.is_valid()) { + _physics_interpolation_ensure_transform_calculated(); + +#ifdef RENDERING_SERVER_DEBUG_PHYSICS_INTERPOLATION + print_line("\t\tinterpolated Camera3D: " + rtos(_interpolation_data.xform_interpolated.origin.x) + "\t( prev " + rtos(_interpolation_data.xform_prev.origin.x) + ", curr " + rtos(_interpolation_data.xform_curr.origin.x) + " ) on tick " + itos(Engine::get_singleton()->get_physics_frames())); +#endif + + RenderingServer::get_singleton()->camera_set_transform(camera, _interpolation_data.camera_xform_interpolated); + } + } break; + + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + if (is_physics_interpolated_and_enabled()) { + _physics_interpolation_ensure_data_flipped(); + _interpolation_data.xform_curr = get_global_transform(); + } + } break; + case NOTIFICATION_TRANSFORM_CHANGED: { + if (is_physics_interpolated_and_enabled()) { + _physics_interpolation_ensure_data_flipped(); + _interpolation_data.xform_curr = get_global_transform(); +#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED) + if (!Engine::get_singleton()->is_in_physics_frame()) { + PHYSICS_INTERPOLATION_NODE_WARNING(get_instance_id(), "Interpolated Camera3D triggered from outside physics process"); + } +#endif + } _request_camera_update(); if (doppler_tracking != DOPPLER_TRACKING_DISABLED) { velocity_tracker->update_position(get_global_transform().origin); } + // Allow auto-reset when first adding to the tree, as a convenience. + if (_is_physics_interpolation_reset_requested() && is_inside_tree()) { + _notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION); + _set_physics_interpolation_reset_requested(false); + } + } break; + + case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: { + if (is_inside_tree()) { + _interpolation_data.xform_curr = get_global_transform(); + _interpolation_data.xform_prev = _interpolation_data.xform_curr; + } + } break; + + case NOTIFICATION_PAUSED: { + if (is_physics_interpolated_and_enabled() && is_inside_tree() && is_visible_in_tree()) { + _physics_interpolation_ensure_transform_calculated(true); + RenderingServer::get_singleton()->camera_set_transform(camera, _interpolation_data.camera_xform_interpolated); + } } break; case NOTIFICATION_EXIT_WORLD: { @@ -151,23 +267,34 @@ void Camera3D::_notification(int p_what) { if (viewport) { viewport->find_world_3d()->_register_camera(this); } + _update_process_mode(); } break; case NOTIFICATION_LOST_CURRENT: { if (viewport) { viewport->find_world_3d()->_remove_camera(this); } + _update_process_mode(); } break; } } -Transform3D Camera3D::get_camera_transform() const { - Transform3D tr = get_global_transform().orthonormalized(); +Transform3D Camera3D::_get_adjusted_camera_transform(const Transform3D &p_xform) const { + Transform3D tr = p_xform.orthonormalized(); tr.origin += tr.basis.get_column(1) * v_offset; tr.origin += tr.basis.get_column(0) * h_offset; return tr; } +Transform3D Camera3D::get_camera_transform() const { + if (is_physics_interpolated_and_enabled() && !Engine::get_singleton()->is_in_physics_frame()) { + _physics_interpolation_ensure_transform_calculated(); + return _interpolation_data.camera_xform_interpolated; + } + + return _get_adjusted_camera_transform(get_global_transform()); +} + Projection Camera3D::_get_camera_projection(real_t p_near) const { Size2 viewport_size = get_viewport()->get_visible_rect().size; Projection cm; @@ -379,6 +506,11 @@ Point2 Camera3D::unproject_position(const Vector3 &p_pos) const { Plane p(get_camera_transform().xform_inv(p_pos), 1.0); p = cm.xform4(p); + + // Prevent divide by zero. + // TODO: Investigate, this was causing NaNs. + ERR_FAIL_COND_V(p.d == 0, Point2()); + p.normal /= p.d; Point2 res; diff --git a/scene/3d/camera_3d.h b/scene/3d/camera_3d.h index dbf2ffc1dd..3e9f940ad6 100644 --- a/scene/3d/camera_3d.h +++ b/scene/3d/camera_3d.h @@ -98,7 +98,39 @@ private: RID pyramid_shape; Vector<Vector3> pyramid_shape_points; + /////////////////////////////////////////////////////// + // INTERPOLATION FUNCTIONS + void _physics_interpolation_ensure_transform_calculated(bool p_force = false) const; + void _physics_interpolation_ensure_data_flipped(); + + // These can be set by derived Camera3Ds, if they wish to do processing + // (while still allowing physics interpolation to function). + bool _desired_process_internal = false; + bool _desired_physics_process_internal = false; + + mutable struct InterpolationData { + Transform3D xform_curr; + Transform3D xform_prev; + Transform3D xform_interpolated; + Transform3D camera_xform_interpolated; // After modification according to camera type. + uint32_t last_update_physics_tick = 0; + uint32_t last_update_frame = UINT32_MAX; + } _interpolation_data; + + void _update_process_mode(); + protected: + // Use from derived classes to set process modes instead of setting directly. + // This is because physics interpolation may need to request process modes additionally. + void set_desired_process_modes(bool p_process_internal, bool p_physics_process_internal); + + // Opportunity for derived classes to interpolate extra attributes. + virtual void physics_interpolation_flip_data() {} + + virtual void _physics_interpolated_changed() override; + virtual Transform3D _get_adjusted_camera_transform(const Transform3D &p_xform) const; + /////////////////////////////////////////////////////// + void _update_camera(); virtual void _request_camera_update(); void _update_camera_mode(); diff --git a/scene/3d/gpu_particles_3d.cpp b/scene/3d/gpu_particles_3d.cpp index 3771b385e5..2cef607d29 100644 --- a/scene/3d/gpu_particles_3d.cpp +++ b/scene/3d/gpu_particles_3d.cpp @@ -459,14 +459,6 @@ void GPUParticles3D::_notification(int p_what) { // Use internal process when emitting and one_shot is on so that when // the shot ends the editor can properly update. case NOTIFICATION_INTERNAL_PROCESS: { - const Vector3 velocity = (get_global_position() - previous_position) / get_process_delta_time(); - - if (velocity != previous_velocity) { - RS::get_singleton()->particles_set_emitter_velocity(particles, velocity); - previous_velocity = velocity; - } - previous_position = get_global_position(); - if (one_shot) { time += get_process_delta_time(); if (time > emission_time) { @@ -487,8 +479,21 @@ void GPUParticles3D::_notification(int p_what) { } } break; + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + // Update velocity in physics process, so that velocity calculations remain correct + // if the physics tick rate is lower than the rendered framerate (especially without physics interpolation). + const Vector3 velocity = (get_global_position() - previous_position) / get_physics_process_delta_time(); + + if (velocity != previous_velocity) { + RS::get_singleton()->particles_set_emitter_velocity(particles, velocity); + previous_velocity = velocity; + } + previous_position = get_global_position(); + } break; + case NOTIFICATION_ENTER_TREE: { set_process_internal(false); + set_physics_process_internal(false); if (sub_emitter != NodePath()) { _attach_sub_emitter(); } @@ -499,6 +504,7 @@ void GPUParticles3D::_notification(int p_what) { } previous_position = get_global_transform().origin; set_process_internal(true); + set_physics_process_internal(true); } break; case NOTIFICATION_EXIT_TREE: { diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp index 038a78609f..3f8b0dfb8e 100644 --- a/scene/3d/lightmap_gi.cpp +++ b/scene/3d/lightmap_gi.cpp @@ -709,7 +709,7 @@ void LightmapGI::_gen_new_positions_from_octree(const GenProbesOctree *p_cell, f const Vector3 *pp = probe_positions.ptr(); bool exists = false; for (int j = 0; j < ppcount; j++) { - if (pp[j].is_equal_approx(real_pos)) { + if (pp[j].distance_to(real_pos) < (p_cell_size * 0.5f)) { exists = true; break; } @@ -1072,6 +1072,7 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa if (env.is_valid()) { environment_image = RS::get_singleton()->environment_bake_panorama(env->get_rid(), true, Size2i(128, 64)); + environment_transform = Basis::from_euler(env->get_sky_rotation()).inverse(); } } } break; diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp index 2e08afb30d..86ce8a881a 100644 --- a/scene/3d/node_3d.cpp +++ b/scene/3d/node_3d.cpp @@ -30,6 +30,7 @@ #include "node_3d.h" +#include "core/math/transform_interpolator.h" #include "scene/3d/visual_instance_3d.h" #include "scene/main/viewport.h" #include "scene/property_utils.h" @@ -176,6 +177,7 @@ void Node3D::_notification(int p_what) { data.parent = nullptr; data.C = nullptr; _update_visibility_parent(true); + _disable_client_physics_interpolation(); } break; case NOTIFICATION_ENTER_WORLD: { @@ -226,6 +228,12 @@ void Node3D::_notification(int p_what) { } #endif } break; + + case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: { + if (data.client_physics_interpolation_data) { + data.client_physics_interpolation_data->global_xform_prev = data.client_physics_interpolation_data->global_xform_curr; + } + } break; } } @@ -341,6 +349,119 @@ Transform3D Node3D::get_transform() const { return data.local_transform; } +// Return false to timeout and remove from the client interpolation list. +bool Node3D::update_client_physics_interpolation_data() { + if (!is_inside_tree() || !_is_physics_interpolated_client_side()) { + return false; + } + + ERR_FAIL_NULL_V(data.client_physics_interpolation_data, false); + ClientPhysicsInterpolationData &pid = *data.client_physics_interpolation_data; + + uint64_t tick = Engine::get_singleton()->get_physics_frames(); + + // Has this update been done already this tick? + // (For instance, get_global_transform_interpolated() could be called multiple times.) + if (pid.current_physics_tick != tick) { + // Timeout? + if (tick >= pid.timeout_physics_tick) { + return false; + } + + if (pid.current_physics_tick == (tick - 1)) { + // Normal interpolation situation, there is a continuous flow of data + // from one tick to the next... + pid.global_xform_prev = pid.global_xform_curr; + } else { + // There has been a gap, we cannot sensibly offer interpolation over + // a multitick gap, so we will teleport. + pid.global_xform_prev = get_global_transform(); + } + pid.current_physics_tick = tick; + } + + pid.global_xform_curr = get_global_transform(); + return true; +} + +void Node3D::_disable_client_physics_interpolation() { + // Disable any current client side interpolation. + // (This can always restart as normal if you later re-attach the node to the SceneTree.) + if (data.client_physics_interpolation_data) { + memdelete(data.client_physics_interpolation_data); + data.client_physics_interpolation_data = nullptr; + + SceneTree *tree = get_tree(); + if (tree && _client_physics_interpolation_node_3d_list.in_list()) { + tree->client_physics_interpolation_remove_node_3d(&_client_physics_interpolation_node_3d_list); + } + } + _set_physics_interpolated_client_side(false); +} + +Transform3D Node3D::_get_global_transform_interpolated(real_t p_interpolation_fraction) { + ERR_FAIL_COND_V(!is_inside_tree(), Transform3D()); + + // Set in motion the mechanisms for client side interpolation if not already active. + if (!_is_physics_interpolated_client_side()) { + _set_physics_interpolated_client_side(true); + + ERR_FAIL_COND_V(data.client_physics_interpolation_data != nullptr, Transform3D()); + data.client_physics_interpolation_data = memnew(ClientPhysicsInterpolationData); + data.client_physics_interpolation_data->global_xform_curr = get_global_transform(); + data.client_physics_interpolation_data->global_xform_prev = data.client_physics_interpolation_data->global_xform_curr; + data.client_physics_interpolation_data->current_physics_tick = Engine::get_singleton()->get_physics_frames(); + } + + // Storing the last tick we requested client interpolation allows us to timeout + // and remove client interpolated nodes from the list to save processing. + // We use some arbitrary timeout here, but this could potentially be user defined. + + // Note: This timeout has to be larger than the number of ticks in a frame, otherwise the interpolated + // data will stop flowing before the next frame is drawn. This should only be relevant at high tick rates. + // We could alternatively do this by frames rather than ticks and avoid this problem, but then the behavior + // would be machine dependent. + data.client_physics_interpolation_data->timeout_physics_tick = Engine::get_singleton()->get_physics_frames() + 256; + + // Make sure data is up to date. + update_client_physics_interpolation_data(); + + // Interpolate the current data. + const Transform3D &xform_curr = data.client_physics_interpolation_data->global_xform_curr; + const Transform3D &xform_prev = data.client_physics_interpolation_data->global_xform_prev; + + Transform3D res; + TransformInterpolator::interpolate_transform_3d(xform_prev, xform_curr, res, p_interpolation_fraction); + + SceneTree *tree = get_tree(); + + // This should not happen, as is_inside_tree() is checked earlier. + ERR_FAIL_NULL_V(tree, res); + if (!_client_physics_interpolation_node_3d_list.in_list()) { + tree->client_physics_interpolation_add_node_3d(&_client_physics_interpolation_node_3d_list); + } + + return res; +} + +Transform3D Node3D::get_global_transform_interpolated() { + // Pass through if physics interpolation is switched off. + // This is a convenience, as it allows you to easy turn off interpolation + // without changing any code. + if (!is_physics_interpolated_and_enabled()) { + return get_global_transform(); + } + + // If we are in the physics frame, the interpolated global transform is meaningless. + // However, there is an exception, we may want to use this as a means of starting off the client + // interpolation pump if not already started (when _is_physics_interpolated_client_side() is false). + if (Engine::get_singleton()->is_in_physics_frame() && _is_physics_interpolated_client_side()) { + return get_global_transform(); + } + + return _get_global_transform_interpolated(Engine::get_singleton()->get_physics_interpolation_fraction()); +} + Transform3D Node3D::get_global_transform() const { ERR_FAIL_COND_V(!is_inside_tree(), Transform3D()); @@ -1140,6 +1261,7 @@ void Node3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_global_transform", "global"), &Node3D::set_global_transform); ClassDB::bind_method(D_METHOD("get_global_transform"), &Node3D::get_global_transform); + ClassDB::bind_method(D_METHOD("get_global_transform_interpolated"), &Node3D::get_global_transform_interpolated); ClassDB::bind_method(D_METHOD("set_global_position", "position"), &Node3D::set_global_position); ClassDB::bind_method(D_METHOD("get_global_position"), &Node3D::get_global_position); ClassDB::bind_method(D_METHOD("set_global_basis", "basis"), &Node3D::set_global_basis); @@ -1236,4 +1358,27 @@ void Node3D::_bind_methods() { } Node3D::Node3D() : - xform_change(this) {} + xform_change(this), _client_physics_interpolation_node_3d_list(this) { + // Default member initializer for bitfield is a C++20 extension, so: + + data.top_level = false; + data.inside_world = false; + + data.ignore_notification = false; + data.notify_local_transform = false; + data.notify_transform = false; + + data.visible = true; + data.disable_scale = false; + data.vi_visible = true; + +#ifdef TOOLS_ENABLED + data.gizmos_disabled = false; + data.gizmos_dirty = false; + data.transform_gizmo_visible = true; +#endif +} + +Node3D::~Node3D() { + _disable_client_physics_interpolation(); +} diff --git a/scene/3d/node_3d.h b/scene/3d/node_3d.h index c1667221df..217ee28cf1 100644 --- a/scene/3d/node_3d.h +++ b/scene/3d/node_3d.h @@ -85,7 +85,15 @@ private: DIRTY_GLOBAL_TRANSFORM = 4 }; + struct ClientPhysicsInterpolationData { + Transform3D global_xform_curr; + Transform3D global_xform_prev; + uint64_t current_physics_tick = 0; + uint64_t timeout_physics_tick = 0; + }; + mutable SelfList<Node> xform_change; + SelfList<Node3D> _client_physics_interpolation_node_3d_list; // This Data struct is to avoid namespace pollution in derived classes. @@ -101,8 +109,19 @@ private: Viewport *viewport = nullptr; - bool top_level = false; - bool inside_world = false; + bool top_level : 1; + bool inside_world : 1; + + // This is cached, and only currently kept up to date in visual instances. + // This is set if a visual instance is (a) in the tree AND (b) visible via is_visible_in_tree() call. + bool vi_visible : 1; + + bool ignore_notification : 1; + bool notify_local_transform : 1; + bool notify_transform : 1; + + bool visible : 1; + bool disable_scale : 1; RID visibility_parent; @@ -110,18 +129,13 @@ private: List<Node3D *> children; List<Node3D *>::Element *C = nullptr; - bool ignore_notification = false; - bool notify_local_transform = false; - bool notify_transform = false; - - bool visible = true; - bool disable_scale = false; + ClientPhysicsInterpolationData *client_physics_interpolation_data = nullptr; #ifdef TOOLS_ENABLED Vector<Ref<Node3DGizmo>> gizmos; - bool gizmos_disabled = false; - bool gizmos_dirty = false; - bool transform_gizmo_visible = true; + bool gizmos_disabled : 1; + bool gizmos_dirty : 1; + bool transform_gizmo_visible : 1; #endif } data; @@ -150,6 +164,11 @@ protected: _FORCE_INLINE_ void _update_local_transform() const; _FORCE_INLINE_ void _update_rotation_and_scale() const; + void _set_vi_visible(bool p_visible) { data.vi_visible = p_visible; } + bool _is_vi_visible() const { return data.vi_visible; } + Transform3D _get_global_transform_interpolated(real_t p_interpolation_fraction); + void _disable_client_physics_interpolation(); + void _notification(int p_what); static void _bind_methods(); @@ -208,6 +227,9 @@ public: Quaternion get_quaternion() const; Transform3D get_global_transform() const; + Transform3D get_global_transform_interpolated(); + bool update_client_physics_interpolation_data(); + #ifdef TOOLS_ENABLED virtual Transform3D get_global_gizmo_transform() const; virtual Transform3D get_local_gizmo_transform() const; @@ -279,6 +301,7 @@ public: NodePath get_visibility_parent() const; Node3D(); + ~Node3D(); }; VARIANT_ENUM_CAST(Node3D::RotationEditMode) diff --git a/scene/3d/physics/shape_cast_3d.cpp b/scene/3d/physics/shape_cast_3d.cpp index ada238c7f2..8ad651fdf5 100644 --- a/scene/3d/physics/shape_cast_3d.cpp +++ b/scene/3d/physics/shape_cast_3d.cpp @@ -157,7 +157,7 @@ void ShapeCast3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_collide_with_bodies", "enable"), &ShapeCast3D::set_collide_with_bodies); ClassDB::bind_method(D_METHOD("is_collide_with_bodies_enabled"), &ShapeCast3D::is_collide_with_bodies_enabled); - ClassDB::bind_method(D_METHOD("_get_collision_result"), &ShapeCast3D::_get_collision_result); + ClassDB::bind_method(D_METHOD("get_collision_result"), &ShapeCast3D::get_collision_result); ClassDB::bind_method(D_METHOD("set_debug_shape_custom_color", "debug_shape_custom_color"), &ShapeCast3D::set_debug_shape_custom_color); ClassDB::bind_method(D_METHOD("get_debug_shape_custom_color"), &ShapeCast3D::get_debug_shape_custom_color); @@ -169,7 +169,7 @@ void ShapeCast3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "margin", PROPERTY_HINT_RANGE, "0,100,0.01,suffix:m"), "set_margin", "get_margin"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_results"), "set_max_results", "get_max_results"); ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collision_mask", "get_collision_mask"); - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "collision_result", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "", "_get_collision_result"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "collision_result", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "", "get_collision_result"); ADD_GROUP("Collide With", "collide_with"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collide_with_areas", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collide_with_areas", "is_collide_with_areas_enabled"); @@ -475,7 +475,7 @@ bool ShapeCast3D::is_collide_with_bodies_enabled() const { return collide_with_bodies; } -Array ShapeCast3D::_get_collision_result() const { +Array ShapeCast3D::get_collision_result() const { Array ret; for (int i = 0; i < result.size(); ++i) { diff --git a/scene/3d/physics/shape_cast_3d.h b/scene/3d/physics/shape_cast_3d.h index 19b73e3f72..9fc5e71670 100644 --- a/scene/3d/physics/shape_cast_3d.h +++ b/scene/3d/physics/shape_cast_3d.h @@ -73,8 +73,6 @@ class ShapeCast3D : public Node3D { real_t collision_safe_fraction = 1.0; real_t collision_unsafe_fraction = 1.0; - Array _get_collision_result() const; - RID debug_instance; Ref<ArrayMesh> debug_mesh; @@ -123,6 +121,7 @@ public: Ref<StandardMaterial3D> get_debug_material(); + Array get_collision_result() const; int get_collision_count() const; Object *get_collider(int p_idx) const; RID get_collider_rid(int p_idx) const; diff --git a/scene/3d/skeleton_ik_3d.cpp b/scene/3d/skeleton_ik_3d.cpp index 0d6316ee35..2476e7d5cd 100644 --- a/scene/3d/skeleton_ik_3d.cpp +++ b/scene/3d/skeleton_ik_3d.cpp @@ -503,7 +503,11 @@ Transform3D SkeletonIK3D::_get_target_transform() { Node3D *target_node_override = cast_to<Node3D>(target_node_override_ref.get_validated_object()); if (target_node_override && target_node_override->is_inside_tree()) { - return target_node_override->get_global_transform(); + // Make sure to use the interpolated transform as target. + // When physics interpolation is off this will pass through to get_global_transform(). + // When using interpolation, ensure that the target matches the interpolated visual position + // of the target when updating the IK each frame. + return target_node_override->get_global_transform_interpolated(); } else { return target; } diff --git a/scene/3d/visual_instance_3d.cpp b/scene/3d/visual_instance_3d.cpp index f14ae3a285..79a01450dd 100644 --- a/scene/3d/visual_instance_3d.cpp +++ b/scene/3d/visual_instance_3d.cpp @@ -30,6 +30,8 @@ #include "visual_instance_3d.h" +#include "core/config/project_settings.h" + AABB VisualInstance3D::get_aabb() const { AABB ret; GDVIRTUAL_CALL(_get_aabb, ret); @@ -41,7 +43,38 @@ void VisualInstance3D::_update_visibility() { return; } - RS::get_singleton()->instance_set_visible(get_instance(), is_visible_in_tree()); + bool already_visible = _is_vi_visible(); + bool visible = is_visible_in_tree(); + _set_vi_visible(visible); + + // If making visible, make sure the rendering server is up to date with the transform. + if (visible && !already_visible) { + if (!_is_using_identity_transform()) { + Transform3D gt = get_global_transform(); + RS::get_singleton()->instance_set_transform(instance, gt); + } + } + + RS::get_singleton()->instance_set_visible(instance, visible); +} + +void VisualInstance3D::_physics_interpolated_changed() { + RenderingServer::get_singleton()->instance_set_interpolated(instance, is_physics_interpolated()); +} + +void VisualInstance3D::set_instance_use_identity_transform(bool p_enable) { + // Prevent sending instance transforms when using global coordinates. + _set_use_identity_transform(p_enable); + + if (is_inside_tree()) { + if (p_enable) { + // Want to make sure instance is using identity transform. + RS::get_singleton()->instance_set_transform(instance, Transform3D()); + } else { + // Want to make sure instance is up to date. + RS::get_singleton()->instance_set_transform(instance, get_global_transform()); + } + } } void VisualInstance3D::_notification(int p_what) { @@ -53,13 +86,52 @@ void VisualInstance3D::_notification(int p_what) { } break; case NOTIFICATION_TRANSFORM_CHANGED: { - Transform3D gt = get_global_transform(); - RenderingServer::get_singleton()->instance_set_transform(instance, gt); + if (_is_vi_visible() || is_physics_interpolated_and_enabled()) { + if (!_is_using_identity_transform()) { + RenderingServer::get_singleton()->instance_set_transform(instance, get_global_transform()); + + // For instance when first adding to the tree, when the previous transform is + // unset, to prevent streaking from the origin. + if (_is_physics_interpolation_reset_requested() && is_physics_interpolated_and_enabled() && is_inside_tree()) { + if (_is_vi_visible()) { + _notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION); + } + _set_physics_interpolation_reset_requested(false); + } + } + } + } break; + + case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: { + if (_is_vi_visible() && is_physics_interpolated() && is_inside_tree()) { + // We must ensure the RenderingServer transform is up to date before resetting. + // This is because NOTIFICATION_TRANSFORM_CHANGED is deferred, + // and cannot be relied to be called in order before NOTIFICATION_RESET_PHYSICS_INTERPOLATION. + if (!_is_using_identity_transform()) { + RenderingServer::get_singleton()->instance_set_transform(instance, get_global_transform()); + } + + RenderingServer::get_singleton()->instance_reset_physics_interpolation(instance); + } +#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED) + else if (GLOBAL_GET("debug/settings/physics_interpolation/enable_warnings")) { + + String node_name = is_inside_tree() ? String(get_path()) : String(get_name()); + if (!_is_vi_visible()) { + WARN_PRINT("[Physics interpolation] NOTIFICATION_RESET_PHYSICS_INTERPOLATION only works with unhidden nodes: \"" + node_name + "\"."); + } + if (!is_physics_interpolated()) { + WARN_PRINT("[Physics interpolation] NOTIFICATION_RESET_PHYSICS_INTERPOLATION only works with interpolated nodes: \"" + node_name + "\"."); + } + } +#endif + } break; case NOTIFICATION_EXIT_WORLD: { RenderingServer::get_singleton()->instance_set_scenario(instance, RID()); RenderingServer::get_singleton()->instance_attach_skeleton(instance, RID()); + _set_vi_visible(false); } break; case NOTIFICATION_VISIBILITY_CHANGED: { diff --git a/scene/3d/visual_instance_3d.h b/scene/3d/visual_instance_3d.h index 59ede26ac1..9b02c928b7 100644 --- a/scene/3d/visual_instance_3d.h +++ b/scene/3d/visual_instance_3d.h @@ -45,6 +45,9 @@ class VisualInstance3D : public Node3D { protected: void _update_visibility(); + virtual void _physics_interpolated_changed() override; + void set_instance_use_identity_transform(bool p_enable); + void _notification(int p_what); static void _bind_methods(); void _validate_property(PropertyInfo &p_property) const; diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 0396f3ab4a..5c46abc732 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -138,6 +138,12 @@ void Node::_notification(int p_notification) { get_tree()->nodes_in_tree_count++; orphan_node_count--; + + // Allow physics interpolated nodes to automatically reset when added to the tree + // (this is to save the user from doing this manually each time). + if (get_tree()->is_physics_interpolation_enabled()) { + _set_physics_interpolation_reset_requested(true); + } } break; case NOTIFICATION_EXIT_TREE: { @@ -437,6 +443,18 @@ void Node::_propagate_physics_interpolated(bool p_interpolated) { data.blocked--; } +void Node::_propagate_physics_interpolation_reset_requested(bool p_requested) { + if (is_physics_interpolated()) { + data.physics_interpolation_reset_requested = p_requested; + } + + data.blocked++; + for (KeyValue<StringName, Node *> &K : data.children) { + K.value->_propagate_physics_interpolation_reset_requested(p_requested); + } + data.blocked--; +} + void Node::move_child(Node *p_child, int p_index) { ERR_FAIL_COND_MSG(data.inside_tree && !Thread::is_main_thread(), "Moving child node positions inside the SceneTree is only allowed from the main thread. Use call_deferred(\"move_child\",child,index)."); ERR_FAIL_NULL(p_child); @@ -890,15 +908,23 @@ void Node::set_physics_interpolation_mode(PhysicsInterpolationMode p_mode) { } // If swapping from interpolated to non-interpolated, use this as an extra means to cause a reset. - if (is_physics_interpolated() && !interpolate) { - reset_physics_interpolation(); + if (is_physics_interpolated() && !interpolate && is_inside_tree()) { + propagate_notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION); } _propagate_physics_interpolated(interpolate); } void Node::reset_physics_interpolation() { - propagate_notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION); + if (is_inside_tree()) { + propagate_notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION); + + // If `reset_physics_interpolation()` is called explicitly by the user + // (e.g. from scripts) then we prevent deferred auto-resets taking place. + // The user is trusted to call reset in the right order, and auto-reset + // will interfere with their control of prev / curr, so should be turned off. + _propagate_physics_interpolation_reset_requested(false); + } } bool Node::_is_enabled() const { @@ -3825,6 +3851,9 @@ Node::Node() { data.unhandled_key_input = false; data.physics_interpolated = true; + data.physics_interpolation_reset_requested = false; + data.physics_interpolated_client_side = false; + data.use_identity_transform = false; data.parent_owned = false; data.in_constructor = true; diff --git a/scene/main/node.h b/scene/main/node.h index ee195ddef9..2f6372dad5 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -225,6 +225,21 @@ private: // is switched on. bool physics_interpolated : 1; + // We can auto-reset physics interpolation when e.g. adding a node for the first time. + bool physics_interpolation_reset_requested : 1; + + // Most nodes need not be interpolated in the scene tree, physics interpolation + // is normally only needed in the RenderingServer. However if we need to read the + // interpolated transform of a node in the SceneTree, it is necessary to duplicate + // the interpolation logic client side, in order to prevent stalling the RenderingServer + // by reading back. + bool physics_interpolated_client_side : 1; + + // For certain nodes (e.g. CPU particles in global mode) + // it can be useful to not send the instance transform to the + // RenderingServer, and specify the mesh in world space. + bool use_identity_transform : 1; + bool parent_owned : 1; bool in_constructor : 1; bool use_placeholder : 1; @@ -263,6 +278,7 @@ private: void _propagate_exit_tree(); void _propagate_after_exit_tree(); void _propagate_physics_interpolated(bool p_interpolated); + void _propagate_physics_interpolation_reset_requested(bool p_requested); void _propagate_process_owner(Node *p_owner, int p_pause_notification, int p_enabled_notification); void _propagate_groups_dirty(); Array _get_node_and_resource(const NodePath &p_path); @@ -334,6 +350,15 @@ protected: void _set_owner_nocheck(Node *p_owner); void _set_name_nocheck(const StringName &p_name); + void _set_physics_interpolated_client_side(bool p_enable) { data.physics_interpolated_client_side = p_enable; } + bool _is_physics_interpolated_client_side() const { return data.physics_interpolated_client_side; } + + void _set_physics_interpolation_reset_requested(bool p_enable) { data.physics_interpolation_reset_requested = p_enable; } + bool _is_physics_interpolation_reset_requested() const { return data.physics_interpolation_reset_requested; } + + void _set_use_identity_transform(bool p_enable) { data.use_identity_transform = p_enable; } + bool _is_using_identity_transform() const { return data.use_identity_transform; } + //call from SceneTree void _call_input(const Ref<InputEvent> &p_event); void _call_shortcut_input(const Ref<InputEvent> &p_event); diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index ced6d9aaa6..f0c9e8a866 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -59,6 +59,7 @@ #include "servers/navigation_server_3d.h" #include "servers/physics_server_2d.h" #ifndef _3D_DISABLED +#include "scene/3d/node_3d.h" #include "scene/resources/3d/world_3d.h" #include "servers/physics_server_3d.h" #endif // _3D_DISABLED @@ -118,6 +119,29 @@ void SceneTreeTimer::release_connections() { SceneTreeTimer::SceneTreeTimer() {} +#ifndef _3D_DISABLED +// This should be called once per physics tick, to make sure the transform previous and current +// is kept up to date on the few Node3Ds that are using client side physics interpolation. +void SceneTree::ClientPhysicsInterpolation::physics_process() { + for (SelfList<Node3D> *E = _node_3d_list.first(); E;) { + Node3D *node_3d = E->self(); + + SelfList<Node3D> *current = E; + + // Get the next element here BEFORE we potentially delete one. + E = E->next(); + + // This will return false if the Node3D has timed out .. + // i.e. if get_global_transform_interpolated() has not been called + // for a few seconds, we can delete from the list to keep processing + // to a minimum. + if (!node_3d->update_client_physics_interpolation_data()) { + _node_3d_list.remove(current); + } + } +} +#endif + void SceneTree::tree_changed() { emit_signal(tree_changed_name); } @@ -466,9 +490,31 @@ bool SceneTree::is_physics_interpolation_enabled() const { return _physics_interpolation_enabled; } +#ifndef _3D_DISABLED +void SceneTree::client_physics_interpolation_add_node_3d(SelfList<Node3D> *p_elem) { + // This ensures that _update_physics_interpolation_data() will be called at least once every + // physics tick, to ensure the previous and current transforms are kept up to date. + _client_physics_interpolation._node_3d_list.add(p_elem); +} + +void SceneTree::client_physics_interpolation_remove_node_3d(SelfList<Node3D> *p_elem) { + _client_physics_interpolation._node_3d_list.remove(p_elem); +} +#endif + void SceneTree::iteration_prepare() { if (_physics_interpolation_enabled) { + // Make sure any pending transforms from the last tick / frame + // are flushed before pumping the interpolation prev and currents. + flush_transform_notifications(); RenderingServer::get_singleton()->tick(); + +#ifndef _3D_DISABLED + // Any objects performing client physics interpolation + // should be given an opportunity to keep their previous transforms + // up to date before each new physics tick. + _client_physics_interpolation.physics_process(); +#endif } } @@ -503,6 +549,14 @@ bool SceneTree::physics_process(double p_time) { return _quit; } +void SceneTree::iteration_end() { + // When physics interpolation is active, we want all pending transforms + // to be flushed to the RenderingServer before finishing a physics tick. + if (_physics_interpolation_enabled) { + flush_transform_notifications(); + } +} + bool SceneTree::process(double p_time) { if (MainLoop::process(p_time)) { _quit = true; @@ -570,6 +624,10 @@ bool SceneTree::process(double p_time) { #endif // _3D_DISABLED #endif // TOOLS_ENABLED + if (_physics_interpolation_enabled) { + RenderingServer::get_singleton()->pre_draw(true); + } + return _quit; } @@ -1761,6 +1819,13 @@ SceneTree::SceneTree() { set_physics_interpolation_enabled(GLOBAL_DEF("physics/common/physics_interpolation", false)); + // Always disable jitter fix if physics interpolation is enabled - + // Jitter fix will interfere with interpolation, and is not necessary + // when interpolation is active. + if (is_physics_interpolation_enabled()) { + Engine::get_singleton()->set_physics_jitter_fix(0); + } + // Initialize network state. set_multiplayer(MultiplayerAPI::create_default_interface()); diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h index 6f0a61ec51..7e44541105 100644 --- a/scene/main/scene_tree.h +++ b/scene/main/scene_tree.h @@ -41,6 +41,9 @@ class PackedScene; class Node; +#ifndef _3D_DISABLED +class Node3D; +#endif class Window; class Material; class Mesh; @@ -120,6 +123,13 @@ private: bool changed = false; }; +#ifndef _3D_DISABLED + struct ClientPhysicsInterpolation { + SelfList<Node3D>::List _node_3d_list; + void physics_process(); + } _client_physics_interpolation; +#endif + Window *root = nullptr; double physics_process_time = 0.0; @@ -315,6 +325,7 @@ public: virtual void iteration_prepare() override; virtual bool physics_process(double p_time) override; + virtual void iteration_end() override; virtual bool process(double p_time) override; virtual void finalize() override; @@ -423,6 +434,11 @@ public: void set_physics_interpolation_enabled(bool p_enabled); bool is_physics_interpolation_enabled() const; +#ifndef _3D_DISABLED + void client_physics_interpolation_add_node_3d(SelfList<Node3D> *p_elem); + void client_physics_interpolation_remove_node_3d(SelfList<Node3D> *p_elem); +#endif + SceneTree(); ~SceneTree(); }; diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 1302e3c53e..c85fda2371 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -1511,6 +1511,7 @@ void Viewport::_gui_show_tooltip() { r.size *= win_scale; vr = window->get_usable_parent_rect(); } + r.size = r.size.ceil(); r.size = r.size.min(panel->get_max_size()); if (r.size.x + r.position.x > vr.size.x + vr.position.x) { @@ -5024,6 +5025,13 @@ Viewport::Viewport() { #endif // _3D_DISABLED set_sdf_oversize(sdf_oversize); // Set to server. + + // Physics interpolation mode for viewports is a special case. + // Typically viewports will be housed within Controls, + // and Controls default to PHYSICS_INTERPOLATION_MODE_OFF. + // Viewports can thus inherit physics interpolation OFF, which is unexpected. + // Setting to ON allows each viewport to have a fresh interpolation state. + set_physics_interpolation_mode(Node::PHYSICS_INTERPOLATION_MODE_ON); } Viewport::~Viewport() { diff --git a/scene/resources/3d/importer_mesh.cpp b/scene/resources/3d/importer_mesh.cpp index f912d2650d..91531699b4 100644 --- a/scene/resources/3d/importer_mesh.cpp +++ b/scene/resources/3d/importer_mesh.cpp @@ -269,7 +269,7 @@ void ImporterMesh::set_surface_material(int p_surface, const Ref<Material> &p_ma } \ write_array[vert_idx] = transformed_vert; -void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_split_angle, Array p_bone_transform_array) { +void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_split_angle, Array p_bone_transform_array, bool p_raycast_normals) { if (!SurfaceTool::simplify_scale_func) { return; } @@ -432,6 +432,7 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli unsigned int index_target = 12; // Start with the smallest target, 4 triangles unsigned int last_index_count = 0; + // Only used for normal raycasting int split_vertex_count = vertex_count; LocalVector<Vector3> split_vertex_normals; LocalVector<int> split_vertex_indices; @@ -441,7 +442,7 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli RandomPCG pcg; pcg.seed(123456789); // Keep seed constant across imports - Ref<StaticRaycaster> raycaster = StaticRaycaster::create(); + Ref<StaticRaycaster> raycaster = p_raycast_normals ? StaticRaycaster::create() : Ref<StaticRaycaster>(); if (raycaster.is_valid()) { raycaster->add_mesh(vertices, indices, 0); raycaster->commit(); @@ -488,19 +489,22 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli } new_indices.resize(new_index_count); - - LocalVector<LocalVector<int>> vertex_corners; - vertex_corners.resize(vertex_count); { int *ptrw = new_indices.ptrw(); for (unsigned int j = 0; j < new_index_count; j++) { - const int &remapped = vertex_inverse_remap[ptrw[j]]; - vertex_corners[remapped].push_back(j); - ptrw[j] = remapped; + ptrw[j] = vertex_inverse_remap[ptrw[j]]; } } if (raycaster.is_valid()) { + LocalVector<LocalVector<int>> vertex_corners; + vertex_corners.resize(vertex_count); + + int *ptrw = new_indices.ptrw(); + for (unsigned int j = 0; j < new_index_count; j++) { + vertex_corners[ptrw[j]].push_back(j); + } + float error_factor = 1.0f / (scale * MAX(mesh_error, 0.15)); const float ray_bias = 0.05; float ray_length = ray_bias + mesh_error * scale * 3.0f; @@ -671,7 +675,10 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli } } - surfaces.write[i].split_normals(split_vertex_indices, split_vertex_normals); + if (raycaster.is_valid()) { + surfaces.write[i].split_normals(split_vertex_indices, split_vertex_normals); + } + surfaces.write[i].lods.sort_custom<Surface::LODComparator>(); for (int j = 0; j < surfaces.write[i].lods.size(); j++) { @@ -682,6 +689,10 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli } } +void ImporterMesh::_generate_lods_bind(float p_normal_merge_angle, float p_normal_split_angle, Array p_skin_pose_transform_array) { + generate_lods(p_normal_merge_angle, p_normal_split_angle, p_skin_pose_transform_array); +} + bool ImporterMesh::has_mesh() const { return mesh.is_valid(); } @@ -1367,7 +1378,7 @@ void ImporterMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_surface_name", "surface_idx", "name"), &ImporterMesh::set_surface_name); ClassDB::bind_method(D_METHOD("set_surface_material", "surface_idx", "material"), &ImporterMesh::set_surface_material); - ClassDB::bind_method(D_METHOD("generate_lods", "normal_merge_angle", "normal_split_angle", "bone_transform_array"), &ImporterMesh::generate_lods); + ClassDB::bind_method(D_METHOD("generate_lods", "normal_merge_angle", "normal_split_angle", "bone_transform_array"), &ImporterMesh::_generate_lods_bind); ClassDB::bind_method(D_METHOD("get_mesh", "base_mesh"), &ImporterMesh::get_mesh, DEFVAL(Ref<ArrayMesh>())); ClassDB::bind_method(D_METHOD("clear"), &ImporterMesh::clear); diff --git a/scene/resources/3d/importer_mesh.h b/scene/resources/3d/importer_mesh.h index ff8683449b..777f936030 100644 --- a/scene/resources/3d/importer_mesh.h +++ b/scene/resources/3d/importer_mesh.h @@ -86,6 +86,8 @@ protected: void _set_data(const Dictionary &p_data); Dictionary _get_data() const; + void _generate_lods_bind(float p_normal_merge_angle, float p_normal_split_angle, Array p_skin_pose_transform_array); + static void _bind_methods(); public: @@ -112,7 +114,7 @@ public: void set_surface_material(int p_surface, const Ref<Material> &p_material); - void generate_lods(float p_normal_merge_angle, float p_normal_split_angle, Array p_skin_pose_transform_array); + void generate_lods(float p_normal_merge_angle, float p_normal_split_angle, Array p_skin_pose_transform_array, bool p_raycast_normals = false); void create_shadow_mesh(); Ref<ImporterMesh> get_shadow_mesh() const; diff --git a/scene/resources/3d/primitive_meshes.cpp b/scene/resources/3d/primitive_meshes.cpp index ee772f960a..128822d2cb 100644 --- a/scene/resources/3d/primitive_meshes.cpp +++ b/scene/resources/3d/primitive_meshes.cpp @@ -324,22 +324,43 @@ Vector2 PrimitiveMesh::get_uv2_scale(Vector2 p_margin_scale) const { } float PrimitiveMesh::get_lightmap_texel_size() const { - float texel_size = GLOBAL_GET("rendering/lightmapping/primitive_meshes/texel_size"); + return texel_size; +} - if (texel_size <= 0.0) { - texel_size = 0.2; +void PrimitiveMesh::_on_settings_changed() { + float new_texel_size = float(GLOBAL_GET("rendering/lightmapping/primitive_meshes/texel_size")); + if (new_texel_size <= 0.0) { + new_texel_size = 0.2; + } + if (texel_size == new_texel_size) { + return; } - return texel_size; + texel_size = new_texel_size; + _update_lightmap_size(); + request_update(); } PrimitiveMesh::PrimitiveMesh() { + ERR_FAIL_NULL(RenderingServer::get_singleton()); mesh = RenderingServer::get_singleton()->mesh_create(); + + ERR_FAIL_NULL(ProjectSettings::get_singleton()); + texel_size = float(GLOBAL_GET("rendering/lightmapping/primitive_meshes/texel_size")); + if (texel_size <= 0.0) { + texel_size = 0.2; + } + ProjectSettings *project_settings = ProjectSettings::get_singleton(); + project_settings->connect("settings_changed", callable_mp(this, &PrimitiveMesh::_on_settings_changed)); } PrimitiveMesh::~PrimitiveMesh() { ERR_FAIL_NULL(RenderingServer::get_singleton()); RenderingServer::get_singleton()->free(mesh); + + ERR_FAIL_NULL(ProjectSettings::get_singleton()); + ProjectSettings *project_settings = ProjectSettings::get_singleton(); + project_settings->disconnect("settings_changed", callable_mp(this, &PrimitiveMesh::_on_settings_changed)); } /** @@ -350,7 +371,6 @@ void CapsuleMesh::_update_lightmap_size() { if (get_add_uv2()) { // size must have changed, update lightmap size hint Size2i _lightmap_size_hint; - float texel_size = get_lightmap_texel_size(); float padding = get_uv2_padding(); float radial_length = radius * Math_PI * 0.5; // circumference of 90 degree bend @@ -365,7 +385,6 @@ void CapsuleMesh::_update_lightmap_size() { void CapsuleMesh::_create_mesh_array(Array &p_arr) const { bool _add_uv2 = get_add_uv2(); - float texel_size = get_lightmap_texel_size(); float _uv2_padding = get_uv2_padding() * texel_size; create_mesh_array(p_arr, radius, height, radial_segments, rings, _add_uv2, _uv2_padding); @@ -613,7 +632,6 @@ void BoxMesh::_update_lightmap_size() { if (get_add_uv2()) { // size must have changed, update lightmap size hint Size2i _lightmap_size_hint; - float texel_size = get_lightmap_texel_size(); float padding = get_uv2_padding(); float width = (size.x + size.z) / texel_size; @@ -632,7 +650,6 @@ void BoxMesh::_create_mesh_array(Array &p_arr) const { // With 3 faces along the width and 2 along the height of the texture we need to adjust our scale // accordingly. bool _add_uv2 = get_add_uv2(); - float texel_size = get_lightmap_texel_size(); float _uv2_padding = get_uv2_padding() * texel_size; BoxMesh::create_mesh_array(p_arr, size, subdivide_w, subdivide_h, subdivide_d, _add_uv2, _uv2_padding); @@ -937,7 +954,6 @@ void CylinderMesh::_update_lightmap_size() { if (get_add_uv2()) { // size must have changed, update lightmap size hint Size2i _lightmap_size_hint; - float texel_size = get_lightmap_texel_size(); float padding = get_uv2_padding(); float top_circumference = top_radius * Math_PI * 2.0; @@ -957,7 +973,6 @@ void CylinderMesh::_update_lightmap_size() { void CylinderMesh::_create_mesh_array(Array &p_arr) const { bool _add_uv2 = get_add_uv2(); - float texel_size = get_lightmap_texel_size(); float _uv2_padding = get_uv2_padding() * texel_size; create_mesh_array(p_arr, top_radius, bottom_radius, height, radial_segments, rings, cap_top, cap_bottom, _add_uv2, _uv2_padding); @@ -1244,7 +1259,6 @@ void PlaneMesh::_update_lightmap_size() { if (get_add_uv2()) { // size must have changed, update lightmap size hint Size2i _lightmap_size_hint; - float texel_size = get_lightmap_texel_size(); float padding = get_uv2_padding(); _lightmap_size_hint.x = MAX(1.0, (size.x / texel_size) + padding); @@ -1416,7 +1430,6 @@ void PrismMesh::_update_lightmap_size() { if (get_add_uv2()) { // size must have changed, update lightmap size hint Size2i _lightmap_size_hint; - float texel_size = get_lightmap_texel_size(); float padding = get_uv2_padding(); // left_to_right does not effect the surface area of the prism so we ignore that. @@ -1440,7 +1453,6 @@ void PrismMesh::_create_mesh_array(Array &p_arr) const { // Only used if we calculate UV2 bool _add_uv2 = get_add_uv2(); - float texel_size = get_lightmap_texel_size(); float _uv2_padding = get_uv2_padding() * texel_size; float horizontal_total = size.x + size.z + 2.0 * _uv2_padding; @@ -1762,7 +1774,6 @@ void SphereMesh::_update_lightmap_size() { if (get_add_uv2()) { // size must have changed, update lightmap size hint Size2i _lightmap_size_hint; - float texel_size = get_lightmap_texel_size(); float padding = get_uv2_padding(); float _width = radius * Math_TAU; @@ -1776,7 +1787,6 @@ void SphereMesh::_update_lightmap_size() { void SphereMesh::_create_mesh_array(Array &p_arr) const { bool _add_uv2 = get_add_uv2(); - float texel_size = get_lightmap_texel_size(); float _uv2_padding = get_uv2_padding() * texel_size; create_mesh_array(p_arr, radius, height, radial_segments, rings, is_hemisphere, _add_uv2, _uv2_padding); @@ -1950,7 +1960,6 @@ void TorusMesh::_update_lightmap_size() { if (get_add_uv2()) { // size must have changed, update lightmap size hint Size2i _lightmap_size_hint; - float texel_size = get_lightmap_texel_size(); float padding = get_uv2_padding(); float min_radius = inner_radius; @@ -2000,7 +2009,6 @@ void TorusMesh::_create_mesh_array(Array &p_arr) const { // Only used if we calculate UV2 bool _add_uv2 = get_add_uv2(); - float texel_size = get_lightmap_texel_size(); float _uv2_padding = get_uv2_padding() * texel_size; float horizontal_total = max_radius * Math_TAU + _uv2_padding; diff --git a/scene/resources/3d/primitive_meshes.h b/scene/resources/3d/primitive_meshes.h index 4d2d0760b3..fc2489923a 100644 --- a/scene/resources/3d/primitive_meshes.h +++ b/scene/resources/3d/primitive_meshes.h @@ -67,6 +67,9 @@ protected: // assume primitive triangles as the type, correct for all but one and it will change this :) Mesh::PrimitiveType primitive_type = Mesh::PRIMITIVE_TRIANGLES; + // Copy of our texel_size project setting. + float texel_size = 0.2; + static void _bind_methods(); virtual void _create_mesh_array(Array &p_arr) const {} @@ -76,6 +79,8 @@ protected: float get_lightmap_texel_size() const; virtual void _update_lightmap_size(){}; + void _on_settings_changed(); + public: virtual int get_surface_count() const override; virtual int surface_get_array_len(int p_idx) const override; diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index 4bedcb1820..fac3a2f663 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -1825,7 +1825,7 @@ void VisualShader::reset_state() { void VisualShader::_get_property_list(List<PropertyInfo> *p_list) const { //mode - p_list->push_back(PropertyInfo(Variant::INT, PNAME("mode"), PROPERTY_HINT_ENUM, "Node3D,CanvasItem,Particles,Sky,Fog")); + p_list->push_back(PropertyInfo(Variant::INT, PNAME("mode"), PROPERTY_HINT_ENUM, "Spatial,CanvasItem,Particles,Sky,Fog")); //render modes HashMap<String, String> blend_mode_enums; @@ -3004,9 +3004,9 @@ VisualShader::VisualShader() { /////////////////////////////////////////////////////////// const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { - // Node3D + // Spatial - // Node3D, Vertex + // Spatial, Vertex { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "vertex", "VERTEX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR_INT, "vertex_id", "VERTEX_ID" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "NORMAL" }, @@ -3043,7 +3043,7 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_4D, "custom2", "CUSTOM2" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_4D, "custom3", "CUSTOM3" }, - // Node3D, Fragment + // Spatial, Fragment { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "fragcoord", "FRAGCOORD" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "vertex", "VERTEX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "NORMAL" }, @@ -3075,7 +3075,7 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR_UINT, "camera_visible_layers", "CAMERA_VISIBLE_LAYERS" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "node_position_view", "NODE_POSITION_VIEW" }, - // Node3D, Light + // Spatial, Light { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "fragcoord", "FRAGCOORD" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "NORMAL" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, @@ -3883,9 +3883,9 @@ VisualShaderNodeParameterRef::VisualShaderNodeParameterRef() { const VisualShaderNodeOutput::Port VisualShaderNodeOutput::ports[] = { //////////////////////////////////////////////////////////////////////// - // Node3D. + // Spatial. //////////////////////////////////////////////////////////////////////// - // Node3D, Vertex. + // Spatial, Vertex. //////////////////////////////////////////////////////////////////////// { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Vertex", "VERTEX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Normal", "NORMAL" }, @@ -3900,7 +3900,7 @@ const VisualShaderNodeOutput::Port VisualShaderNodeOutput::ports[] = { { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "Model View Matrix", "MODELVIEW_MATRIX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "Projection Matrix", "PROJECTION_MATRIX" }, //////////////////////////////////////////////////////////////////////// - // Node3D, Fragment. + // Spatial, Fragment. //////////////////////////////////////////////////////////////////////// { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Albedo", "ALBEDO" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "Alpha", "ALPHA" }, @@ -3932,7 +3932,7 @@ const VisualShaderNodeOutput::Port VisualShaderNodeOutput::ports[] = { { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "Depth", "DEPTH" }, //////////////////////////////////////////////////////////////////////// - // Node3D, Light. + // Spatial, Light. //////////////////////////////////////////////////////////////////////// { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Diffuse", "DIFFUSE_LIGHT" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Specular", "SPECULAR_LIGHT" }, diff --git a/servers/rendering/renderer_rd/shaders/effects/screen_space_reflection.glsl b/servers/rendering/renderer_rd/shaders/effects/screen_space_reflection.glsl index d629f2738d..b730b2c819 100644 --- a/servers/rendering/renderer_rd/shaders/effects/screen_space_reflection.glsl +++ b/servers/rendering/renderer_rd/shaders/effects/screen_space_reflection.glsl @@ -237,7 +237,7 @@ void main() { // This is an ad-hoc term to fade out the SSR as roughness increases. Values used // are meant to match the visual appearance of a ReflectionProbe. - float roughness_fade = smoothstep(0.4, 0.7, 1.0 - normal_roughness.w); + float roughness_fade = smoothstep(0.4, 0.7, 1.0 - roughness); // Schlick term. float metallic = texelFetch(source_metallic, ssC << 1, 0).w; diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp index 99622996d4..c5454e748a 100644 --- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp @@ -1439,12 +1439,10 @@ void MeshStorage::multimesh_allocate_data(RID p_multimesh, int p_instances, RS:: multimesh->motion_vectors_current_offset = 0; multimesh->motion_vectors_previous_offset = 0; multimesh->motion_vectors_last_change = -1; + multimesh->motion_vectors_enabled = false; if (multimesh->instances) { uint32_t buffer_size = multimesh->instances * multimesh->stride_cache * sizeof(float); - if (multimesh->motion_vectors_enabled) { - buffer_size *= 2; - } multimesh->buffer = RD::get_singleton()->storage_buffer_create(buffer_size); } @@ -1934,6 +1932,7 @@ void MeshStorage::multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_b ERR_FAIL_NULL(multimesh); ERR_FAIL_COND(p_buffer.size() != (multimesh->instances * (int)multimesh->stride_cache)); + bool used_motion_vectors = multimesh->motion_vectors_enabled; bool uses_motion_vectors = (RSG::viewport->get_num_viewports_with_motion_vectors() > 0) || (RendererCompositorStorage::get_singleton()->get_num_compositor_effects_with_motion_vectors() > 0); if (uses_motion_vectors) { _multimesh_enable_motion_vectors(multimesh); @@ -1952,6 +1951,11 @@ void MeshStorage::multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_b { const float *r = p_buffer.ptr(); RD::get_singleton()->buffer_update(multimesh->buffer, multimesh->motion_vectors_current_offset * multimesh->stride_cache * sizeof(float), p_buffer.size() * sizeof(float), r); + if (multimesh->motion_vectors_enabled && !used_motion_vectors) { + // Motion vectors were just enabled, and the other half of the buffer will be empty. + // Need to ensure that both halves are filled for correct operation. + RD::get_singleton()->buffer_update(multimesh->buffer, multimesh->motion_vectors_previous_offset * multimesh->stride_cache * sizeof(float), p_buffer.size() * sizeof(float), r); + } multimesh->buffer_set = true; } diff --git a/servers/rendering/renderer_scene_cull.cpp b/servers/rendering/renderer_scene_cull.cpp index b02d3def88..06753c3fb7 100644 --- a/servers/rendering/renderer_scene_cull.cpp +++ b/servers/rendering/renderer_scene_cull.cpp @@ -34,10 +34,16 @@ #include "core/object/worker_thread_pool.h" #include "core/os/os.h" #include "rendering_light_culler.h" +#include "rendering_server_constants.h" #include "rendering_server_default.h" #include <new> +#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED) +// This is used only to obtain node paths for user-friendly physics interpolation warnings. +#include "scene/main/node.h" +#endif + /* HALTON SEQUENCE */ #ifndef _3D_DISABLED @@ -53,6 +59,20 @@ static float get_halton_value(int p_index, int p_base) { } #endif // _3D_DISABLED +/* EVENT QUEUING */ + +void RendererSceneCull::tick() { + if (_interpolation_data.interpolation_enabled) { + update_interpolation_tick(true); + } +} + +void RendererSceneCull::pre_draw(bool p_will_draw) { + if (_interpolation_data.interpolation_enabled) { + update_interpolation_frame(p_will_draw); + } +} + /* CAMERA API */ RID RendererSceneCull::camera_allocate() { @@ -93,6 +113,7 @@ void RendererSceneCull::camera_set_frustum(RID p_camera, float p_size, Vector2 p void RendererSceneCull::camera_set_transform(RID p_camera, const Transform3D &p_transform) { Camera *camera = camera_owner.get_or_null(p_camera); ERR_FAIL_NULL(camera); + camera->transform = p_transform.orthonormalized(); } @@ -924,8 +945,45 @@ void RendererSceneCull::instance_set_transform(RID p_instance, const Transform3D Instance *instance = instance_owner.get_or_null(p_instance); ERR_FAIL_NULL(instance); - if (instance->transform == p_transform) { - return; //must be checked to avoid worst evil +#ifdef RENDERING_SERVER_DEBUG_PHYSICS_INTERPOLATION + print_line("instance_set_transform " + rtos(p_transform.origin.x) + " .. tick " + itos(Engine::get_singleton()->get_physics_frames())); +#endif + + if (!_interpolation_data.interpolation_enabled || !instance->interpolated || !instance->scenario) { + if (instance->transform == p_transform) { + return; // Must be checked to avoid worst evil. + } + +#ifdef DEBUG_ENABLED + + for (int i = 0; i < 4; i++) { + const Vector3 &v = i < 3 ? p_transform.basis.rows[i] : p_transform.origin; + ERR_FAIL_COND(!v.is_finite()); + } + +#endif + instance->transform = p_transform; + _instance_queue_update(instance, true); + +#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED) + if (_interpolation_data.interpolation_enabled && !instance->interpolated && Engine::get_singleton()->is_in_physics_frame()) { + PHYSICS_INTERPOLATION_NODE_WARNING(instance->object_id, "Non-interpolated instance triggered from physics process"); + } +#endif + + return; + } + + float new_checksum = TransformInterpolator::checksum_transform_3d(p_transform); + bool checksums_match = (instance->transform_checksum_curr == new_checksum) && (instance->transform_checksum_prev == new_checksum); + + // We can't entirely reject no changes because we need the interpolation + // system to keep on stewing. + + // Optimized check. First checks the checksums. If they pass it does the slow check at the end. + // Alternatively we can do this non-optimized and ignore the checksum... if no change. + if (checksums_match && (instance->transform_curr == p_transform) && (instance->transform_prev == p_transform)) { + return; } #ifdef DEBUG_ENABLED @@ -936,8 +994,69 @@ void RendererSceneCull::instance_set_transform(RID p_instance, const Transform3D } #endif - instance->transform = p_transform; + + instance->transform_curr = p_transform; + +#ifdef RENDERING_SERVER_DEBUG_PHYSICS_INTERPOLATION + print_line("\tprev " + rtos(instance->transform_prev.origin.x) + ", curr " + rtos(instance->transform_curr.origin.x)); +#endif + + // Keep checksums up to date. + instance->transform_checksum_curr = new_checksum; + + if (!instance->on_interpolate_transform_list) { + _interpolation_data.instance_transform_update_list_curr->push_back(p_instance); + instance->on_interpolate_transform_list = true; + } else { + DEV_ASSERT(_interpolation_data.instance_transform_update_list_curr->size()); + } + + // If the instance is invisible, then we are simply updating the data flow, there is no need to calculate the interpolated + // transform or anything else. + // Ideally we would not even call the VisualServer::set_transform() when invisible but that would entail having logic + // to keep track of the previous transform on the SceneTree side. The "early out" below is less efficient but a lot cleaner codewise. + if (!instance->visible) { + return; + } + + // Decide on the interpolation method... slerp if possible. + instance->interpolation_method = TransformInterpolator::find_method(instance->transform_prev.basis, instance->transform_curr.basis); + + if (!instance->on_interpolate_list) { + _interpolation_data.instance_interpolate_update_list.push_back(p_instance); + instance->on_interpolate_list = true; + } else { + DEV_ASSERT(_interpolation_data.instance_interpolate_update_list.size()); + } + _instance_queue_update(instance, true); + +#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED) + if (!Engine::get_singleton()->is_in_physics_frame()) { + PHYSICS_INTERPOLATION_NODE_WARNING(instance->object_id, "Interpolated instance triggered from outside physics process"); + } +#endif +} + +void RendererSceneCull::instance_set_interpolated(RID p_instance, bool p_interpolated) { + Instance *instance = instance_owner.get_or_null(p_instance); + ERR_FAIL_NULL(instance); + instance->interpolated = p_interpolated; +} + +void RendererSceneCull::instance_reset_physics_interpolation(RID p_instance) { + Instance *instance = instance_owner.get_or_null(p_instance); + ERR_FAIL_NULL(instance); + + if (_interpolation_data.interpolation_enabled && instance->interpolated) { + instance->transform_prev = instance->transform_curr; + instance->transform_checksum_prev = instance->transform_checksum_curr; + +#ifdef RENDERING_SERVER_DEBUG_PHYSICS_INTERPOLATION + print_line("instance_reset_physics_interpolation .. tick " + itos(Engine::get_singleton()->get_physics_frames())); + print_line("\tprev " + rtos(instance->transform_prev.origin.x) + ", curr " + rtos(instance->transform_curr.origin.x)); +#endif + } } void RendererSceneCull::instance_attach_object_instance_id(RID p_instance, ObjectID p_id) { @@ -990,6 +1109,23 @@ void RendererSceneCull::instance_set_visible(RID p_instance, bool p_visible) { if (p_visible) { if (instance->scenario != nullptr) { + // Special case for physics interpolation, we want to ensure the interpolated data is up to date + if (_interpolation_data.interpolation_enabled && instance->interpolated && !instance->on_interpolate_list) { + // Do all the extra work we normally do on instance_set_transform(), because this is optimized out for hidden instances. + // This prevents a glitch of stale interpolation transform data when unhiding before the next physics tick. + instance->interpolation_method = TransformInterpolator::find_method(instance->transform_prev.basis, instance->transform_curr.basis); + _interpolation_data.instance_interpolate_update_list.push_back(p_instance); + instance->on_interpolate_list = true; + + // We must also place on the transform update list for a tick, so the system + // can auto-detect if the instance is no longer moving, and remove from the interpolate lists again. + // If this step is ignored, an unmoving instance could remain on the interpolate lists indefinitely + // (or rather until the object is deleted) and cause unnecessary updates and drawcalls. + if (!instance->on_interpolate_transform_list) { + _interpolation_data.instance_transform_update_list_curr->push_back(p_instance); + instance->on_interpolate_transform_list = true; + } + } _instance_queue_update(instance, true, false); } } else if (instance->indexer_id.is_valid()) { @@ -1574,11 +1710,22 @@ void RendererSceneCull::instance_geometry_get_shader_parameter_list(RID p_instan void RendererSceneCull::_update_instance(Instance *p_instance) { p_instance->version++; + // When not using interpolation the transform is used straight. + const Transform3D *instance_xform = &p_instance->transform; + + // Can possibly use the most up to date current transform here when using physics interpolation ... + // uncomment the next line for this.. + //if (_interpolation_data.interpolation_enabled && p_instance->interpolated) { + // instance_xform = &p_instance->transform_curr; + //} + // However it does seem that using the interpolated transform (transform) works for keeping AABBs + // up to date to avoid culling errors. + if (p_instance->base_type == RS::INSTANCE_LIGHT) { InstanceLightData *light = static_cast<InstanceLightData *>(p_instance->base_data); - RSG::light_storage->light_instance_set_transform(light->instance, p_instance->transform); - RSG::light_storage->light_instance_set_aabb(light->instance, p_instance->transform.xform(p_instance->aabb)); + RSG::light_storage->light_instance_set_transform(light->instance, *instance_xform); + RSG::light_storage->light_instance_set_aabb(light->instance, instance_xform->xform(p_instance->aabb)); light->make_shadow_dirty(); RS::LightBakeMode bake_mode = RSG::light_storage->light_get_bake_mode(p_instance->base); @@ -1601,7 +1748,7 @@ void RendererSceneCull::_update_instance(Instance *p_instance) { } else if (p_instance->base_type == RS::INSTANCE_REFLECTION_PROBE) { InstanceReflectionProbeData *reflection_probe = static_cast<InstanceReflectionProbeData *>(p_instance->base_data); - RSG::light_storage->reflection_probe_instance_set_transform(reflection_probe->instance, p_instance->transform); + RSG::light_storage->reflection_probe_instance_set_transform(reflection_probe->instance, *instance_xform); if (p_instance->scenario && p_instance->array_index >= 0) { InstanceData &idata = p_instance->scenario->instance_data[p_instance->array_index]; @@ -1610,17 +1757,17 @@ void RendererSceneCull::_update_instance(Instance *p_instance) { } else if (p_instance->base_type == RS::INSTANCE_DECAL) { InstanceDecalData *decal = static_cast<InstanceDecalData *>(p_instance->base_data); - RSG::texture_storage->decal_instance_set_transform(decal->instance, p_instance->transform); + RSG::texture_storage->decal_instance_set_transform(decal->instance, *instance_xform); } else if (p_instance->base_type == RS::INSTANCE_LIGHTMAP) { InstanceLightmapData *lightmap = static_cast<InstanceLightmapData *>(p_instance->base_data); - RSG::light_storage->lightmap_instance_set_transform(lightmap->instance, p_instance->transform); + RSG::light_storage->lightmap_instance_set_transform(lightmap->instance, *instance_xform); } else if (p_instance->base_type == RS::INSTANCE_VOXEL_GI) { InstanceVoxelGIData *voxel_gi = static_cast<InstanceVoxelGIData *>(p_instance->base_data); - scene_render->voxel_gi_instance_set_transform_to_data(voxel_gi->probe_instance, p_instance->transform); + scene_render->voxel_gi_instance_set_transform_to_data(voxel_gi->probe_instance, *instance_xform); } else if (p_instance->base_type == RS::INSTANCE_PARTICLES) { - RSG::particles_storage->particles_set_emission_transform(p_instance->base, p_instance->transform); + RSG::particles_storage->particles_set_emission_transform(p_instance->base, *instance_xform); } else if (p_instance->base_type == RS::INSTANCE_PARTICLES_COLLISION) { InstanceParticlesCollisionData *collision = static_cast<InstanceParticlesCollisionData *>(p_instance->base_data); @@ -1628,13 +1775,13 @@ void RendererSceneCull::_update_instance(Instance *p_instance) { if (RSG::particles_storage->particles_collision_is_heightfield(p_instance->base)) { heightfield_particle_colliders_update_list.insert(p_instance); } - RSG::particles_storage->particles_collision_instance_set_transform(collision->instance, p_instance->transform); + RSG::particles_storage->particles_collision_instance_set_transform(collision->instance, *instance_xform); } else if (p_instance->base_type == RS::INSTANCE_FOG_VOLUME) { InstanceFogVolumeData *volume = static_cast<InstanceFogVolumeData *>(p_instance->base_data); - scene_render->fog_volume_instance_set_transform(volume->instance, p_instance->transform); + scene_render->fog_volume_instance_set_transform(volume->instance, *instance_xform); } else if (p_instance->base_type == RS::INSTANCE_OCCLUDER) { if (p_instance->scenario) { - RendererSceneOcclusionCull::get_singleton()->scenario_set_instance(p_instance->scenario->self, p_instance->self, p_instance->base, p_instance->transform, p_instance->visible); + RendererSceneOcclusionCull::get_singleton()->scenario_set_instance(p_instance->scenario->self, p_instance->self, p_instance->base, *instance_xform, p_instance->visible); } } @@ -1654,7 +1801,7 @@ void RendererSceneCull::_update_instance(Instance *p_instance) { } AABB new_aabb; - new_aabb = p_instance->transform.xform(p_instance->aabb); + new_aabb = instance_xform->xform(p_instance->aabb); p_instance->transformed_aabb = new_aabb; if ((1 << p_instance->base_type) & RS::INSTANCE_GEOMETRY_MASK) { @@ -1681,11 +1828,11 @@ void RendererSceneCull::_update_instance(Instance *p_instance) { } ERR_FAIL_NULL(geom->geometry_instance); - geom->geometry_instance->set_transform(p_instance->transform, p_instance->aabb, p_instance->transformed_aabb); + geom->geometry_instance->set_transform(*instance_xform, p_instance->aabb, p_instance->transformed_aabb); } // note: we had to remove is equal approx check here, it meant that det == 0.000004 won't work, which is the case for some of our scenes. - if (p_instance->scenario == nullptr || !p_instance->visible || p_instance->transform.basis.determinant() == 0) { + if (p_instance->scenario == nullptr || !p_instance->visible || instance_xform->basis.determinant() == 0) { p_instance->prev_transformed_aabb = p_instance->transformed_aabb; return; } @@ -3089,7 +3236,7 @@ void RendererSceneCull::_render_scene(const RendererSceneRender::CameraData *p_c Vector<Instance *> lights_with_shadow; for (Instance *E : scenario->directional_lights) { - if (!E->visible) { + if (!E->visible || !(E->layer_mask & p_visible_layers)) { continue; } @@ -4180,6 +4327,8 @@ bool RendererSceneCull::free(RID p_rid) { Instance *instance = instance_owner.get_or_null(p_rid); + _interpolation_data.notify_free_instance(p_rid, *instance); + instance_geometry_set_lightmap(p_rid, RID(), Rect2(), 0); instance_set_scenario(p_rid, RID()); instance_set_base(p_rid, RID()); @@ -4240,6 +4389,106 @@ void RendererSceneCull::set_scene_render(RendererSceneRender *p_scene_render) { geometry_instance_pair_mask = scene_render->geometry_instance_get_pair_mask(); } +/* INTERPOLATION API */ + +void RendererSceneCull::update_interpolation_tick(bool p_process) { + // TODO (MultiMesh): Update interpolation in storage. + + // INSTANCES + + // Detect any that were on the previous transform list that are no longer active; + // we should remove them from the interpolate list. + + for (const RID &rid : *_interpolation_data.instance_transform_update_list_prev) { + Instance *instance = instance_owner.get_or_null(rid); + + bool active = true; + + // No longer active? (Either the instance deleted or no longer being transformed.) + if (instance && !instance->on_interpolate_transform_list) { + active = false; + instance->on_interpolate_list = false; + + // Make sure the most recent transform is set... + instance->transform = instance->transform_curr; + + // ... and that both prev and current are the same, just in case of any interpolations. + instance->transform_prev = instance->transform_curr; + + // Make sure instances are updated one more time to ensure the AABBs are correct. + _instance_queue_update(instance, true); + } + + if (!instance) { + active = false; + } + + if (!active) { + _interpolation_data.instance_interpolate_update_list.erase(rid); + } + } + + // Now for any in the transform list (being actively interpolated), keep the previous transform + // value up to date, ready for the next tick. + if (p_process) { + for (const RID &rid : *_interpolation_data.instance_transform_update_list_curr) { + Instance *instance = instance_owner.get_or_null(rid); + if (instance) { + instance->transform_prev = instance->transform_curr; + instance->transform_checksum_prev = instance->transform_checksum_curr; + instance->on_interpolate_transform_list = false; + } + } + } + + // We maintain a mirror list for the transform updates, so we can detect when an instance + // is no longer being transformed, and remove it from the interpolate list. + SWAP(_interpolation_data.instance_transform_update_list_curr, _interpolation_data.instance_transform_update_list_prev); + + // Prepare for the next iteration. + _interpolation_data.instance_transform_update_list_curr->clear(); +} + +void RendererSceneCull::update_interpolation_frame(bool p_process) { + // TODO (MultiMesh): Update interpolation in storage. + + if (p_process) { + real_t f = Engine::get_singleton()->get_physics_interpolation_fraction(); + + for (const RID &rid : _interpolation_data.instance_interpolate_update_list) { + Instance *instance = instance_owner.get_or_null(rid); + if (instance) { + TransformInterpolator::interpolate_transform_3d_via_method(instance->transform_prev, instance->transform_curr, instance->transform, f, instance->interpolation_method); + +#ifdef RENDERING_SERVER_DEBUG_PHYSICS_INTERPOLATION + print_line("\t\tinterpolated: " + rtos(instance->transform.origin.x) + "\t( prev " + rtos(instance->transform_prev.origin.x) + ", curr " + rtos(instance->transform_curr.origin.x) + " ) on tick " + itos(Engine::get_singleton()->get_physics_frames())); +#endif + + // Make sure AABBs are constantly up to date through the interpolation. + _instance_queue_update(instance, true); + } + } + } +} + +void RendererSceneCull::set_physics_interpolation_enabled(bool p_enabled) { + _interpolation_data.interpolation_enabled = p_enabled; +} + +void RendererSceneCull::InterpolationData::notify_free_instance(RID p_rid, Instance &r_instance) { + r_instance.on_interpolate_list = false; + r_instance.on_interpolate_transform_list = false; + + if (!interpolation_enabled) { + return; + } + + // If the instance was on any of the lists, remove. + instance_interpolate_update_list.erase_multiple_unordered(p_rid); + instance_transform_update_list_curr->erase_multiple_unordered(p_rid); + instance_transform_update_list_prev->erase_multiple_unordered(p_rid); +} + RendererSceneCull::RendererSceneCull() { render_pass = 1; singleton = this; diff --git a/servers/rendering/renderer_scene_cull.h b/servers/rendering/renderer_scene_cull.h index 0039d14475..53f1f2d246 100644 --- a/servers/rendering/renderer_scene_cull.h +++ b/servers/rendering/renderer_scene_cull.h @@ -32,6 +32,7 @@ #define RENDERER_SCENE_CULL_H #include "core/math/dynamic_bvh.h" +#include "core/math/transform_interpolator.h" #include "core/templates/bin_sorted_array.h" #include "core/templates/local_vector.h" #include "core/templates/paged_allocator.h" @@ -66,6 +67,11 @@ public: static RendererSceneCull *singleton; + /* EVENT QUEUING */ + + void tick(); + void pre_draw(bool p_will_draw); + /* CAMERA API */ struct Camera { @@ -406,8 +412,16 @@ public: RID mesh_instance; //only used for meshes and when skeleton/blendshapes exist + // This is the main transform to be drawn with ... + // This will either be the interpolated transform (when using fixed timestep interpolation) + // or the ONLY transform (when not using FTI). Transform3D transform; + // For interpolation we store the current transform (this physics tick) + // and the transform in the previous tick. + Transform3D transform_curr; + Transform3D transform_prev; + float lod_bias; bool ignore_occlusion_culling; @@ -418,13 +432,23 @@ public: RS::ShadowCastingSetting cast_shadows; uint32_t layer_mask; - //fit in 32 bits - bool mirror : 8; - bool receive_shadows : 8; - bool visible : 8; - bool baked_light : 2; //this flag is only to know if it actually did use baked light - bool dynamic_gi : 2; //same above for dynamic objects - bool redraw_if_visible : 4; + // Fit in 32 bits. + bool mirror : 1; + bool receive_shadows : 1; + bool visible : 1; + bool baked_light : 1; // This flag is only to know if it actually did use baked light. + bool dynamic_gi : 1; // Same as above for dynamic objects. + bool redraw_if_visible : 1; + + bool on_interpolate_list : 1; + bool on_interpolate_transform_list : 1; + bool interpolated : 1; + TransformInterpolator::Method interpolation_method : 3; + + // For fixed timestep interpolation. + // Note 32 bits is plenty for checksum, no need for real_t + float transform_checksum_curr; + float transform_checksum_prev; Instance *lightmap = nullptr; Rect2 lightmap_uv_scale; @@ -574,6 +598,14 @@ public: baked_light = true; dynamic_gi = false; redraw_if_visible = false; + + on_interpolate_list = false; + on_interpolate_transform_list = false; + interpolated = true; + interpolation_method = TransformInterpolator::INTERP_LERP; + transform_checksum_curr = 0.0; + transform_checksum_prev = 0.0; + lightmap_slice_index = 0; lightmap = nullptr; lightmap_cull_index = 0; @@ -1027,6 +1059,8 @@ public: virtual void instance_set_layer_mask(RID p_instance, uint32_t p_mask); virtual void instance_set_pivot_data(RID p_instance, float p_sorting_offset, bool p_use_aabb_center); virtual void instance_set_transform(RID p_instance, const Transform3D &p_transform); + virtual void instance_set_interpolated(RID p_instance, bool p_interpolated); + virtual void instance_reset_physics_interpolation(RID p_instance); virtual void instance_attach_object_instance_id(RID p_instance, ObjectID p_id); virtual void instance_set_blend_shape_weight(RID p_instance, int p_shape, float p_weight); virtual void instance_set_surface_override_material(RID p_instance, int p_surface, RID p_material); @@ -1393,6 +1427,22 @@ public: virtual void update_visibility_notifiers(); + /* INTERPOLATION */ + + void update_interpolation_tick(bool p_process = true); + void update_interpolation_frame(bool p_process = true); + virtual void set_physics_interpolation_enabled(bool p_enabled); + + struct InterpolationData { + void notify_free_instance(RID p_rid, Instance &r_instance); + LocalVector<RID> instance_interpolate_update_list; + LocalVector<RID> instance_transform_update_lists[2]; + LocalVector<RID> *instance_transform_update_list_curr = &instance_transform_update_lists[0]; + LocalVector<RID> *instance_transform_update_list_prev = &instance_transform_update_lists[1]; + + bool interpolation_enabled = false; + } _interpolation_data; + RendererSceneCull(); virtual ~RendererSceneCull(); }; diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp index 22044a8c2d..da48b24a1a 100644 --- a/servers/rendering/rendering_device.cpp +++ b/servers/rendering/rendering_device.cpp @@ -5065,13 +5065,19 @@ void RenderingDevice::swap_buffers() { void RenderingDevice::submit() { _THREAD_SAFE_METHOD_ + ERR_FAIL_COND_MSG(is_main_instance, "Only local devices can submit and sync."); + ERR_FAIL_COND_MSG(local_device_processing, "device already submitted, call sync to wait until done."); _end_frame(); _execute_frame(false); + local_device_processing = true; } void RenderingDevice::sync() { _THREAD_SAFE_METHOD_ + ERR_FAIL_COND_MSG(is_main_instance, "Only local devices can submit and sync."); + ERR_FAIL_COND_MSG(!local_device_processing, "sync can only be called after a submit"); _begin_frame(); + local_device_processing = false; } void RenderingDevice::_free_pending_resources(int p_frame) { @@ -5323,7 +5329,7 @@ Error RenderingDevice::initialize(RenderingContextDriver *p_context, DisplayServ Error err; RenderingContextDriver::SurfaceID main_surface = 0; - const bool main_instance = (singleton == this) && (p_main_window != DisplayServer::INVALID_WINDOW_ID); + is_main_instance = (singleton == this) && (p_main_window != DisplayServer::INVALID_WINDOW_ID); if (p_main_window != DisplayServer::INVALID_WINDOW_ID) { // Retrieve the surface from the main window if it was specified. main_surface = p_context->surface_get_from_window(p_main_window); @@ -5371,7 +5377,7 @@ Error RenderingDevice::initialize(RenderingContextDriver *p_context, DisplayServ err = driver->initialize(device_index, frame_count); ERR_FAIL_COND_V_MSG(err != OK, FAILED, "Failed to initialize driver for device."); - if (main_instance) { + if (is_main_instance) { // Only the singleton instance with a display should print this information. String rendering_method; if (OS::get_singleton()->get_current_rendering_method() == "mobile") { @@ -5499,7 +5505,7 @@ Error RenderingDevice::initialize(RenderingContextDriver *p_context, DisplayServ compute_list = nullptr; bool project_pipeline_cache_enable = GLOBAL_GET("rendering/rendering_device/pipeline_cache/enable"); - if (main_instance && project_pipeline_cache_enable) { + if (is_main_instance && project_pipeline_cache_enable) { // Only the instance that is not a local device and is also the singleton is allowed to manage a pipeline cache. pipeline_cache_file_path = vformat("user://vulkan/pipelines.%s.%s", OS::get_singleton()->get_current_rendering_method(), diff --git a/servers/rendering/rendering_device.h b/servers/rendering/rendering_device.h index 38ffd5d23d..52f086e54f 100644 --- a/servers/rendering/rendering_device.h +++ b/servers/rendering/rendering_device.h @@ -88,6 +88,9 @@ private: RenderingDeviceDriver *driver = nullptr; RenderingContextDriver::Device device; + bool local_device_processing = false; + bool is_main_instance = false; + protected: static void _bind_methods(); diff --git a/servers/rendering/rendering_method.h b/servers/rendering/rendering_method.h index aa5e7d83cc..57fbf97d8c 100644 --- a/servers/rendering/rendering_method.h +++ b/servers/rendering/rendering_method.h @@ -83,6 +83,8 @@ public: virtual void instance_set_layer_mask(RID p_instance, uint32_t p_mask) = 0; virtual void instance_set_pivot_data(RID p_instance, float p_sorting_offset, bool p_use_aabb_center) = 0; virtual void instance_set_transform(RID p_instance, const Transform3D &p_transform) = 0; + virtual void instance_set_interpolated(RID p_instance, bool p_interpolated) = 0; + virtual void instance_reset_physics_interpolation(RID p_instance) = 0; virtual void instance_attach_object_instance_id(RID p_instance, ObjectID p_id) = 0; virtual void instance_set_blend_shape_weight(RID p_instance, int p_shape, float p_weight) = 0; virtual void instance_set_surface_override_material(RID p_instance, int p_surface, RID p_material) = 0; @@ -350,6 +352,16 @@ public: virtual bool free(RID p_rid) = 0; + /* Physics interpolation */ + + virtual void update_interpolation_tick(bool p_process = true) = 0; + virtual void set_physics_interpolation_enabled(bool p_enabled) = 0; + + /* Event queueing */ + + virtual void tick() = 0; + virtual void pre_draw(bool p_will_draw) = 0; + RenderingMethod(); virtual ~RenderingMethod(); }; diff --git a/servers/rendering/rendering_server_constants.h b/servers/rendering/rendering_server_constants.h new file mode 100644 index 0000000000..6d27a3a022 --- /dev/null +++ b/servers/rendering/rendering_server_constants.h @@ -0,0 +1,48 @@ +/**************************************************************************/ +/* rendering_server_constants.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 RENDERING_SERVER_CONSTANTS_H +#define RENDERING_SERVER_CONSTANTS_H + +// Use for constants etc. that need not be included as often as rendering_server.h +// to reduce dependencies and prevent slow compilation. + +// This is a "cheap" include, and can be used from scene side code as well as servers. + +// N.B. ONLY allow these defined in DEV_ENABLED builds, they will slow +// performance, and are only necessary to use for debugging. +#ifdef DEV_ENABLED + +// Uncomment this define to produce debugging output for physics interpolation. +//#define RENDERING_SERVER_DEBUG_PHYSICS_INTERPOLATION + +#endif // DEV_ENABLED + +#endif // RENDERING_SERVER_CONSTANTS_H diff --git a/servers/rendering/rendering_server_default.cpp b/servers/rendering/rendering_server_default.cpp index 51ff009eaf..86efccef9a 100644 --- a/servers/rendering/rendering_server_default.cpp +++ b/servers/rendering/rendering_server_default.cpp @@ -381,12 +381,9 @@ void RenderingServerDefault::_thread_loop() { /* INTERPOLATION */ -void RenderingServerDefault::tick() { - RSG::canvas->tick(); -} - void RenderingServerDefault::set_physics_interpolation_enabled(bool p_enabled) { RSG::canvas->set_physics_interpolation_enabled(p_enabled); + RSG::scene->set_physics_interpolation_enabled(p_enabled); } /* EVENT QUEUING */ @@ -411,6 +408,15 @@ void RenderingServerDefault::draw(bool p_swap_buffers, double frame_step) { } } +void RenderingServerDefault::tick() { + RSG::canvas->tick(); + RSG::scene->tick(); +} + +void RenderingServerDefault::pre_draw(bool p_will_draw) { + RSG::scene->pre_draw(p_will_draw); +} + void RenderingServerDefault::_call_on_render_thread(const Callable &p_callable) { p_callable.call(); } diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h index 164ec3cc09..6e195a8002 100644 --- a/servers/rendering/rendering_server_default.h +++ b/servers/rendering/rendering_server_default.h @@ -802,6 +802,8 @@ public: FUNC2(instance_set_layer_mask, RID, uint32_t) FUNC3(instance_set_pivot_data, RID, float, bool) FUNC2(instance_set_transform, RID, const Transform3D &) + FUNC2(instance_set_interpolated, RID, bool) + FUNC1(instance_reset_physics_interpolation, RID) FUNC2(instance_attach_object_instance_id, RID, ObjectID) FUNC3(instance_set_blend_shape_weight, RID, int, float) FUNC3(instance_set_surface_override_material, RID, int, RID) @@ -1048,7 +1050,6 @@ public: /* INTERPOLATION */ - virtual void tick() override; virtual void set_physics_interpolation_enabled(bool p_enabled) override; /* EVENT QUEUING */ @@ -1060,6 +1061,8 @@ public: virtual bool has_changed() const override; virtual void init() override; virtual void finish() override; + virtual void tick() override; + virtual void pre_draw(bool p_will_draw) override; virtual bool is_on_render_thread() override { return Thread::get_caller_id() == server_thread; diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp index 0235d72cfa..431dca0ad4 100644 --- a/servers/rendering/shader_language.cpp +++ b/servers/rendering/shader_language.cpp @@ -9217,6 +9217,7 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f tk = _get_token(); if (tk.type == TK_IDENTIFIER) { current_uniform_group_name = tk.text; + current_uniform_subgroup_name = ""; tk = _get_token(); if (tk.type == TK_PERIOD) { tk = _get_token(); diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp index 70b585d683..9fc67b04b1 100644 --- a/servers/rendering_server.cpp +++ b/servers/rendering_server.cpp @@ -3116,6 +3116,8 @@ void RenderingServer::_bind_methods() { ClassDB::bind_method(D_METHOD("instance_set_layer_mask", "instance", "mask"), &RenderingServer::instance_set_layer_mask); ClassDB::bind_method(D_METHOD("instance_set_pivot_data", "instance", "sorting_offset", "use_aabb_center"), &RenderingServer::instance_set_pivot_data); ClassDB::bind_method(D_METHOD("instance_set_transform", "instance", "transform"), &RenderingServer::instance_set_transform); + ClassDB::bind_method(D_METHOD("instance_set_interpolated", "instance", "interpolated"), &RenderingServer::instance_set_interpolated); + ClassDB::bind_method(D_METHOD("instance_reset_physics_interpolation", "instance"), &RenderingServer::instance_reset_physics_interpolation); ClassDB::bind_method(D_METHOD("instance_attach_object_instance_id", "instance", "id"), &RenderingServer::instance_attach_object_instance_id); ClassDB::bind_method(D_METHOD("instance_set_blend_shape_weight", "instance", "shape", "weight"), &RenderingServer::instance_set_blend_shape_weight); ClassDB::bind_method(D_METHOD("instance_set_surface_override_material", "instance", "surface", "material"), &RenderingServer::instance_set_surface_override_material); diff --git a/servers/rendering_server.h b/servers/rendering_server.h index 693c822488..8450cb0198 100644 --- a/servers/rendering_server.h +++ b/servers/rendering_server.h @@ -1334,6 +1334,8 @@ public: virtual void instance_set_layer_mask(RID p_instance, uint32_t p_mask) = 0; virtual void instance_set_pivot_data(RID p_instance, float p_sorting_offset, bool p_use_aabb_center) = 0; virtual void instance_set_transform(RID p_instance, const Transform3D &p_transform) = 0; + virtual void instance_set_interpolated(RID p_instance, bool p_interpolated) = 0; + virtual void instance_reset_physics_interpolation(RID p_instance) = 0; virtual void instance_attach_object_instance_id(RID p_instance, ObjectID p_id) = 0; virtual void instance_set_blend_shape_weight(RID p_instance, int p_shape, float p_weight) = 0; virtual void instance_set_surface_override_material(RID p_instance, int p_surface, RID p_material) = 0; @@ -1647,7 +1649,6 @@ public: /* INTERPOLATION */ - virtual void tick() = 0; virtual void set_physics_interpolation_enabled(bool p_enabled) = 0; /* EVENT QUEUING */ @@ -1659,6 +1660,8 @@ public: virtual bool has_changed() const = 0; virtual void init(); virtual void finish() = 0; + virtual void tick() = 0; + virtual void pre_draw(bool p_will_draw) = 0; /* STATUS INFORMATION */ diff --git a/thirdparty/README.md b/thirdparty/README.md index 9a56f6baa4..47618d675b 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -845,7 +845,7 @@ Files extracted from upstream source: Some downstream changes have been made and are identified by `// -- GODOT begin --` and `// -- GODOT end --` comments. They can be reapplied using the patches included in the `patches` -folder. +folder, in order. ## squish diff --git a/thirdparty/spirv-reflect/patches/specialization-constants.patch b/thirdparty/spirv-reflect/patches/1-specialization-constants.patch index ff11841451..ff11841451 100644 --- a/thirdparty/spirv-reflect/patches/specialization-constants.patch +++ b/thirdparty/spirv-reflect/patches/1-specialization-constants.patch diff --git a/thirdparty/spirv-reflect/patches/2-zero-size-for-sc-sized-arrays.patch b/thirdparty/spirv-reflect/patches/2-zero-size-for-sc-sized-arrays.patch new file mode 100644 index 0000000000..dbf6069d07 --- /dev/null +++ b/thirdparty/spirv-reflect/patches/2-zero-size-for-sc-sized-arrays.patch @@ -0,0 +1,18 @@ +diff --git a/thirdparty/spirv-reflect/spirv_reflect.c b/thirdparty/spirv-reflect/spirv_reflect.c +index c96dd85439..2ca9c8580d 100644 +--- a/thirdparty/spirv-reflect/spirv_reflect.c ++++ b/thirdparty/spirv-reflect/spirv_reflect.c +@@ -2692,6 +2692,13 @@ static SpvReflectResult ParseDescriptorBlockVariableSizes(SpvReflectPrvParser* p + // ...then array + uint32_t element_count = (p_member_var->array.dims_count > 0 ? 1 : 0); + for (uint32_t i = 0; i < p_member_var->array.dims_count; ++i) { ++// -- GODOT begin -- ++ if (p_member_var->array.spec_constant_op_ids[i] != (uint32_t)INVALID_VALUE) { ++ // Force size to be reported as 0 to effectively disable buffer size validation, since ++ // the value is unreliable anyway as only valid for the default values of the SCs involved. ++ element_count = 0; ++ } ++// -- GODOT end -- + element_count *= p_member_var->array.dims[i]; + } + p_member_var->size = element_count * p_member_var->array.stride; diff --git a/thirdparty/spirv-reflect/spirv_reflect.c b/thirdparty/spirv-reflect/spirv_reflect.c index c96dd85439..d6c926b40a 100644 --- a/thirdparty/spirv-reflect/spirv_reflect.c +++ b/thirdparty/spirv-reflect/spirv_reflect.c @@ -2692,6 +2692,13 @@ static SpvReflectResult ParseDescriptorBlockVariableSizes(SpvReflectPrvParser* p // ...then array uint32_t element_count = (p_member_var->array.dims_count > 0 ? 1 : 0); for (uint32_t i = 0; i < p_member_var->array.dims_count; ++i) { +// -- GODOT begin -- + if (p_member_var->array.spec_constant_op_ids[i] != (uint32_t)INVALID_VALUE) { + // Force size to be reported as 0 to effectively disable buffer size validation, since + // the value is unreliable anyway as only valid for the default values of the SCs involved. + element_count = 0; + } +// -- GODOT end -- element_count *= p_member_var->array.dims[i]; } p_member_var->size = element_count * p_member_var->array.stride; |
