@@ -180,15 +180,21 @@ PhysicsSystem::~PhysicsSystem() {
180180}
181181
182182bool PhysicsSystem::Initialize () {
183- // This is a placeholder implementation
184- // In a real implementation, this would initialize the physics engine
185-
186- // Initialize Vulkan resources if GPU acceleration is enabled and the renderer is set
187- if (gpuAccelerationEnabled && renderer) {
188- if (!InitializeVulkanResources ()) {
189- std::cerr << " Failed to initialize Vulkan resources for physics system" << std::endl;
190- gpuAccelerationEnabled = false ;
191- }
183+ // Enforce GPU-only physics. If GPU resources cannot be initialized, initialization fails.
184+
185+ // Renderer must be set for GPU compute physics
186+ if (!renderer) {
187+ std::cerr << " PhysicsSystem::Initialize: Renderer is not set. GPU-only physics cannot proceed." << std::endl;
188+ return false ;
189+ }
190+
191+ // Always keep GPU acceleration enabled (CPU fallback is not allowed)
192+ gpuAccelerationEnabled = true ;
193+
194+ // Initialize Vulkan resources; fail hard if not available
195+ if (!InitializeVulkanResources ()) {
196+ std::cerr << " PhysicsSystem::Initialize: Failed to initialize Vulkan resources for physics (GPU-only)." << std::endl;
197+ return false ;
192198 }
193199
194200 initialized = true ;
@@ -292,7 +298,7 @@ bool PhysicsSystem::Raycast(const glm::vec3& origin, const glm::vec3& direction,
292298 switch (shape) {
293299 case CollisionShape::Sphere: {
294300 // Sphere intersection test
295- float radius = 0 .5f ; // Default radius
301+ float radius = 0 .0335f ; // Tennis ball radius to match actual ball
296302
297303 // Calculate coefficients for quadratic equation
298304 glm::vec3 oc = origin - position;
@@ -765,14 +771,10 @@ bool PhysicsSystem::InitializeVulkanResources() {
765771
766772 // Create persistent mapped memory pointers for improved performance
767773 try {
768- // Map physics buffer memory persistently
769- vulkanResources.persistentPhysicsMemory = vulkanResources.physicsBufferMemory .mapMemory (0 , physicsBufferSize);
770-
771- // Map counter buffer memory persistently
772- vulkanResources.persistentCounterMemory = vulkanResources.counterBufferMemory .mapMemory (0 , counterBufferSize);
773-
774- // Map params buffer memory persistently
775- vulkanResources.persistentParamsMemory = vulkanResources.paramsBufferMemory .mapMemory (0 , paramsBufferSize);
774+ // Map entire memory objects persistently to satisfy VK_WHOLE_SIZE flush alignment requirements
775+ vulkanResources.persistentPhysicsMemory = vulkanResources.physicsBufferMemory .mapMemory (0 , VK_WHOLE_SIZE);
776+ vulkanResources.persistentCounterMemory = vulkanResources.counterBufferMemory .mapMemory (0 , VK_WHOLE_SIZE);
777+ vulkanResources.persistentParamsMemory = vulkanResources.paramsBufferMemory .mapMemory (0 , VK_WHOLE_SIZE);
776778 } catch (const std::exception& e) {
777779 throw std::runtime_error (" Failed to create persistent mapped memory: " + std::string (e.what ()));
778780 }
@@ -877,10 +879,10 @@ bool PhysicsSystem::InitializeVulkanResources() {
877879
878880 raiiDevice.updateDescriptorSets (descriptorWrites, nullptr );
879881
880- // Create a command pool
882+ // Create a command pool bound to the compute queue family used by the renderer
881883 vk::CommandPoolCreateInfo commandPoolInfo;
882884 commandPoolInfo.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer;
883- commandPoolInfo.queueFamilyIndex = 0 ; // Assuming the compute queue family index is 0
885+ commandPoolInfo.queueFamilyIndex = renderer-> GetComputeQueueFamilyIndex ();
884886 vulkanResources.commandPool = vk::raii::CommandPool (raiiDevice, commandPoolInfo);
885887
886888 // Allocate command buffer
@@ -1009,8 +1011,10 @@ void PhysicsSystem::UpdateGPUPhysicsData(float deltaTime) const {
10091011
10101012 // For dynamic bodies (balls), allow forces to be applied by
10111013 // The shader will add gravity and other forces each frame
1012- gpuData[i].force = glm::vec4 (initialForce, concreteRigidBody->IsKinematic () ? 1 .0f : 0 .0f );
1013- gpuData[i].torque = glm::vec4 (initialTorque, 1 .0f ); // Always use gravity
1014+ bool isKinematic = concreteRigidBody->IsKinematic ();
1015+ gpuData[i].force = glm::vec4 (initialForce, isKinematic ? 1 .0f : 0 .0f );
1016+ // Use gravity only for dynamic bodies
1017+ gpuData[i].torque = glm::vec4 (initialTorque, isKinematic ? 0 .0f : 1 .0f );
10141018
10151019 // Set collider data based on a collider type
10161020 switch (concreteRigidBody->GetShape ()) {
@@ -1040,7 +1044,9 @@ void PhysicsSystem::UpdateGPUPhysicsData(float deltaTime) const {
10401044 glm::vec3 localCenter = 0 .5f * (localMin + localMax);
10411045 glm::vec3 localHalfExtents = 0 .5f * (localMax - localMin);
10421046
1043- glm::mat4 model = xform->GetModelMatrix ();
1047+ glm::mat4 model = (meshComp->GetInstanceCount () > 0 )
1048+ ? meshComp->GetInstance (0 ).getModelMatrix ()
1049+ : xform->GetModelMatrix ();
10441050 glm::vec3 centerWS = glm::vec3 (model * glm::vec4 (localCenter, 1 .0f ));
10451051
10461052 glm::mat3 RS = glm::mat3 (model);
@@ -1057,7 +1063,8 @@ void PhysicsSystem::UpdateGPUPhysicsData(float deltaTime) const {
10571063 }
10581064 }
10591065
1060- gpuData[i].colliderData = glm::vec4 (halfExtents, static_cast <float >(2 )); // 2 = Mesh (as Box)
1066+ // Encode Mesh collider as Mesh (type=2) for GPU narrowphase handling (sphere vs mesh)
1067+ gpuData[i].colliderData = glm::vec4 (halfExtents, static_cast <float >(2 )); // 2 = Mesh (represented as world AABB)
10611068 gpuData[i].colliderData2 = glm::vec4 (localOffset, 0 .0f );
10621069 }
10631070 break ;
@@ -1089,14 +1096,26 @@ void PhysicsSystem::UpdateGPUPhysicsData(float deltaTime) const {
10891096 // Use VK_WHOLE_SIZE to avoid nonCoherentAtomSize alignment validation errors
10901097 try {
10911098 const vk::raii::Device& device = renderer->GetRaiiDevice ();
1092- vk::MappedMemoryRange flushRange;
1093- flushRange.memory = *vulkanResources.paramsBufferMemory ;
1094- flushRange.offset = 0 ;
1095- flushRange.size = VK_WHOLE_SIZE;
1096-
1097- device.flushMappedMemoryRanges (flushRange);
1099+ // Flush params buffer
1100+ vk::MappedMemoryRange flushRangeParams;
1101+ flushRangeParams.memory = *vulkanResources.paramsBufferMemory ;
1102+ flushRangeParams.offset = 0 ;
1103+ flushRangeParams.size = VK_WHOLE_SIZE;
1104+ device.flushMappedMemoryRanges (flushRangeParams);
1105+ // Flush physics buffer (object data)
1106+ vk::MappedMemoryRange flushRangePhysics;
1107+ flushRangePhysics.memory = *vulkanResources.physicsBufferMemory ;
1108+ flushRangePhysics.offset = 0 ;
1109+ flushRangePhysics.size = VK_WHOLE_SIZE;
1110+ device.flushMappedMemoryRanges (flushRangePhysics);
1111+ // Flush counter buffer (pair and collision counters)
1112+ vk::MappedMemoryRange flushRangeCounter;
1113+ flushRangeCounter.memory = *vulkanResources.counterBufferMemory ;
1114+ flushRangeCounter.offset = 0 ;
1115+ flushRangeCounter.size = VK_WHOLE_SIZE;
1116+ device.flushMappedMemoryRanges (flushRangeCounter);
10981117 } catch (const std::exception& e) {
1099- fprintf (stderr, " WARNING: Failed to flush params buffer memory: %s" , e.what ());
1118+ fprintf (stderr, " WARNING: Failed to flush mapped physics memory: %s" , e.what ());
11001119 }
11011120}
11021121
@@ -1118,6 +1137,39 @@ void PhysicsSystem::ReadbackGPUPhysicsData() const {
11181137 return ;
11191138 }
11201139
1140+ // Ensure GPU writes to HOST_VISIBLE memory are visible to the host before reading
1141+ try {
1142+ vk::MappedMemoryRange invalidateRangePhysics;
1143+ invalidateRangePhysics.memory = *vulkanResources.physicsBufferMemory ;
1144+ invalidateRangePhysics.offset = 0 ;
1145+ invalidateRangePhysics.size = VK_WHOLE_SIZE;
1146+
1147+ vk::MappedMemoryRange invalidateRangeCounter;
1148+ invalidateRangeCounter.memory = *vulkanResources.counterBufferMemory ;
1149+ invalidateRangeCounter.offset = 0 ;
1150+ invalidateRangeCounter.size = VK_WHOLE_SIZE;
1151+
1152+ device.invalidateMappedMemoryRanges ({invalidateRangePhysics, invalidateRangeCounter});
1153+ } catch (const std::exception&) {
1154+ // On HOST_COHERENT heaps this may not be required; ignore errors
1155+ }
1156+
1157+ // Optional debug: read and log pair/collision counters for a few frames
1158+ if (vulkanResources.persistentCounterMemory ) {
1159+ static uint32_t lastPairCount = UINT32_MAX;
1160+ static uint32_t lastCollisionCount = UINT32_MAX;
1161+ static int debugFrames = 0 ;
1162+ const uint32_t * counters = static_cast <const uint32_t *>(vulkanResources.persistentCounterMemory );
1163+ uint32_t pairCount = counters[0 ];
1164+ uint32_t collisionCount = counters[1 ];
1165+ if (debugFrames < 120 && (pairCount != lastPairCount || collisionCount != lastCollisionCount)) {
1166+ std::cout << " Physics GPU counters - pairs: " << pairCount << " , collisions: " << collisionCount << std::endl;
1167+ lastPairCount = pairCount;
1168+ lastCollisionCount = collisionCount;
1169+ debugFrames++;
1170+ }
1171+ }
1172+
11211173 // Skip physics buffer operations if no rigid bodies exist
11221174 if (!rigidBodies.empty ()) {
11231175 // Use persistent mapped memory for physics buffer readback
@@ -1176,11 +1228,11 @@ void PhysicsSystem::SimulatePhysicsOnGPU(const float deltaTime) const {
11761228 nullptr
11771229 );
11781230
1179- // Add a memory barrier to ensure host-written uniform buffer data is visible to shader
1180- // This ensures the PhysicsParams data uploaded from CPU is properly synchronized before shader execution
1231+ // Add a memory barrier to ensure all host-written buffer data (uniform + storage) is visible to compute shaders
1232+ // We use ShaderRead | ShaderWrite since compute will read and write storage buffers
11811233 vk::MemoryBarrier hostToDeviceBarrier;
11821234 hostToDeviceBarrier.srcAccessMask = vk::AccessFlagBits::eHostWrite;
1183- hostToDeviceBarrier.dstAccessMask = vk::AccessFlagBits::eUniformRead ;
1235+ hostToDeviceBarrier.dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite ;
11841236
11851237 vulkanResources.commandBuffer .pipelineBarrier (
11861238 vk::PipelineStageFlagBits::eHost,
0 commit comments