Opaque struct size doesn't match C++
There is a discrepancy between C++ and Rust struct sizes for a few structs (PxRaycastQueryResult, PxSweepQueryResult, PxOverlapQueryResult, etc.). For example:
printf("c++ size: %d\n", sizeof(PxRaycastQueryResult));
c++ size: 80
println!("rust size: {}", std::mem::size_of::<PxRaycastQueryResult>());
rust size: 1
Issue is that this code:
https://github.com/EmbarkStudios/physx-rs/blob/77a79caf17d63240797eb23cac7f40e7774114ad/physx-sys/pxbind/main.cpp#L445-L468
Generates this:
#[repr(C)]
pub struct PxRaycastQueryResult{
pxbind_dummy: u8
}
I believe it should instead generate this:
#[repr(C)]
pub struct PxRaycastQueryResult{
pxbind_dummy: [u8; 80]
}
... where "80" is sizeof(PxRaycastQueryResult), which may differ on various platforms.
Why it matters:
I'm trying to initialize PxRaycastQueryResult for batch raycast query following a guide for physx 4.1 vehicles sdk.
And I found no way to initialize it in rust (no PxRaycastQueryResult::new(), and no phys_PxRaycastQueryResult_new()). So I'm trying to manually allocate memory, but how would I know how much memory to allocate?
Code I'm trying to port is:
PxRaycastQueryResult sqResults[4];
PxBatchQueryDesc sqDesc(4, 0, 0);
sqDesc.queryMemory.userRaycastResultBuffer = sqResults;
sqDesc.queryMemory.raycastTouchBufferSize = 4;
PxBatchQuery* batchQuery = scene->createBatchQuery(sqDesc);
PxVehicleSuspensionRaycasts(...);
I'm expecting this to be equivalent of the code above, but it will only allocate 4 bytes and segfault trying to access the rest.
// this segfaults on access later:
let mut sq_results: MaybeUninit<[ PxRaycastQueryResult; 4 ]> = MaybeUninit::uninit();
// this works, but is very ugly and platform-dependent:
//let mut sq_results = [0u8; 80 * 4];
let mut sq_desc = unsafe { PxBatchQueryDesc_new(4, 0, 0) };
let mut batch_query = unsafe {
sq_desc.queryMemory.userRaycastResultBuffer = sq_results.as_mut_ptr() as *mut PxRaycastQueryResult;
PxScene_createBatchQuery_mut(scene.as_mut_ptr(), &sq_desc as *const _)
};
phys_PxVehicleSuspensionRaycasts(...);
The reason for this is that in some implementations of C++, the size of a base object can be smaller than its alignment. If base objects in C++ are translated to struct fields in Rust, then the offset of the next field may be incorrect. Bindgen suffers from the same issue: https://github.com/rust-lang/rust-bindgen/issues/380.
This can be easily solved by patching PhysX; add an explicit padding field of the correct size to the offending base class. Since physx-rs builds PhysX from source, such patches could be applied prior to building. (Make sure to initialize the padding field in the constructor so that the generated copy and move constructors does not trigger UB.)
Example patch (in this case for `PxControllerDesc` whose derived classes suffer from the same issue)
diff --git a/physx/include/characterkinematic/PxController.h b/physx/include/characterkinematic/PxController.h
index cdeea5d..c2bc99e 100644
--- a/physx/include/characterkinematic/PxController.h
+++ b/physx/include/characterkinematic/PxController.h
@@ -524,6 +524,8 @@ public:
protected:
const PxControllerShapeType::Enum mType; //!< The type of the controller. This gets set by the derived class' ctor, the user should not have to change it.
+ PxU32 bindgen380Workaround;
+
/**
\brief constructor sets to default.
*/
@@ -561,11 +563,12 @@ PX_INLINE PxControllerDesc::PxControllerDesc(PxControllerShapeType::Enum t) :
registerDeletionListener (true),
clientID (PX_DEFAULT_CLIENT),
userData (NULL),
- mType (t)
+ mType (t),
+ bindgen380Workaround (0)
{
}
-PX_INLINE PxControllerDesc::PxControllerDesc(const PxControllerDesc& other) : mType(other.mType)
+PX_INLINE PxControllerDesc::PxControllerDesc(const PxControllerDesc& other) : mType(other.mType), bindgen380Workaround(0)
{
copy(other);
}