Skip to content

Instantly share code, notes, and snippets.

@Pierre-Terdiman
Last active November 9, 2024 07:48
Show Gist options
  • Save Pierre-Terdiman/9388e5c29a8f678d7278275c73bce29b to your computer and use it in GitHub Desktop.
Save Pierre-Terdiman/9388e5c29a8f678d7278275c73bce29b to your computer and use it in GitHub Desktop.
Adding PhysX to tinyBVH benchmark
See https://github.com/jbikker/tinybvh
PhysX is a general purpose library not focused on raycasts and the best setup for raycast performance may not be obvious.
Here is the code I used in the tiny BVH benchmark. Results: https://x.com/PierreTerdiman/status/1854629430246748455
Adapted from https://github.com/NVIDIA-Omniverse/PhysX/tree/main/physx/snippets/snippetpathtracing
For building:
#ifdef PHYSX_BUILD
const bool quantized = false; // quantized trees use less memory but are usually slower for raycasts
const bool useSAH = false; // SAH build is much slower but can give slightly better perf. Not the PhysX default.
PxTriangleMesh* triangleMesh;
{
printf( "- physx builder: " );
const PxTolerancesScale scale;
PxCookingParams params(scale);
params.midphaseDesc.setToDefault(PxMeshMidPhase::eBVH34);
params.midphaseDesc.mBVH34Desc.quantized = quantized;
//params.midphaseDesc.mBVH34Desc.numPrimsPerLeaf = 2; // something else to play with
if(useSAH)
params.midphaseDesc.mBVH34Desc.buildStrategy = PxBVH34BuildStrategy::eSAH;
//params.midphaseDesc.setToDefault(PxMeshMidPhase::eBVH33); // legacy version
// We don't need the extra data structures for just doing raycasts vs the mesh
params.meshPreprocessParams |= PxMeshPreprocessingFlag::eDISABLE_ACTIVE_EDGES_PRECOMPUTE;
params.meshPreprocessParams |= PxMeshPreprocessingFlag::eDISABLE_CLEAN_MESH;
{
PxVec3* physx_verts = new PxVec3[verts];
PxU32* physx_indices = new unsigned int[verts];
for (int i = 0; i < verts; i++)
{
physx_verts[i].x = triangles[i].x,
physx_verts[i].y = triangles[i].y,
physx_verts[i].z = triangles[i].z,
physx_indices[i] = i; // Note: not using shared vertices.
}
t.reset();
PxTriangleMeshDesc meshDesc;
meshDesc.points.count = verts;
meshDesc.points.stride = sizeof(PxVec3);
meshDesc.points.data = physx_verts;
meshDesc.triangles.count = verts/3;
meshDesc.triangles.stride = sizeof(int)*3;
meshDesc.triangles.data = physx_indices;
triangleMesh = PxCreateTriangleMesh(params, meshDesc);
float physxBuildTime = t.elapsed();
printf( "%.2fms for %i triangles\n ", physxBuildTime * 1000.0f, verts / 3 );
delete [] physx_indices;
delete [] physx_verts;
}
}
#endif
For queries:
#ifdef PHYSX_BUILD
// trace all rays three times to estimate average performance
{
PX_SIMD_GUARD; // Put this once in the calling code, see comment below
PxGeomRaycastHit* hits = new PxGeomRaycastHit[N];
const PxTriangleMeshGeometry meshGeom(triangleMesh);
const PxTransform idt(PxIdentity);
printf( "- PhysX (quantized: %d) (SAH: %d): ", quantized, useSAH);
t.reset();
for (int pass = 0; pass < 3; pass++)
{
for (int i = 0; i < N; i++)
{
// Pass PxGeometryQueryFlag::Enum(0) to avoid the PX_SIMD_GUARD cost for each raycast. The call has some more overhead that we could
// get rid of for ultimate performance (e.g. internal tests to select between the legacy & current implementations, the code that deals
// with that extra geom transform we don't need here, etc)
// PhysX does backface culling by default, TinyBVH does not. So pass eMESH_BOTH_SIDES to disable culling.
// PhysX also computes impact position & normal but we only need the UVs, so pass eUV. Distance is always returned.
PxGeometryQuery::raycast((const PxVec3&)rays[i].O, (const PxVec3&)rays[i].D, meshGeom, idt, 10000.0f, PxHitFlag::eUV|PxHitFlag::eMESH_BOTH_SIDES, 1, &hits[i], sizeof(PxGeomRaycastHit), PxGeometryQueryFlag::Enum(0));
}
}
float traceTimeAlt = t.elapsed() / 3.0f;
mrays = (float)N / traceTimeAlt;
printf( "%.2fms for %.2fM rays (%.2fMRays/s)\n", traceTimeAlt * 1000, (float)N * 1e-6f, mrays * 1e-6f );
delete [] hits;
}
PX_RELEASE(triangleMesh);
#endif
For the Fenster test (to draw the test image) you might need some additional bits to retrieve the proper touched primitives. PhysX internally changes the order of triangles and returns an index into that internal array. You need to use a remap table to get the intial user-side index. This code worked and drew the proper test image:
PX_SIMD_GUARD;
const PxTriangleMeshGeometry meshGeom(triangleMesh);
const PxTransform idt(PxIdentity);
const PxU32* remap = triangleMesh->getTrianglesRemap();
PxGeomRaycastHit hit;
for (int i = 0; i < N; i++)
{
if(PxGeometryQuery::raycast((const PxVec3&)rays[i].O, (const PxVec3&)rays[i].D, meshGeom, idt, 10000.0f, PxHitFlag::eUV|PxHitFlag::eMESH_BOTH_SIDES, 1, &hit, sizeof(PxGeomRaycastHit), PxGeometryQueryFlag::Enum(0)))
{
rays[i].hit.t = hit.distance;
rays[i].hit.u = hit.u;
rays[i].hit.v = hit.v;
rays[i].hit.prim = remap[hit.faceIndex];
}
else
{
rays[i].hit.t = -1.0f;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment