diff --git a/scene/2d/cpu_particles_2d.cpp b/scene/2d/cpu_particles_2d.cpp index e04e6d7dce30..cbbc1e521c1f 100644 --- a/scene/2d/cpu_particles_2d.cpp +++ b/scene/2d/cpu_particles_2d.cpp @@ -53,6 +53,7 @@ void CPUParticles2D::set_amount(int p_amount) { ERR_FAIL_COND_MSG(p_amount < 1, "Amount of particles must be greater than 0."); particles.resize(p_amount); + particles_prev.resize(p_amount); { Particle *w = particles.ptrw(); @@ -62,6 +63,7 @@ void CPUParticles2D::set_amount(int p_amount) { } particle_data.resize((8 + 4 + 4) * p_amount); + particle_data_prev.resize(particle_data.size()); RS::get_singleton()->multimesh_allocate_data(multimesh, p_amount, RS::MULTIMESH_TRANSFORM_2D, true, true); particle_order.resize(p_amount); @@ -94,7 +96,10 @@ void CPUParticles2D::set_lifetime_randomness(double p_random) { void CPUParticles2D::set_use_local_coordinates(bool p_enable) { local_coords = p_enable; + // Prevent sending item transforms when using global coordinates, + // and inform RenderingServer to use identity mode. set_notify_transform(!p_enable); + set_use_identity_transform(!p_enable); } void CPUParticles2D::set_speed_scale(double p_scale) { @@ -226,6 +231,31 @@ void CPUParticles2D::_texture_changed() { } } +void CPUParticles2D::_refresh_interpolation_state() { + if (!is_inside_tree()) { + return; + } + bool interpolated = is_physics_interpolated_and_enabled(); + + if (_interpolated == interpolated) { + return; + } + + bool curr_redraw = do_redraw; + + // Remove all connections. + // This isn't very efficient, but should only happen rarely. + _set_do_redraw(false); + + _interpolated = interpolated; + + set_process_internal(!_interpolated); + set_physics_process_internal(!_interpolated); + + // Re-establish all connections. + _set_do_redraw(curr_redraw); +} + Ref CPUParticles2D::get_texture() const { return texture; } @@ -557,13 +587,23 @@ static real_t rand_from_seed(uint32_t &seed) { return (seed % uint32_t(65536)) / 65535.0; } -void CPUParticles2D::_update_internal() { +void CPUParticles2D::_update_internal(bool p_on_physics_tick) { if (particles.size() == 0 || !is_visible_in_tree()) { _set_do_redraw(false); return; } - double delta = get_process_delta_time(); + // Change update mode? + _refresh_interpolation_state(); + + // Is this update occurring on a physics tick (i.e. interpolated), or on a frame tick? + double delta = 0.0; + if (p_on_physics_tick) { + delta = get_physics_process_delta_time(); + } else { + delta = get_process_delta_time(); + } + if (!active && !emitting) { set_process_internal(false); _set_do_redraw(false); @@ -616,6 +656,12 @@ void CPUParticles2D::_update_internal() { } _update_particle_data_buffer(); + + // If we are interpolating, we send the data to the RenderingServer + // right away on a physics tick instead of waiting until a render frame. + if (p_on_physics_tick && do_redraw) { + _update_render_thread(); + } } void CPUParticles2D::_particles_process(double p_delta) { @@ -623,6 +669,7 @@ void CPUParticles2D::_particles_process(double p_delta) { int pcount = particles.size(); Particle *w = particles.ptrw(); + Particle *w_prev = particles_prev.ptrw(); Particle *parray = w; @@ -655,6 +702,12 @@ void CPUParticles2D::_particles_process(double p_delta) { continue; } + // For interpolation we need to keep a record of previous particles. + if (_interpolated) { + DEV_ASSERT(particles.size() == particles_prev.size()); + p.copy_to(w_prev[i]); + } + double local_delta = p_delta; // The phase is a ratio between 0 (birth) and 1 (end of life) for each particle. @@ -815,106 +868,10 @@ void CPUParticles2D::_particles_process(double p_delta) { p.active = false; tv = 1.0; } else { - uint32_t alt_seed = p.seed; - - p.time += local_delta; - p.custom[1] = p.time / lifetime; - tv = p.time / p.lifetime; - - real_t tex_linear_velocity = 1.0; - if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { - tex_linear_velocity = curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY]->sample(tv); - } - - real_t tex_orbit_velocity = 1.0; - if (curve_parameters[PARAM_ORBIT_VELOCITY].is_valid()) { - tex_orbit_velocity = curve_parameters[PARAM_ORBIT_VELOCITY]->sample(tv); - } - - real_t tex_angular_velocity = 1.0; - if (curve_parameters[PARAM_ANGULAR_VELOCITY].is_valid()) { - tex_angular_velocity = curve_parameters[PARAM_ANGULAR_VELOCITY]->sample(tv); - } - - real_t tex_linear_accel = 1.0; - if (curve_parameters[PARAM_LINEAR_ACCEL].is_valid()) { - tex_linear_accel = curve_parameters[PARAM_LINEAR_ACCEL]->sample(tv); - } - - real_t tex_tangential_accel = 1.0; - if (curve_parameters[PARAM_TANGENTIAL_ACCEL].is_valid()) { - tex_tangential_accel = curve_parameters[PARAM_TANGENTIAL_ACCEL]->sample(tv); - } - - real_t tex_radial_accel = 1.0; - if (curve_parameters[PARAM_RADIAL_ACCEL].is_valid()) { - tex_radial_accel = curve_parameters[PARAM_RADIAL_ACCEL]->sample(tv); - } - - real_t tex_damping = 1.0; - if (curve_parameters[PARAM_DAMPING].is_valid()) { - tex_damping = curve_parameters[PARAM_DAMPING]->sample(tv); - } - - real_t tex_angle = 1.0; - if (curve_parameters[PARAM_ANGLE].is_valid()) { - tex_angle = curve_parameters[PARAM_ANGLE]->sample(tv); - } - real_t tex_anim_speed = 1.0; - if (curve_parameters[PARAM_ANIM_SPEED].is_valid()) { - tex_anim_speed = curve_parameters[PARAM_ANIM_SPEED]->sample(tv); - } - - real_t tex_anim_offset = 1.0; - if (curve_parameters[PARAM_ANIM_OFFSET].is_valid()) { - tex_anim_offset = curve_parameters[PARAM_ANIM_OFFSET]->sample(tv); - } - - Vector2 force = gravity; - Vector2 pos = p.transform[2]; - - //apply linear acceleration - force += p.velocity.length() > 0.0 ? p.velocity.normalized() * tex_linear_accel * Math::lerp(parameters_min[PARAM_LINEAR_ACCEL], parameters_max[PARAM_LINEAR_ACCEL], rand_from_seed(alt_seed)) : Vector2(); - //apply radial acceleration - Vector2 org = emission_xform[2]; - Vector2 diff = pos - org; - force += diff.length() > 0.0 ? diff.normalized() * (tex_radial_accel)*Math::lerp(parameters_min[PARAM_RADIAL_ACCEL], parameters_max[PARAM_RADIAL_ACCEL], rand_from_seed(alt_seed)) : Vector2(); - //apply tangential acceleration; - Vector2 yx = Vector2(diff.y, diff.x); - force += yx.length() > 0.0 ? (yx * Vector2(-1.0, 1.0)).normalized() * (tex_tangential_accel * Math::lerp(parameters_min[PARAM_TANGENTIAL_ACCEL], parameters_max[PARAM_TANGENTIAL_ACCEL], rand_from_seed(alt_seed))) : Vector2(); - //apply attractor forces - p.velocity += force * local_delta; - //orbit velocity - real_t orbit_amount = tex_orbit_velocity * Math::lerp(parameters_min[PARAM_ORBIT_VELOCITY], parameters_max[PARAM_ORBIT_VELOCITY], rand_from_seed(alt_seed)); - if (orbit_amount != 0.0) { - real_t ang = orbit_amount * local_delta * Math_TAU; - // Not sure why the ParticleProcessMaterial code uses a clockwise rotation matrix, - // but we use -ang here to reproduce its behavior. - Transform2D rot = Transform2D(-ang, Vector2()); - p.transform[2] -= diff; - p.transform[2] += rot.basis_xform(diff); - } - if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { - p.velocity = p.velocity.normalized() * tex_linear_velocity; - } - - if (parameters_max[PARAM_DAMPING] + tex_damping > 0.0) { - real_t v = p.velocity.length(); - real_t damp = tex_damping * Math::lerp(parameters_min[PARAM_DAMPING], parameters_max[PARAM_DAMPING], rand_from_seed(alt_seed)); - v -= damp * local_delta; - if (v < 0.0) { - p.velocity = Vector2(); - } else { - p.velocity = p.velocity.normalized() * v; - } - } - real_t base_angle = (tex_angle)*Math::lerp(parameters_min[PARAM_ANGLE], parameters_max[PARAM_ANGLE], p.angle_rand); - base_angle += p.custom[1] * lifetime * tex_angular_velocity * Math::lerp(parameters_min[PARAM_ANGULAR_VELOCITY], parameters_max[PARAM_ANGULAR_VELOCITY], rand_from_seed(alt_seed)); - p.rotation = Math::deg_to_rad(base_angle); //angle - p.custom[2] = tex_anim_offset * Math::lerp(parameters_min[PARAM_ANIM_OFFSET], parameters_max[PARAM_ANIM_OFFSET], p.anim_offset_rand) + tv * tex_anim_speed * Math::lerp(parameters_min[PARAM_ANIM_SPEED], parameters_max[PARAM_ANIM_SPEED], rand_from_seed(alt_seed)); + _particle_process(p, emission_xform, local_delta, tv); } - //apply color - //apply hue rotation + // Apply color. + // Apply hue rotation. Vector2 tex_scale = Vector2(1.0, 1.0); if (split_scale) { @@ -980,7 +937,7 @@ void CPUParticles2D::_particles_process(double p_delta) { p.transform.columns[1] = Vector2(Math::sin(p.rotation), Math::cos(p.rotation)); } - //scale by scale + // Scale by scale. Vector2 base_scale = tex_scale * Math::lerp(parameters_min[PARAM_SCALE], parameters_max[PARAM_SCALE], p.scale_rand); if (base_scale.x < 0.00001) { base_scale.x = 0.00001; @@ -993,6 +950,11 @@ void CPUParticles2D::_particles_process(double p_delta) { p.transform[2] += p.velocity * local_delta; + // Teleport if starting a new particle, so we don't get a streak from the old position to this new start. + if (restart && _interpolated) { + p.copy_to(w_prev[i]); + } + should_be_active = true; } if (!Math::is_equal_approx(time, 0.0) && active && !should_be_active) { @@ -1001,6 +963,106 @@ void CPUParticles2D::_particles_process(double p_delta) { } } +void CPUParticles2D::_particle_process(Particle &r_p, const Transform2D &p_emission_xform, double p_local_delta, float &r_tv) { + uint32_t alt_seed = r_p.seed; + + r_p.time += p_local_delta; + r_p.custom[1] = r_p.time / lifetime; + r_tv = r_p.time / r_p.lifetime; + + real_t tex_linear_velocity = 1.0; + if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { + tex_linear_velocity = curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY]->sample(r_tv); + } + + real_t tex_orbit_velocity = 1.0; + if (curve_parameters[PARAM_ORBIT_VELOCITY].is_valid()) { + tex_orbit_velocity = curve_parameters[PARAM_ORBIT_VELOCITY]->sample(r_tv); + } + + real_t tex_angular_velocity = 1.0; + if (curve_parameters[PARAM_ANGULAR_VELOCITY].is_valid()) { + tex_angular_velocity = curve_parameters[PARAM_ANGULAR_VELOCITY]->sample(r_tv); + } + + real_t tex_linear_accel = 1.0; + if (curve_parameters[PARAM_LINEAR_ACCEL].is_valid()) { + tex_linear_accel = curve_parameters[PARAM_LINEAR_ACCEL]->sample(r_tv); + } + + real_t tex_tangential_accel = 1.0; + if (curve_parameters[PARAM_TANGENTIAL_ACCEL].is_valid()) { + tex_tangential_accel = curve_parameters[PARAM_TANGENTIAL_ACCEL]->sample(r_tv); + } + + real_t tex_radial_accel = 1.0; + if (curve_parameters[PARAM_RADIAL_ACCEL].is_valid()) { + tex_radial_accel = curve_parameters[PARAM_RADIAL_ACCEL]->sample(r_tv); + } + + real_t tex_damping = 1.0; + if (curve_parameters[PARAM_DAMPING].is_valid()) { + tex_damping = curve_parameters[PARAM_DAMPING]->sample(r_tv); + } + + real_t tex_angle = 1.0; + if (curve_parameters[PARAM_ANGLE].is_valid()) { + tex_angle = curve_parameters[PARAM_ANGLE]->sample(r_tv); + } + real_t tex_anim_speed = 1.0; + if (curve_parameters[PARAM_ANIM_SPEED].is_valid()) { + tex_anim_speed = curve_parameters[PARAM_ANIM_SPEED]->sample(r_tv); + } + + real_t tex_anim_offset = 1.0; + if (curve_parameters[PARAM_ANIM_OFFSET].is_valid()) { + tex_anim_offset = curve_parameters[PARAM_ANIM_OFFSET]->sample(r_tv); + } + + Vector2 force = gravity; + Vector2 pos = r_p.transform[2]; + + // Apply linear acceleration. + force += r_p.velocity.length() > 0.0 ? r_p.velocity.normalized() * tex_linear_accel * Math::lerp(parameters_min[PARAM_LINEAR_ACCEL], parameters_max[PARAM_LINEAR_ACCEL], rand_from_seed(alt_seed)) : Vector2(); + // Apply radial acceleration. + Vector2 org = p_emission_xform[2]; + Vector2 diff = pos - org; + force += diff.length() > 0.0 ? diff.normalized() * (tex_radial_accel)*Math::lerp(parameters_min[PARAM_RADIAL_ACCEL], parameters_max[PARAM_RADIAL_ACCEL], rand_from_seed(alt_seed)) : Vector2(); + // Apply tangential acceleration. + Vector2 yx = Vector2(diff.y, diff.x); + force += yx.length() > 0.0 ? (yx * Vector2(-1.0, 1.0)).normalized() * (tex_tangential_accel * Math::lerp(parameters_min[PARAM_TANGENTIAL_ACCEL], parameters_max[PARAM_TANGENTIAL_ACCEL], rand_from_seed(alt_seed))) : Vector2(); + // Apply attractor forces. + r_p.velocity += force * p_local_delta; + // Orbit velocity. + real_t orbit_amount = tex_orbit_velocity * Math::lerp(parameters_min[PARAM_ORBIT_VELOCITY], parameters_max[PARAM_ORBIT_VELOCITY], rand_from_seed(alt_seed)); + if (orbit_amount != 0.0) { + real_t ang = orbit_amount * p_local_delta * Math_TAU; + // Not sure why the ParticleProcessMaterial code uses a clockwise rotation matrix, + // but we use -ang here to reproduce its behavior. + Transform2D rot = Transform2D(-ang, Vector2()); + r_p.transform[2] -= diff; + r_p.transform[2] += rot.basis_xform(diff); + } + if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { + r_p.velocity = r_p.velocity.normalized() * tex_linear_velocity; + } + + if (parameters_max[PARAM_DAMPING] + tex_damping > 0.0) { + real_t v = r_p.velocity.length(); + real_t damp = tex_damping * Math::lerp(parameters_min[PARAM_DAMPING], parameters_max[PARAM_DAMPING], rand_from_seed(alt_seed)); + v -= damp * p_local_delta; + if (v < 0.0) { + r_p.velocity = Vector2(); + } else { + r_p.velocity = r_p.velocity.normalized() * v; + } + } + real_t base_angle = (tex_angle)*Math::lerp(parameters_min[PARAM_ANGLE], parameters_max[PARAM_ANGLE], r_p.angle_rand); + base_angle += r_p.custom[1] * lifetime * tex_angular_velocity * Math::lerp(parameters_min[PARAM_ANGULAR_VELOCITY], parameters_max[PARAM_ANGULAR_VELOCITY], rand_from_seed(alt_seed)); + r_p.rotation = Math::deg_to_rad(base_angle); //angle + r_p.custom[2] = tex_anim_offset * Math::lerp(parameters_min[PARAM_ANIM_OFFSET], parameters_max[PARAM_ANIM_OFFSET], r_p.anim_offset_rand) + r_tv * tex_anim_speed * Math::lerp(parameters_min[PARAM_ANIM_SPEED], parameters_max[PARAM_ANIM_SPEED], rand_from_seed(alt_seed)); +} + void CPUParticles2D::_update_particle_data_buffer() { MutexLock lock(update_mutex); @@ -1013,6 +1075,13 @@ void CPUParticles2D::_update_particle_data_buffer() { const Particle *r = particles.ptr(); float *ptr = w; + float *w_prev = nullptr; + + if (_interpolated) { + DEV_ASSERT(particle_data.size() == particle_data_prev.size()); + w_prev = particle_data_prev.ptrw(); + } + if (draw_order != DRAW_ORDER_INDEX) { ow = particle_order.ptrw(); order = ow; @@ -1027,42 +1096,20 @@ void CPUParticles2D::_update_particle_data_buffer() { } } - for (int i = 0; i < pc; i++) { - int idx = order ? order[i] : i; - - Transform2D t = r[idx].transform; - - if (!local_coords) { - t = inv_emission_transform * t; + if (_interpolated) { + for (int i = 0; i < pc; i++) { + int idx = order ? order[i] : i; + _fill_particle_data(r[idx], ptr, r[idx].active); + ptr += 16; + _fill_particle_data(particles_prev[idx], w_prev, r[idx].active); + w_prev += 16; } - - if (r[idx].active) { - ptr[0] = t.columns[0][0]; - ptr[1] = t.columns[1][0]; - ptr[2] = 0; - ptr[3] = t.columns[2][0]; - ptr[4] = t.columns[0][1]; - ptr[5] = t.columns[1][1]; - ptr[6] = 0; - ptr[7] = t.columns[2][1]; - - } else { - memset(ptr, 0, sizeof(float) * 8); + } else { + for (int i = 0; i < pc; i++) { + int idx = order ? order[i] : i; + _fill_particle_data(r[idx], ptr, r[idx].active); + ptr += 16; } - - Color c = r[idx].color; - - ptr[8] = c.r; - ptr[9] = c.g; - ptr[10] = c.b; - ptr[11] = c.a; - - ptr[12] = r[idx].custom[0]; - ptr[13] = r[idx].custom[1]; - ptr[14] = r[idx].custom[2]; - ptr[15] = r[idx].custom[3]; - - ptr += 16; } } @@ -1075,34 +1122,56 @@ void CPUParticles2D::_set_do_redraw(bool p_do_redraw) { { MutexLock lock(update_mutex); + if (!_interpolated) { + if (do_redraw) { + RS::get_singleton()->connect("frame_pre_draw", callable_mp(this, &CPUParticles2D::_update_render_thread)); + } else { + if (RS::get_singleton()->is_connected("frame_pre_draw", callable_mp(this, &CPUParticles2D::_update_render_thread))) { + RS::get_singleton()->disconnect("frame_pre_draw", callable_mp(this, &CPUParticles2D::_update_render_thread)); + } + } + } + if (do_redraw) { - RS::get_singleton()->connect("frame_pre_draw", callable_mp(this, &CPUParticles2D::_update_render_thread)); RS::get_singleton()->canvas_item_set_update_when_visible(get_canvas_item(), true); RS::get_singleton()->multimesh_set_visible_instances(multimesh, -1); } else { - if (RS::get_singleton()->is_connected("frame_pre_draw", callable_mp(this, &CPUParticles2D::_update_render_thread))) { - RS::get_singleton()->disconnect("frame_pre_draw", callable_mp(this, &CPUParticles2D::_update_render_thread)); - } RS::get_singleton()->canvas_item_set_update_when_visible(get_canvas_item(), false); RS::get_singleton()->multimesh_set_visible_instances(multimesh, 0); } } - queue_redraw(); // redraw to update render list + queue_redraw(); // Redraw to update render list. } void CPUParticles2D::_update_render_thread() { MutexLock lock(update_mutex); - RS::get_singleton()->multimesh_set_buffer(multimesh, particle_data); + if (_interpolated) { + // TODO: Implement the actual MultiMesh interpolation. + //RS::get_singleton()->multimesh_set_buffer_interpolated(multimesh, particle_data, particle_data_prev); + } else { + RS::get_singleton()->multimesh_set_buffer(multimesh, particle_data); + } } void CPUParticles2D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { set_process_internal(emitting); + + // For interpolated version to update the particles right away, + // we need a sequence of events. + // First ensure we are in _interpolated mode if the Node is set to interpolated. + _refresh_interpolation_state(); + + // Now, if we are interpolating, we want to force a single tick update. + // If we don't do this, it may be an entire tick before the first update happens. + if (_interpolated) { + _update_internal(true); + } } break; case NOTIFICATION_EXIT_TREE: { @@ -1110,13 +1179,13 @@ void CPUParticles2D::_notification(int p_what) { } break; case NOTIFICATION_DRAW: { - // first update before rendering to avoid one frame delay after emitting starts - if (emitting && (time == 0)) { - _update_internal(); + // First update before rendering to avoid one frame delay after emitting starts. + if (emitting && (time == 0) && !_interpolated) { + _update_internal(false); } if (!do_redraw) { - return; // don't add to render list + return; // Don't add to render list. } RID texrid; @@ -1128,7 +1197,7 @@ void CPUParticles2D::_notification(int p_what) { } break; case NOTIFICATION_INTERNAL_PROCESS: { - _update_internal(); + _update_internal(false); } break; case NOTIFICATION_TRANSFORM_CHANGED: { diff --git a/scene/2d/cpu_particles_2d.h b/scene/2d/cpu_particles_2d.h index 3f858c327763..98d0ff836760 100644 --- a/scene/2d/cpu_particles_2d.h +++ b/scene/2d/cpu_particles_2d.h @@ -80,10 +80,20 @@ class CPUParticles2D : public Node2D { bool emitting = false; bool active = false; - struct Particle { + struct ParticleBase { Transform2D transform; Color color; real_t custom[4] = {}; + void blank() { + for (int n = 0; n < 4; n++) { + custom[n] = real_t(0.0); + } + } + }; + + // Warning - beware of adding non-trivial types to this structure + // as it is zeroed to initialize in set_amount(). + struct Particle : public ParticleBase { real_t rotation = 0.0; Vector2 velocity; bool active = false; @@ -97,6 +107,12 @@ class CPUParticles2D : public Node2D { Color base_color; uint32_t seed = 0; + + void copy_to(ParticleBase &r_other) { + r_other.transform = transform; + r_other.color = color; + memcpy(r_other.custom, custom, sizeof(real_t) * 4); + } }; double time = 0.0; @@ -108,7 +124,9 @@ class CPUParticles2D : public Node2D { RID multimesh; Vector particles; + Vector particles_prev; Vector particle_data; + Vector particle_data_prev; Vector particle_order; struct SortLifetime { @@ -176,12 +194,15 @@ class CPUParticles2D : public Node2D { Vector2 gravity = Vector2(0, 980); - void _update_internal(); + void _update_internal(bool p_on_physics_tick); void _particles_process(double p_delta); + void _particle_process(Particle &r_p, const Transform2D &p_emission_xform, double p_local_delta, float &r_tv); void _update_particle_data_buffer(); Mutex update_mutex; + bool _interpolated = false; + void _update_render_thread(); void _update_mesh_texture(); @@ -190,6 +211,37 @@ class CPUParticles2D : public Node2D { void _texture_changed(); + void _refresh_interpolation_state(); + + void _fill_particle_data(const ParticleBase &p_source, float *r_dest, bool p_active) const { + if (p_active) { + Transform2D t = p_source.transform; + + r_dest[0] = t.columns[0][0]; + r_dest[1] = t.columns[1][0]; + r_dest[2] = 0; + r_dest[3] = t.columns[2][0]; + r_dest[4] = t.columns[0][1]; + r_dest[5] = t.columns[1][1]; + r_dest[6] = 0; + r_dest[7] = t.columns[2][1]; + + Color c = p_source.color; + + r_dest[8] = c.r; + r_dest[9] = c.g; + r_dest[10] = c.b; + r_dest[11] = c.a; + + r_dest[12] = p_source.custom[0]; + r_dest[13] = p_source.custom[1]; + r_dest[14] = p_source.custom[2]; + r_dest[15] = p_source.custom[3]; + } else { + memset(r_dest, 0, sizeof(float) * 16); + } + } + protected: static void _bind_methods(); void _notification(int p_what); diff --git a/scene/2d/node_2d.cpp b/scene/2d/node_2d.cpp index aae7eff7bd0b..0e252d63a770 100644 --- a/scene/2d/node_2d.cpp +++ b/scene/2d/node_2d.cpp @@ -374,7 +374,9 @@ void Node2D::set_transform(const Transform2D &p_transform) { transform = p_transform; _set_xform_dirty(true); - RenderingServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), transform); + if (!_is_using_identity_transform()) { + RenderingServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), transform); + } _notify_transform(); } diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp index 768c83954bc6..a7d69f6b1a95 100644 --- a/scene/main/canvas_item.cpp +++ b/scene/main/canvas_item.cpp @@ -549,6 +549,24 @@ int CanvasItem::get_light_mask() const { return light_mask; } +void CanvasItem::set_use_identity_transform(bool p_enable) { + // Prevent sending item transforms to RenderingServer when using global coordinates. + _set_use_identity_transform(p_enable); + + // Let RenderingServer know not to concatenate the parent transform during the render. + RenderingServer::get_singleton()->canvas_item_set_ignore_parent_transform(get_canvas_item(), p_enable); + + if (is_inside_tree()) { + if (p_enable) { + // Make sure item is using identity transform in server. + RenderingServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), Transform2D()); + } else { + // Make sure item transform is up to date in server if switching identity transform off. + RenderingServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), get_transform()); + } + } +} + void CanvasItem::item_rect_changed(bool p_size_changed) { ERR_MAIN_THREAD_GUARD; if (p_size_changed) { diff --git a/scene/main/canvas_item.h b/scene/main/canvas_item.h index 383edeec93a6..132496cc6752 100644 --- a/scene/main/canvas_item.h +++ b/scene/main/canvas_item.h @@ -159,6 +159,8 @@ class CanvasItem : public Node { } } + void set_use_identity_transform(bool p_enable); + void item_rect_changed(bool p_size_changed = true); void _notification(int p_what); diff --git a/scene/main/node.cpp b/scene/main/node.cpp index d4e2d6eb16d0..887aa4a45f21 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -3350,6 +3350,10 @@ void Node::request_ready() { data.ready_first = true; } +void Node::_set_use_identity_transform(bool p_enabled) { + data.use_identity_transform = p_enabled; +} + void Node::_call_input(const Ref &p_event) { if (p_event->get_device() != InputEvent::DEVICE_ID_INTERNAL) { GDVIRTUAL_CALL(_input, p_event); diff --git a/scene/main/node.h b/scene/main/node.h index f49eeec9cd79..9c76b3033f21 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -224,6 +224,10 @@ class Node : public Object { // is switched on. bool physics_interpolated : 1; + // For certain nodes (e.g. CPUParticles2D in global mode) it can be useful to not send the + // canvas item transform to the RenderingServer, and handle the particles in world space. + bool use_identity_transform : 1; + bool parent_owned : 1; bool in_constructor : 1; bool use_placeholder : 1; @@ -333,6 +337,9 @@ class Node : public Object { void _set_owner_nocheck(Node *p_owner); void _set_name_nocheck(const StringName &p_name); + void _set_use_identity_transform(bool p_enabled); + _FORCE_INLINE_ bool _is_using_identity_transform() const { return data.use_identity_transform; } + //call from SceneTree void _call_input(const Ref &p_event); void _call_shortcut_input(const Ref &p_event); diff --git a/servers/rendering/renderer_canvas_cull.cpp b/servers/rendering/renderer_canvas_cull.cpp index e32164ea981b..1efd7376215e 100644 --- a/servers/rendering/renderer_canvas_cull.cpp +++ b/servers/rendering/renderer_canvas_cull.cpp @@ -41,6 +41,11 @@ void RendererCanvasCull::_render_canvas_item_tree(RID p_to_render_target, Canvas::ChildItem *p_child_items, int p_child_item_count, const Transform2D &p_transform, const Rect2 &p_clip_rect, const Color &p_modulate, RendererCanvasRender::Light *p_lights, RendererCanvasRender::Light *p_directional_lights, RenderingServer::CanvasItemTextureFilter p_default_filter, RenderingServer::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, uint32_t p_canvas_cull_mask, RenderingMethod::RenderInfo *r_render_info) { RENDER_TIMESTAMP("Cull CanvasItem Tree"); + // This is used to avoid passing the camera transform down the rendering + // function calls, as it won't be used in 99% of cases, because the camera + // transform is normally concatenated with the item global transform. + _current_camera_transform = p_transform; + memset(z_list, 0, z_range * sizeof(RendererCanvasRender::Item *)); memset(z_last_list, 0, z_range * sizeof(RendererCanvasRender::Item *)); @@ -253,7 +258,12 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2 TransformInterpolator::interpolate_transform_2d(ci->xform_prev, ci->xform_curr, final_xform, f); } - Transform2D parent_xform = p_parent_xform; + Transform2D parent_xform; + if (!p_canvas_item->ignore_parent_xform) { + parent_xform = p_parent_xform; + } else { + parent_xform = _current_camera_transform; + } Point2 repeat_size = p_repeat_size; int repeat_times = p_repeat_times; @@ -589,6 +599,13 @@ void RendererCanvasCull::canvas_item_set_draw_behind_parent(RID p_item, bool p_e canvas_item->behind = p_enable; } +void RendererCanvasCull::canvas_item_set_ignore_parent_transform(RID p_item, bool p_enable) { + Item *canvas_item = canvas_item_owner.get_or_null(p_item); + ERR_FAIL_NULL(canvas_item); + + canvas_item->ignore_parent_xform = p_enable; +} + void RendererCanvasCull::canvas_item_set_update_when_visible(RID p_item, bool p_update) { Item *canvas_item = canvas_item_owner.get_or_null(p_item); ERR_FAIL_NULL(canvas_item); diff --git a/servers/rendering/renderer_canvas_cull.h b/servers/rendering/renderer_canvas_cull.h index 961506ca282d..91aad586daae 100644 --- a/servers/rendering/renderer_canvas_cull.h +++ b/servers/rendering/renderer_canvas_cull.h @@ -195,6 +195,8 @@ class RendererCanvasCull { RendererCanvasRender::Item **z_list; RendererCanvasRender::Item **z_last_list; + Transform2D _current_camera_transform; + public: void render_canvas(RID p_render_target, Canvas *p_canvas, const Transform2D &p_transform, RendererCanvasRender::Light *p_lights, RendererCanvasRender::Light *p_directional_lights, const Rect2 &p_clip_rect, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_transforms_to_pixel, bool p_snap_2d_vertices_to_pixel, uint32_t p_canvas_cull_mask, RenderingMethod::RenderInfo *r_render_info = nullptr); @@ -228,6 +230,7 @@ class RendererCanvasCull { void canvas_item_set_self_modulate(RID p_item, const Color &p_color); void canvas_item_set_draw_behind_parent(RID p_item, bool p_enable); + void canvas_item_set_ignore_parent_transform(RID p_item, bool p_enable); void canvas_item_set_update_when_visible(RID p_item, bool p_update); diff --git a/servers/rendering/renderer_canvas_render.h b/servers/rendering/renderer_canvas_render.h index 4a5654893293..0b45bbfbf34f 100644 --- a/servers/rendering/renderer_canvas_render.h +++ b/servers/rendering/renderer_canvas_render.h @@ -323,6 +323,7 @@ class RendererCanvasRender { bool update_when_visible : 1; bool on_interpolate_transform_list : 1; bool interpolated : 1; + bool ignore_parent_xform : 1; struct CanvasGroup { RS::CanvasGroupMode mode; @@ -485,6 +486,7 @@ class RendererCanvasRender { repeat_source = false; on_interpolate_transform_list = false; interpolated = true; + ignore_parent_xform = false; } virtual ~Item() { clear(); diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h index 139624c777b0..acb00de94ad6 100644 --- a/servers/rendering/rendering_server_default.h +++ b/servers/rendering/rendering_server_default.h @@ -885,6 +885,7 @@ class RenderingServerDefault : public RenderingServer { FUNC2(canvas_item_set_self_modulate, RID, const Color &) FUNC2(canvas_item_set_draw_behind_parent, RID, bool) + FUNC2(canvas_item_set_ignore_parent_transform, RID, bool) FUNC6(canvas_item_add_line, RID, const Point2 &, const Point2 &, const Color &, float, bool) FUNC5(canvas_item_add_polyline, RID, const Vector &, const Vector &, float, bool) diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp index 994f6ad8b455..0297268a7592 100644 --- a/servers/rendering_server.cpp +++ b/servers/rendering_server.cpp @@ -3219,6 +3219,7 @@ void RenderingServer::_bind_methods() { ClassDB::bind_method(D_METHOD("canvas_item_set_modulate", "item", "color"), &RenderingServer::canvas_item_set_modulate); ClassDB::bind_method(D_METHOD("canvas_item_set_self_modulate", "item", "color"), &RenderingServer::canvas_item_set_self_modulate); ClassDB::bind_method(D_METHOD("canvas_item_set_draw_behind_parent", "item", "enabled"), &RenderingServer::canvas_item_set_draw_behind_parent); + ClassDB::bind_method(D_METHOD("canvas_item_set_ignore_parent_transform", "item", "enabled"), &RenderingServer::canvas_item_set_ignore_parent_transform); ClassDB::bind_method(D_METHOD("canvas_item_set_interpolated", "item", "interpolated"), &RenderingServer::canvas_item_set_interpolated); ClassDB::bind_method(D_METHOD("canvas_item_reset_physics_interpolation", "item"), &RenderingServer::canvas_item_reset_physics_interpolation); ClassDB::bind_method(D_METHOD("canvas_item_transform_physics_interpolation", "item", "transform"), &RenderingServer::canvas_item_transform_physics_interpolation); diff --git a/servers/rendering_server.h b/servers/rendering_server.h index a3a77bc57bad..4841414d900c 100644 --- a/servers/rendering_server.h +++ b/servers/rendering_server.h @@ -1421,6 +1421,7 @@ class RenderingServer : public Object { virtual void canvas_item_set_visibility_layer(RID p_item, uint32_t p_visibility_layer) = 0; virtual void canvas_item_set_draw_behind_parent(RID p_item, bool p_enable) = 0; + virtual void canvas_item_set_ignore_parent_transform(RID p_item, bool p_enable) = 0; enum NinePatchAxisMode { NINE_PATCH_STRETCH,