Skip to content

Commit a09f08a

Browse files
committed
[physx] fixed errors during async loading
1 parent 7bb9797 commit a09f08a

4 files changed

Lines changed: 59 additions & 27 deletions

File tree

source/runtime/Physics/PhysicsWorld.cpp

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ namespace spartan
5353
{
5454
namespace
5555
{
56-
mutex scene_mutex;
56+
recursive_mutex physx_mutex;
5757
}
5858

5959
namespace settings
@@ -95,6 +95,7 @@ namespace spartan
9595
PxRaycastBuffer hit;
9696
PxQueryFilterData filter_data(PxQueryFlag::eDYNAMIC); // only pick dynamic bodies - static/kinematic can be moved as per usual from the editor
9797
PxScene* scene = static_cast<PxScene*>(PhysicsWorld::GetScene());
98+
lock_guard<recursive_mutex> lock(PhysicsWorld::GetMutex());
9899
if (scene->raycast(origin, direction, 1000.0f, hit, PxHitFlag::eDEFAULT, filter_data) && hit.hasBlock)
99100
{
100101
PxRigidActor* actor = hit.block.actor;
@@ -142,6 +143,7 @@ namespace spartan
142143
{
143144
if (picked_body && joint)
144145
{
146+
lock_guard<recursive_mutex> lock(PhysicsWorld::GetMutex());
145147
joint->release();
146148
joint = nullptr;
147149

@@ -178,6 +180,7 @@ namespace spartan
178180
PxVec3 target = origin + direction * pick_distance;
179181

180182
// move dummy actor to target
183+
lock_guard<recursive_mutex> lock(PhysicsWorld::GetMutex());
181184
dummy_actor->setKinematicTarget(PxTransform(target));
182185
}
183186
}
@@ -319,6 +322,7 @@ namespace spartan
319322
while (accumulated_time >= fixed_time_step)
320323
{
321324
// simulate one fixed time step
325+
lock_guard<recursive_mutex> lock(physx_mutex);
322326
scene->simulate(fixed_time_step);
323327
scene->fetchResults(true); // block
324328
accumulated_time -= fixed_time_step;
@@ -345,6 +349,8 @@ namespace spartan
345349
// debug visualization (editor only, skip during play)
346350
if (cvar_physics.GetValueAs<bool>() && !Engine::IsFlagSet(EngineMode::Playing))
347351
{
352+
lock_guard<recursive_mutex> lock(physx_mutex);
353+
348354
// run a near-zero step so physx populates the render buffer (physx requires dt > 0)
349355
scene->simulate(numeric_limits<float>::min());
350356
scene->fetchResults(true);
@@ -369,7 +375,7 @@ namespace spartan
369375
{
370376
if (actor && scene && !actor->getScene())
371377
{
372-
lock_guard<mutex> lock(scene_mutex);
378+
lock_guard<recursive_mutex> lock(physx_mutex);
373379
scene->addActor(*actor);
374380
}
375381
}
@@ -378,7 +384,7 @@ namespace spartan
378384
{
379385
if (actor && scene && actor->getScene() == scene)
380386
{
381-
lock_guard<mutex> lock(scene_mutex);
387+
lock_guard<recursive_mutex> lock(physx_mutex);
382388
scene->removeActor(*actor);
383389
}
384390
}
@@ -400,6 +406,11 @@ namespace spartan
400406
{
401407
return static_cast<void*>(physics);
402408
}
409+
410+
recursive_mutex& PhysicsWorld::GetMutex()
411+
{
412+
return physx_mutex;
413+
}
403414

404415
float PhysicsWorld::GetInterpolationAlpha()
405416
{
@@ -428,7 +439,7 @@ namespace spartan
428439
PxRaycastBuffer hit;
429440
PxQueryFilterData filter_data(PxQueryFlag::eSTATIC);
430441

431-
lock_guard<mutex> lock(scene_mutex);
442+
lock_guard<recursive_mutex> lock(physx_mutex);
432443
if (scene->raycast(px_origin, px_direction, max_distance, hit, PxHitFlag::eDEFAULT, filter_data) && hit.hasBlock)
433444
{
434445
hit_position = Vector3(hit.block.position.x, hit.block.position.y, hit.block.position.z);

source/runtime/Physics/PhysicsWorld.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2222
#pragma once
2323

2424
//= INCLUDES ===============
25+
#include <mutex>
2526
#include <vector>
2627
#include "../Math/Vector3.h"
2728
//==========================
@@ -58,5 +59,11 @@ namespace spartan
5859

5960
// cast a ray against static geometry and return the closest hit position + the entity that was hit
6061
static bool RaycastStatic(const math::Vector3& origin, const math::Vector3& direction, float max_distance, math::Vector3& hit_position, Entity*& hit_entity);
62+
63+
// global physx scene lock, used to serialize all reads and writes that touch
64+
// PxScene/PxRigidActor/PxShape state, async scene loading runs Physics::Create on
65+
// worker threads which races with the main thread, recursive so the same thread
66+
// can re-enter under nested helpers like PhysicsWorld::AddActor
67+
static std::recursive_mutex& GetMutex();
6168
};
6269
}

source/runtime/Rendering/Renderer_Passes_Lighting.cpp

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -425,20 +425,19 @@ namespace spartan
425425

426426
void Renderer::Pass_ReSTIR_PathTracing(RHI_CommandList* cmd_list)
427427
{
428-
const uint32_t min_rt_dimension = 64;
429428
if (Window::IsMinimized())
430-
{
431429
return;
432-
}
433430

434431
RHI_Texture* tex_gi = GetRenderTarget(Renderer_RenderTarget::restir_output);
435432
RHI_Texture* reservoir0 = GetRenderTarget(Renderer_RenderTarget::restir_reservoir0);
436-
SP_ASSERT(tex_gi != nullptr);
437433

434+
// restir resources are gated by cvar_restir_pt, nothing to do when they are not allocated
435+
if (!tex_gi)
436+
return;
437+
438+
const uint32_t min_rt_dimension = 64;
438439
if (tex_gi->GetWidth() < min_rt_dimension || tex_gi->GetHeight() < min_rt_dimension)
439-
{
440440
return;
441-
}
442441

443442
if (!cvar_restir_pt.GetValueAs<bool>() || !RHI_Device::IsSupportedRayTracing() || !reservoir0)
444443
{
@@ -453,9 +452,7 @@ namespace spartan
453452

454453
RHI_AccelerationStructure* tlas = GetTopLevelAccelerationStructure();
455454
if (!tlas)
456-
{
457455
return;
458-
}
459456

460457
RHI_Shader* shader_rgen = GetShader(Renderer_Shader::restir_pt_ray_generation_r);
461458
RHI_Shader* shader_miss = GetShader(Renderer_Shader::restir_pt_ray_miss_r);
@@ -498,21 +495,21 @@ namespace spartan
498495

499496
void Renderer::Pass_ReSTIR_Denoising(RHI_CommandList* cmd_list)
500497
{
498+
if (Window::IsMinimized())
499+
return;
500+
501501
RHI_Texture* tex_gi_raw = GetRenderTarget(Renderer_RenderTarget::restir_output);
502502
RHI_Texture* tex_gi_denoised = GetRenderTarget(Renderer_RenderTarget::restir_denoised);
503503
RHI_Texture* tex_gi_history = GetRenderTarget(Renderer_RenderTarget::restir_denoised_history);
504504
RHI_Texture* tex_gi_ping = GetRenderTarget(Renderer_RenderTarget::restir_denoised_ping);
505-
SP_ASSERT(tex_gi_raw && tex_gi_denoised && tex_gi_history && tex_gi_ping);
506505

507-
const uint32_t min_rt_dimension = 64;
508-
if (Window::IsMinimized())
509-
{
506+
// restir resources are gated by cvar_restir_pt, nothing to do when they are not allocated
507+
if (!tex_gi_raw || !tex_gi_denoised || !tex_gi_history || !tex_gi_ping)
510508
return;
511-
}
509+
510+
const uint32_t min_rt_dimension = 64;
512511
if (tex_gi_raw->GetWidth() < min_rt_dimension || tex_gi_raw->GetHeight() < min_rt_dimension)
513-
{
514512
return;
515-
}
516513

517514
if (!cvar_restir_pt.GetValueAs<bool>() || !RHI_Device::IsSupportedRayTracing())
518515
{
@@ -835,7 +832,6 @@ namespace spartan
835832
RHI_Texture* tex_light_specular = GetRenderTarget(Renderer_RenderTarget::light_specular);
836833
RHI_Texture* tex_light_volumetric = GetRenderTarget(Renderer_RenderTarget::light_volumetric);
837834
RHI_Texture* tex_gi = GetRenderTarget(Renderer_RenderTarget::restir_denoised);
838-
SP_ASSERT(tex_gi != nullptr);
839835

840836
cmd_list->InsertBarrier(tex_out, RHI_BarrierType::EnsureReadThenWrite);
841837

source/runtime/World/Components/Physics.cpp

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,9 @@ namespace spartan
225225

226226
void Physics::Remove()
227227
{
228+
// serialize physx writes, async scene loading runs this on worker threads in parallel
229+
lock_guard<recursive_mutex> physx_lock(PhysicsWorld::GetMutex());
230+
228231
// release controller if it exists
229232
// skip if controller_manager was already released during shutdown
230233
if (m_controller && controller_manager)
@@ -267,22 +270,24 @@ namespace spartan
267270

268271
void Physics::PreTick()
269272
{
270-
// deferred creation after loading (renderable component needs to be available first)
271-
if (m_needs_creation)
272-
{
273-
m_needs_creation = false;
274-
Create();
275-
}
276-
277273
// during world load worker threads are busy inside pxphysics/pxscene creating actors and
278274
// shapes, the editor sync path below writes to pxrigidactor on the main thread which physx
279275
// flags as concurrent api access and corrupts the internal pruner aabb tree, the entities
280276
// were just placed at the right pose so this sync would be a no-op anyway
277+
// we also defer Create until loading completes, this avoids racing with workers that are
278+
// still populating sibling components like Render whose mesh data we read for the shape
281279
if (ProgressTracker::IsLoading())
282280
{
283281
return;
284282
}
285283

284+
// deferred creation after loading (renderable component needs to be available first)
285+
if (m_needs_creation)
286+
{
287+
m_needs_creation = false;
288+
Create();
289+
}
290+
286291
// sync physics transforms to entities before other components (like camera) tick
287292
// this ensures child entities have up-to-date parent transforms when they compute matrices
288293
const bool is_playing = Engine::IsFlagSet(EngineMode::Playing);
@@ -1192,6 +1197,7 @@ namespace spartan
11921197
// get the actor used by the controller to avoid returning itself
11931198
PxRigidActor* actor_to_ignore = controller->getActor();
11941199

1200+
lock_guard<recursive_mutex> physx_lock(PhysicsWorld::GetMutex());
11951201
if (scene->raycast(ray_start, ray_dir, ray_length, hit, PxHitFlag::eDEFAULT, filter_data))
11961202
{
11971203
for (PxU32 i = 0; i < hit.nbTouches; ++i)
@@ -1511,6 +1517,10 @@ namespace spartan
15111517
return;
15121518
}
15131519

1520+
// car::set_chassis below mutates shapes on car::body which is already attached to the
1521+
// scene, async load runs this on worker threads so serialize against other physx writes
1522+
lock_guard<recursive_mutex> physx_lock(PhysicsWorld::GetMutex());
1523+
15141524
// collect all entities with renderables in the hierarchy
15151525
vector<Entity*> mesh_entities;
15161526
mesh_entities.push_back(chassis_entity);
@@ -1713,6 +1723,9 @@ namespace spartan
17131723
// recalculate and update body height based on actual wheel radius
17141724
if (car::body)
17151725
{
1726+
// car::body is in the scene, async load reaches this from car prefab workers
1727+
lock_guard<recursive_mutex> physx_lock(PhysicsWorld::GetMutex());
1728+
17161729
// calculate correct body height using actual spring stiffness
17171730
float front_mass_per_wheel = car::cfg.mass * 0.40f * 0.5f;
17181731
float front_omega = 2.0f * math::pi * car::tuning::spec.front_spring_freq;
@@ -2551,6 +2564,11 @@ namespace spartan
25512564

25522565
void Physics::Create()
25532566
{
2567+
// serialize the entire physx setup, the car prefab path calls this on loader workers
2568+
// in parallel with the main thread's PreTick, so without this lock physx flags concurrent
2569+
// scene writes (addActor, setGlobalPose, setSimulationFilterData) and corrupts the scene
2570+
lock_guard<recursive_mutex> physx_lock(PhysicsWorld::GetMutex());
2571+
25542572
// clear previous state
25552573
Remove();
25562574

0 commit comments

Comments
 (0)