@@ -446,10 +446,20 @@ fn resolve_feature_set(
446446 // ------------------------------------------------------------------
447447 // Step 5: Build the indexed command list.
448448 // Core functions first (in req_commands order), then extension functions.
449+ // Before assigning indices, reorder commands to minimize PFN range
450+ // fragmentation — see optimize_command_order for the algorithm.
449451 // ------------------------------------------------------------------
450452 let core_cmd_names: Vec < String > = req_commands. keys ( ) . cloned ( ) . collect ( ) ;
451453 let ext_cmd_names: Vec < String > = ext_commands. keys ( ) . cloned ( ) . collect ( ) ;
452454
455+ let ( core_cmd_names, ext_cmd_names) = optimize_command_order (
456+ & core_cmd_names,
457+ & ext_cmd_names,
458+ & selected_features,
459+ & selected_exts,
460+ requests,
461+ ) ;
462+
453463 let all_cmd_names: Vec < & str > = core_cmd_names
454464 . iter ( )
455465 . chain ( ext_cmd_names. iter ( ) )
@@ -1257,6 +1267,96 @@ fn indices_to_ranges(ext_idx: u16, sorted: &[u16]) -> Vec<PfnRange> {
12571267 ranges
12581268}
12591269
1270+ // ---------------------------------------------------------------------------
1271+ // PFN ordering optimization
1272+ // ---------------------------------------------------------------------------
1273+
1274+ /// Reorder command names to minimize PFN range table fragmentation.
1275+ ///
1276+ /// The pfnArray index of each command determines how many `PfnRange` entries
1277+ /// are needed in the range tables. When commands required by the same
1278+ /// feature or extension are scattered across the array, each disjoint group
1279+ /// becomes a separate range. By placing commands with identical consumer
1280+ /// sets adjacent, most consumers collapse to a single contiguous range.
1281+ ///
1282+ /// **Algorithm**: assign each command a "consumer signature" — the sorted
1283+ /// set of feature/extension indices that include it. Sort commands by
1284+ /// signature (lexicographic on the index lists). This groups commands with
1285+ /// identical consumers together, and orders the groups so that consumers
1286+ /// with overlapping command sets are near each other.
1287+ ///
1288+ /// **Effect**: in a merged GL 4.6 + GLES 3.2 build, GLES 2.0 drops from
1289+ /// ~34 ranges to ~1–3 because its cherry-picked subset of GL 1.0 commands
1290+ /// are now contiguous rather than interleaved with GL-only commands.
1291+ fn optimize_command_order (
1292+ core_cmds : & [ String ] ,
1293+ ext_cmds : & [ String ] ,
1294+ selected_features : & [ SelectedFeature < ' _ > ] ,
1295+ selected_exts : & [ SelectedExt < ' _ > ] ,
1296+ requests : & [ ApiRequest ] ,
1297+ ) -> ( Vec < String > , Vec < String > ) {
1298+ let num_features = selected_features. len ( ) ;
1299+
1300+ // Build command → sorted consumer-index set.
1301+ // Consumers 0..num_features are features, num_features.. are extensions.
1302+ let mut consumers: HashMap < & str , Vec < u32 > > = HashMap :: new ( ) ;
1303+
1304+ // Feature consumers — respect API/profile filtering.
1305+ for ( fi, feat) in selected_features. iter ( ) . enumerate ( ) {
1306+ let profile = requests
1307+ . iter ( )
1308+ . find ( |r| r. name == feat. api )
1309+ . and_then ( |r| r. profile . as_deref ( ) ) ;
1310+
1311+ for require in & feat. raw . requires {
1312+ if !api_profile_matches (
1313+ require. api . as_deref ( ) ,
1314+ require. profile . as_deref ( ) ,
1315+ & feat. api ,
1316+ profile,
1317+ ) {
1318+ continue ;
1319+ }
1320+ for cmd in & require. commands {
1321+ consumers. entry ( cmd. as_str ( ) ) . or_default ( ) . push ( fi as u32 ) ;
1322+ }
1323+ }
1324+ }
1325+
1326+ // Extension consumers.
1327+ for ( ei, ext) in selected_exts. iter ( ) . enumerate ( ) {
1328+ for require in & ext. raw . requires {
1329+ for cmd in & require. commands {
1330+ consumers
1331+ . entry ( cmd. as_str ( ) )
1332+ . or_default ( )
1333+ . push ( ( num_features + ei) as u32 ) ;
1334+ }
1335+ }
1336+ }
1337+
1338+ // Deduplicate and sort each consumer list so the signature is canonical.
1339+ for list in consumers. values_mut ( ) {
1340+ list. sort_unstable ( ) ;
1341+ list. dedup ( ) ;
1342+ }
1343+
1344+ // Sort each command list by consumer signature (lexicographic).
1345+ // Commands with identical consumers become adjacent; the lexicographic
1346+ // ordering naturally clusters related consumer groups nearby.
1347+ let sort_by_consumers = |names : & [ String ] | -> Vec < String > {
1348+ let mut sorted = names. to_vec ( ) ;
1349+ sorted. sort_by ( |a, b| {
1350+ let ca = consumers. get ( a. as_str ( ) ) . map ( Vec :: as_slice) . unwrap_or ( & [ ] ) ;
1351+ let cb = consumers. get ( b. as_str ( ) ) . map ( Vec :: as_slice) . unwrap_or ( & [ ] ) ;
1352+ ca. cmp ( cb) . then_with ( || a. cmp ( b) ) // tie-break alphabetically for stability
1353+ } ) ;
1354+ sorted
1355+ } ;
1356+
1357+ ( sort_by_consumers ( core_cmds) , sort_by_consumers ( ext_cmds) )
1358+ }
1359+
12601360// ---------------------------------------------------------------------------
12611361// Types list
12621362// ---------------------------------------------------------------------------
0 commit comments