5959from newton ._src .geometry .hashtable import (
6060 HASHTABLE_EMPTY_KEY ,
6161 HashTable ,
62+ hashtable_find ,
6263 hashtable_find_or_insert ,
6364)
6465
@@ -1096,8 +1097,11 @@ def clear_active(self):
10961097 later-scheduled blocks (or even later-issued warps/lanes under
10971098 independent thread scheduling), causing some entries to be skipped.
10981099 """
1099- # Use fixed thread count for efficient GPU utilization
1100- num_threads = min (1024 , self .hashtable .capacity )
1100+ # The clear is a grid-stride loop over the active entries with several
1101+ # scattered stores each, so it needs many resident warps to hide the
1102+ # store latency; the active count is only known on the device, and
1103+ # surplus threads exit immediately.
1104+ num_threads = min (65536 , self .hashtable .capacity )
11011105
11021106 wp .launch (
11031107 _clear_active_kernel ,
@@ -1567,41 +1571,55 @@ def _export_and_reduce_contact_centered_two_spatial_depths(
15671571 pos_2d = project_point_to_plane (bin_id , centered_position )
15681572 key = make_contact_key (shape_a , shape_b , bin_id )
15691573
1574+ # === Voxel bin: inner depth coverage ===
1575+ voxel_idx = compute_voxel_index (position_local , aabb_lower_voxel , aabb_upper_voxel , voxel_res )
1576+ voxel_idx = wp .clamp (voxel_idx , 0 , wp .static (NUM_VOXEL_DEPTH_SLOTS - 1 ))
1577+
1578+ voxels_per_group = wp .static (NUM_SPATIAL_DIRECTIONS + 1 )
1579+ voxel_group = voxel_idx // voxels_per_group
1580+ voxel_local_slot = voxel_idx % voxels_per_group
1581+ voxel_bin_id = wp .static (NUM_NORMAL_BINS ) + voxel_group
1582+ voxel_key = make_contact_key (shape_a , shape_b , voxel_bin_id )
1583+
1584+ # Resolve both keys up front so their probes and the slot reads below can
1585+ # overlap. Missing voxel keys are published only after a contact ID is
1586+ # available; deleting a speculative key after publication would race with
1587+ # concurrent threads that have already found it.
15701588 entry_idx = hashtable_find_or_insert (key , reducer_data .ht_keys , reducer_data .ht_active_slots )
1571- might_win = False
1589+ voxel_entry_idx = - 1
1590+ if use_inner :
1591+ voxel_entry_idx = hashtable_find (voxel_key , reducer_data .ht_keys )
15721592
1593+ might_win = False
15731594 if entry_idx >= 0 :
1595+ # Read every slot before comparing so the loads issue back to back.
1596+ slot_values = replaced_values_vec_type ()
1597+ for dir_i in range (wp .static (NUM_SPATIAL_DIRECTIONS + 1 )):
1598+ slot_values [dir_i ] = reducer_data .ht_values [dir_i * ht_capacity + entry_idx ]
1599+ voxel_slot_value = wp .uint64 (0 )
1600+ if voxel_entry_idx >= 0 :
1601+ voxel_slot_value = reducer_data .ht_values [voxel_local_slot * ht_capacity + voxel_entry_idx ]
1602+
15741603 if use_inner :
15751604 if deterministic != 0 :
15761605 max_depth_probe = _make_preprune_probe_det (- depth , fingerprint )
15771606 else :
15781607 max_depth_probe = _make_contact_value_fast (- depth , 0 , 0 )
1579- if reducer_data .ht_values [wp .static (NUM_SPATIAL_DIRECTIONS ) * ht_capacity + entry_idx ] < max_depth_probe :
1608+ if slot_values [wp .static (NUM_SPATIAL_DIRECTIONS )] < max_depth_probe :
1609+ might_win = True
1610+ if voxel_entry_idx >= 0 and voxel_slot_value < max_depth_probe :
1611+ might_win = True
1612+ if voxel_entry_idx < 0 :
15801613 might_win = True
15811614
15821615 for dir_i in range (wp .static (NUM_SPATIAL_DIRECTIONS )):
1583- if not might_win :
1584- dir_2d = get_spatial_direction_2d (dir_i )
1585- score = wp .dot (pos_2d , dir_2d )
1586- probe = make_spatial_preprune_probe (score , use_inner , fingerprint , deterministic )
1587- if reducer_data .ht_values [dir_i * ht_capacity + entry_idx ] < probe :
1588- might_win = True
1616+ dir_2d = get_spatial_direction_2d (dir_i )
1617+ score = wp .dot (pos_2d , dir_2d )
1618+ probe = make_spatial_preprune_probe (score , use_inner , fingerprint , deterministic )
1619+ if slot_values [dir_i ] < probe :
1620+ might_win = True
15891621 else :
15901622 wp .atomic_add (reducer_data .ht_insert_failures , 0 , 1 )
1591-
1592- # === Voxel bin: inner depth coverage ===
1593- voxel_idx = compute_voxel_index (position_local , aabb_lower_voxel , aabb_upper_voxel , voxel_res )
1594- voxel_idx = wp .clamp (voxel_idx , 0 , wp .static (NUM_VOXEL_DEPTH_SLOTS - 1 ))
1595-
1596- voxels_per_group = wp .static (NUM_SPATIAL_DIRECTIONS + 1 )
1597- voxel_group = voxel_idx // voxels_per_group
1598- voxel_local_slot = voxel_idx % voxels_per_group
1599- voxel_bin_id = wp .static (NUM_NORMAL_BINS ) + voxel_group
1600- voxel_key = make_contact_key (shape_a , shape_b , voxel_bin_id )
1601-
1602- voxel_entry_idx = - 1
1603- if use_inner and not might_win :
1604- voxel_entry_idx = hashtable_find_or_insert (voxel_key , reducer_data .ht_keys , reducer_data .ht_active_slots )
16051623 if voxel_entry_idx >= 0 :
16061624 if deterministic != 0 :
16071625 voxel_probe = _make_preprune_probe_det (- depth , fingerprint )
@@ -1613,11 +1631,6 @@ def _export_and_reduce_contact_centered_two_spatial_depths(
16131631 if not might_win :
16141632 return - 1
16151633
1616- # Compete with reserved ID zero before materializing contact geometry, so
1617- # stale pre-prune survivors consume no buffer space.
1618- if use_inner and voxel_entry_idx < 0 :
1619- voxel_entry_idx = hashtable_find_or_insert (voxel_key , reducer_data .ht_keys , reducer_data .ht_active_slots )
1620-
16211634 won_mask = int (0 )
16221635 replaced_values = replaced_values_vec_type ()
16231636 if use_inner and entry_idx >= 0 :
@@ -1664,7 +1677,8 @@ def _export_and_reduce_contact_centered_two_spatial_depths(
16641677 won_mask |= 1 << wp .static (NUM_SPATIAL_DIRECTIONS + 1 )
16651678 replaced_values [wp .static (NUM_SPATIAL_DIRECTIONS + 1 )] = previous_value
16661679
1667- if won_mask == 0 :
1680+ voxel_entry_missing = use_inner and voxel_entry_idx < 0
1681+ if won_mask == 0 and not voxel_entry_missing :
16681682 return - 1
16691683
16701684 # Avoid allocating candidates superseded during their own slot updates.
@@ -1693,7 +1707,11 @@ def _export_and_reduce_contact_centered_two_spatial_depths(
16931707 if reducer_data .ht_values [voxel_local_slot * ht_capacity + voxel_entry_idx ] == provisional_value :
16941708 still_wins = True
16951709
1696- if not still_wins :
1710+ # Without a surviving slot win, a contact may still claim the voxel slot of
1711+ # an entry that is not published yet. That claim happens after the contact
1712+ # ID exists, so a losing claimant returns its ID below.
1713+ voxel_only = voxel_entry_missing and not still_wins
1714+ if not still_wins and not voxel_only :
16971715 return - 1
16981716 contact_id = export_contact_to_buffer (shape_a , shape_b , position , normal , depth , fingerprint , reducer_data )
16991717 if contact_id < 0 :
@@ -1756,9 +1774,30 @@ def _export_and_reduce_contact_centered_two_spatial_depths(
17561774 voxel_entry_idx = hashtable_find_or_insert (voxel_key , reducer_data .ht_keys , reducer_data .ht_active_slots )
17571775 if voxel_entry_idx >= 0 :
17581776 voxel_value = make_contact_value (- depth , fingerprint , contact_id , deterministic )
1759- reduction_update_slot (voxel_entry_idx , voxel_local_slot , voxel_value , reducer_data .ht_values , ht_capacity )
1777+ if voxel_only :
1778+ previous_value = reduction_try_update_slot (
1779+ voxel_entry_idx ,
1780+ voxel_local_slot ,
1781+ voxel_value ,
1782+ reducer_data .ht_values ,
1783+ ht_capacity ,
1784+ )
1785+ if previous_value >= voxel_value :
1786+ reclaim_contact_id (contact_id , reducer_data )
1787+ return - 1
1788+ else :
1789+ reduction_update_slot (
1790+ voxel_entry_idx ,
1791+ voxel_local_slot ,
1792+ voxel_value ,
1793+ reducer_data .ht_values ,
1794+ ht_capacity ,
1795+ )
17601796 else :
17611797 wp .atomic_add (reducer_data .ht_insert_failures , 0 , 1 )
1798+ if voxel_only :
1799+ reclaim_contact_id (contact_id , reducer_data )
1800+ return - 1
17621801
17631802 return contact_id
17641803
0 commit comments