Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions doc/classes/PhysicsServer2D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1024,6 +1024,26 @@
<constant name="SPACE_PARAM_SOLVER_ITERATIONS" value="8" enum="SpaceParameter">
Constant to set/get the number of solver iterations for all contacts and constraints. The greater the number of iterations, the more accurate the collisions will be. However, a greater number of iterations requires more CPU power, which can decrease performance. The default value of this parameter is [member ProjectSettings.physics/2d/solver/solver_iterations].
</constant>
<constant name="SPACE_PARAM_SOLVER_MIN_CONSTRAINTS_FOR_THREADING" value="9" enum="SpaceParameter">
Constant to set/get the minimum number of constraints a physics step must have before constraint setup and eligible island solving are distributed across the [WorkerThreadPool]. Below this threshold the work is done on the calling thread instead, avoiding thread dispatch and synchronization overhead that would otherwise outweigh the benefit for light workloads.
How constraint setup compares against this threshold depends on [constant SPACE_PARAM_SOLVER_SETUP_THREADING_MODE]: the static mode uses the raw candidate constraint count, while the predicted modes use an estimate of useful work (candidate count scaled by a recent step's ratio of active, post-prune, to candidate constraints), which reduces unnecessary threading in scenes with many broadphase pairs but few real contacts. Island solving always uses the [i]actual[/i] current post-prune constraint count, and additionally requires more than one island, since each island is a single unit of parallel work (so reaching the threshold does not by itself thread solving).
A value of [code]0[/code] threads as aggressively as possible, that is, whenever there is actual parallel work to distribute. The default value of this parameter is [member ProjectSettings.physics/2d/solver/min_constraints_for_threading].
</constant>
<constant name="SPACE_PARAM_SOLVER_SETUP_THREADING_MODE" value="10" enum="SpaceParameter">
Constant to set/get how the 2D solver decides whether to distribute constraint [i]setup[/i] across the [WorkerThreadPool] (see the [enum SolverSetupThreadingMode] values). Only affects setup scheduling; island solving always uses the actual post-prune work. The default value of this parameter is [member ProjectSettings.physics/2d/solver/setup_threading_mode].
</constant>
<constant name="SPACE_PARAM_SOLVER_SETUP_PREDICTION_WINDOW" value="11" enum="SpaceParameter">
Constant to set/get how many past steps [constant SOLVER_SETUP_THREADING_PREDICTED_BIASED] maxes its work estimate over (the [i]K[/i] in max-over-K). Clamped to [code]1[/code]–[code]8[/code]; [code]1[/code] makes the biased mode behave like [constant SOLVER_SETUP_THREADING_PREDICTED]. Larger values bias further toward threading (they keep threading for more steps after a heavy one). Has no effect in the static or (unbiased) predicted modes. The default value of this parameter is [member ProjectSettings.physics/2d/solver/setup_prediction_window].
</constant>
<constant name="SOLVER_SETUP_THREADING_STATIC" value="0" enum="SolverSetupThreadingMode">
Thread constraint setup purely on the raw candidate constraint count (no prediction). Predictable and free of transition artifacts, but threads setup even for scenes with many broadphase pairs that produce few real contacts.
</constant>
<constant name="SOLVER_SETUP_THREADING_PREDICTED" value="1" enum="SolverSetupThreadingMode">
Thread constraint setup based on an estimate of useful work: the candidate count scaled by the [i]previous[/i] completed step's active/candidate ratio. Avoids threading light workloads, but assumes temporal coherence — an abrupt workload change costs one suboptimally-scheduled step before the estimate adapts, and workloads that alternate sparse/dense every step defeat it. This is the default.
</constant>
<constant name="SOLVER_SETUP_THREADING_PREDICTED_BIASED" value="2" enum="SolverSetupThreadingMode">
As [constant SOLVER_SETUP_THREADING_PREDICTED], but takes the maximum estimate over the last [i]K[/i] steps (K = [member ProjectSettings.physics/2d/solver/setup_prediction_window], default 2), biasing toward threading. This trades a costly mis-serialized dense step for a cheap redundantly-threaded sparse step across transitions, which removes the alternating-workload regression at the cost of adapting a step or two slower when a workload becomes persistently sparse.
</constant>
<constant name="SHAPE_WORLD_BOUNDARY" value="0" enum="ShapeType">
This is the constant for creating world boundary shapes. A world boundary shape is an [i]infinite[/i] line with an origin point, and a normal. Thus, it can be used for front/behind checks.
</constant>
Expand Down
15 changes: 15 additions & 0 deletions doc/classes/ProjectSettings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2505,10 +2505,25 @@
Default solver bias for all physics contacts. Defines how much bodies react to enforce contact separation. See [constant PhysicsServer2D.SPACE_PARAM_CONTACT_DEFAULT_BIAS].
Individual shapes can have a specific bias value (see [member Shape2D.custom_solver_bias]).
</member>
<member name="physics/2d/solver/min_constraints_for_threading" type="int" setter="" getter="" default="256">
Minimum number of constraints a 2D physics step must have before constraint setup and eligible island solving are distributed across the [WorkerThreadPool]. Below this threshold the work runs on the calling thread instead, avoiding thread dispatch and synchronization overhead that would otherwise outweigh the benefit for light workloads (such as scenes with many fast, mostly-separated bodies).
How constraint setup compares against this threshold depends on [member physics/2d/solver/setup_threading_mode]: [b]Static[/b] uses the raw candidate constraint count; [b]Predicted[/b] scales the candidate count by the previous completed step's ratio of active (post-prune) to candidate constraints (falling back to the raw count when there is no usable previous-step ratio, e.g. the first step); [b]Predicted Biased[/b] takes the maximum of that estimate over the last [member physics/2d/solver/setup_prediction_window] steps. The predicted modes reduce unnecessary threading in scenes with many broadphase pairs but few real contacts. Island solving always uses the [i]actual[/i] current post-prune constraint count, and additionally requires more than one non-empty island, since each island is a single unit of parallel work, so reaching the threshold does not by itself thread solving.
A value of [code]0[/code] threads as aggressively as possible, that is, whenever there is actual parallel work to distribute. Raising it keeps more steps single-threaded; lowering it threads sooner. See [constant PhysicsServer2D.SPACE_PARAM_SOLVER_MIN_CONSTRAINTS_FOR_THREADING].
[b]Note:[/b] The setup estimate assumes temporal coherence between consecutive physics steps. An abrupt workload change may cause one step to choose a suboptimal scheduling path, and workloads that repeatedly alternate between sparse and dense contacts can defeat the estimate. For persistent workloads, the estimate typically adapts on the following step.
[b]Note:[/b] This value is read when a physics space is created, so changing it at runtime does not affect existing physics spaces. Use [method PhysicsServer2D.space_set_param] to change it for an existing space.
</member>
<member name="physics/2d/solver/pairing_expansion" type="float" setter="" getter="" default="25.0">
Maximum broadphase pairing margin, in pixels. Each object's collision pairs are kept alive within a margin that scales with its size (up to this maximum), so large moving objects (such as big [Area2D] sensors) re-pair less often, improving performance in scenes with many large overlapping shapes. Small objects keep a proportionally tiny margin so dense scenes do not accumulate excess pairs. Set to [code]0[/code] to disable and use the legacy pair-count-based margin.
[b]Note:[/b] This value is read when a physics space is created, so changing it at runtime does not affect existing physics spaces.
</member>
<member name="physics/2d/solver/setup_prediction_window" type="int" setter="" getter="" default="2">
Number of past steps the [constant PhysicsServer2D.SOLVER_SETUP_THREADING_PREDICTED_BIASED] mode maxes its setup-work estimate over (the [i]K[/i] in max-over-K), clamped to 1–8. [code]1[/code] makes biased behave like plain predicted; larger values bias further toward threading. Only used by the biased mode. See [constant PhysicsServer2D.SPACE_PARAM_SOLVER_SETUP_PREDICTION_WINDOW].
[b]Note:[/b] This value is read when a physics space is created, so changing it at runtime does not affect existing physics spaces. Use [method PhysicsServer2D.space_set_param] to change it for an existing space.
</member>
<member name="physics/2d/solver/setup_threading_mode" type="int" setter="" getter="" default="1">
How the 2D solver decides whether to distribute constraint setup across the [WorkerThreadPool]: [code]0[/code] static (raw candidate count), [code]1[/code] predicted (default; scale by the previous step's active/candidate ratio), [code]2[/code] predicted biased (max over the last two steps, biased toward threading). See [enum PhysicsServer2D.SolverSetupThreadingMode] for the trade-offs, and [member physics/2d/solver/min_constraints_for_threading] for the threshold itself. Only affects setup; island solving always uses the actual post-prune work.
[b]Note:[/b] This value is read when a physics space is created, so changing it at runtime does not affect existing physics spaces. Use [method PhysicsServer2D.space_set_param] to change it for an existing space.
</member>
<member name="physics/2d/solver/solver_iterations" type="int" setter="" getter="" default="16">
Number of solver iterations for all contacts and constraints. The greater the number of iterations, the more accurate the collisions will be. However, a greater number of iterations requires more CPU power, which can decrease performance. See [constant PhysicsServer2D.SPACE_PARAM_SOLVER_ITERATIONS].
</member>
Expand Down
18 changes: 18 additions & 0 deletions modules/godot_physics_2d/godot_space_2d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1173,6 +1173,15 @@ void GodotSpace2D::set_param(PhysicsServer2D::SpaceParameter p_param, real_t p_v
case PhysicsServer2D::SPACE_PARAM_SOLVER_ITERATIONS:
solver_iterations = p_value;
break;
case PhysicsServer2D::SPACE_PARAM_SOLVER_MIN_CONSTRAINTS_FOR_THREADING:
solver_min_constraints_for_threading = MAX(0, (int)p_value);
break;
case PhysicsServer2D::SPACE_PARAM_SOLVER_SETUP_THREADING_MODE:
solver_setup_threading_mode = CLAMP((int)p_value, 0, PhysicsServer2D::SOLVER_SETUP_THREADING_PREDICTED_BIASED);
break;
case PhysicsServer2D::SPACE_PARAM_SOLVER_SETUP_PREDICTION_WINDOW:
solver_setup_prediction_window = CLAMP((int)p_value, 1, SOLVER_SETUP_HISTORY_MAX);
break;
}
}

Expand All @@ -1196,6 +1205,12 @@ real_t GodotSpace2D::get_param(PhysicsServer2D::SpaceParameter p_param) const {
return constraint_bias;
case PhysicsServer2D::SPACE_PARAM_SOLVER_ITERATIONS:
return solver_iterations;
case PhysicsServer2D::SPACE_PARAM_SOLVER_MIN_CONSTRAINTS_FOR_THREADING:
return solver_min_constraints_for_threading;
case PhysicsServer2D::SPACE_PARAM_SOLVER_SETUP_THREADING_MODE:
return solver_setup_threading_mode;
case PhysicsServer2D::SPACE_PARAM_SOLVER_SETUP_PREDICTION_WINDOW:
return solver_setup_prediction_window;
}
return 0;
}
Expand All @@ -1221,6 +1236,9 @@ GodotSpace2D::GodotSpace2D() {
body_angular_velocity_sleep_threshold = GLOBAL_GET("physics/2d/sleep_threshold_angular");
body_time_to_sleep = GLOBAL_GET("physics/2d/time_before_sleep");
solver_iterations = GLOBAL_GET("physics/2d/solver/solver_iterations");
solver_min_constraints_for_threading = GLOBAL_GET("physics/2d/solver/min_constraints_for_threading");
solver_setup_threading_mode = GLOBAL_GET("physics/2d/solver/setup_threading_mode");
solver_setup_prediction_window = CLAMP((int)GLOBAL_GET("physics/2d/solver/setup_prediction_window"), 1, SOLVER_SETUP_HISTORY_MAX);
contact_recycle_radius = GLOBAL_GET("physics/2d/solver/contact_recycle_radius");
contact_max_separation = GLOBAL_GET("physics/2d/solver/contact_max_separation");
contact_max_allowed_penetration = GLOBAL_GET("physics/2d/solver/contact_max_allowed_penetration");
Expand Down
29 changes: 29 additions & 0 deletions modules/godot_physics_2d/godot_space_2d.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,21 @@ class GodotSpace2D {

GodotArea2D *area = nullptr;

static constexpr int SOLVER_SETUP_HISTORY_MAX = 8;

int solver_iterations = 0;
int solver_min_constraints_for_threading = 256;
int solver_setup_threading_mode = PhysicsServer2D::SOLVER_SETUP_THREADING_PREDICTED;
// Number of past steps the biased mode maxes over (K). Clamped to [1, MAX].
int solver_setup_prediction_window = 2;

// Ring of the last few completed steps' (setup, active) constraint counts,
// used to predict how much useful work this step's setup will contain
// (candidate count is a poor proxy when most pairs don't actually collide).
// A setup count of 0 means no history for that slot -> static-gate fallback.
uint32_t solver_setup_hist_setup[SOLVER_SETUP_HISTORY_MAX] = {};
uint32_t solver_setup_hist_active[SOLVER_SETUP_HISTORY_MAX] = {};
uint32_t solver_setup_hist_head = 0; // next write slot; (head-1) is most recent

real_t contact_recycle_radius = 0.0;
real_t contact_max_separation = 0.0;
Expand Down Expand Up @@ -163,6 +177,21 @@ class GodotSpace2D {
const HashSet<GodotCollisionObject2D *> &get_objects() const;

_FORCE_INLINE_ int get_solver_iterations() const { return solver_iterations; }
_FORCE_INLINE_ int get_solver_min_constraints_for_threading() const { return solver_min_constraints_for_threading; }
_FORCE_INLINE_ int get_solver_setup_threading_mode() const { return solver_setup_threading_mode; }
_FORCE_INLINE_ int get_solver_setup_prediction_window() const { return solver_setup_prediction_window; }
// p_back == 0 is the most recent completed step, 1 the one before it, etc.
_FORCE_INLINE_ uint32_t get_solver_prev_setup_at(uint32_t p_back) const {
return solver_setup_hist_setup[(solver_setup_hist_head + SOLVER_SETUP_HISTORY_MAX - 1 - p_back) % SOLVER_SETUP_HISTORY_MAX];
}
_FORCE_INLINE_ uint32_t get_solver_prev_active_at(uint32_t p_back) const {
return solver_setup_hist_active[(solver_setup_hist_head + SOLVER_SETUP_HISTORY_MAX - 1 - p_back) % SOLVER_SETUP_HISTORY_MAX];
}
_FORCE_INLINE_ void set_solver_prev_constraint_counts(uint32_t p_setup, uint32_t p_active) {
solver_setup_hist_setup[solver_setup_hist_head] = p_setup;
solver_setup_hist_active[solver_setup_hist_head] = p_active;
solver_setup_hist_head = (solver_setup_hist_head + 1) % SOLVER_SETUP_HISTORY_MAX;
}
_FORCE_INLINE_ real_t get_contact_recycle_radius() const { return contact_recycle_radius; }
_FORCE_INLINE_ real_t get_contact_max_separation() const { return contact_max_separation; }
_FORCE_INLINE_ real_t get_contact_max_allowed_penetration() const { return contact_max_allowed_penetration; }
Expand Down
Loading
Loading