mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54533426d0 | ||
|
|
c5bd7c6c7b |
No files matched your search
@@ -2,7 +2,25 @@
|
||||
|
||||
Client-first performance research for Minecraft Java **26.3**, preserving vanilla gameplay rules. Any language or renderer may be used when measured gains justify it. Not affiliated with Mojang or Microsoft.
|
||||
|
||||
**First playable release:** pinned Java performance packs, distant terrain, an original frame/tick recorder, and Mac/client/server starter downloads. The M2 Pro/16 GB Mac is the first testing target. A Rust rewrite or custom renderer has not been completed; no M2 FPS improvement has been measured yet.
|
||||
**Current focus: the physical M2 Pro / 16 GB Mac.** Initial matched renderer tests show 13–15% higher average FPS at 2560×1600 and 70 → 89 FPS in one full Retina terrain-view pair through Vulkan/MoltenVK. Repeated warmed-cache, movement and gameplay validation continue. [Measurements and limits](docs/m2-pro-performance.md). Work on NVIDIA, AMD, Windows and Linux is deferred.
|
||||
|
||||
The playable foundation includes pinned Java performance packs, distant terrain,
|
||||
an original frame/tick recorder and starter downloads. The local `m2-metal`
|
||||
profile preserves the user's 32 detailed chunks and 128-chunk LOD through the
|
||||
game's Vulkan/MoltenVK backend. `m2-metal-batched` adds an original, opt-in cloud
|
||||
rendering change for this M2 Pro. Two prototype pairs improve roughly 84 → 96
|
||||
FPS at full Retina resolution, with identical frozen sky/cloud pixels. The
|
||||
production artifact confirms +14.1% in a fixed view and +24.7–25.3% in two
|
||||
matched flyover pairs. Isolated stalls and broader gameplay testing remain.
|
||||
The published 0.21.0 starter
|
||||
predates these local 0.22.0 profiles.
|
||||
|
||||
Local Prism imports also carry the tested heap and collector settings:
|
||||
`python3 tools/build_mac_candidate.py --gc zgc` produces a separate ZGC candidate;
|
||||
use `--gc g1` for its control. Two route pairs improve 1% lows by 6.5–12.8%
|
||||
with ZGC and reduce frames over 16 ms, while average FPS stays within 0.7%.
|
||||
Worst frames remain worse than G1 in these pairs, so this is an optional
|
||||
smoother-play configuration. [Collector measurements and import instructions](docs/m2-pro-performance.md).
|
||||
|
||||
## Play on your Mac
|
||||
|
||||
@@ -14,13 +32,15 @@ If Prism is already installed, follow the [Mac import and first comparison guide
|
||||
|
||||
Core and balanced also pass [macOS title/demo-world rendering probes](docs/client-graphics-smoke.md)
|
||||
on the hosted ARM64 software renderer, including readback, saves and clean exits.
|
||||
Physical M2 GPU performance and macOS 27 remain to be tested.
|
||||
Physical M2 GPU testing on macOS 27 is now underway; see the linked observations.
|
||||
|
||||
| Profile | Full-detail chunks | Distant LOD radius | Suggested maximum heap |
|
||||
| --- | ---: | ---: | ---: |
|
||||
| baseline | 12 | none | 6144 MiB |
|
||||
| core-performance | 12 | none | 6144 MiB |
|
||||
| m2-balanced | 12 | 128 | 6144 MiB |
|
||||
| m2-metal (local Mac test) | 32 | 128 | 6144 MiB |
|
||||
| m2-metal-batched (M2 Pro experiment) | 32 | 128 | 6144 MiB |
|
||||
| adaptive (experiment) | 12 | 128 | 6144 MiB |
|
||||
| worker-pacing (experiment) | 12 | 128 | 6144 MiB |
|
||||
| adaptive-pacing (experiment) | 12 | 128 | 6144 MiB |
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,117 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"performance_measurement": false,
|
||||
"protocol": "Freeze world ticks and DH wall-clock cloud movement after 120 seconds warmup; hide HUD; render control, batched, control with five seconds settling per image.",
|
||||
"gpu": "Apple M2 Pro",
|
||||
"graphics_backend": "Vulkan",
|
||||
"framebuffer": [
|
||||
3456,
|
||||
2234
|
||||
],
|
||||
"fixture_sha256": "4385a40953ed82d88077ed3d5d63e904b4193e0bf44ea58f0ed37220b07c23ab",
|
||||
"original_mod_sha256": "6828ab3385bde5a97cc46fa98a5122211f5ac4e2197da8ed9555c29393ebbba6",
|
||||
"batch_draws_exercised": 18500,
|
||||
"image_sha256": {
|
||||
"cloud-image-control-a.png": "9a645c433f5dc0ed2795ad2f81690659dc1437110c780a2b3f5481186a09ace1",
|
||||
"cloud-image-batched.png": "7bc48a6f9b115b84eea95d1f9a47fd72859e34eef5fe73db3c28a73a3394a84a",
|
||||
"cloud-image-control-b.png": "f82b9cb66e081d1d35cd820b64d2ecdcc6f51ca7dbac1961ff77af24f951188f"
|
||||
},
|
||||
"rgb_difference_regions": {
|
||||
"full": {
|
||||
"y_pixels": [
|
||||
0,
|
||||
2234
|
||||
]
|
||||
},
|
||||
"sky": {
|
||||
"y_pixels": [
|
||||
80,
|
||||
893
|
||||
]
|
||||
},
|
||||
"near_terrain": {
|
||||
"y_pixels": [
|
||||
1340,
|
||||
2234
|
||||
]
|
||||
}
|
||||
},
|
||||
"rgb_differences": {
|
||||
"full": {
|
||||
"control_a_vs_batched": {
|
||||
"mean_abs_channel_difference": 0.00039249443228665847,
|
||||
"root_mean_square_channel_difference": 0.09972327790283458,
|
||||
"pixels_any_rgb_change_percent": 0.007486363937796346,
|
||||
"pixels_over_8_levels_percent": 0.0017874017706157367,
|
||||
"max_channel_difference": 69
|
||||
},
|
||||
"batched_vs_control_b": {
|
||||
"mean_abs_channel_difference": 0.008503067423212529,
|
||||
"root_mean_square_channel_difference": 0.2330295622442773,
|
||||
"pixels_any_rgb_change_percent": 0.4726123420869392,
|
||||
"pixels_over_8_levels_percent": 0.02376726267449186,
|
||||
"max_channel_difference": 63
|
||||
},
|
||||
"control_a_vs_control_b": {
|
||||
"mean_abs_channel_difference": 0.008845480066757297,
|
||||
"root_mean_square_channel_difference": 0.2529589023079416,
|
||||
"pixels_any_rgb_change_percent": 0.4744256482310421,
|
||||
"pixels_over_8_levels_percent": 0.025593521005338377,
|
||||
"max_channel_difference": 70
|
||||
}
|
||||
},
|
||||
"sky": {
|
||||
"control_a_vs_batched": {
|
||||
"mean_abs_channel_difference": 0.0,
|
||||
"root_mean_square_channel_difference": 0.0,
|
||||
"pixels_any_rgb_change_percent": 0.0,
|
||||
"pixels_over_8_levels_percent": 0.0,
|
||||
"max_channel_difference": 0
|
||||
},
|
||||
"batched_vs_control_b": {
|
||||
"mean_abs_channel_difference": 0.0,
|
||||
"root_mean_square_channel_difference": 0.0,
|
||||
"pixels_any_rgb_change_percent": 0.0,
|
||||
"pixels_over_8_levels_percent": 0.0,
|
||||
"max_channel_difference": 0
|
||||
},
|
||||
"control_a_vs_control_b": {
|
||||
"mean_abs_channel_difference": 0.0,
|
||||
"root_mean_square_channel_difference": 0.0,
|
||||
"pixels_any_rgb_change_percent": 0.0,
|
||||
"pixels_over_8_levels_percent": 0.0,
|
||||
"max_channel_difference": 0
|
||||
}
|
||||
},
|
||||
"near_terrain": {
|
||||
"control_a_vs_batched": {
|
||||
"mean_abs_channel_difference": 1.0788659651448615e-07,
|
||||
"root_mean_square_channel_difference": 0.000328460951278057,
|
||||
"pixels_any_rgb_change_percent": 3.236597895434584e-05,
|
||||
"pixels_over_8_levels_percent": 0.0,
|
||||
"max_channel_difference": 1
|
||||
},
|
||||
"batched_vs_control_b": {
|
||||
"mean_abs_channel_difference": 4.315463860579446e-07,
|
||||
"root_mean_square_channel_difference": 0.000656921902556114,
|
||||
"pixels_any_rgb_change_percent": 0.00012946391581738337,
|
||||
"pixels_over_8_levels_percent": 0.0,
|
||||
"max_channel_difference": 1
|
||||
},
|
||||
"control_a_vs_control_b": {
|
||||
"mean_abs_channel_difference": 5.394329825724308e-07,
|
||||
"root_mean_square_channel_difference": 0.000734461015011982,
|
||||
"pixels_any_rgb_change_percent": 0.00016182989477172923,
|
||||
"pixels_over_8_levels_percent": 0.0,
|
||||
"max_channel_difference": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"limits": [
|
||||
"One daytime fixed scene on the pinned M2 Pro Vulkan/DH build.",
|
||||
"Sky-region RGB pixels are identical across all three images.",
|
||||
"Far-terrain cache generation continues: whole-image differences must be compared with the control/control variation.",
|
||||
"Near-terrain RGB differs by at most one channel level, including control/control variation.",
|
||||
"Other weather, heights, moving camera, callbacks and shader stacks are not covered."
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,98 @@
|
||||
{
|
||||
"gpu": "Apple M2 Pro",
|
||||
"graphics_backend": "Vulkan",
|
||||
"framebuffer_width": 3456,
|
||||
"framebuffer_height": 2234,
|
||||
"original_mod_sha256": "804fd1777770e15d8d39911a708e828a028da36d3cb43d1bc2e1c7cddc318e64",
|
||||
"fixture_sha256": "8e9711a36f5ebd99bd81c65385aa58813f324b8642bbf75bc1222c30d3fb0dbb",
|
||||
"production_cloud_batch": true,
|
||||
"cloud_batch": {
|
||||
"draws": 18463,
|
||||
"passes": 998
|
||||
},
|
||||
"schema": 1,
|
||||
"performance_measurement": false,
|
||||
"protocol": "Frozen world ticks and DH wall clock after 120 seconds warmup; hidden HUD; control, production batch, control with five seconds settling per image.",
|
||||
"rgb_regions_y_end_exclusive": {
|
||||
"sky": [
|
||||
80,
|
||||
893
|
||||
],
|
||||
"near_terrain": [
|
||||
1340,
|
||||
2234
|
||||
]
|
||||
},
|
||||
"readback_sha256": {
|
||||
"cloud-image-control-a.png": "ba3caf0704a3d0a1c8914e3dc44f965e17c95ba36bb9fdd4ec4221151c3bbaf1",
|
||||
"cloud-image-batched.png": "0c7ffc6c14313d2fef649817d037c95d1bf38f2884aa063716741551b0713eb4",
|
||||
"cloud-image-control-b.png": "3a06c42a107a3916ad26f9965a4602b3fed79b9b8ff53510d1946a0c80f269e4"
|
||||
},
|
||||
"pairs": {
|
||||
"control_a_vs_batched": {
|
||||
"full": {
|
||||
"maximum_channel_difference": 252,
|
||||
"mean_absolute_channel_difference": 0.014238338887231009,
|
||||
"changed_pixels": 36459,
|
||||
"total_pixels": 7720704
|
||||
},
|
||||
"sky": {
|
||||
"maximum_channel_difference": 0,
|
||||
"mean_absolute_channel_difference": 0.0,
|
||||
"changed_pixels": 0,
|
||||
"total_pixels": 2809728
|
||||
},
|
||||
"near_terrain": {
|
||||
"maximum_channel_difference": 0,
|
||||
"mean_absolute_channel_difference": 0.0,
|
||||
"changed_pixels": 0,
|
||||
"total_pixels": 3089664
|
||||
}
|
||||
},
|
||||
"control_a_vs_control_b": {
|
||||
"full": {
|
||||
"maximum_channel_difference": 253,
|
||||
"mean_absolute_channel_difference": 0.026262371928777478,
|
||||
"changed_pixels": 36131,
|
||||
"total_pixels": 7720704
|
||||
},
|
||||
"sky": {
|
||||
"maximum_channel_difference": 0,
|
||||
"mean_absolute_channel_difference": 0.0,
|
||||
"changed_pixels": 0,
|
||||
"total_pixels": 2809728
|
||||
},
|
||||
"near_terrain": {
|
||||
"maximum_channel_difference": 1,
|
||||
"mean_absolute_channel_difference": 3.2365978954345846e-07,
|
||||
"changed_pixels": 3,
|
||||
"total_pixels": 3089664
|
||||
}
|
||||
},
|
||||
"batched_vs_control_b": {
|
||||
"full": {
|
||||
"maximum_channel_difference": 229,
|
||||
"mean_absolute_channel_difference": 0.0222054879969495,
|
||||
"changed_pixels": 38818,
|
||||
"total_pixels": 7720704
|
||||
},
|
||||
"sky": {
|
||||
"maximum_channel_difference": 0,
|
||||
"mean_absolute_channel_difference": 0.0,
|
||||
"changed_pixels": 0,
|
||||
"total_pixels": 2809728
|
||||
},
|
||||
"near_terrain": {
|
||||
"maximum_channel_difference": 1,
|
||||
"mean_absolute_channel_difference": 3.2365978954345846e-07,
|
||||
"changed_pixels": 3,
|
||||
"total_pixels": 3089664
|
||||
}
|
||||
}
|
||||
},
|
||||
"limits": [
|
||||
"One fixed daytime view; no proof for other weather, camera height, movement or shaders.",
|
||||
"Distant-cache generation continues and changes far terrain in both A/B and A/A comparisons.",
|
||||
"Only private validation freezes simulation and clouds; playable renderer preserves normal simulation and time."
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,867 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"profiling": false,
|
||||
"experiment": "Original 0.22.0 M2 Pro DH cloud renderer grouping, explicit opt-in profile",
|
||||
"common_seed": "m2-vk-retina-01 saved demo world including DH cache",
|
||||
"runs": {
|
||||
"m2-metal-production-control02": {
|
||||
"scene": "overlook",
|
||||
"camera_yaw": 141.5,
|
||||
"camera_pitch": 16.95,
|
||||
"player_x": -41.5,
|
||||
"player_y": 100.0,
|
||||
"player_z": 72.5,
|
||||
"warmup_seconds": 120,
|
||||
"gpu": "Apple M2 Pro",
|
||||
"graphics_backend": "Vulkan",
|
||||
"fixture_sha256": "a71cb439bb27c6dbb894b10154ee89f85444e7225a7122bce9acd3632d2e1140",
|
||||
"original_mod_sha256": "804fd1777770e15d8d39911a708e828a028da36d3cb43d1bc2e1c7cddc318e64",
|
||||
"profiling_enabled": false,
|
||||
"frames": {
|
||||
"minecraft": "26.3",
|
||||
"profile": "m2-metal",
|
||||
"adaptive_budget_enabled": false,
|
||||
"precise_worker_pacing": false,
|
||||
"debounce_queue_wait": false,
|
||||
"wakeable_idle_wait": false,
|
||||
"dispatch_retry_backoff": false,
|
||||
"tick_membership_dedup": false,
|
||||
"terrain_quad_reuse": false,
|
||||
"terrain_position_arithmetic": false,
|
||||
"culling_ray_scratch": false,
|
||||
"empty_culling_whitelist": false,
|
||||
"interruptible_terrain_read": true,
|
||||
"adaptive_target_fps": 60,
|
||||
"world_difficulty": "NORMAL",
|
||||
"render_distance": 32,
|
||||
"simulation_distance": 12,
|
||||
"vsync": false,
|
||||
"fps_limit": 260,
|
||||
"fps_limit_unlimited": true,
|
||||
"inactivity_fps_limit": "minimized",
|
||||
"framebuffer_width": 3456,
|
||||
"framebuffer_height": 2234,
|
||||
"graphics_preset": "CUSTOM",
|
||||
"ambient_occlusion": true,
|
||||
"improved_transparency": false,
|
||||
"mipmap_levels": 4,
|
||||
"entity_distance_scaling": 1.0,
|
||||
"entity_shadows": true,
|
||||
"particles": "ALL",
|
||||
"clouds": "OFF",
|
||||
"cloud_range": 128,
|
||||
"fov": 110,
|
||||
"dh_rendering_enabled": true,
|
||||
"dh_renderer_mode": "DEFAULT",
|
||||
"dh_lod_radius_chunks": 128,
|
||||
"dh_horizontal_quality": "MEDIUM",
|
||||
"dh_vertical_quality": "MEDIUM",
|
||||
"dh_max_horizontal_resolution": "BLOCK",
|
||||
"dh_transparency": "COMPLETE",
|
||||
"dh_lod_shading": "AUTO",
|
||||
"dh_lod_only_mode": false,
|
||||
"dh_camera_quality_dropoff": true,
|
||||
"dh_ambient_occlusion": true,
|
||||
"dh_thread_count": 2,
|
||||
"dh_runtime_ratio": 0.5,
|
||||
"dh_capture_status": "available",
|
||||
"iris_capture_status": "not_installed",
|
||||
"iris_shaders_enabled": false,
|
||||
"gpu": "Apple M2 Pro",
|
||||
"graphics_backend": "Vulkan",
|
||||
"graphics_driver": "1.2.350 MoltenVK 1.4.2",
|
||||
"frame_limiter_effective_fps_start": 260,
|
||||
"frame_limiter_reason_start": "NONE",
|
||||
"java_vendor": "Microsoft",
|
||||
"java_vm_name": "OpenJDK 64-Bit Server VM",
|
||||
"java_runtime_version": "25.0.1+8-LTS",
|
||||
"mods": [
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+38c7a2d55d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+26.3",
|
||||
"[email protected]+74ed1ea502",
|
||||
"[email protected]+0ae6f91102",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+4be74c3f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+74ed1ea55d",
|
||||
"[email protected]+6b5b47fd5d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+427eab975d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+47f74c9802",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+cf6bc2db5d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+ffef5f6702",
|
||||
"[email protected]+4068fd645d",
|
||||
"[email protected]+ed66f0a35d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f5d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+79385d0b5d",
|
||||
"[email protected]+b9d63e335d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+901a437c02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+4fc5413f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+48607d035d",
|
||||
"[email protected]+74ed1ea502",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+52268a0e02",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+26.3",
|
||||
"java@25",
|
||||
"[email protected]+mc26.3",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+mc26.3",
|
||||
"[email protected]",
|
||||
"[email protected]"
|
||||
],
|
||||
"frame_limiter_effective_fps_end": 260,
|
||||
"frame_limiter_reason_end": "NONE",
|
||||
"frame_limiter_observations": 9951,
|
||||
"frame_limiter_throttled_observations": 0,
|
||||
"frame_limiter_afk_observations": 0,
|
||||
"visual_settings_end": {
|
||||
"world_difficulty": "NORMAL",
|
||||
"render_distance": 32,
|
||||
"simulation_distance": 12,
|
||||
"vsync": false,
|
||||
"fps_limit": 260,
|
||||
"fps_limit_unlimited": true,
|
||||
"inactivity_fps_limit": "minimized",
|
||||
"framebuffer_width": 3456,
|
||||
"framebuffer_height": 2234,
|
||||
"graphics_preset": "CUSTOM",
|
||||
"ambient_occlusion": true,
|
||||
"improved_transparency": false,
|
||||
"mipmap_levels": 4,
|
||||
"entity_distance_scaling": 1.0,
|
||||
"entity_shadows": true,
|
||||
"particles": "ALL",
|
||||
"clouds": "OFF",
|
||||
"cloud_range": 128,
|
||||
"fov": 110,
|
||||
"dh_rendering_enabled": true,
|
||||
"dh_renderer_mode": "DEFAULT",
|
||||
"dh_lod_radius_chunks": 128,
|
||||
"dh_horizontal_quality": "MEDIUM",
|
||||
"dh_vertical_quality": "MEDIUM",
|
||||
"dh_max_horizontal_resolution": "BLOCK",
|
||||
"dh_transparency": "COMPLETE",
|
||||
"dh_lod_shading": "AUTO",
|
||||
"dh_lod_only_mode": false,
|
||||
"dh_camera_quality_dropoff": true,
|
||||
"dh_ambient_occlusion": true,
|
||||
"dh_thread_count": 2,
|
||||
"dh_runtime_ratio": 0.5,
|
||||
"dh_capture_status": "available",
|
||||
"iris_capture_status": "not_installed",
|
||||
"iris_shaders_enabled": false,
|
||||
"dh_cloud_batch_requested": false,
|
||||
"dh_cloud_batch_enabled": false
|
||||
},
|
||||
"visual_settings_changed": [],
|
||||
"label": "m2-metal-production-control02",
|
||||
"kind": "frames",
|
||||
"schema": 1,
|
||||
"elapsed_seconds": 120.009546791,
|
||||
"dropped_samples": 0,
|
||||
"excluded_frames": 0,
|
||||
"peak_players": 0,
|
||||
"java_version": "25.0.1",
|
||||
"os": "Mac OS X",
|
||||
"os_version": "27.0.1",
|
||||
"architecture": "aarch64",
|
||||
"logical_processors": 12,
|
||||
"garbage_collectors": [
|
||||
"G1 Concurrent GC",
|
||||
"G1 Old Generation",
|
||||
"G1 Young Generation"
|
||||
],
|
||||
"compact_object_headers": false,
|
||||
"heap_used_end_bytes": 1952922832,
|
||||
"heap_committed_end_bytes": 2801795072,
|
||||
"heap_max_bytes": 6442450944,
|
||||
"heap_used_peak_sampled_bytes": 2624148856,
|
||||
"nonheap_used_peak_sampled_bytes": 252569024,
|
||||
"resource_samples": 121,
|
||||
"resource_sample_interval_ms": 1000,
|
||||
"process_rss_last_sampled_bytes": 3533422592,
|
||||
"process_rss_peak_sampled_bytes": 3582558208,
|
||||
"process_rss_samples": 120,
|
||||
"process_rss_sample_interval_ms": 1000,
|
||||
"process_rss_last_sample_age_ms": 137.019084,
|
||||
"process_cpu_seconds": 211.445484,
|
||||
"process_cpu_mean_cores": 1.7619055288012897,
|
||||
"gc_collections": 50,
|
||||
"gc_time_ms": 271,
|
||||
"stats": {
|
||||
"count": 9950,
|
||||
"meanMs": 12.058937282211057,
|
||||
"p50Ms": 12.07575,
|
||||
"p95Ms": 12.558958,
|
||||
"p99Ms": 13.02825,
|
||||
"maxMs": 52.341917,
|
||||
"meanRate": 82.92604701370881,
|
||||
"lowOnePercentRate": 69.04374876082869,
|
||||
"over16Ms": 14,
|
||||
"over33Ms": 1,
|
||||
"over50Ms": 1,
|
||||
"over100Ms": 0
|
||||
},
|
||||
"samples_file": "m2-metal-production-control02-frames-1791138458891.csv",
|
||||
"note": "Frame start intervals include frame limiting and presentation waits; menus, pauses and unfocused frames are excluded. Not GPU timings. Process RSS, when available, is sampled separately.",
|
||||
"dh_cloud_batch_requested": false,
|
||||
"dh_cloud_batch_enabled": false,
|
||||
"dh_cloud_batch_draws_recorded": 0,
|
||||
"dh_cloud_batch_passes_recorded": 0,
|
||||
"samples_sha256": "d304bc1caece306b2fda1c24435710ca907e97bf1ae53041c76909ec394bcabc"
|
||||
},
|
||||
"ticks": {
|
||||
"java_vendor": "Microsoft",
|
||||
"java_vm_name": "OpenJDK 64-Bit Server VM",
|
||||
"java_runtime_version": "25.0.1+8-LTS",
|
||||
"mods": [
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+38c7a2d55d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+26.3",
|
||||
"[email protected]+74ed1ea502",
|
||||
"[email protected]+0ae6f91102",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+4be74c3f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+74ed1ea55d",
|
||||
"[email protected]+6b5b47fd5d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+427eab975d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+47f74c9802",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+cf6bc2db5d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+ffef5f6702",
|
||||
"[email protected]+4068fd645d",
|
||||
"[email protected]+ed66f0a35d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f5d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+79385d0b5d",
|
||||
"[email protected]+b9d63e335d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+901a437c02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+4fc5413f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+48607d035d",
|
||||
"[email protected]+74ed1ea502",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+52268a0e02",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+26.3",
|
||||
"java@25",
|
||||
"[email protected]+mc26.3",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+mc26.3",
|
||||
"[email protected]",
|
||||
"[email protected]"
|
||||
],
|
||||
"profile": "m2-metal",
|
||||
"minecraft": "26.3",
|
||||
"graphics_backend": "Vulkan",
|
||||
"server_runtime_kind": "integrated",
|
||||
"graphics_driver": "1.2.350 MoltenVK 1.4.2",
|
||||
"gpu": "Apple M2 Pro",
|
||||
"client_graphics_capture_status": "captured_at_recording_start",
|
||||
"label": "m2-metal-production-control02",
|
||||
"kind": "ticks",
|
||||
"schema": 1,
|
||||
"elapsed_seconds": 120.002829792,
|
||||
"observed_ticks_per_second": 19.999528379121575,
|
||||
"tick_state_capture_status": "sampled_server_ticks",
|
||||
"tick_state_samples": 2400,
|
||||
"tick_state_invalid_samples": 0,
|
||||
"tick_state_first": {
|
||||
"runs_game_elements": true,
|
||||
"stepping": false,
|
||||
"sprinting": false,
|
||||
"configured_tick_rate": 20.0,
|
||||
"frozen": false
|
||||
},
|
||||
"tick_state_last": {
|
||||
"runs_game_elements": true,
|
||||
"stepping": false,
|
||||
"sprinting": false,
|
||||
"configured_tick_rate": 20.0,
|
||||
"frozen": false
|
||||
},
|
||||
"tick_state_changed": [],
|
||||
"tick_state_frozen_samples": 0,
|
||||
"tick_state_stepping_samples": 0,
|
||||
"tick_state_sprinting_samples": 0,
|
||||
"tick_state_suppressed_samples": 0,
|
||||
"tick_state_nonstandard_rate_samples": 0,
|
||||
"dropped_samples": 0,
|
||||
"excluded_frames": 0,
|
||||
"peak_players": 1,
|
||||
"java_version": "25.0.1",
|
||||
"os": "Mac OS X",
|
||||
"os_version": "27.0.1",
|
||||
"architecture": "aarch64",
|
||||
"logical_processors": 12,
|
||||
"garbage_collectors": [
|
||||
"G1 Concurrent GC",
|
||||
"G1 Old Generation",
|
||||
"G1 Young Generation"
|
||||
],
|
||||
"compact_object_headers": false,
|
||||
"heap_used_end_bytes": 1952922832,
|
||||
"heap_committed_end_bytes": 2801795072,
|
||||
"heap_max_bytes": 6442450944,
|
||||
"heap_used_peak_sampled_bytes": 2631028672,
|
||||
"nonheap_used_peak_sampled_bytes": 252569024,
|
||||
"resource_samples": 118,
|
||||
"resource_sample_interval_ms": 1000,
|
||||
"process_rss_last_sampled_bytes": 3533422592,
|
||||
"process_rss_peak_sampled_bytes": 3582558208,
|
||||
"process_rss_samples": 119,
|
||||
"process_rss_sample_interval_ms": 1000,
|
||||
"process_rss_last_sample_age_ms": 135.860667,
|
||||
"process_cpu_seconds": 211.439715,
|
||||
"process_cpu_mean_cores": 1.7619560752566157,
|
||||
"gc_collections": 50,
|
||||
"gc_time_ms": 271,
|
||||
"stats": {
|
||||
"count": 2400,
|
||||
"meanMs": 5.11966473375,
|
||||
"p50Ms": 5.12125,
|
||||
"p95Ms": 5.81475,
|
||||
"p99Ms": 6.266792,
|
||||
"maxMs": 15.523875,
|
||||
"meanRate": 195.3252902299972,
|
||||
"lowOnePercentRate": 114.53856252586394,
|
||||
"over16Ms": 0,
|
||||
"over33Ms": 0,
|
||||
"over50Ms": 0,
|
||||
"over100Ms": 0
|
||||
},
|
||||
"samples_file": "m2-metal-production-control02-ticks-1791138458891.csv",
|
||||
"note": "Tick execution durations exclude scheduler sleep. meanRate is execution capacity, not observed TPS.",
|
||||
"samples_sha256": "55e2d86888534b363d1a20134d5b3f7891cc2721afc3344ad745f4e66b882230"
|
||||
}
|
||||
},
|
||||
"m2-metal-production-batch02": {
|
||||
"scene": "overlook",
|
||||
"camera_yaw": 141.5,
|
||||
"camera_pitch": 16.95,
|
||||
"player_x": -41.5,
|
||||
"player_y": 100.0,
|
||||
"player_z": 72.5,
|
||||
"warmup_seconds": 120,
|
||||
"gpu": "Apple M2 Pro",
|
||||
"graphics_backend": "Vulkan",
|
||||
"fixture_sha256": "a71cb439bb27c6dbb894b10154ee89f85444e7225a7122bce9acd3632d2e1140",
|
||||
"original_mod_sha256": "804fd1777770e15d8d39911a708e828a028da36d3cb43d1bc2e1c7cddc318e64",
|
||||
"profiling_enabled": false,
|
||||
"frames": {
|
||||
"minecraft": "26.3",
|
||||
"profile": "m2-metal-batched",
|
||||
"adaptive_budget_enabled": false,
|
||||
"precise_worker_pacing": false,
|
||||
"debounce_queue_wait": false,
|
||||
"wakeable_idle_wait": false,
|
||||
"dispatch_retry_backoff": false,
|
||||
"tick_membership_dedup": false,
|
||||
"terrain_quad_reuse": false,
|
||||
"terrain_position_arithmetic": false,
|
||||
"culling_ray_scratch": false,
|
||||
"empty_culling_whitelist": false,
|
||||
"interruptible_terrain_read": true,
|
||||
"adaptive_target_fps": 60,
|
||||
"world_difficulty": "NORMAL",
|
||||
"render_distance": 32,
|
||||
"simulation_distance": 12,
|
||||
"vsync": false,
|
||||
"fps_limit": 260,
|
||||
"fps_limit_unlimited": true,
|
||||
"inactivity_fps_limit": "minimized",
|
||||
"framebuffer_width": 3456,
|
||||
"framebuffer_height": 2234,
|
||||
"graphics_preset": "CUSTOM",
|
||||
"ambient_occlusion": true,
|
||||
"improved_transparency": false,
|
||||
"mipmap_levels": 4,
|
||||
"entity_distance_scaling": 1.0,
|
||||
"entity_shadows": true,
|
||||
"particles": "ALL",
|
||||
"clouds": "OFF",
|
||||
"cloud_range": 128,
|
||||
"fov": 110,
|
||||
"dh_rendering_enabled": true,
|
||||
"dh_renderer_mode": "DEFAULT",
|
||||
"dh_lod_radius_chunks": 128,
|
||||
"dh_horizontal_quality": "MEDIUM",
|
||||
"dh_vertical_quality": "MEDIUM",
|
||||
"dh_max_horizontal_resolution": "BLOCK",
|
||||
"dh_transparency": "COMPLETE",
|
||||
"dh_lod_shading": "AUTO",
|
||||
"dh_lod_only_mode": false,
|
||||
"dh_camera_quality_dropoff": true,
|
||||
"dh_ambient_occlusion": true,
|
||||
"dh_thread_count": 2,
|
||||
"dh_runtime_ratio": 0.5,
|
||||
"dh_capture_status": "available",
|
||||
"iris_capture_status": "not_installed",
|
||||
"iris_shaders_enabled": false,
|
||||
"gpu": "Apple M2 Pro",
|
||||
"graphics_backend": "Vulkan",
|
||||
"graphics_driver": "1.2.350 MoltenVK 1.4.2",
|
||||
"frame_limiter_effective_fps_start": 260,
|
||||
"frame_limiter_reason_start": "NONE",
|
||||
"java_vendor": "Microsoft",
|
||||
"java_vm_name": "OpenJDK 64-Bit Server VM",
|
||||
"java_runtime_version": "25.0.1+8-LTS",
|
||||
"mods": [
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+38c7a2d55d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+26.3",
|
||||
"[email protected]+74ed1ea502",
|
||||
"[email protected]+0ae6f91102",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+4be74c3f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+74ed1ea55d",
|
||||
"[email protected]+6b5b47fd5d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+427eab975d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+47f74c9802",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+cf6bc2db5d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+ffef5f6702",
|
||||
"[email protected]+4068fd645d",
|
||||
"[email protected]+ed66f0a35d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f5d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+79385d0b5d",
|
||||
"[email protected]+b9d63e335d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+901a437c02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+4fc5413f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+48607d035d",
|
||||
"[email protected]+74ed1ea502",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+52268a0e02",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+26.3",
|
||||
"java@25",
|
||||
"[email protected]+mc26.3",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+mc26.3",
|
||||
"[email protected]",
|
||||
"[email protected]"
|
||||
],
|
||||
"frame_limiter_effective_fps_end": 260,
|
||||
"frame_limiter_reason_end": "NONE",
|
||||
"frame_limiter_observations": 11356,
|
||||
"frame_limiter_throttled_observations": 0,
|
||||
"frame_limiter_afk_observations": 0,
|
||||
"visual_settings_end": {
|
||||
"world_difficulty": "NORMAL",
|
||||
"render_distance": 32,
|
||||
"simulation_distance": 12,
|
||||
"vsync": false,
|
||||
"fps_limit": 260,
|
||||
"fps_limit_unlimited": true,
|
||||
"inactivity_fps_limit": "minimized",
|
||||
"framebuffer_width": 3456,
|
||||
"framebuffer_height": 2234,
|
||||
"graphics_preset": "CUSTOM",
|
||||
"ambient_occlusion": true,
|
||||
"improved_transparency": false,
|
||||
"mipmap_levels": 4,
|
||||
"entity_distance_scaling": 1.0,
|
||||
"entity_shadows": true,
|
||||
"particles": "ALL",
|
||||
"clouds": "OFF",
|
||||
"cloud_range": 128,
|
||||
"fov": 110,
|
||||
"dh_rendering_enabled": true,
|
||||
"dh_renderer_mode": "DEFAULT",
|
||||
"dh_lod_radius_chunks": 128,
|
||||
"dh_horizontal_quality": "MEDIUM",
|
||||
"dh_vertical_quality": "MEDIUM",
|
||||
"dh_max_horizontal_resolution": "BLOCK",
|
||||
"dh_transparency": "COMPLETE",
|
||||
"dh_lod_shading": "AUTO",
|
||||
"dh_lod_only_mode": false,
|
||||
"dh_camera_quality_dropoff": true,
|
||||
"dh_ambient_occlusion": true,
|
||||
"dh_thread_count": 2,
|
||||
"dh_runtime_ratio": 0.5,
|
||||
"dh_capture_status": "available",
|
||||
"iris_shaders_enabled": false,
|
||||
"iris_capture_status": "not_installed",
|
||||
"dh_cloud_batch_requested": true,
|
||||
"dh_cloud_batch_enabled": true
|
||||
},
|
||||
"visual_settings_changed": [],
|
||||
"label": "m2-metal-production-batch02",
|
||||
"kind": "frames",
|
||||
"schema": 1,
|
||||
"elapsed_seconds": 120.013808792,
|
||||
"dropped_samples": 0,
|
||||
"excluded_frames": 0,
|
||||
"peak_players": 0,
|
||||
"java_version": "25.0.1",
|
||||
"os": "Mac OS X",
|
||||
"os_version": "27.0.1",
|
||||
"architecture": "aarch64",
|
||||
"logical_processors": 12,
|
||||
"garbage_collectors": [
|
||||
"G1 Concurrent GC",
|
||||
"G1 Old Generation",
|
||||
"G1 Young Generation"
|
||||
],
|
||||
"compact_object_headers": false,
|
||||
"heap_used_end_bytes": 2362702304,
|
||||
"heap_committed_end_bytes": 2629828608,
|
||||
"heap_max_bytes": 6442450944,
|
||||
"heap_used_peak_sampled_bytes": 2411913496,
|
||||
"nonheap_used_peak_sampled_bytes": 252099304,
|
||||
"resource_samples": 121,
|
||||
"resource_sample_interval_ms": 1000,
|
||||
"process_rss_last_sampled_bytes": 3365011456,
|
||||
"process_rss_peak_sampled_bytes": 3365011456,
|
||||
"process_rss_samples": 120,
|
||||
"process_rss_sample_interval_ms": 1000,
|
||||
"process_rss_last_sample_age_ms": 124.224792,
|
||||
"process_cpu_seconds": 200.945491,
|
||||
"process_cpu_mean_cores": 1.6743530850542827,
|
||||
"gc_collections": 55,
|
||||
"gc_time_ms": 264,
|
||||
"stats": {
|
||||
"count": 11355,
|
||||
"meanMs": 10.567351849405547,
|
||||
"p50Ms": 10.566083,
|
||||
"p95Ms": 10.803542,
|
||||
"p99Ms": 10.929875,
|
||||
"maxMs": 18.12125,
|
||||
"meanRate": 94.63108773616293,
|
||||
"lowOnePercentRate": 84.62637566863958,
|
||||
"over16Ms": 3,
|
||||
"over33Ms": 0,
|
||||
"over50Ms": 0,
|
||||
"over100Ms": 0
|
||||
},
|
||||
"samples_file": "m2-metal-production-batch02-frames-1791138162792.csv",
|
||||
"note": "Frame start intervals include frame limiting and presentation waits; menus, pauses and unfocused frames are excluded. Not GPU timings. Process RSS, when available, is sampled separately.",
|
||||
"dh_cloud_batch_requested": true,
|
||||
"dh_cloud_batch_enabled": true,
|
||||
"dh_cloud_batch_draws_recorded": 431310,
|
||||
"dh_cloud_batch_passes_recorded": 22718,
|
||||
"samples_sha256": "fa71172923ef6f0c362db90e5c203d82f10c606f6e1477c1086c52698197b880"
|
||||
},
|
||||
"ticks": {
|
||||
"java_vendor": "Microsoft",
|
||||
"java_vm_name": "OpenJDK 64-Bit Server VM",
|
||||
"java_runtime_version": "25.0.1+8-LTS",
|
||||
"mods": [
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+38c7a2d55d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+26.3",
|
||||
"[email protected]+74ed1ea502",
|
||||
"[email protected]+0ae6f91102",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+4be74c3f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+74ed1ea55d",
|
||||
"[email protected]+6b5b47fd5d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+427eab975d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+47f74c9802",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+cf6bc2db5d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+ffef5f6702",
|
||||
"[email protected]+4068fd645d",
|
||||
"[email protected]+ed66f0a35d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f5d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+79385d0b5d",
|
||||
"[email protected]+b9d63e335d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+901a437c02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+4fc5413f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+48607d035d",
|
||||
"[email protected]+74ed1ea502",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+52268a0e02",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+26.3",
|
||||
"java@25",
|
||||
"[email protected]+mc26.3",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+mc26.3",
|
||||
"[email protected]",
|
||||
"[email protected]"
|
||||
],
|
||||
"profile": "m2-metal-batched",
|
||||
"minecraft": "26.3",
|
||||
"graphics_backend": "Vulkan",
|
||||
"server_runtime_kind": "integrated",
|
||||
"graphics_driver": "1.2.350 MoltenVK 1.4.2",
|
||||
"gpu": "Apple M2 Pro",
|
||||
"client_graphics_capture_status": "captured_at_recording_start",
|
||||
"label": "m2-metal-production-batch02",
|
||||
"kind": "ticks",
|
||||
"schema": 1,
|
||||
"elapsed_seconds": 120.007075208,
|
||||
"observed_ticks_per_second": 19.998820868188357,
|
||||
"tick_state_capture_status": "sampled_server_ticks",
|
||||
"tick_state_samples": 2400,
|
||||
"tick_state_invalid_samples": 0,
|
||||
"tick_state_first": {
|
||||
"sprinting": false,
|
||||
"stepping": false,
|
||||
"runs_game_elements": true,
|
||||
"frozen": false,
|
||||
"configured_tick_rate": 20.0
|
||||
},
|
||||
"tick_state_last": {
|
||||
"sprinting": false,
|
||||
"stepping": false,
|
||||
"runs_game_elements": true,
|
||||
"frozen": false,
|
||||
"configured_tick_rate": 20.0
|
||||
},
|
||||
"tick_state_changed": [],
|
||||
"tick_state_frozen_samples": 0,
|
||||
"tick_state_stepping_samples": 0,
|
||||
"tick_state_sprinting_samples": 0,
|
||||
"tick_state_suppressed_samples": 0,
|
||||
"tick_state_nonstandard_rate_samples": 0,
|
||||
"dropped_samples": 0,
|
||||
"excluded_frames": 0,
|
||||
"peak_players": 1,
|
||||
"java_version": "25.0.1",
|
||||
"os": "Mac OS X",
|
||||
"os_version": "27.0.1",
|
||||
"architecture": "aarch64",
|
||||
"logical_processors": 12,
|
||||
"garbage_collectors": [
|
||||
"G1 Concurrent GC",
|
||||
"G1 Old Generation",
|
||||
"G1 Young Generation"
|
||||
],
|
||||
"compact_object_headers": false,
|
||||
"heap_used_end_bytes": 2362702304,
|
||||
"heap_committed_end_bytes": 2629828608,
|
||||
"heap_max_bytes": 6442450944,
|
||||
"heap_used_peak_sampled_bytes": 2420113408,
|
||||
"nonheap_used_peak_sampled_bytes": 252091760,
|
||||
"resource_samples": 119,
|
||||
"resource_sample_interval_ms": 1000,
|
||||
"process_rss_last_sampled_bytes": 3365011456,
|
||||
"process_rss_peak_sampled_bytes": 3365011456,
|
||||
"process_rss_samples": 119,
|
||||
"process_rss_sample_interval_ms": 1000,
|
||||
"process_rss_last_sample_age_ms": 122.8665,
|
||||
"process_cpu_seconds": 200.937019,
|
||||
"process_cpu_mean_cores": 1.6743764369869834,
|
||||
"gc_collections": 55,
|
||||
"gc_time_ms": 264,
|
||||
"stats": {
|
||||
"count": 2400,
|
||||
"meanMs": 5.300246929583333,
|
||||
"p50Ms": 5.254125,
|
||||
"p95Ms": 5.920583,
|
||||
"p99Ms": 6.653792,
|
||||
"maxMs": 14.089208,
|
||||
"meanRate": 188.6704550345568,
|
||||
"lowOnePercentRate": 102.21677433315672,
|
||||
"over16Ms": 0,
|
||||
"over33Ms": 0,
|
||||
"over50Ms": 0,
|
||||
"over100Ms": 0
|
||||
},
|
||||
"samples_file": "m2-metal-production-batch02-ticks-1791138162792.csv",
|
||||
"note": "Tick execution durations exclude scheduler sleep. meanRate is execution capacity, not observed TPS.",
|
||||
"samples_sha256": "423cfd784899704629a0d0a08edc3b4325195c282de75202320fe3b6349adb47"
|
||||
}
|
||||
}
|
||||
},
|
||||
"comparison": {
|
||||
"kind": "frames",
|
||||
"mismatches": [],
|
||||
"warnings": [
|
||||
"World, route, warm-up, shader state and LOD cache must also match; these are not verified by this tool.",
|
||||
"Experiment dh_cloud_batch_requested differs: False vs True.",
|
||||
"Experiment dh_cloud_batch_enabled differs: False vs True."
|
||||
],
|
||||
"metrics": [
|
||||
{
|
||||
"metric": "meanRate",
|
||||
"baseline": 82.92604701370881,
|
||||
"candidate": 94.63108773616293,
|
||||
"change_percent": 14.115035195780056
|
||||
},
|
||||
{
|
||||
"metric": "lowOnePercentRate",
|
||||
"baseline": 69.04374876082869,
|
||||
"candidate": 84.62637566863958,
|
||||
"change_percent": 22.569207477117104
|
||||
},
|
||||
{
|
||||
"metric": "p95Ms",
|
||||
"baseline": 12.558958,
|
||||
"candidate": 10.803542,
|
||||
"change_percent": -13.9774016283835
|
||||
},
|
||||
{
|
||||
"metric": "p99Ms",
|
||||
"baseline": 13.02825,
|
||||
"candidate": 10.929875,
|
||||
"change_percent": -16.106345825417844
|
||||
},
|
||||
{
|
||||
"metric": "maxMs",
|
||||
"baseline": 52.341917,
|
||||
"candidate": 18.12125,
|
||||
"change_percent": -65.37908613473213
|
||||
},
|
||||
{
|
||||
"metric": "heap_used_end_bytes",
|
||||
"baseline": 1952922832,
|
||||
"candidate": 2362702304,
|
||||
"change_percent": 20.982880904738167
|
||||
},
|
||||
{
|
||||
"metric": "heap_used_peak_sampled_bytes",
|
||||
"baseline": 2624148856,
|
||||
"candidate": 2411913496,
|
||||
"change_percent": -8.087778996025063
|
||||
},
|
||||
{
|
||||
"metric": "nonheap_used_peak_sampled_bytes",
|
||||
"baseline": 252569024,
|
||||
"candidate": 252099304,
|
||||
"change_percent": -0.18597688368943155
|
||||
},
|
||||
{
|
||||
"metric": "process_rss_last_sampled_bytes",
|
||||
"baseline": 3533422592,
|
||||
"candidate": 3365011456,
|
||||
"change_percent": -4.766232501634493
|
||||
},
|
||||
{
|
||||
"metric": "process_rss_peak_sampled_bytes",
|
||||
"baseline": 3582558208,
|
||||
"candidate": 3365011456,
|
||||
"change_percent": -6.072385691158044
|
||||
},
|
||||
{
|
||||
"metric": "process_cpu_mean_cores",
|
||||
"baseline": 1.7619055288012897,
|
||||
"candidate": 1.6743530850542827,
|
||||
"change_percent": -4.969190590290795
|
||||
},
|
||||
{
|
||||
"metric": "gc_time_ms",
|
||||
"baseline": 271,
|
||||
"candidate": 264,
|
||||
"change_percent": -2.583025830258301
|
||||
}
|
||||
]
|
||||
},
|
||||
"limits": [
|
||||
"One production-artifact pair, candidate then control; two earlier private prototype pairs ran in opposite orders.",
|
||||
"Same original JAR, fixture binary, seed/cache, 120-second warmup and 120-second recording.",
|
||||
"Simulation, wall-clock clouds and further LOD generation remain active.",
|
||||
"No compilation, JFR, GPU timing or CPU-heavy analysis overlaps these recording intervals.",
|
||||
"This candidate has no frame over 33 ms; earlier prototype runs include isolated 51.69/65.59 ms stalls.",
|
||||
"Movement smoke is a separate 30-second functional check, not a matched throughput comparison."
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,851 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"profiling": false,
|
||||
"common_seed": "m2-vk-retina-01 saved demo world, including terrain and Distant Horizons cache",
|
||||
"runs": {
|
||||
"m2-gl-retina-cache-02": {
|
||||
"scene": "overlook",
|
||||
"camera_yaw": 141.5,
|
||||
"camera_pitch": 16.95,
|
||||
"player_x": -41.5,
|
||||
"player_y": 100.0,
|
||||
"player_z": 72.5,
|
||||
"warmup_seconds": 120,
|
||||
"gpu": "Apple M2 Pro",
|
||||
"graphics_backend": "OpenGL",
|
||||
"fixture_sha256": "a71cb439bb27c6dbb894b10154ee89f85444e7225a7122bce9acd3632d2e1140",
|
||||
"original_mod_sha256": "6828ab3385bde5a97cc46fa98a5122211f5ac4e2197da8ed9555c29393ebbba6",
|
||||
"frames": {
|
||||
"minecraft": "26.3",
|
||||
"profile": "m2-balanced",
|
||||
"adaptive_budget_enabled": false,
|
||||
"precise_worker_pacing": false,
|
||||
"debounce_queue_wait": false,
|
||||
"wakeable_idle_wait": false,
|
||||
"dispatch_retry_backoff": false,
|
||||
"tick_membership_dedup": false,
|
||||
"terrain_quad_reuse": false,
|
||||
"terrain_position_arithmetic": false,
|
||||
"culling_ray_scratch": false,
|
||||
"empty_culling_whitelist": false,
|
||||
"interruptible_terrain_read": true,
|
||||
"adaptive_target_fps": 60,
|
||||
"world_difficulty": "NORMAL",
|
||||
"render_distance": 32,
|
||||
"simulation_distance": 12,
|
||||
"vsync": false,
|
||||
"fps_limit": 260,
|
||||
"fps_limit_unlimited": true,
|
||||
"inactivity_fps_limit": "minimized",
|
||||
"framebuffer_width": 3456,
|
||||
"framebuffer_height": 2234,
|
||||
"graphics_preset": "CUSTOM",
|
||||
"ambient_occlusion": true,
|
||||
"improved_transparency": false,
|
||||
"mipmap_levels": 4,
|
||||
"entity_distance_scaling": 1.0,
|
||||
"entity_shadows": true,
|
||||
"particles": "ALL",
|
||||
"clouds": "OFF",
|
||||
"cloud_range": 128,
|
||||
"fov": 110,
|
||||
"dh_rendering_enabled": true,
|
||||
"dh_renderer_mode": "DEFAULT",
|
||||
"dh_lod_radius_chunks": 128,
|
||||
"dh_horizontal_quality": "MEDIUM",
|
||||
"dh_vertical_quality": "MEDIUM",
|
||||
"dh_max_horizontal_resolution": "BLOCK",
|
||||
"dh_transparency": "COMPLETE",
|
||||
"dh_lod_shading": "AUTO",
|
||||
"dh_lod_only_mode": false,
|
||||
"dh_camera_quality_dropoff": true,
|
||||
"dh_ambient_occlusion": true,
|
||||
"dh_thread_count": 2,
|
||||
"dh_runtime_ratio": 0.5,
|
||||
"dh_capture_status": "available",
|
||||
"iris_capture_status": "not_installed",
|
||||
"iris_shaders_enabled": false,
|
||||
"gpu": "Apple M2 Pro",
|
||||
"graphics_backend": "OpenGL",
|
||||
"graphics_driver": "4.1 Metal - 91.7",
|
||||
"frame_limiter_effective_fps_start": 260,
|
||||
"frame_limiter_reason_start": "NONE",
|
||||
"java_vendor": "Microsoft",
|
||||
"java_vm_name": "OpenJDK 64-Bit Server VM",
|
||||
"java_runtime_version": "25.0.1+8-LTS",
|
||||
"mods": [
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+38c7a2d55d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+26.3",
|
||||
"[email protected]+74ed1ea502",
|
||||
"[email protected]+0ae6f91102",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+4be74c3f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+74ed1ea55d",
|
||||
"[email protected]+6b5b47fd5d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+427eab975d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+47f74c9802",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+cf6bc2db5d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+ffef5f6702",
|
||||
"[email protected]+4068fd645d",
|
||||
"[email protected]+ed66f0a35d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f5d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+79385d0b5d",
|
||||
"[email protected]+b9d63e335d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+901a437c02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+4fc5413f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+48607d035d",
|
||||
"[email protected]+74ed1ea502",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+52268a0e02",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+26.3",
|
||||
"java@25",
|
||||
"[email protected]+mc26.3",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+mc26.3",
|
||||
"[email protected]",
|
||||
"[email protected]"
|
||||
],
|
||||
"frame_limiter_effective_fps_end": 260,
|
||||
"frame_limiter_reason_end": "NONE",
|
||||
"frame_limiter_observations": 8023,
|
||||
"frame_limiter_throttled_observations": 0,
|
||||
"frame_limiter_afk_observations": 0,
|
||||
"visual_settings_end": {
|
||||
"world_difficulty": "NORMAL",
|
||||
"render_distance": 32,
|
||||
"simulation_distance": 12,
|
||||
"vsync": false,
|
||||
"fps_limit": 260,
|
||||
"fps_limit_unlimited": true,
|
||||
"inactivity_fps_limit": "minimized",
|
||||
"framebuffer_width": 3456,
|
||||
"framebuffer_height": 2234,
|
||||
"graphics_preset": "CUSTOM",
|
||||
"ambient_occlusion": true,
|
||||
"improved_transparency": false,
|
||||
"mipmap_levels": 4,
|
||||
"entity_distance_scaling": 1.0,
|
||||
"entity_shadows": true,
|
||||
"particles": "ALL",
|
||||
"clouds": "OFF",
|
||||
"cloud_range": 128,
|
||||
"fov": 110,
|
||||
"dh_rendering_enabled": true,
|
||||
"dh_renderer_mode": "DEFAULT",
|
||||
"dh_lod_radius_chunks": 128,
|
||||
"dh_horizontal_quality": "MEDIUM",
|
||||
"dh_vertical_quality": "MEDIUM",
|
||||
"dh_max_horizontal_resolution": "BLOCK",
|
||||
"dh_transparency": "COMPLETE",
|
||||
"dh_lod_shading": "AUTO",
|
||||
"dh_lod_only_mode": false,
|
||||
"dh_camera_quality_dropoff": true,
|
||||
"dh_ambient_occlusion": true,
|
||||
"dh_thread_count": 2,
|
||||
"dh_runtime_ratio": 0.5,
|
||||
"dh_capture_status": "available",
|
||||
"iris_capture_status": "not_installed",
|
||||
"iris_shaders_enabled": false
|
||||
},
|
||||
"visual_settings_changed": [],
|
||||
"label": "m2-gl-retina-cache-02",
|
||||
"kind": "frames",
|
||||
"schema": 1,
|
||||
"elapsed_seconds": 120.017216333,
|
||||
"dropped_samples": 0,
|
||||
"excluded_frames": 0,
|
||||
"peak_players": 0,
|
||||
"java_version": "25.0.1",
|
||||
"os": "Mac OS X",
|
||||
"os_version": "27.0.1",
|
||||
"architecture": "aarch64",
|
||||
"logical_processors": 12,
|
||||
"garbage_collectors": [
|
||||
"G1 Concurrent GC",
|
||||
"G1 Old Generation",
|
||||
"G1 Young Generation"
|
||||
],
|
||||
"compact_object_headers": false,
|
||||
"heap_used_end_bytes": 2223416320,
|
||||
"heap_committed_end_bytes": 2600468480,
|
||||
"heap_max_bytes": 6442450944,
|
||||
"heap_used_peak_sampled_bytes": 2375691312,
|
||||
"nonheap_used_peak_sampled_bytes": 252126832,
|
||||
"resource_samples": 121,
|
||||
"resource_sample_interval_ms": 1000,
|
||||
"process_rss_last_sampled_bytes": 4128243712,
|
||||
"process_rss_peak_sampled_bytes": 4128243712,
|
||||
"process_rss_samples": 120,
|
||||
"process_rss_sample_interval_ms": 1000,
|
||||
"process_rss_last_sample_age_ms": 142.62575,
|
||||
"process_cpu_seconds": 196.751059,
|
||||
"process_cpu_mean_cores": 1.6393569607054885,
|
||||
"gc_collections": 45,
|
||||
"gc_time_ms": 241,
|
||||
"stats": {
|
||||
"count": 8022,
|
||||
"meanMs": 14.956412173896783,
|
||||
"p50Ms": 14.811708,
|
||||
"p95Ms": 15.797583,
|
||||
"p99Ms": 16.476458,
|
||||
"maxMs": 25.481334,
|
||||
"meanRate": 66.86095491171913,
|
||||
"lowOnePercentRate": 56.973365580408306,
|
||||
"over16Ms": 58,
|
||||
"over33Ms": 0,
|
||||
"over50Ms": 0,
|
||||
"over100Ms": 0
|
||||
},
|
||||
"samples_file": "m2-gl-retina-cache-02-frames-1791123561936.csv",
|
||||
"note": "Frame start intervals include frame limiting and presentation waits; menus, pauses and unfocused frames are excluded. Not GPU timings. Process RSS, when available, is sampled separately.",
|
||||
"samples_sha256": "1082fa0ea56ef9da4b4908c607118805fa2e9af51dc7bcded6859dc44f8e15fd"
|
||||
},
|
||||
"ticks": {
|
||||
"java_vendor": "Microsoft",
|
||||
"java_vm_name": "OpenJDK 64-Bit Server VM",
|
||||
"java_runtime_version": "25.0.1+8-LTS",
|
||||
"mods": [
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+38c7a2d55d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+26.3",
|
||||
"[email protected]+74ed1ea502",
|
||||
"[email protected]+0ae6f91102",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+4be74c3f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+74ed1ea55d",
|
||||
"[email protected]+6b5b47fd5d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+427eab975d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+47f74c9802",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+cf6bc2db5d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+ffef5f6702",
|
||||
"[email protected]+4068fd645d",
|
||||
"[email protected]+ed66f0a35d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f5d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+79385d0b5d",
|
||||
"[email protected]+b9d63e335d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+901a437c02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+4fc5413f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+48607d035d",
|
||||
"[email protected]+74ed1ea502",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+52268a0e02",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+26.3",
|
||||
"java@25",
|
||||
"[email protected]+mc26.3",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+mc26.3",
|
||||
"[email protected]",
|
||||
"[email protected]"
|
||||
],
|
||||
"profile": "m2-balanced",
|
||||
"minecraft": "26.3",
|
||||
"graphics_backend": "OpenGL",
|
||||
"server_runtime_kind": "integrated",
|
||||
"graphics_driver": "4.1 Metal - 91.7",
|
||||
"gpu": "Apple M2 Pro",
|
||||
"client_graphics_capture_status": "captured_at_recording_start",
|
||||
"label": "m2-gl-retina-cache-02",
|
||||
"kind": "ticks",
|
||||
"schema": 1,
|
||||
"elapsed_seconds": 120.010891792,
|
||||
"observed_ticks_per_second": 20.006517443111377,
|
||||
"tick_state_capture_status": "sampled_server_ticks",
|
||||
"tick_state_samples": 2401,
|
||||
"tick_state_invalid_samples": 0,
|
||||
"tick_state_first": {
|
||||
"stepping": false,
|
||||
"runs_game_elements": true,
|
||||
"frozen": false,
|
||||
"configured_tick_rate": 20.0,
|
||||
"sprinting": false
|
||||
},
|
||||
"tick_state_last": {
|
||||
"stepping": false,
|
||||
"runs_game_elements": true,
|
||||
"frozen": false,
|
||||
"configured_tick_rate": 20.0,
|
||||
"sprinting": false
|
||||
},
|
||||
"tick_state_changed": [],
|
||||
"tick_state_frozen_samples": 0,
|
||||
"tick_state_stepping_samples": 0,
|
||||
"tick_state_sprinting_samples": 0,
|
||||
"tick_state_suppressed_samples": 0,
|
||||
"tick_state_nonstandard_rate_samples": 0,
|
||||
"dropped_samples": 0,
|
||||
"excluded_frames": 0,
|
||||
"peak_players": 1,
|
||||
"java_version": "25.0.1",
|
||||
"os": "Mac OS X",
|
||||
"os_version": "27.0.1",
|
||||
"architecture": "aarch64",
|
||||
"logical_processors": 12,
|
||||
"garbage_collectors": [
|
||||
"G1 Concurrent GC",
|
||||
"G1 Old Generation",
|
||||
"G1 Young Generation"
|
||||
],
|
||||
"compact_object_headers": false,
|
||||
"heap_used_end_bytes": 2223416320,
|
||||
"heap_committed_end_bytes": 2600468480,
|
||||
"heap_max_bytes": 6442450944,
|
||||
"heap_used_peak_sampled_bytes": 2330627552,
|
||||
"nonheap_used_peak_sampled_bytes": 252126832,
|
||||
"resource_samples": 118,
|
||||
"resource_sample_interval_ms": 1000,
|
||||
"process_rss_last_sampled_bytes": 4128243712,
|
||||
"process_rss_peak_sampled_bytes": 4128243712,
|
||||
"process_rss_samples": 119,
|
||||
"process_rss_sample_interval_ms": 1000,
|
||||
"process_rss_last_sample_age_ms": 141.511041,
|
||||
"process_cpu_seconds": 196.745797,
|
||||
"process_cpu_mean_cores": 1.6393995083462518,
|
||||
"gc_collections": 45,
|
||||
"gc_time_ms": 241,
|
||||
"stats": {
|
||||
"count": 2401,
|
||||
"meanMs": 5.977001070803832,
|
||||
"p50Ms": 6.014167,
|
||||
"p95Ms": 6.926959,
|
||||
"p99Ms": 7.41225,
|
||||
"maxMs": 13.996875,
|
||||
"meanRate": 167.30798407996815,
|
||||
"lowOnePercentRate": 112.11489369272508,
|
||||
"over16Ms": 0,
|
||||
"over33Ms": 0,
|
||||
"over50Ms": 0,
|
||||
"over100Ms": 0
|
||||
},
|
||||
"samples_file": "m2-gl-retina-cache-02-ticks-1791123561936.csv",
|
||||
"note": "Tick execution durations exclude scheduler sleep. meanRate is execution capacity, not observed TPS.",
|
||||
"samples_sha256": "25f8cec870cb44cb6c3a0548914858ae752511c582462ac23b16f6df2d64abdf"
|
||||
}
|
||||
},
|
||||
"m2-vk-retina-cache-02": {
|
||||
"scene": "overlook",
|
||||
"camera_yaw": 141.5,
|
||||
"camera_pitch": 16.95,
|
||||
"player_x": -41.5,
|
||||
"player_y": 100.0,
|
||||
"player_z": 72.5,
|
||||
"warmup_seconds": 120,
|
||||
"gpu": "Apple M2 Pro",
|
||||
"graphics_backend": "Vulkan",
|
||||
"fixture_sha256": "a71cb439bb27c6dbb894b10154ee89f85444e7225a7122bce9acd3632d2e1140",
|
||||
"original_mod_sha256": "6828ab3385bde5a97cc46fa98a5122211f5ac4e2197da8ed9555c29393ebbba6",
|
||||
"frames": {
|
||||
"minecraft": "26.3",
|
||||
"profile": "m2-balanced",
|
||||
"adaptive_budget_enabled": false,
|
||||
"precise_worker_pacing": false,
|
||||
"debounce_queue_wait": false,
|
||||
"wakeable_idle_wait": false,
|
||||
"dispatch_retry_backoff": false,
|
||||
"tick_membership_dedup": false,
|
||||
"terrain_quad_reuse": false,
|
||||
"terrain_position_arithmetic": false,
|
||||
"culling_ray_scratch": false,
|
||||
"empty_culling_whitelist": false,
|
||||
"interruptible_terrain_read": true,
|
||||
"adaptive_target_fps": 60,
|
||||
"world_difficulty": "NORMAL",
|
||||
"render_distance": 32,
|
||||
"simulation_distance": 12,
|
||||
"vsync": false,
|
||||
"fps_limit": 260,
|
||||
"fps_limit_unlimited": true,
|
||||
"inactivity_fps_limit": "minimized",
|
||||
"framebuffer_width": 3456,
|
||||
"framebuffer_height": 2234,
|
||||
"graphics_preset": "CUSTOM",
|
||||
"ambient_occlusion": true,
|
||||
"improved_transparency": false,
|
||||
"mipmap_levels": 4,
|
||||
"entity_distance_scaling": 1.0,
|
||||
"entity_shadows": true,
|
||||
"particles": "ALL",
|
||||
"clouds": "OFF",
|
||||
"cloud_range": 128,
|
||||
"fov": 110,
|
||||
"dh_rendering_enabled": true,
|
||||
"dh_renderer_mode": "DEFAULT",
|
||||
"dh_lod_radius_chunks": 128,
|
||||
"dh_horizontal_quality": "MEDIUM",
|
||||
"dh_vertical_quality": "MEDIUM",
|
||||
"dh_max_horizontal_resolution": "BLOCK",
|
||||
"dh_transparency": "COMPLETE",
|
||||
"dh_lod_shading": "AUTO",
|
||||
"dh_lod_only_mode": false,
|
||||
"dh_camera_quality_dropoff": true,
|
||||
"dh_ambient_occlusion": true,
|
||||
"dh_thread_count": 2,
|
||||
"dh_runtime_ratio": 0.5,
|
||||
"dh_capture_status": "available",
|
||||
"iris_capture_status": "not_installed",
|
||||
"iris_shaders_enabled": false,
|
||||
"gpu": "Apple M2 Pro",
|
||||
"graphics_backend": "Vulkan",
|
||||
"graphics_driver": "1.2.350 MoltenVK 1.4.2",
|
||||
"frame_limiter_effective_fps_start": 260,
|
||||
"frame_limiter_reason_start": "NONE",
|
||||
"java_vendor": "Microsoft",
|
||||
"java_vm_name": "OpenJDK 64-Bit Server VM",
|
||||
"java_runtime_version": "25.0.1+8-LTS",
|
||||
"mods": [
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+38c7a2d55d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+26.3",
|
||||
"[email protected]+74ed1ea502",
|
||||
"[email protected]+0ae6f91102",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+4be74c3f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+74ed1ea55d",
|
||||
"[email protected]+6b5b47fd5d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+427eab975d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+47f74c9802",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+cf6bc2db5d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+ffef5f6702",
|
||||
"[email protected]+4068fd645d",
|
||||
"[email protected]+ed66f0a35d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f5d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+79385d0b5d",
|
||||
"[email protected]+b9d63e335d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+901a437c02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+4fc5413f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+48607d035d",
|
||||
"[email protected]+74ed1ea502",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+52268a0e02",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+26.3",
|
||||
"java@25",
|
||||
"[email protected]+mc26.3",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+mc26.3",
|
||||
"[email protected]",
|
||||
"[email protected]"
|
||||
],
|
||||
"frame_limiter_effective_fps_end": 260,
|
||||
"frame_limiter_reason_end": "NONE",
|
||||
"frame_limiter_observations": 9756,
|
||||
"frame_limiter_throttled_observations": 0,
|
||||
"frame_limiter_afk_observations": 0,
|
||||
"visual_settings_end": {
|
||||
"world_difficulty": "NORMAL",
|
||||
"render_distance": 32,
|
||||
"simulation_distance": 12,
|
||||
"vsync": false,
|
||||
"fps_limit": 260,
|
||||
"fps_limit_unlimited": true,
|
||||
"inactivity_fps_limit": "minimized",
|
||||
"framebuffer_width": 3456,
|
||||
"framebuffer_height": 2234,
|
||||
"graphics_preset": "CUSTOM",
|
||||
"ambient_occlusion": true,
|
||||
"improved_transparency": false,
|
||||
"mipmap_levels": 4,
|
||||
"entity_distance_scaling": 1.0,
|
||||
"entity_shadows": true,
|
||||
"particles": "ALL",
|
||||
"clouds": "OFF",
|
||||
"cloud_range": 128,
|
||||
"fov": 110,
|
||||
"dh_rendering_enabled": true,
|
||||
"dh_renderer_mode": "DEFAULT",
|
||||
"dh_lod_radius_chunks": 128,
|
||||
"dh_horizontal_quality": "MEDIUM",
|
||||
"dh_vertical_quality": "MEDIUM",
|
||||
"dh_max_horizontal_resolution": "BLOCK",
|
||||
"dh_transparency": "COMPLETE",
|
||||
"dh_lod_shading": "AUTO",
|
||||
"dh_lod_only_mode": false,
|
||||
"dh_camera_quality_dropoff": true,
|
||||
"dh_ambient_occlusion": true,
|
||||
"dh_thread_count": 2,
|
||||
"dh_runtime_ratio": 0.5,
|
||||
"dh_capture_status": "available",
|
||||
"iris_capture_status": "not_installed",
|
||||
"iris_shaders_enabled": false
|
||||
},
|
||||
"visual_settings_changed": [],
|
||||
"label": "m2-vk-retina-cache-02",
|
||||
"kind": "frames",
|
||||
"schema": 1,
|
||||
"elapsed_seconds": 120.00796975,
|
||||
"dropped_samples": 0,
|
||||
"excluded_frames": 0,
|
||||
"peak_players": 0,
|
||||
"java_version": "25.0.1",
|
||||
"os": "Mac OS X",
|
||||
"os_version": "27.0.1",
|
||||
"architecture": "aarch64",
|
||||
"logical_processors": 12,
|
||||
"garbage_collectors": [
|
||||
"G1 Concurrent GC",
|
||||
"G1 Old Generation",
|
||||
"G1 Young Generation"
|
||||
],
|
||||
"compact_object_headers": false,
|
||||
"heap_used_end_bytes": 1898423224,
|
||||
"heap_committed_end_bytes": 3028287488,
|
||||
"heap_max_bytes": 6442450944,
|
||||
"heap_used_peak_sampled_bytes": 2853250848,
|
||||
"nonheap_used_peak_sampled_bytes": 255258208,
|
||||
"resource_samples": 121,
|
||||
"resource_sample_interval_ms": 1000,
|
||||
"process_rss_last_sampled_bytes": 3610017792,
|
||||
"process_rss_peak_sampled_bytes": 3755999232,
|
||||
"process_rss_samples": 120,
|
||||
"process_rss_sample_interval_ms": 1000,
|
||||
"process_rss_last_sample_age_ms": 26.511833,
|
||||
"process_cpu_seconds": 196.25092,
|
||||
"process_cpu_mean_cores": 1.635315724520871,
|
||||
"gc_collections": 33,
|
||||
"gc_time_ms": 181,
|
||||
"stats": {
|
||||
"count": 9755,
|
||||
"meanMs": 12.29954284551512,
|
||||
"p50Ms": 12.233542,
|
||||
"p95Ms": 13.203208,
|
||||
"p99Ms": 13.592833,
|
||||
"maxMs": 53.125791,
|
||||
"meanRate": 81.3038348303033,
|
||||
"lowOnePercentRate": 66.85283320659684,
|
||||
"over16Ms": 6,
|
||||
"over33Ms": 3,
|
||||
"over50Ms": 1,
|
||||
"over100Ms": 0
|
||||
},
|
||||
"samples_file": "m2-vk-retina-cache-02-frames-1791122829137.csv",
|
||||
"note": "Frame start intervals include frame limiting and presentation waits; menus, pauses and unfocused frames are excluded. Not GPU timings. Process RSS, when available, is sampled separately.",
|
||||
"samples_sha256": "83234c8d2571d6e2c64f043bfbe8a3a0f065ded7091fe06330cc8b50485d238b"
|
||||
},
|
||||
"ticks": {
|
||||
"java_vendor": "Microsoft",
|
||||
"java_vm_name": "OpenJDK 64-Bit Server VM",
|
||||
"java_runtime_version": "25.0.1+8-LTS",
|
||||
"mods": [
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+38c7a2d55d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+26.3",
|
||||
"[email protected]+74ed1ea502",
|
||||
"[email protected]+0ae6f91102",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+4be74c3f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+74ed1ea55d",
|
||||
"[email protected]+6b5b47fd5d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+427eab975d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+47f74c9802",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+cf6bc2db5d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+ffef5f6702",
|
||||
"[email protected]+4068fd645d",
|
||||
"[email protected]+ed66f0a35d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f5d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+79385d0b5d",
|
||||
"[email protected]+b9d63e335d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+901a437c02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+4fc5413f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+48607d035d",
|
||||
"[email protected]+74ed1ea502",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+52268a0e02",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+26.3",
|
||||
"java@25",
|
||||
"[email protected]+mc26.3",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+mc26.3",
|
||||
"[email protected]",
|
||||
"[email protected]"
|
||||
],
|
||||
"profile": "m2-balanced",
|
||||
"minecraft": "26.3",
|
||||
"graphics_backend": "Vulkan",
|
||||
"server_runtime_kind": "integrated",
|
||||
"graphics_driver": "1.2.350 MoltenVK 1.4.2",
|
||||
"gpu": "Apple M2 Pro",
|
||||
"client_graphics_capture_status": "captured_at_recording_start",
|
||||
"label": "m2-vk-retina-cache-02",
|
||||
"kind": "ticks",
|
||||
"schema": 1,
|
||||
"elapsed_seconds": 120.000482416,
|
||||
"observed_ticks_per_second": 20.00825289748892,
|
||||
"tick_state_capture_status": "sampled_server_ticks",
|
||||
"tick_state_samples": 2401,
|
||||
"tick_state_invalid_samples": 0,
|
||||
"tick_state_first": {
|
||||
"runs_game_elements": true,
|
||||
"stepping": false,
|
||||
"sprinting": false,
|
||||
"configured_tick_rate": 20.0,
|
||||
"frozen": false
|
||||
},
|
||||
"tick_state_last": {
|
||||
"runs_game_elements": true,
|
||||
"stepping": false,
|
||||
"sprinting": false,
|
||||
"configured_tick_rate": 20.0,
|
||||
"frozen": false
|
||||
},
|
||||
"tick_state_changed": [],
|
||||
"tick_state_frozen_samples": 0,
|
||||
"tick_state_stepping_samples": 0,
|
||||
"tick_state_sprinting_samples": 0,
|
||||
"tick_state_suppressed_samples": 0,
|
||||
"tick_state_nonstandard_rate_samples": 0,
|
||||
"dropped_samples": 0,
|
||||
"excluded_frames": 0,
|
||||
"peak_players": 1,
|
||||
"java_version": "25.0.1",
|
||||
"os": "Mac OS X",
|
||||
"os_version": "27.0.1",
|
||||
"architecture": "aarch64",
|
||||
"logical_processors": 12,
|
||||
"garbage_collectors": [
|
||||
"G1 Concurrent GC",
|
||||
"G1 Old Generation",
|
||||
"G1 Young Generation"
|
||||
],
|
||||
"compact_object_headers": false,
|
||||
"heap_used_end_bytes": 1898423224,
|
||||
"heap_committed_end_bytes": 3028287488,
|
||||
"heap_max_bytes": 6442450944,
|
||||
"heap_used_peak_sampled_bytes": 2850402968,
|
||||
"nonheap_used_peak_sampled_bytes": 255178424,
|
||||
"resource_samples": 118,
|
||||
"resource_sample_interval_ms": 1000,
|
||||
"process_rss_last_sampled_bytes": 3610017792,
|
||||
"process_rss_peak_sampled_bytes": 3755999232,
|
||||
"process_rss_samples": 119,
|
||||
"process_rss_sample_interval_ms": 1000,
|
||||
"process_rss_last_sample_age_ms": 25.188583,
|
||||
"process_cpu_seconds": 196.232982,
|
||||
"process_cpu_mean_cores": 1.6352682760034947,
|
||||
"gc_collections": 33,
|
||||
"gc_time_ms": 181,
|
||||
"stats": {
|
||||
"count": 2401,
|
||||
"meanMs": 5.043994845064557,
|
||||
"p50Ms": 5.053042,
|
||||
"p95Ms": 5.693458,
|
||||
"p99Ms": 6.132666,
|
||||
"maxMs": 18.295667,
|
||||
"meanRate": 198.25555551042623,
|
||||
"lowOnePercentRate": 124.6633839307103,
|
||||
"over16Ms": 1,
|
||||
"over33Ms": 0,
|
||||
"over50Ms": 0,
|
||||
"over100Ms": 0
|
||||
},
|
||||
"samples_file": "m2-vk-retina-cache-02-ticks-1791122829137.csv",
|
||||
"note": "Tick execution durations exclude scheduler sleep. meanRate is execution capacity, not observed TPS.",
|
||||
"samples_sha256": "c943b4155ed5c17a4f35cfc627cbd1a90e56c4531e7e468890e050c9fb92ee64"
|
||||
}
|
||||
}
|
||||
},
|
||||
"comparison": {
|
||||
"kind": "frames",
|
||||
"mismatches": [
|
||||
"graphics_backend: 'OpenGL' vs 'Vulkan'",
|
||||
"graphics_driver: '4.1 Metal - 91.7' vs '1.2.350 MoltenVK 1.4.2'"
|
||||
],
|
||||
"warnings": [
|
||||
"World, route, warm-up, shader state and LOD cache must also match; these are not verified by this tool."
|
||||
],
|
||||
"metrics": [
|
||||
{
|
||||
"metric": "meanRate",
|
||||
"baseline": 66.86095491171913,
|
||||
"candidate": 81.3038348303033,
|
||||
"change_percent": 21.60136650404416
|
||||
},
|
||||
{
|
||||
"metric": "lowOnePercentRate",
|
||||
"baseline": 56.973365580408306,
|
||||
"candidate": 66.85283320659684,
|
||||
"change_percent": 17.34050204958548
|
||||
},
|
||||
{
|
||||
"metric": "p95Ms",
|
||||
"baseline": 15.797583,
|
||||
"candidate": 13.203208,
|
||||
"change_percent": -16.422607179845173
|
||||
},
|
||||
{
|
||||
"metric": "p99Ms",
|
||||
"baseline": 16.476458,
|
||||
"candidate": 13.592833,
|
||||
"change_percent": -17.501486059685888
|
||||
},
|
||||
{
|
||||
"metric": "maxMs",
|
||||
"baseline": 25.481334,
|
||||
"candidate": 53.125791,
|
||||
"change_percent": 108.48904927818927
|
||||
},
|
||||
{
|
||||
"metric": "heap_used_end_bytes",
|
||||
"baseline": 2223416320,
|
||||
"candidate": 1898423224,
|
||||
"change_percent": -14.616835051386145
|
||||
},
|
||||
{
|
||||
"metric": "heap_used_peak_sampled_bytes",
|
||||
"baseline": 2375691312,
|
||||
"candidate": 2853250848,
|
||||
"change_percent": 20.10191869574005
|
||||
},
|
||||
{
|
||||
"metric": "nonheap_used_peak_sampled_bytes",
|
||||
"baseline": 252126832,
|
||||
"candidate": 255258208,
|
||||
"change_percent": 1.2419844310739503
|
||||
},
|
||||
{
|
||||
"metric": "process_rss_last_sampled_bytes",
|
||||
"baseline": 4128243712,
|
||||
"candidate": 3610017792,
|
||||
"change_percent": -12.553181356362709
|
||||
},
|
||||
{
|
||||
"metric": "process_rss_peak_sampled_bytes",
|
||||
"baseline": 4128243712,
|
||||
"candidate": 3755999232,
|
||||
"change_percent": -9.017018034036074
|
||||
},
|
||||
{
|
||||
"metric": "process_cpu_mean_cores",
|
||||
"baseline": 1.6393569607054885,
|
||||
"candidate": 1.635315724520871,
|
||||
"change_percent": -0.24651349776063647
|
||||
},
|
||||
{
|
||||
"metric": "gc_time_ms",
|
||||
"baseline": 241,
|
||||
"candidate": 181,
|
||||
"change_percent": -24.896265560165975
|
||||
}
|
||||
]
|
||||
},
|
||||
"limits": [
|
||||
"One matched pair, 120 seconds warmup and 120 seconds recording from independent copies of the same generated world and LOD cache.",
|
||||
"Stationary private spectator scene; moving gameplay and thermal-repeat tests remain necessary.",
|
||||
"Weather, simulation and additional LOD generation continue normally.",
|
||||
"Vulkan has one 53.13 ms stall versus OpenGL maximum 25.48 ms."
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,847 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"profiling": false,
|
||||
"runs": {
|
||||
"m2-gl-retina-01": {
|
||||
"scene": "overlook",
|
||||
"camera_yaw": 141.5,
|
||||
"camera_pitch": 16.95,
|
||||
"player_x": -41.5,
|
||||
"player_y": 100.0,
|
||||
"player_z": 72.5,
|
||||
"warmup_seconds": 60,
|
||||
"gpu": "Apple M2 Pro",
|
||||
"graphics_backend": "OpenGL",
|
||||
"frames": {
|
||||
"minecraft": "26.3",
|
||||
"profile": "m2-balanced",
|
||||
"adaptive_budget_enabled": false,
|
||||
"precise_worker_pacing": false,
|
||||
"debounce_queue_wait": false,
|
||||
"wakeable_idle_wait": false,
|
||||
"dispatch_retry_backoff": false,
|
||||
"tick_membership_dedup": false,
|
||||
"terrain_quad_reuse": false,
|
||||
"terrain_position_arithmetic": false,
|
||||
"culling_ray_scratch": false,
|
||||
"empty_culling_whitelist": false,
|
||||
"interruptible_terrain_read": true,
|
||||
"adaptive_target_fps": 60,
|
||||
"world_difficulty": "NORMAL",
|
||||
"render_distance": 32,
|
||||
"simulation_distance": 12,
|
||||
"vsync": false,
|
||||
"fps_limit": 260,
|
||||
"fps_limit_unlimited": true,
|
||||
"inactivity_fps_limit": "minimized",
|
||||
"framebuffer_width": 3456,
|
||||
"framebuffer_height": 2234,
|
||||
"graphics_preset": "CUSTOM",
|
||||
"ambient_occlusion": true,
|
||||
"improved_transparency": false,
|
||||
"mipmap_levels": 4,
|
||||
"entity_distance_scaling": 1.0,
|
||||
"entity_shadows": true,
|
||||
"particles": "ALL",
|
||||
"clouds": "OFF",
|
||||
"cloud_range": 128,
|
||||
"fov": 110,
|
||||
"dh_rendering_enabled": true,
|
||||
"dh_renderer_mode": "DEFAULT",
|
||||
"dh_lod_radius_chunks": 128,
|
||||
"dh_horizontal_quality": "MEDIUM",
|
||||
"dh_vertical_quality": "MEDIUM",
|
||||
"dh_max_horizontal_resolution": "BLOCK",
|
||||
"dh_transparency": "COMPLETE",
|
||||
"dh_lod_shading": "AUTO",
|
||||
"dh_lod_only_mode": false,
|
||||
"dh_camera_quality_dropoff": true,
|
||||
"dh_ambient_occlusion": true,
|
||||
"dh_thread_count": 2,
|
||||
"dh_runtime_ratio": 0.5,
|
||||
"dh_capture_status": "available",
|
||||
"iris_capture_status": "not_installed",
|
||||
"iris_shaders_enabled": false,
|
||||
"gpu": "Apple M2 Pro",
|
||||
"graphics_backend": "OpenGL",
|
||||
"graphics_driver": "4.1 Metal - 91.7",
|
||||
"frame_limiter_effective_fps_start": 260,
|
||||
"frame_limiter_reason_start": "NONE",
|
||||
"java_vendor": "Microsoft",
|
||||
"java_vm_name": "OpenJDK 64-Bit Server VM",
|
||||
"java_runtime_version": "25.0.1+8-LTS",
|
||||
"mods": [
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+38c7a2d55d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+26.3",
|
||||
"[email protected]+74ed1ea502",
|
||||
"[email protected]+0ae6f91102",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+4be74c3f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+74ed1ea55d",
|
||||
"[email protected]+6b5b47fd5d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+427eab975d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+47f74c9802",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+cf6bc2db5d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+ffef5f6702",
|
||||
"[email protected]+4068fd645d",
|
||||
"[email protected]+ed66f0a35d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f5d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+79385d0b5d",
|
||||
"[email protected]+b9d63e335d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+901a437c02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+4fc5413f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+48607d035d",
|
||||
"[email protected]+74ed1ea502",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+52268a0e02",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+26.3",
|
||||
"java@25",
|
||||
"[email protected]+mc26.3",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+mc26.3",
|
||||
"[email protected]",
|
||||
"[email protected]"
|
||||
],
|
||||
"frame_limiter_effective_fps_end": 260,
|
||||
"frame_limiter_reason_end": "NONE",
|
||||
"frame_limiter_observations": 8432,
|
||||
"frame_limiter_throttled_observations": 0,
|
||||
"frame_limiter_afk_observations": 0,
|
||||
"visual_settings_end": {
|
||||
"world_difficulty": "NORMAL",
|
||||
"render_distance": 32,
|
||||
"simulation_distance": 12,
|
||||
"vsync": false,
|
||||
"fps_limit": 260,
|
||||
"fps_limit_unlimited": true,
|
||||
"inactivity_fps_limit": "minimized",
|
||||
"framebuffer_width": 3456,
|
||||
"framebuffer_height": 2234,
|
||||
"graphics_preset": "CUSTOM",
|
||||
"ambient_occlusion": true,
|
||||
"improved_transparency": false,
|
||||
"mipmap_levels": 4,
|
||||
"entity_distance_scaling": 1.0,
|
||||
"entity_shadows": true,
|
||||
"particles": "ALL",
|
||||
"clouds": "OFF",
|
||||
"cloud_range": 128,
|
||||
"fov": 110,
|
||||
"dh_rendering_enabled": true,
|
||||
"dh_renderer_mode": "DEFAULT",
|
||||
"dh_lod_radius_chunks": 128,
|
||||
"dh_horizontal_quality": "MEDIUM",
|
||||
"dh_vertical_quality": "MEDIUM",
|
||||
"dh_max_horizontal_resolution": "BLOCK",
|
||||
"dh_transparency": "COMPLETE",
|
||||
"dh_lod_shading": "AUTO",
|
||||
"dh_lod_only_mode": false,
|
||||
"dh_camera_quality_dropoff": true,
|
||||
"dh_ambient_occlusion": true,
|
||||
"dh_thread_count": 2,
|
||||
"dh_runtime_ratio": 0.5,
|
||||
"dh_capture_status": "available",
|
||||
"iris_capture_status": "not_installed",
|
||||
"iris_shaders_enabled": false
|
||||
},
|
||||
"visual_settings_changed": [],
|
||||
"label": "m2-gl-retina-01",
|
||||
"kind": "frames",
|
||||
"schema": 1,
|
||||
"elapsed_seconds": 120.003805666,
|
||||
"dropped_samples": 0,
|
||||
"excluded_frames": 0,
|
||||
"peak_players": 0,
|
||||
"java_version": "25.0.1",
|
||||
"os": "Mac OS X",
|
||||
"os_version": "27.0.1",
|
||||
"architecture": "aarch64",
|
||||
"logical_processors": 12,
|
||||
"garbage_collectors": [
|
||||
"G1 Concurrent GC",
|
||||
"G1 Old Generation",
|
||||
"G1 Young Generation"
|
||||
],
|
||||
"compact_object_headers": false,
|
||||
"heap_used_end_bytes": 2950894968,
|
||||
"heap_committed_end_bytes": 3208642560,
|
||||
"heap_max_bytes": 6442450944,
|
||||
"heap_used_peak_sampled_bytes": 3018115440,
|
||||
"nonheap_used_peak_sampled_bytes": 281112144,
|
||||
"resource_samples": 121,
|
||||
"resource_sample_interval_ms": 1000,
|
||||
"process_rss_last_sampled_bytes": 4540743680,
|
||||
"process_rss_peak_sampled_bytes": 4557029376,
|
||||
"process_rss_samples": 120,
|
||||
"process_rss_sample_interval_ms": 1000,
|
||||
"process_rss_last_sample_age_ms": 129.569166,
|
||||
"process_cpu_seconds": 230.873908,
|
||||
"process_cpu_mean_cores": 1.9238882193667979,
|
||||
"gc_collections": 58,
|
||||
"gc_time_ms": 358,
|
||||
"stats": {
|
||||
"count": 8431,
|
||||
"meanMs": 14.229055637884,
|
||||
"p50Ms": 14.047,
|
||||
"p95Ms": 15.58675,
|
||||
"p99Ms": 16.267291,
|
||||
"maxMs": 22.121167,
|
||||
"meanRate": 70.27873285824819,
|
||||
"lowOnePercentRate": 56.463349822969725,
|
||||
"over16Ms": 48,
|
||||
"over33Ms": 0,
|
||||
"over50Ms": 0,
|
||||
"over100Ms": 0
|
||||
},
|
||||
"samples_file": "m2-gl-retina-01-frames-1791121841107.csv",
|
||||
"note": "Frame start intervals include frame limiting and presentation waits; menus, pauses and unfocused frames are excluded. Not GPU timings. Process RSS, when available, is sampled separately."
|
||||
},
|
||||
"ticks": {
|
||||
"java_vendor": "Microsoft",
|
||||
"java_vm_name": "OpenJDK 64-Bit Server VM",
|
||||
"java_runtime_version": "25.0.1+8-LTS",
|
||||
"mods": [
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+38c7a2d55d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+26.3",
|
||||
"[email protected]+74ed1ea502",
|
||||
"[email protected]+0ae6f91102",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+4be74c3f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+74ed1ea55d",
|
||||
"[email protected]+6b5b47fd5d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+427eab975d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+47f74c9802",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+cf6bc2db5d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+ffef5f6702",
|
||||
"[email protected]+4068fd645d",
|
||||
"[email protected]+ed66f0a35d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f5d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+79385d0b5d",
|
||||
"[email protected]+b9d63e335d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+901a437c02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+4fc5413f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+48607d035d",
|
||||
"[email protected]+74ed1ea502",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+52268a0e02",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+26.3",
|
||||
"java@25",
|
||||
"[email protected]+mc26.3",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+mc26.3",
|
||||
"[email protected]",
|
||||
"[email protected]"
|
||||
],
|
||||
"profile": "m2-balanced",
|
||||
"minecraft": "26.3",
|
||||
"graphics_backend": "OpenGL",
|
||||
"server_runtime_kind": "integrated",
|
||||
"graphics_driver": "4.1 Metal - 91.7",
|
||||
"gpu": "Apple M2 Pro",
|
||||
"client_graphics_capture_status": "captured_at_recording_start",
|
||||
"label": "m2-gl-retina-01",
|
||||
"kind": "ticks",
|
||||
"schema": 1,
|
||||
"elapsed_seconds": 119.99747575,
|
||||
"observed_ticks_per_second": 20.000420717183292,
|
||||
"tick_state_capture_status": "sampled_server_ticks",
|
||||
"tick_state_samples": 2400,
|
||||
"tick_state_invalid_samples": 0,
|
||||
"tick_state_first": {
|
||||
"stepping": false,
|
||||
"runs_game_elements": true,
|
||||
"frozen": false,
|
||||
"configured_tick_rate": 20.0,
|
||||
"sprinting": false
|
||||
},
|
||||
"tick_state_last": {
|
||||
"stepping": false,
|
||||
"runs_game_elements": true,
|
||||
"frozen": false,
|
||||
"configured_tick_rate": 20.0,
|
||||
"sprinting": false
|
||||
},
|
||||
"tick_state_changed": [],
|
||||
"tick_state_frozen_samples": 0,
|
||||
"tick_state_stepping_samples": 0,
|
||||
"tick_state_sprinting_samples": 0,
|
||||
"tick_state_suppressed_samples": 0,
|
||||
"tick_state_nonstandard_rate_samples": 0,
|
||||
"dropped_samples": 0,
|
||||
"excluded_frames": 0,
|
||||
"peak_players": 1,
|
||||
"java_version": "25.0.1",
|
||||
"os": "Mac OS X",
|
||||
"os_version": "27.0.1",
|
||||
"architecture": "aarch64",
|
||||
"logical_processors": 12,
|
||||
"garbage_collectors": [
|
||||
"G1 Concurrent GC",
|
||||
"G1 Old Generation",
|
||||
"G1 Young Generation"
|
||||
],
|
||||
"compact_object_headers": false,
|
||||
"heap_used_end_bytes": 2950894968,
|
||||
"heap_committed_end_bytes": 3208642560,
|
||||
"heap_max_bytes": 6442450944,
|
||||
"heap_used_peak_sampled_bytes": 3025083096,
|
||||
"nonheap_used_peak_sampled_bytes": 281091216,
|
||||
"resource_samples": 118,
|
||||
"resource_sample_interval_ms": 1000,
|
||||
"process_rss_last_sampled_bytes": 4540743680,
|
||||
"process_rss_peak_sampled_bytes": 4557029376,
|
||||
"process_rss_samples": 119,
|
||||
"process_rss_sample_interval_ms": 1000,
|
||||
"process_rss_last_sample_age_ms": 128.609959,
|
||||
"process_cpu_seconds": 230.865902,
|
||||
"process_cpu_mean_cores": 1.9239229871883368,
|
||||
"gc_collections": 58,
|
||||
"gc_time_ms": 358,
|
||||
"stats": {
|
||||
"count": 2400,
|
||||
"meanMs": 6.497739260833333,
|
||||
"p50Ms": 6.537167,
|
||||
"p95Ms": 7.496959,
|
||||
"p99Ms": 7.97775,
|
||||
"maxMs": 14.175542,
|
||||
"meanRate": 153.8996810825786,
|
||||
"lowOnePercentRate": 108.63386371191973,
|
||||
"over16Ms": 0,
|
||||
"over33Ms": 0,
|
||||
"over50Ms": 0,
|
||||
"over100Ms": 0
|
||||
},
|
||||
"samples_file": "m2-gl-retina-01-ticks-1791121841107.csv",
|
||||
"note": "Tick execution durations exclude scheduler sleep. meanRate is execution capacity, not observed TPS."
|
||||
},
|
||||
"fixture_sha256": "a71cb439bb27c6dbb894b10154ee89f85444e7225a7122bce9acd3632d2e1140",
|
||||
"original_mod_sha256": "6828ab3385bde5a97cc46fa98a5122211f5ac4e2197da8ed9555c29393ebbba6"
|
||||
},
|
||||
"m2-vk-retina-01": {
|
||||
"scene": "overlook",
|
||||
"camera_yaw": 141.5,
|
||||
"camera_pitch": 16.95,
|
||||
"player_x": -41.5,
|
||||
"player_y": 100.0,
|
||||
"player_z": 72.5,
|
||||
"warmup_seconds": 60,
|
||||
"gpu": "Apple M2 Pro",
|
||||
"graphics_backend": "Vulkan",
|
||||
"frames": {
|
||||
"minecraft": "26.3",
|
||||
"profile": "m2-balanced",
|
||||
"adaptive_budget_enabled": false,
|
||||
"precise_worker_pacing": false,
|
||||
"debounce_queue_wait": false,
|
||||
"wakeable_idle_wait": false,
|
||||
"dispatch_retry_backoff": false,
|
||||
"tick_membership_dedup": false,
|
||||
"terrain_quad_reuse": false,
|
||||
"terrain_position_arithmetic": false,
|
||||
"culling_ray_scratch": false,
|
||||
"empty_culling_whitelist": false,
|
||||
"interruptible_terrain_read": true,
|
||||
"adaptive_target_fps": 60,
|
||||
"world_difficulty": "NORMAL",
|
||||
"render_distance": 32,
|
||||
"simulation_distance": 12,
|
||||
"vsync": false,
|
||||
"fps_limit": 260,
|
||||
"fps_limit_unlimited": true,
|
||||
"inactivity_fps_limit": "minimized",
|
||||
"framebuffer_width": 3456,
|
||||
"framebuffer_height": 2234,
|
||||
"graphics_preset": "CUSTOM",
|
||||
"ambient_occlusion": true,
|
||||
"improved_transparency": false,
|
||||
"mipmap_levels": 4,
|
||||
"entity_distance_scaling": 1.0,
|
||||
"entity_shadows": true,
|
||||
"particles": "ALL",
|
||||
"clouds": "OFF",
|
||||
"cloud_range": 128,
|
||||
"fov": 110,
|
||||
"dh_rendering_enabled": true,
|
||||
"dh_renderer_mode": "DEFAULT",
|
||||
"dh_lod_radius_chunks": 128,
|
||||
"dh_horizontal_quality": "MEDIUM",
|
||||
"dh_vertical_quality": "MEDIUM",
|
||||
"dh_max_horizontal_resolution": "BLOCK",
|
||||
"dh_transparency": "COMPLETE",
|
||||
"dh_lod_shading": "AUTO",
|
||||
"dh_lod_only_mode": false,
|
||||
"dh_camera_quality_dropoff": true,
|
||||
"dh_ambient_occlusion": true,
|
||||
"dh_thread_count": 2,
|
||||
"dh_runtime_ratio": 0.5,
|
||||
"dh_capture_status": "available",
|
||||
"iris_shaders_enabled": false,
|
||||
"iris_capture_status": "not_installed",
|
||||
"gpu": "Apple M2 Pro",
|
||||
"graphics_backend": "Vulkan",
|
||||
"graphics_driver": "1.2.350 MoltenVK 1.4.2",
|
||||
"frame_limiter_effective_fps_start": 260,
|
||||
"frame_limiter_reason_start": "NONE",
|
||||
"java_vendor": "Microsoft",
|
||||
"java_vm_name": "OpenJDK 64-Bit Server VM",
|
||||
"java_runtime_version": "25.0.1+8-LTS",
|
||||
"mods": [
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+38c7a2d55d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+26.3",
|
||||
"[email protected]+74ed1ea502",
|
||||
"[email protected]+0ae6f91102",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+4be74c3f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+74ed1ea55d",
|
||||
"[email protected]+6b5b47fd5d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+427eab975d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+47f74c9802",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+cf6bc2db5d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+ffef5f6702",
|
||||
"[email protected]+4068fd645d",
|
||||
"[email protected]+ed66f0a35d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f5d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+79385d0b5d",
|
||||
"[email protected]+b9d63e335d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+901a437c02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+4fc5413f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+48607d035d",
|
||||
"[email protected]+74ed1ea502",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+52268a0e02",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+26.3",
|
||||
"java@25",
|
||||
"[email protected]+mc26.3",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+mc26.3",
|
||||
"[email protected]",
|
||||
"[email protected]"
|
||||
],
|
||||
"frame_limiter_effective_fps_end": 260,
|
||||
"frame_limiter_reason_end": "NONE",
|
||||
"frame_limiter_observations": 10674,
|
||||
"frame_limiter_throttled_observations": 0,
|
||||
"frame_limiter_afk_observations": 0,
|
||||
"visual_settings_end": {
|
||||
"world_difficulty": "NORMAL",
|
||||
"render_distance": 32,
|
||||
"simulation_distance": 12,
|
||||
"vsync": false,
|
||||
"fps_limit": 260,
|
||||
"fps_limit_unlimited": true,
|
||||
"inactivity_fps_limit": "minimized",
|
||||
"framebuffer_width": 3456,
|
||||
"framebuffer_height": 2234,
|
||||
"graphics_preset": "CUSTOM",
|
||||
"ambient_occlusion": true,
|
||||
"improved_transparency": false,
|
||||
"mipmap_levels": 4,
|
||||
"entity_distance_scaling": 1.0,
|
||||
"entity_shadows": true,
|
||||
"particles": "ALL",
|
||||
"clouds": "OFF",
|
||||
"cloud_range": 128,
|
||||
"fov": 110,
|
||||
"dh_rendering_enabled": true,
|
||||
"dh_renderer_mode": "DEFAULT",
|
||||
"dh_lod_radius_chunks": 128,
|
||||
"dh_horizontal_quality": "MEDIUM",
|
||||
"dh_vertical_quality": "MEDIUM",
|
||||
"dh_max_horizontal_resolution": "BLOCK",
|
||||
"dh_transparency": "COMPLETE",
|
||||
"dh_lod_shading": "AUTO",
|
||||
"dh_lod_only_mode": false,
|
||||
"dh_camera_quality_dropoff": true,
|
||||
"dh_ambient_occlusion": true,
|
||||
"dh_thread_count": 2,
|
||||
"dh_runtime_ratio": 0.5,
|
||||
"dh_capture_status": "available",
|
||||
"iris_shaders_enabled": false,
|
||||
"iris_capture_status": "not_installed"
|
||||
},
|
||||
"visual_settings_changed": [],
|
||||
"label": "m2-vk-retina-01",
|
||||
"kind": "frames",
|
||||
"schema": 1,
|
||||
"elapsed_seconds": 120.010259334,
|
||||
"dropped_samples": 0,
|
||||
"excluded_frames": 0,
|
||||
"peak_players": 0,
|
||||
"java_version": "25.0.1",
|
||||
"os": "Mac OS X",
|
||||
"os_version": "27.0.1",
|
||||
"architecture": "aarch64",
|
||||
"logical_processors": 12,
|
||||
"garbage_collectors": [
|
||||
"G1 Concurrent GC",
|
||||
"G1 Old Generation",
|
||||
"G1 Young Generation"
|
||||
],
|
||||
"compact_object_headers": false,
|
||||
"heap_used_end_bytes": 2720985624,
|
||||
"heap_committed_end_bytes": 3279945728,
|
||||
"heap_max_bytes": 6442450944,
|
||||
"heap_used_peak_sampled_bytes": 3092108504,
|
||||
"nonheap_used_peak_sampled_bytes": 281973344,
|
||||
"resource_samples": 121,
|
||||
"resource_sample_interval_ms": 1000,
|
||||
"process_rss_last_sampled_bytes": 3836805120,
|
||||
"process_rss_peak_sampled_bytes": 3938910208,
|
||||
"process_rss_samples": 120,
|
||||
"process_rss_sample_interval_ms": 1000,
|
||||
"process_rss_last_sample_age_ms": 123.285709,
|
||||
"process_cpu_seconds": 238.795316,
|
||||
"process_cpu_mean_cores": 1.9897908505922803,
|
||||
"gc_collections": 55,
|
||||
"gc_time_ms": 371,
|
||||
"stats": {
|
||||
"count": 10673,
|
||||
"meanMs": 11.242102072144665,
|
||||
"p50Ms": 11.04175,
|
||||
"p95Ms": 12.467041,
|
||||
"p99Ms": 12.960375,
|
||||
"maxMs": 51.66175,
|
||||
"meanRate": 88.9513361097983,
|
||||
"lowOnePercentRate": 68.73329439688561,
|
||||
"over16Ms": 15,
|
||||
"over33Ms": 1,
|
||||
"over50Ms": 1,
|
||||
"over100Ms": 0
|
||||
},
|
||||
"samples_file": "m2-vk-retina-01-frames-1791122160740.csv",
|
||||
"note": "Frame start intervals include frame limiting and presentation waits; menus, pauses and unfocused frames are excluded. Not GPU timings. Process RSS, when available, is sampled separately."
|
||||
},
|
||||
"ticks": {
|
||||
"java_vendor": "Microsoft",
|
||||
"java_vm_name": "OpenJDK 64-Bit Server VM",
|
||||
"java_runtime_version": "25.0.1+8-LTS",
|
||||
"mods": [
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+38c7a2d55d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+26.3",
|
||||
"[email protected]+74ed1ea502",
|
||||
"[email protected]+0ae6f91102",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+4be74c3f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+74ed1ea55d",
|
||||
"[email protected]+6b5b47fd5d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+427eab975d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+47f74c9802",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+cf6bc2db5d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+ffef5f6702",
|
||||
"[email protected]+4068fd645d",
|
||||
"[email protected]+ed66f0a35d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f5d",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+79385d0b5d",
|
||||
"[email protected]+b9d63e335d",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+901a437c02",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+4fc5413f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+48607d035d",
|
||||
"[email protected]+74ed1ea502",
|
||||
"[email protected]+3434d6d902",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+fcdff87f02",
|
||||
"[email protected]+52268a0e02",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+26.3",
|
||||
"java@25",
|
||||
"[email protected]+mc26.3",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]+mc26.3",
|
||||
"[email protected]",
|
||||
"[email protected]"
|
||||
],
|
||||
"profile": "m2-balanced",
|
||||
"minecraft": "26.3",
|
||||
"graphics_backend": "Vulkan",
|
||||
"client_graphics_capture_status": "captured_at_recording_start",
|
||||
"gpu": "Apple M2 Pro",
|
||||
"graphics_driver": "1.2.350 MoltenVK 1.4.2",
|
||||
"server_runtime_kind": "integrated",
|
||||
"label": "m2-vk-retina-01",
|
||||
"kind": "ticks",
|
||||
"schema": 1,
|
||||
"elapsed_seconds": 120.003517459,
|
||||
"observed_ticks_per_second": 19.999413774016883,
|
||||
"tick_state_capture_status": "sampled_server_ticks",
|
||||
"tick_state_samples": 2400,
|
||||
"tick_state_invalid_samples": 0,
|
||||
"tick_state_first": {
|
||||
"sprinting": false,
|
||||
"configured_tick_rate": 20.0,
|
||||
"frozen": false,
|
||||
"runs_game_elements": true,
|
||||
"stepping": false
|
||||
},
|
||||
"tick_state_last": {
|
||||
"sprinting": false,
|
||||
"configured_tick_rate": 20.0,
|
||||
"frozen": false,
|
||||
"runs_game_elements": true,
|
||||
"stepping": false
|
||||
},
|
||||
"tick_state_changed": [],
|
||||
"tick_state_frozen_samples": 0,
|
||||
"tick_state_stepping_samples": 0,
|
||||
"tick_state_sprinting_samples": 0,
|
||||
"tick_state_suppressed_samples": 0,
|
||||
"tick_state_nonstandard_rate_samples": 0,
|
||||
"dropped_samples": 0,
|
||||
"excluded_frames": 0,
|
||||
"peak_players": 1,
|
||||
"java_version": "25.0.1",
|
||||
"os": "Mac OS X",
|
||||
"os_version": "27.0.1",
|
||||
"architecture": "aarch64",
|
||||
"logical_processors": 12,
|
||||
"garbage_collectors": [
|
||||
"G1 Concurrent GC",
|
||||
"G1 Old Generation",
|
||||
"G1 Young Generation"
|
||||
],
|
||||
"compact_object_headers": false,
|
||||
"heap_used_end_bytes": 2720985624,
|
||||
"heap_committed_end_bytes": 3279945728,
|
||||
"heap_max_bytes": 6442450944,
|
||||
"heap_used_peak_sampled_bytes": 3100333200,
|
||||
"nonheap_used_peak_sampled_bytes": 281989632,
|
||||
"resource_samples": 118,
|
||||
"resource_sample_interval_ms": 1000,
|
||||
"process_rss_last_sampled_bytes": 3836805120,
|
||||
"process_rss_peak_sampled_bytes": 3938910208,
|
||||
"process_rss_samples": 119,
|
||||
"process_rss_sample_interval_ms": 1000,
|
||||
"process_rss_last_sample_age_ms": 122.220292,
|
||||
"process_cpu_seconds": 238.792129,
|
||||
"process_cpu_mean_cores": 1.98987608077059,
|
||||
"gc_collections": 55,
|
||||
"gc_time_ms": 371,
|
||||
"stats": {
|
||||
"count": 2400,
|
||||
"meanMs": 5.4780099325,
|
||||
"p50Ms": 5.4815,
|
||||
"p95Ms": 6.318041,
|
||||
"p99Ms": 6.849375,
|
||||
"maxMs": 14.798334,
|
||||
"meanRate": 182.54804433033036,
|
||||
"lowOnePercentRate": 121.38208617176281,
|
||||
"over16Ms": 0,
|
||||
"over33Ms": 0,
|
||||
"over50Ms": 0,
|
||||
"over100Ms": 0
|
||||
},
|
||||
"samples_file": "m2-vk-retina-01-ticks-1791122160740.csv",
|
||||
"note": "Tick execution durations exclude scheduler sleep. meanRate is execution capacity, not observed TPS."
|
||||
},
|
||||
"fixture_sha256": "a71cb439bb27c6dbb894b10154ee89f85444e7225a7122bce9acd3632d2e1140",
|
||||
"original_mod_sha256": "6828ab3385bde5a97cc46fa98a5122211f5ac4e2197da8ed9555c29393ebbba6"
|
||||
}
|
||||
},
|
||||
"comparison": {
|
||||
"kind": "frames",
|
||||
"mismatches": [
|
||||
"graphics_backend: 'OpenGL' vs 'Vulkan'",
|
||||
"graphics_driver: '4.1 Metal - 91.7' vs '1.2.350 MoltenVK 1.4.2'"
|
||||
],
|
||||
"warnings": [
|
||||
"World, route, warm-up, shader state and LOD cache must also match; these are not verified by this tool."
|
||||
],
|
||||
"metrics": [
|
||||
{
|
||||
"metric": "meanRate",
|
||||
"baseline": 70.27873285824819,
|
||||
"candidate": 88.9513361097983,
|
||||
"change_percent": 26.56935105704403
|
||||
},
|
||||
{
|
||||
"metric": "lowOnePercentRate",
|
||||
"baseline": 56.463349822969725,
|
||||
"candidate": 68.73329439688561,
|
||||
"change_percent": 21.730812309907233
|
||||
},
|
||||
{
|
||||
"metric": "p95Ms",
|
||||
"baseline": 15.58675,
|
||||
"candidate": 12.467041,
|
||||
"change_percent": -20.01513464962228
|
||||
},
|
||||
{
|
||||
"metric": "p99Ms",
|
||||
"baseline": 16.267291,
|
||||
"candidate": 12.960375,
|
||||
"change_percent": -20.32862140352687
|
||||
},
|
||||
{
|
||||
"metric": "maxMs",
|
||||
"baseline": 22.121167,
|
||||
"candidate": 51.66175,
|
||||
"change_percent": 133.53989416561976
|
||||
},
|
||||
{
|
||||
"metric": "heap_used_end_bytes",
|
||||
"baseline": 2950894968,
|
||||
"candidate": 2720985624,
|
||||
"change_percent": -7.791173406480933
|
||||
},
|
||||
{
|
||||
"metric": "heap_used_peak_sampled_bytes",
|
||||
"baseline": 3018115440,
|
||||
"candidate": 3092108504,
|
||||
"change_percent": 2.451631339853577
|
||||
},
|
||||
{
|
||||
"metric": "nonheap_used_peak_sampled_bytes",
|
||||
"baseline": 281112144,
|
||||
"candidate": 281973344,
|
||||
"change_percent": 0.30635460558403604
|
||||
},
|
||||
{
|
||||
"metric": "process_rss_last_sampled_bytes",
|
||||
"baseline": 4540743680,
|
||||
"candidate": 3836805120,
|
||||
"change_percent": -15.502715185191862
|
||||
},
|
||||
{
|
||||
"metric": "process_rss_peak_sampled_bytes",
|
||||
"baseline": 4557029376,
|
||||
"candidate": 3938910208,
|
||||
"change_percent": -13.564081268718164
|
||||
},
|
||||
{
|
||||
"metric": "process_cpu_mean_cores",
|
||||
"baseline": 1.9238882193667979,
|
||||
"candidate": 1.9897908505922803,
|
||||
"change_percent": 3.4254916975983596
|
||||
},
|
||||
{
|
||||
"metric": "gc_time_ms",
|
||||
"baseline": 358,
|
||||
"candidate": 371,
|
||||
"change_percent": 3.6312849162011274
|
||||
}
|
||||
]
|
||||
},
|
||||
"limits": [
|
||||
"Single matched-code pair from equal starting world copies; weather and LOD generation continue normally.",
|
||||
"Fixed private spectator camera; not a movement or survival gameplay equivalence test.",
|
||||
"Near terrain and water visually inspected; cloud appearance and distant-cache coverage are not pixel identical.",
|
||||
"Repeated copies of a common generated world and LOD cache are the next validation stage.",
|
||||
"Vulkan has one 51.66 ms stall; repeat before claiming consistently better worst-case pacing."
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
{
|
||||
"note": "Diagnostic overlap is not causation. GC and thread waits can overlap each other; sampled stacks can miss short work. Profiling intervals are not ordinary FPS measurements.",
|
||||
"slow_frames": [
|
||||
{
|
||||
"render_thread_wait_overlap_ms": 0.0,
|
||||
"render_thread_sample_leaf_methods": [],
|
||||
"start": "2026-10-04T18:30:25.104841333Z",
|
||||
"render_thread_sample_inclusive_methods": [],
|
||||
"gc_pause_overlap_ms": 0.0,
|
||||
"interval_ms": 43.896459
|
||||
},
|
||||
{
|
||||
"render_thread_wait_overlap_ms": 0.0,
|
||||
"render_thread_sample_leaf_methods": [
|
||||
{
|
||||
"samples": 3,
|
||||
"method": "org.lwjgl.util.vma.Vma.nvmaCreateBuffer"
|
||||
}
|
||||
],
|
||||
"start": "2026-10-04T18:31:21.695948833Z",
|
||||
"render_thread_sample_inclusive_methods": [
|
||||
{
|
||||
"samples": 3,
|
||||
"method": "com.mojang.blaze3d.framegraph.FrameGraphBuilder.execute"
|
||||
},
|
||||
{
|
||||
"samples": 3,
|
||||
"method": "com.mojang.renderpearl.backend.vulkan.VulkanDevice.createBuffer"
|
||||
},
|
||||
{
|
||||
"samples": 3,
|
||||
"method": "com.mojang.renderpearl.backend.vulkan.VulkanGpuBuffer$Direct.<init>"
|
||||
},
|
||||
{
|
||||
"samples": 3,
|
||||
"method": "com.mojang.renderpearl.frontend.FrontendGpuDevice.createBuffer"
|
||||
},
|
||||
{
|
||||
"samples": 3,
|
||||
"method": "com.seibel.distanthorizons.common.render.blaze.wrappers.buffer.BlazeVertexBufferWrapper.uploadVertexBuffer"
|
||||
},
|
||||
{
|
||||
"samples": 3,
|
||||
"method": "com.seibel.distanthorizons.core.api.internal.ClientApi.renderLodLayer"
|
||||
},
|
||||
{
|
||||
"samples": 3,
|
||||
"method": "com.seibel.distanthorizons.core.api.internal.ClientApi.renderLods"
|
||||
},
|
||||
{
|
||||
"samples": 3,
|
||||
"method": "com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.LodBufferContainer.lambda$uploadBuffersAsync$0"
|
||||
},
|
||||
{
|
||||
"samples": 3,
|
||||
"method": "com.seibel.distanthorizons.core.render.RenderThreadTaskHandler$QueuedRunnable.run"
|
||||
},
|
||||
{
|
||||
"samples": 3,
|
||||
"method": "com.seibel.distanthorizons.core.render.RenderThreadTaskHandler.runRenderThreadTasks"
|
||||
},
|
||||
{
|
||||
"samples": 3,
|
||||
"method": "net.fabricmc.loader.impl.game.minecraft.MinecraftGameProvider.launch"
|
||||
},
|
||||
{
|
||||
"samples": 3,
|
||||
"method": "net.fabricmc.loader.impl.launch.knot.Knot.launch"
|
||||
}
|
||||
],
|
||||
"gc_pause_overlap_ms": 0.0,
|
||||
"interval_ms": 63.974708
|
||||
}
|
||||
],
|
||||
"frame_events": 11408,
|
||||
"slow_frame_threshold_ms": 25,
|
||||
"schema": 1,
|
||||
"profiling": true,
|
||||
"original_mod_sha256": "804fd1777770e15d8d39911a708e828a028da36d3cb43d1bc2e1c7cddc318e64",
|
||||
"fixture_sha256": "c4e31f8e0571b27821d65c2231f3e9c06cebf4820cb912af9c608b2f3e994739",
|
||||
"gpu": "Apple M2 Pro",
|
||||
"graphics_backend": "Vulkan",
|
||||
"frame_csv_samples": 11409,
|
||||
"limits": [
|
||||
"One profiling capture with normal simulation; not an ordinary throughput comparison.",
|
||||
"JFR frame events cover one fewer interval than the recorder CSV.",
|
||||
"Samples identify DH terrain vertex-buffer upload inside Vulkan allocation during the 63.97 ms frame; this is a candidate, not proof of a general cause.",
|
||||
"The first 43.90 ms diagnostic interval has no sampled stack and must not be attributed to GC or allocation."
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
{
|
||||
"label": "m2-metal-upload-jfr01",
|
||||
"diagnostic_only": true,
|
||||
"device": "Apple M2 Pro",
|
||||
"backend": "Vulkan",
|
||||
"scene": "flyover",
|
||||
"warmup_seconds": 120,
|
||||
"recording_seconds": 120.010957,
|
||||
"resolution": [
|
||||
3456,
|
||||
2234
|
||||
],
|
||||
"original_mod_sha256": "804fd1777770e15d8d39911a708e828a028da36d3cb43d1bc2e1c7cddc318e64",
|
||||
"fixture_sha256": "f39f6bbe2d88d9d2a8daec42e6d235610c1af0e6fb3d36b3a0ec5b8706e08a47",
|
||||
"profiling_enabled": true,
|
||||
"allocation_and_upload": {
|
||||
"instrumented_calls_total": 6836,
|
||||
"bytes_allocated_during_recording": 849158016,
|
||||
"capacity_reuse_candidates": 0,
|
||||
"dropped_samples": 0,
|
||||
"dropped_slow_events": 0,
|
||||
"allocation_duration": {
|
||||
"count": 2336,
|
||||
"meanMs": 0.00452784375,
|
||||
"p50Ms": 0.003125,
|
||||
"p95Ms": 0.010416,
|
||||
"p99Ms": 0.014209,
|
||||
"maxMs": 0.097875,
|
||||
"meanRate": 220855.67771635228,
|
||||
"lowOnePercentRate": 48281.383905400675,
|
||||
"over16Ms": 0,
|
||||
"over33Ms": 0,
|
||||
"over50Ms": 0,
|
||||
"over100Ms": 0
|
||||
},
|
||||
"slow_allocations": [],
|
||||
"whole_upload": {
|
||||
"bytes": 849158016,
|
||||
"dropped_samples": 0,
|
||||
"dropped_slow_events": 0,
|
||||
"duration": {
|
||||
"count": 2336,
|
||||
"meanMs": 0.0395248642979452,
|
||||
"p50Ms": 0.0145,
|
||||
"p95Ms": 0.052208,
|
||||
"p99Ms": 0.074666,
|
||||
"maxMs": 46.492708,
|
||||
"meanRate": 25300.529622614984,
|
||||
"lowOnePercentRate": 494.69449430380934,
|
||||
"over16Ms": 1,
|
||||
"over33Ms": 1,
|
||||
"over50Ms": 0,
|
||||
"over100Ms": 0
|
||||
},
|
||||
"slow_events": [
|
||||
{
|
||||
"recording_offset_ns": 10745292,
|
||||
"duration_ns": 46492708,
|
||||
"bytes": 542720
|
||||
}
|
||||
]
|
||||
},
|
||||
"staging_copy_and_barrier": {
|
||||
"bytes": 849158016,
|
||||
"dropped_samples": 0,
|
||||
"dropped_slow_events": 0,
|
||||
"duration": {
|
||||
"count": 2336,
|
||||
"meanMs": 0.013740746575342466,
|
||||
"p50Ms": 0.007583,
|
||||
"p95Ms": 0.042125,
|
||||
"p99Ms": 0.059959,
|
||||
"maxMs": 0.116333,
|
||||
"meanRate": 72776.24942115466,
|
||||
"lowOnePercentRate": 14014.271199504828,
|
||||
"over16Ms": 0,
|
||||
"over33Ms": 0,
|
||||
"over50Ms": 0,
|
||||
"over100Ms": 0
|
||||
},
|
||||
"slow_events": []
|
||||
},
|
||||
"note": "Original DH vertex allocations only. Durations include Java and driver work; profiling is separate from ordinary FPS measurements."
|
||||
},
|
||||
"frame_events": 13345,
|
||||
"slow_frame_threshold_ms": 25,
|
||||
"slow_frames": [
|
||||
{
|
||||
"interval_ms": 51.230167,
|
||||
"gc_pause_overlap_ms": 0.0,
|
||||
"render_thread_wait_overlap_ms": 0.0,
|
||||
"terrain_upload_overlap_ms": 0.0
|
||||
},
|
||||
{
|
||||
"interval_ms": 40.096958,
|
||||
"gc_pause_overlap_ms": 0.0,
|
||||
"render_thread_wait_overlap_ms": 0.0,
|
||||
"terrain_upload_overlap_ms": 0.0
|
||||
},
|
||||
{
|
||||
"interval_ms": 27.714625,
|
||||
"gc_pause_overlap_ms": 24.492167,
|
||||
"render_thread_wait_overlap_ms": 0.0,
|
||||
"terrain_upload_overlap_ms": 0.0
|
||||
},
|
||||
{
|
||||
"interval_ms": 26.068541,
|
||||
"gc_pause_overlap_ms": 22.856917,
|
||||
"render_thread_wait_overlap_ms": 0.0,
|
||||
"terrain_upload_overlap_ms": 0.0
|
||||
},
|
||||
{
|
||||
"interval_ms": 31.879791,
|
||||
"gc_pause_overlap_ms": 29.20725,
|
||||
"render_thread_wait_overlap_ms": 0.0,
|
||||
"terrain_upload_overlap_ms": 0.0
|
||||
},
|
||||
{
|
||||
"interval_ms": 28.467666,
|
||||
"gc_pause_overlap_ms": 23.655583,
|
||||
"render_thread_wait_overlap_ms": 0.0,
|
||||
"terrain_upload_overlap_ms": 0.0
|
||||
},
|
||||
{
|
||||
"interval_ms": 29.939875,
|
||||
"gc_pause_overlap_ms": 20.903792,
|
||||
"render_thread_wait_overlap_ms": 0.0,
|
||||
"terrain_upload_overlap_ms": 0.0
|
||||
},
|
||||
{
|
||||
"interval_ms": 28.263583,
|
||||
"gc_pause_overlap_ms": 20.792166,
|
||||
"render_thread_wait_overlap_ms": 0.0,
|
||||
"terrain_upload_overlap_ms": 0.0
|
||||
}
|
||||
],
|
||||
"limits": [
|
||||
"Profiling is excluded from ordinary FPS comparisons.",
|
||||
"Six of eight captured intervals over 25 ms overlap GC pauses; this does not explain every hitch.",
|
||||
"The first whole-upload interval includes private event setup outside its JFR duration and is unattributed. Destination allocation and staging timers do not explain it.",
|
||||
"Zero in-place capacity-reuse candidates were observed; this does not evaluate a cross-wrapper pool.",
|
||||
"GC and upload overlap are not exclusive and do not prove causation."
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
{
|
||||
"label": "m2-metal-zgc-upload-jfr01",
|
||||
"diagnostic_only": true,
|
||||
"device": "Apple M2 Pro",
|
||||
"backend": "Vulkan",
|
||||
"scene": "flyover",
|
||||
"warmup_seconds": 120,
|
||||
"recording_seconds": 120.01719675,
|
||||
"original_mod_sha256": "804fd1777770e15d8d39911a708e828a028da36d3cb43d1bc2e1c7cddc318e64",
|
||||
"fixture_sha256": "70c35d7804c87a834a9aede18c774749adf4283f07f5ed973c78f5cb97893f00",
|
||||
"profiling_enabled": true,
|
||||
"garbage_collectors": [
|
||||
"ZGC Major Cycles",
|
||||
"ZGC Major Pauses",
|
||||
"ZGC Minor Cycles",
|
||||
"ZGC Minor Pauses"
|
||||
],
|
||||
"allocation_and_upload": {
|
||||
"instrumented_calls_total": 6963,
|
||||
"bytes_allocated_during_recording": 787634112,
|
||||
"capacity_reuse_candidates": 0,
|
||||
"dropped_samples": 0,
|
||||
"dropped_slow_events": 0,
|
||||
"allocation_duration": {
|
||||
"count": 2269,
|
||||
"meanMs": 0.0044483962097840455,
|
||||
"p50Ms": 0.002542,
|
||||
"p95Ms": 0.010292,
|
||||
"p99Ms": 0.016208,
|
||||
"maxMs": 0.048875,
|
||||
"meanRate": 224800.1196027785,
|
||||
"lowOnePercentRate": 45552.221661071664,
|
||||
"over16Ms": 0,
|
||||
"over33Ms": 0,
|
||||
"over50Ms": 0,
|
||||
"over100Ms": 0
|
||||
},
|
||||
"slow_allocations": [],
|
||||
"whole_upload": {
|
||||
"bytes": 787634112,
|
||||
"dropped_samples": 0,
|
||||
"dropped_slow_events": 0,
|
||||
"duration": {
|
||||
"count": 2269,
|
||||
"meanMs": 0.021492267078007932,
|
||||
"p50Ms": 0.014875,
|
||||
"p95Ms": 0.060458,
|
||||
"p99Ms": 0.101667,
|
||||
"maxMs": 0.291,
|
||||
"meanRate": 46528.36279999772,
|
||||
"lowOnePercentRate": 7846.821849509556,
|
||||
"over16Ms": 0,
|
||||
"over33Ms": 0,
|
||||
"over50Ms": 0,
|
||||
"over100Ms": 0
|
||||
},
|
||||
"slow_events": []
|
||||
},
|
||||
"staging_copy_and_barrier": {
|
||||
"bytes": 787634112,
|
||||
"dropped_samples": 0,
|
||||
"dropped_slow_events": 0,
|
||||
"duration": {
|
||||
"count": 2269,
|
||||
"meanMs": 0.015113673865138828,
|
||||
"p50Ms": 0.007125,
|
||||
"p95Ms": 0.049916,
|
||||
"p99Ms": 0.074375,
|
||||
"maxMs": 0.282083,
|
||||
"meanRate": 66165.24935784133,
|
||||
"lowOnePercentRate": 9546.218431589516,
|
||||
"over16Ms": 0,
|
||||
"over33Ms": 0,
|
||||
"over50Ms": 0,
|
||||
"over100Ms": 0
|
||||
},
|
||||
"slow_events": []
|
||||
},
|
||||
"note": "Original DH destination allocation, complete upload, and staging/copy/barrier timings. Nested durations are not additive. Profiling is separate from ordinary FPS measurements."
|
||||
},
|
||||
"jfr_summary": {
|
||||
"gc_cycles": 61,
|
||||
"gc_pause_events": 193,
|
||||
"gc_pause_total_ms": 2.163793,
|
||||
"gc_pause_longest_ms": 0.030584,
|
||||
"heap_used_peak_at_gc_bytes": 6092226560
|
||||
},
|
||||
"frame_events": 13295,
|
||||
"slow_frame_threshold_ms": 25,
|
||||
"slow_frames": [
|
||||
{
|
||||
"interval_ms": 89.86475,
|
||||
"gc_pause_overlap_ms": 0.0,
|
||||
"render_thread_wait_overlap_ms": 0.0,
|
||||
"terrain_upload_overlap_ms": 0.0
|
||||
},
|
||||
{
|
||||
"interval_ms": 25.813,
|
||||
"gc_pause_overlap_ms": 0.0,
|
||||
"render_thread_wait_overlap_ms": 0.0,
|
||||
"terrain_upload_overlap_ms": 0.0
|
||||
},
|
||||
{
|
||||
"interval_ms": 39.459208,
|
||||
"gc_pause_overlap_ms": 0.012042,
|
||||
"render_thread_wait_overlap_ms": 0.0,
|
||||
"terrain_upload_overlap_ms": 0.0
|
||||
},
|
||||
{
|
||||
"interval_ms": 25.424875,
|
||||
"gc_pause_overlap_ms": 0.0,
|
||||
"render_thread_wait_overlap_ms": 0.0,
|
||||
"terrain_upload_overlap_ms": 0.0
|
||||
}
|
||||
],
|
||||
"limits": [
|
||||
"JFR covers 15 seconds of late warmup plus the timed route. Pause totals include both.",
|
||||
"Profiling results are excluded from ordinary FPS comparisons.",
|
||||
"The first 89.86 ms diagnostic interval has no samples or recorded overlaps and remains unattributed.",
|
||||
"One later 39.46 ms interval samples class-loader/mixin processing; event-level class loading was not yet enabled.",
|
||||
"Native allocation and complete uploads were fast in this capture; that does not explain all previous stalls."
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
# Physical M2 Pro work
|
||||
|
||||
The current target is the user's M2 Pro MacBook Pro: 8 performance cores,
|
||||
4 efficiency cores, 19 GPU cores and 16 GB unified memory. Work on other GPU
|
||||
vendors and operating systems is deferred while this device is being optimized.
|
||||
Gameplay rules, save formats, simulation distance 12 and normal 20 TPS remain
|
||||
the compatibility target.
|
||||
|
||||
## First renderer observations
|
||||
|
||||
These are early measurements on the actual Apple GPU, not software rendering.
|
||||
Vulkan successfully uses the bundled MoltenVK 1.4.2 Metal path; OpenGL uses
|
||||
Apple's 4.1 driver. Both use the same core mods and Distant Horizons 3.3.4,
|
||||
32 detailed chunks, 128-chunk medium LOD, FOV 110, 6 GiB maximum heap, G1,
|
||||
VSync off and uncapped rendering. Original scheduling experiments remain off.
|
||||
|
||||
| Scene and framebuffer | OpenGL FPS | Vulkan FPS | FPS change | p99, OpenGL → Vulkan |
|
||||
| --- | ---: | ---: | ---: | ---: |
|
||||
| Survival spawn, 2560×1600, pair 1 | 102.65 | 115.71 | +12.72% | 13.24 → 11.29 ms |
|
||||
| Survival spawn, 2560×1600, pair 2 | 100.73 | 115.55 | +14.72% | 14.58 → 12.04 ms |
|
||||
| Fixed elevated view, 3456×2234, fresh-cache pair | 70.28 | 88.95 | +26.57% | 16.27 → 12.96 ms |
|
||||
| Same view, common generated cache | 66.86 | 81.30 | +21.60% | 16.48 → 13.59 ms |
|
||||
|
||||
Initial runs warm for 60 seconds; the common-cache pair warms for 120 seconds.
|
||||
All record 120 active seconds. CSV counts and
|
||||
means are checked against JSON, with no lost/excluded samples, no captured
|
||||
visual-setting changes and no abnormal sampled tick-manager state. Original
|
||||
mod and fixture code match within each pair. Captured-setting differences are
|
||||
the intended backend and driver. Spawn order is Vulkan/OpenGL/OpenGL/Vulkan;
|
||||
the fresh Retina pair is OpenGL/Vulkan and the cached pair Vulkan/OpenGL.
|
||||
|
||||
The elevated scene uses a private spectator camera at a fixed position to
|
||||
measure visible terrain without collisions. The spawn scene runs ordinary
|
||||
survival and has slight position drift. Neither fixture is in a playable pack.
|
||||
World simulation, weather and distant-cache generation still vary. Near terrain
|
||||
and water were visually inspected, but skies and distant-cache coverage are not
|
||||
pixel identical. The cached pair starts from independent copies of the same
|
||||
generated world and Distant Horizons database; it still allows further LOD
|
||||
generation and weather changes. These pairs do not establish a general gain.
|
||||
Vulkan has a single 51.66/53.13 ms stall in the fresh/cached pair; OpenGL's
|
||||
maximum is 22.12/25.48 ms respectively.
|
||||
Movement, fresh terrain, reloads and longer thermal behavior remain necessary.
|
||||
|
||||
The supplied user recording is a separate route/world workload at 3456×2234;
|
||||
these rows must not be treated as a matched comparison with its 84.27 FPS.
|
||||
[Initial selected observations](../benchmarks/m2-pro-2026-10-04/initial-observations.json)
|
||||
retain the first spawn pairs. [Fresh Retina](../benchmarks/m2-pro-2026-10-04/retina-initial-observations.json)
|
||||
and [common-cache Retina observations](../benchmarks/m2-pro-2026-10-04/retina-cached-observations.json)
|
||||
retain the two larger-framebuffer pairs. Raw files, logs, images and worlds remain in
|
||||
gitignored `.local/`.
|
||||
|
||||
## Mac-specific playable profile
|
||||
|
||||
`m2-metal` requests the game's existing Vulkan/MoltenVK backend and preserves
|
||||
the supplied 32 detailed chunks, 128-chunk medium LOD, simulation distance 12
|
||||
and FOV 110. Vanilla clouds are off, matching the supplied capture; DH clouds
|
||||
remain enabled. It shares the balanced terrain configuration and keeps original
|
||||
controllers disabled. This is a separate local test profile; it does not
|
||||
overwrite the existing balanced instance or implement a new Metal renderer.
|
||||
The game retains its OpenGL fallback, so inspect the recorded actual backend.
|
||||
|
||||
```sh
|
||||
python3 tools/build_mod.py
|
||||
python3 tools/build_pack.py --profile m2-metal
|
||||
python3 tools/build_pack.py --profile m2-metal-batched
|
||||
```
|
||||
|
||||
Import the resulting `.mrpack` from `dist/` into Prism as a new instance. Keep
|
||||
native Java 25 and maximum heap 6144 MiB. Use a copy of your world and compare
|
||||
the actual framebuffer size and visual settings before judging the difference.
|
||||
|
||||
`m2-metal-batched` adds the cloud-rendering experiment to otherwise identical
|
||||
quality settings. It is explicitly opt-in and requires Minecraft 26.3, DH 3.3.4,
|
||||
Mac ARM, the actual Apple M2 Pro Vulkan device and no Iris. Unsupported devices
|
||||
or backends use the original renderer. All other profiles leave the experiment
|
||||
off. These 0.22.0 artifacts are local builds, not a published starter update.
|
||||
|
||||
Two ordinary full-Retina flyover pairs compare G1 and ZGC on that same batched
|
||||
renderer and exact fixture, in opposite order. ZGC changes average FPS by
|
||||
-0.33%/-0.64%, improves 1% lows from 60.01 → 67.69 and 61.40 → 65.38 FPS,
|
||||
and reduces frames over 16 ms from 45 to 21/26 per 120-second run. p99 changes
|
||||
12.65 → 12.40 and 12.43 → 12.48 ms; it does not consistently improve. ZGC's
|
||||
worst frames are 52.67/43.17 ms versus G1's 42.72/38.80 ms. Sampled peak RSS
|
||||
falls 17.45%/14.92%, while peak JVM heap grows 38.41%/39.49%. RSS is not a
|
||||
complete unified-memory measure. Collector MXBean time includes concurrent
|
||||
work and must not be interpreted as pause time. Both pairs have matching end
|
||||
positions and quality settings, normal sampled 20 TPS, and no profiling.
|
||||
[Collector observations](../benchmarks/m2-pro-2026-10-04/collector-observations.json)
|
||||
retain both pairs. ZGC is an optional smoother-play candidate; G1 remains
|
||||
available and isolated worst-frame hitches remain unresolved.
|
||||
|
||||
The local Prism ZIPs carry the selected collector and 512–6144 MiB memory
|
||||
settings, alongside the same pinned mods and game configuration:
|
||||
|
||||
```sh
|
||||
python3 tools/build_mac_candidate.py --gc zgc
|
||||
python3 tools/build_mac_candidate.py --gc g1
|
||||
```
|
||||
|
||||
Import `dist/horizon-m2-metal-batched-zgc-0.22.0-prism.zip` into Prism as a new
|
||||
instance, or use the G1 counterpart for comparison. `dist/Mac-Import-zgc.command`
|
||||
opens its import in an installed Prism Launcher. Enable automatic native Java
|
||||
25 selection in Prism. Select fullscreen when comparing with these full-Retina
|
||||
runs. The archives are generated from explicit configuration and pinned mod
|
||||
inputs, with no world, account, game binary, logs or private benchmark fixture.
|
||||
Archive hashes/settings are checked locally; Prism UI import is a separate
|
||||
verification step.
|
||||
|
||||
The verified production-artifact pair at 3456×2234 averages 82.93 → 94.63 FPS
|
||||
(+14.12%), with 1% lows 69.04 → 84.63 (+22.57%) and p99 13.03 → 10.93 ms
|
||||
(-16.11%). Both warm and record for 120 seconds from the common copied cache;
|
||||
candidate runs first, control second, with identical original JAR and fixture
|
||||
hashes. Captured quality settings match, with no lost/excluded frames or abnormal
|
||||
sampled simulation state. The candidate's maximum is 18.12 ms versus 52.34 ms
|
||||
for this control; earlier prototype stalls remain part of the evidence.
|
||||
[Production timing observations](../benchmarks/m2-pro-2026-10-04/cloud-batch-production-observations.json)
|
||||
retain this pair. It is one final-artifact pair, separate from the two prototype
|
||||
pairs and from the user's supplied route.
|
||||
|
||||
A separate 30-second flyover smoke travels about 326 horizontal blocks using
|
||||
vanilla forward input at full Retina resolution, with batching active, normal
|
||||
sampled simulation and no captured graphics changes. Its short run is a
|
||||
functional check. Two clean 120-second flyover pairs measure 96.32 → 120.13
|
||||
FPS (+24.72%) and, in reverse order, 95.43 → 119.62 FPS (+25.34%). p99
|
||||
improves 14.29 → 12.68 ms and 14.40 → 12.74 ms. Each pair reaches exactly
|
||||
matching end positions, runs at sampled normal 20 TPS and uses about 6.2 CPU
|
||||
cores. Candidate worst frames are 52.20/59.15 ms versus control 47.46/64.62 ms;
|
||||
isolated stalls persist. Asynchronous terrain completion, weather and hardware
|
||||
clocks can still vary; these results do not establish a general-world speedup.
|
||||
[Flyover observations](../benchmarks/m2-pro-2026-10-04/cloud-batch-flyover-observations.json)
|
||||
retain both clean pairs. An earlier flyover attempt was discarded after
|
||||
memory-heavy diagnostic analysis reached its recording interval.
|
||||
|
||||
## Profiling the next change
|
||||
|
||||
A separate 60-second Metal/JFR capture at 3456×2234 measures approximately
|
||||
10.48 ms per GPU render interval and 0.62 ms per CPU `GameRenderer.render()`
|
||||
call. Render-thread stack samples frequently include Vulkan submission waits.
|
||||
These intervals cover different portions of the frame and sampling is not an
|
||||
exact CPU/GPU utilization measurement. Profiling results are excluded from
|
||||
ordinary FPS comparisons. OpenGL timestamps returned invalid zero intervals
|
||||
on this driver; that profiling run was rejected rather than reporting a GPU win.
|
||||
|
||||
A later JFR diagnostic captures a 63.97 ms frame with no overlapping top-level
|
||||
GC pause or recorded render-thread park/monitor wait. Three stack samples inside
|
||||
that interval are in Vulkan native buffer creation, reached from DH terrain
|
||||
vertex-buffer upload. This identifies a buffer-allocation candidate; one capture
|
||||
does not establish a general cause or a fix. The first 43.90 ms diagnostic
|
||||
interval has no stack samples and remains unattributed.
|
||||
[Selected stall observations](../benchmarks/m2-pro-2026-10-04/stall-diagnostic-observations.json)
|
||||
retain the correlation. `java tools/JfrSummary.java --frame-stalls recording.jfr`
|
||||
uses two bounded streaming passes; overlap coverage avoids double counting
|
||||
within each event category. GC and thread waits can overlap each other.
|
||||
|
||||
A separate full-route upload/JFR diagnostic measures 2,336 original terrain
|
||||
uploads and 849 MB of destination allocations in 120 seconds. Destination
|
||||
allocation peaks at 0.098 ms and staging/copy/barrier work at 0.116 ms, with
|
||||
zero existing-capacity reuse candidates. Six of eight frame intervals over
|
||||
25 ms overlap 20.79–29.21 ms top-level GC pauses. Two intervals remain
|
||||
unattributed; the first whole-upload timing also includes private event setup
|
||||
outside its JFR duration. These measurements weaken allocation as a general
|
||||
explanation and motivate clean G1/ZGC comparisons before changing the collector.
|
||||
They do not prove that garbage collection explains every hitch or that pooling
|
||||
would help. [Upload diagnostic observations](../benchmarks/m2-pro-2026-10-04/upload-diagnostic-observations.json)
|
||||
retain bounded counters and overlap measurements. The private `--vbo-profile`
|
||||
option measures complete uploads as well as their nested allocation and copy
|
||||
work; those timings must not be added together. Combined JFR events allow
|
||||
streaming overlap analysis without expanding the full recording to JSON.
|
||||
|
||||
A later ZGC upload/JFR diagnostic captures 193 top-level pauses totaling
|
||||
2.16 ms, with a longest pause of 0.031 ms, across 15 seconds of late warmup
|
||||
plus the timed route. Complete uploads peak at 0.291 ms. Four frame intervals
|
||||
over 25 ms have no meaningful GC or upload overlap; one 39.46 ms interval
|
||||
samples class-loader/mixin processing. The initial 89.86 ms diagnostic interval
|
||||
has no samples and remains unattributed. [ZGC diagnostic observations](../benchmarks/m2-pro-2026-10-04/zgc-diagnostic-observations.json)
|
||||
retain the bounded data. This supports separating GC-related frame tails from
|
||||
other hitches, not claiming that ZGC fixes every stall. Subsequent private JFR
|
||||
captures enable individual class-load durations, and Apple Metal System Trace
|
||||
is available for native driver and thread-wait diagnosis. Neither profiler is
|
||||
part of the playable candidate.
|
||||
|
||||
The optional private pass profiler samples every twentieth frame and brackets
|
||||
the actual Vulkan render-pass operations with device timestamps. Pass timing
|
||||
can disturb scheduling; it selects candidates rather than establishing a
|
||||
performance gain. The initial profile points to the main world pass, distant
|
||||
terrain geometry, TAA/sharpen and repeated generic rendering. SSAO is a smaller
|
||||
cost in this view. Larger renderer or native-kernel changes should follow these
|
||||
measurements and preserve the visible scene.
|
||||
|
||||
The common-cache pass capture has no missing or invalid queries across 254
|
||||
sampled frames. The main pass averages 4.12 ms, the two distant-terrain passes
|
||||
2.29 ms combined, and 38.25 generic-object passes per frame total 1.69 ms.
|
||||
TAA/sharpen total 1.06 ms. Per-label frame totals include frames where that
|
||||
label occurs; they are diagnostic intervals affected by timestamp insertion,
|
||||
not percentages of total GPU utilization. The cloud batching prototype uses
|
||||
separate transient uniform snapshots for each draw. Its matched Retina pairs
|
||||
improve 83.71 → 96.46 FPS (+15.23%) and 84.11 → 96.62 FPS (+14.87%), with
|
||||
opposite run order. p99 improves 12.98 → 10.72 ms and 12.90 → 10.65 ms.
|
||||
Candidate worst frames are 51.69/65.59 ms versus control 37.10/43.90 ms;
|
||||
the experiment has not solved isolated stalls.
|
||||
[Cloud batching observations](../benchmarks/m2-pro-2026-10-04/cloud-batch-initial-observations.json)
|
||||
retain both prototype pairs. A frozen daytime A/B/A check produces identical
|
||||
RGB pixels across the sky/cloud region (rows 80–892), and at most one channel
|
||||
level of near-terrain variation, including control/control variation.
|
||||
[Image observations](../benchmarks/m2-pro-2026-10-04/cloud-batch-image-observations.json)
|
||||
retain the readback hashes and differences. The original 0.22.0 production
|
||||
artifact also passes a frozen A/B/A check: sky/cloud pixels are identical,
|
||||
near terrain is identical in A/B, and three near-terrain pixels vary by one
|
||||
channel level between the control images. Its 18,463 recorded batch draws
|
||||
confirm that the production code was exercised.
|
||||
[Production image observations](../benchmarks/m2-pro-2026-10-04/cloud-batch-production-image-observations.json)
|
||||
retain that artifact's hash and differences. Distant-cache generation still
|
||||
changes far pixels in both A/B and A/A; other weather, camera heights and
|
||||
movement need tests. The batch closes before geometry uploads, callback
|
||||
GPU commands and incompatible draws, preserving draw order. Each draw gets
|
||||
its own transient uniform slice; no shared uniform is overwritten in flight.
|
||||
|
||||
```sh
|
||||
python3 tools/mac_benchmark.py --profile m2-metal --backend vulkan \
|
||||
--label m2-local-test --seed-world /path/to/disposable/Demo_World \
|
||||
--scene overlook --fullscreen --warmup 120 --seconds 120
|
||||
```
|
||||
|
||||
Use the same fixture JAR with `--fixture` for a matched pair. `--gpu-profile`
|
||||
adds whole-render timestamps; `--pass-profile` adds sampled Vulkan pass timing.
|
||||
`--jfr-profile` captures private JVM events and aligned frame intervals for
|
||||
stall diagnosis. Its recording begins before the timed interval to warm the
|
||||
profiler, and profiling results remain separate from ordinary FPS comparisons.
|
||||
Keep profiling and ordinary timing labels separate. `--cloud-batch` tests the
|
||||
private renderer prototype; `--cloud-image-check --scene overlook` with
|
||||
`m2-metal-batched` validates the production renderer in a separate frozen A/B/A
|
||||
image check without recording FPS. `--scene spawn` uses normal
|
||||
survival; `overlook` changes only the private copied world to a fixed spectator
|
||||
view. `--scene flyover` holds vanilla forward input during recording, exercising
|
||||
normal spectator movement and terrain loading in that private copy. Existing
|
||||
labels cannot be overwritten. No account data is needed.
|
||||
|
||||
## AFK measurement correction
|
||||
|
||||
Minecraft's default `afk` inactivity option caps stationary gameplay at 30 FPS
|
||||
after one minute and 10 FPS after ten minutes, even when the configured cap is
|
||||
unlimited. A focused stationary run measured 29.96 FPS and was rejected as a
|
||||
renderer comparison. Private benchmarks select the vanilla `minimized` option;
|
||||
playable profiles retain idle power saving.
|
||||
|
||||
The recorder now captures the inactivity option, effective limiter values and
|
||||
reasons at the boundaries, and counts limiter/AFK observations during active
|
||||
gameplay. Both comparison tools warn about these observations and mark different
|
||||
inactivity options as mismatched. Legacy reports remain usable with an unknown
|
||||
limiter warning. Disabling the idle cap is a measurement correction, not a game
|
||||
performance optimization.
|
||||
+12
-2
@@ -2,8 +2,11 @@
|
||||
|
||||
For the 16 GB M2 Pro, start with the published 0.21.0 packs. Core and balanced
|
||||
pass title/demo-world rendering and save/exit probes on the hosted macOS ARM64
|
||||
software renderer. Gameplay and FPS on this physical Mac remain unmeasured.
|
||||
The Linux software-rendering observations do not predict an M2 speedup.
|
||||
software renderer. [Physical M2 measurements](m2-pro-performance.md) now compare
|
||||
OpenGL with the bundled Metal-backed Vulkan path, including full Retina.
|
||||
Repeated gameplay validation continues; Linux software-rendering observations
|
||||
do not predict an M2 speedup. The new local `m2-metal` profile is built separately
|
||||
from the published starter below.
|
||||
|
||||
## Import after installing Prism
|
||||
|
||||
@@ -55,6 +58,13 @@ Warm each run for at least three minutes after entering the world and dismissing
|
||||
menus. Longer generation workloads may need more time. Press **F8** (possibly
|
||||
**Fn+F8**) in focused gameplay for a 120-second recording. Keep the view or route
|
||||
consistent and avoid menus during recording. After it finishes, save and quit.
|
||||
|
||||
For a stationary comparison, select the game's **Minimized** inactivity FPS
|
||||
option on both sides; the default **AFK** option imposes a 30 FPS idle cap after
|
||||
one minute. Restore your preferred idle behavior after testing. New recordings
|
||||
capture effective limiter observations; older recordings do not establish that
|
||||
a configured unlimited cap stayed unlimited throughout a stationary run.
|
||||
|
||||
Repeat three pairs in this order:
|
||||
|
||||
| Pair | First run | Second run |
|
||||
|
||||
@@ -2,7 +2,40 @@
|
||||
|
||||
## Starting point
|
||||
|
||||
The first playable Java foundation is in the root README. Client optimization has priority. Vanilla gameplay, world formats and multiplayer behavior must be preserved; existing modpack compatibility is not required. Any language, renderer or architecture is in scope when measured gains justify it. Windows/Linux and Apple Silicon/Intel macOS are targets, with the M2 Pro/16 GB Mac prioritized.
|
||||
The first playable Java foundation is in the root README. Current implementation
|
||||
and performance work targets the physical **M2 Pro / 16 GB MacBook Pro**: 8
|
||||
performance cores, 4 efficiency cores and a 19-core GPU. Windows/Linux,
|
||||
NVIDIA/AMD and other Macs are deferred until this device has a convincing,
|
||||
repeatable gameplay improvement. Existing portable packs remain available.
|
||||
|
||||
Vanilla gameplay, world formats and multiplayer behavior must be preserved;
|
||||
existing modpack compatibility is not required. Any language, renderer or
|
||||
architecture is in scope when measured gains justify it. Full Retina resolution,
|
||||
32 detailed chunks and 128-chunk distant terrain are the user's supplied workload;
|
||||
reducing those settings is a separate quality tradeoff, not proof of a faster
|
||||
renderer.
|
||||
|
||||
The user's 4 October capture reports 84.27 average FPS, 48.18 slowest-1%-average
|
||||
FPS and 18.14 ms p99 at 3456×2234, OpenGL, FOV 110 and normal simulation distance
|
||||
12. About 81.59 seconds of frame intervals were accepted within 120.09 elapsed
|
||||
seconds; menu, paused or unfocused frames were excluded. Tick execution averages
|
||||
7.89 ms with no execution above 50 ms. Its 1,646 tick invocations correspond to
|
||||
82.3 seconds at 20 TPS, so the 13.71 observed invocation rate across the whole
|
||||
recording does not demonstrate an overloaded server. GPU time and movement route
|
||||
are unknown. Original captures remain private under `.local/user-baseline/`.
|
||||
|
||||
Initial physical-Mac inspection verifies the existing Vulkan backend creates a
|
||||
MoltenVK 1.4.2 device on this GPU and renders a nonuniform demo-world framebuffer.
|
||||
This establishes an available Metal-backed path, not an FPS gain or complete
|
||||
visual equivalence. Matched private OpenGL/Vulkan measurements come first.
|
||||
|
||||
A stationary 60-second warm-up exposed Minecraft's default AFK limiter: an
|
||||
otherwise unlimited recording settled at 29.96 FPS. That run is rejected as a
|
||||
renderer baseline. Private stationary instances now select the vanilla
|
||||
`minimized` inactivity option; playable defaults retain their normal idle power
|
||||
saving. Recorder metadata captures the option, effective limiter boundaries and
|
||||
throttled observations. The private benchmark checks active duration, sample
|
||||
files, unchanged settings and normal tick-manager state before accepting a run.
|
||||
|
||||
Inspect both pinned game JARs locally, retain bytecode as the reference, and measure actual Minecraft workloads before selecting a subsystem to replace. The Rust face-count experiment establishes a small correctness/benchmark workflow; it does not identify a Minecraft bottleneck.
|
||||
|
||||
@@ -46,7 +79,7 @@ jfr summary .local/baseline.jfr
|
||||
jfr print --events jdk.ExecutionSample,jdk.GarbageCollection .local/baseline.jfr > .local/baseline-events.txt
|
||||
```
|
||||
|
||||
Collect CPU profiles, allocation/GC information, frame-time percentiles, server tick-time percentiles, and relevant GPU timings. JFR samples alone do not measure FPS or GPU cost. JVM compilation/warm-up, profiling overhead, world generation, and background activity must be controlled when comparing runs. The [core comparison](core-performance.md) now contains stationary Linux software-rendering observations; Mac GPU, movement/generation and populated dedicated-server measurements remain open.
|
||||
Collect CPU profiles, allocation/GC information, frame-time percentiles, server tick-time percentiles, and relevant GPU timings. JFR samples alone do not measure FPS or GPU cost. JVM compilation/warm-up, profiling overhead, world generation, and background activity must be controlled when comparing runs. The [core comparison](core-performance.md) contains stationary Linux software-rendering observations; [physical M2 Pro observations](m2-pro-performance.md) now include repeated renderer measurements and GPU pass timings. Movement/generation and populated dedicated-server measurements remain open.
|
||||
|
||||
## First native integration experiment
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.mojang.brigadier.arguments.IntegerArgumentType;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.mojang.blaze3d.platform.InputConstants;
|
||||
import com.mojang.blaze3d.platform.FramerateLimitTracker.FramerateThrottleReason;
|
||||
import dev.horizonlab.HorizonLab;
|
||||
import dev.horizonlab.metrics.Recording;
|
||||
import dev.horizonlab.metrics.Reports;
|
||||
@@ -30,6 +31,11 @@ public final class HorizonClient implements ClientModInitializer {
|
||||
private static DistantTerrainBudget terrainBudget;
|
||||
private static long previousBudgetFrame;
|
||||
private static int targetFps = 60;
|
||||
private static long limiterObservations;
|
||||
private static long throttledObservations;
|
||||
private static long afkObservations;
|
||||
private static long cloudDrawsStart, cloudPassesStart;
|
||||
private static Map<String, Object> visualSettingsStart = Map.of();
|
||||
|
||||
@Override public void onInitializeClient() {
|
||||
KeyMapping benchmarkKey = KeyMappingHelper.registerKeyMapping(new KeyMapping(
|
||||
@@ -103,14 +109,22 @@ public final class HorizonClient implements ClientModInitializer {
|
||||
context.put("empty_culling_whitelist", dev.horizonlab.mixin.HorizonMixinPlugin.cullingEmptyEnabled());
|
||||
context.put("interruptible_terrain_read", dev.horizonlab.mixin.HorizonMixinPlugin.terrainReadWaitEnabled());
|
||||
context.put("adaptive_target_fps", targetFps);
|
||||
context.putAll(visualSettings(client));
|
||||
visualSettingsStart = visualSettings(client);
|
||||
context.putAll(visualSettingsStart);
|
||||
var device = RenderSystem.getDevice().getDeviceInfo();
|
||||
context.put("gpu", device.name());
|
||||
context.put("graphics_backend", device.backendName());
|
||||
context.put("graphics_driver", device.driverInfo());
|
||||
context.put("frame_limiter_effective_fps_start", client.getFramerateLimitTracker().getFramerateLimit());
|
||||
context.put("frame_limiter_reason_start", client.getFramerateLimitTracker().getThrottleReason().name());
|
||||
cloudDrawsStart = dev.horizonlab.render.CloudBatchStatus.draws;
|
||||
cloudPassesStart = dev.horizonlab.render.CloudBatchStatus.passes;
|
||||
context.putAll(dev.horizonlab.metrics.SoftwareContext.capture());
|
||||
frames = new Recording(label, "frames", output, seconds, context);
|
||||
previousStart = 0;
|
||||
limiterObservations = 0;
|
||||
throttledObservations = 0;
|
||||
afkObservations = 0;
|
||||
if (client.getSingleplayerServer() != null) HorizonLab.startTicks(label, seconds, output, profile, "integrated", context);
|
||||
message("Recording " + seconds + " seconds. Close chat, keep the game focused, and run your route.");
|
||||
return 1;
|
||||
@@ -124,6 +138,7 @@ public final class HorizonClient implements ClientModInitializer {
|
||||
context.put("vsync", client.options.enableVsync().get());
|
||||
context.put("fps_limit", client.options.framerateLimit().get());
|
||||
context.put("fps_limit_unlimited", client.options.framerateLimit().get() >= Options.UNLIMITED_FRAMERATE_CUTOFF);
|
||||
context.put("inactivity_fps_limit", client.options.inactivityFpsLimit().get().getSerializedName());
|
||||
context.put("framebuffer_width", client.getWindow().getWidth());
|
||||
context.put("framebuffer_height", client.getWindow().getHeight());
|
||||
context.put("graphics_preset", client.options.graphicsPreset().get().toString());
|
||||
@@ -142,6 +157,8 @@ public final class HorizonClient implements ClientModInitializer {
|
||||
catch (RuntimeException | LinkageError unavailable) { context.put("dh_capture_status", "unavailable"); }
|
||||
} else context.put("dh_capture_status", "not_installed");
|
||||
context.putAll(ShaderSettings.read(loader.isModLoaded("iris")));
|
||||
context.put("dh_cloud_batch_requested", dev.horizonlab.render.CloudBatchStatus.requested);
|
||||
context.put("dh_cloud_batch_enabled", dev.horizonlab.render.CloudBatchStatus.active);
|
||||
return context;
|
||||
}
|
||||
|
||||
@@ -165,6 +182,10 @@ public final class HorizonClient implements ClientModInitializer {
|
||||
previousBudgetFrame = now;
|
||||
}
|
||||
if (recording != null) {
|
||||
var reason = client.getFramerateLimitTracker().getThrottleReason();
|
||||
limiterObservations++;
|
||||
if (reason != FramerateThrottleReason.NONE) throttledObservations++;
|
||||
if (reason == FramerateThrottleReason.SHORT_AFK || reason == FramerateThrottleReason.LONG_AFK) afkObservations++;
|
||||
if (previousStart != 0) recording.add(now - previousStart);
|
||||
previousStart = now;
|
||||
}
|
||||
@@ -182,18 +203,17 @@ public final class HorizonClient implements ClientModInitializer {
|
||||
frames = null;
|
||||
previousStart = 0;
|
||||
HorizonLab.finishTicks();
|
||||
var limiter = Minecraft.getInstance().getFramerateLimitTracker();
|
||||
recording.context.put("frame_limiter_effective_fps_end", limiter.getFramerateLimit());
|
||||
recording.context.put("frame_limiter_reason_end", limiter.getThrottleReason().name());
|
||||
recording.context.put("frame_limiter_observations", limiterObservations);
|
||||
recording.context.put("frame_limiter_throttled_observations", throttledObservations);
|
||||
recording.context.put("frame_limiter_afk_observations", afkObservations);
|
||||
recording.context.put("dh_cloud_batch_draws_recorded", dev.horizonlab.render.CloudBatchStatus.draws - cloudDrawsStart);
|
||||
recording.context.put("dh_cloud_batch_passes_recorded", dev.horizonlab.render.CloudBatchStatus.passes - cloudPassesStart);
|
||||
Map<String, Object> end = visualSettings(Minecraft.getInstance());
|
||||
java.util.List<String> changed = new java.util.ArrayList<>();
|
||||
for (var setting : end.entrySet()) {
|
||||
// Adaptive duty cycle is expected to change; visual quality changes are separate.
|
||||
if (!setting.getKey().equals("dh_runtime_ratio")
|
||||
&& !java.util.Objects.equals(recording.context.get(setting.getKey()), setting.getValue()))
|
||||
changed.add(setting.getKey());
|
||||
}
|
||||
for (String setting : recording.context.keySet()) {
|
||||
if ((setting.startsWith("dh_") || setting.startsWith("iris_")) && !end.containsKey(setting))
|
||||
changed.add(setting);
|
||||
}
|
||||
var changed = dev.horizonlab.metrics.VisualSettingsChanges.between(visualSettingsStart, end);
|
||||
visualSettingsStart = Map.of();
|
||||
recording.context.put("visual_settings_end", end);
|
||||
recording.context.put("visual_settings_changed", changed);
|
||||
Reports.write(recording.finish()).whenComplete((path, error) -> {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package dev.horizonlab.metrics;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/** Compare visual snapshots independently of changing measurement metadata. */
|
||||
public final class VisualSettingsChanges {
|
||||
private VisualSettingsChanges() {}
|
||||
|
||||
public static List<String> between(Map<String, ?> before, Map<String, ?> after) {
|
||||
var changed = new ArrayList<String>();
|
||||
for (var setting : after.entrySet()) {
|
||||
if (!setting.getKey().equals("dh_runtime_ratio")
|
||||
&& !Objects.equals(before.get(setting.getKey()), setting.getValue()))
|
||||
changed.add(setting.getKey());
|
||||
}
|
||||
for (String setting : before.keySet()) {
|
||||
if ((setting.startsWith("dh_") || setting.startsWith("iris_")) && !after.containsKey(setting))
|
||||
changed.add(setting);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package dev.horizonlab.mixin;
|
||||
|
||||
import com.mojang.renderpearl.frontend.FrontendCommandEncoder;
|
||||
import dev.horizonlab.render.DhCloudBatch;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
/** Preserve command order when uploads or callbacks interrupt a batch. */
|
||||
@Mixin(FrontendCommandEncoder.class)
|
||||
public abstract class DhCloudBarrierMixin {
|
||||
@Inject(method = {"submit", "clearColorTexture", "clearColorAndDepthTextures", "clearDepthTexture",
|
||||
"writeToBuffer", "copyToBuffer", "writeToTexture", "copyBufferToTexture", "copyTextureToBuffer",
|
||||
"copyTextureToTexture", "writeTimestamp"}, at = @At("HEAD"))
|
||||
private void horizon$beforeCommand(CallbackInfo callback) {
|
||||
var batch = DhCloudBatch.current;
|
||||
if (batch != null) batch.closePass();
|
||||
}
|
||||
|
||||
@Inject(method = {"createRenderPass", "createFence"}, at = @At("HEAD"))
|
||||
private void horizon$beforeResource(CallbackInfoReturnable<?> callback) {
|
||||
var batch = DhCloudBatch.current;
|
||||
if (batch != null) batch.closePass();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package dev.horizonlab.mixin;
|
||||
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.mojang.renderpearl.api.pipeline.RenderPipeline;
|
||||
import com.seibel.distanthorizons.common.render.blaze.BlazeDhGenericObjectRenderer;
|
||||
import com.seibel.distanthorizons.common.render.blaze.wrappers.uniform.BlazeUniformBufferWrapper;
|
||||
import com.seibel.distanthorizons.core.render.RenderParams;
|
||||
import com.seibel.distanthorizons.core.render.renderer.RenderableBoxGroup;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
|
||||
import dev.horizonlab.render.DhCloudBatch;
|
||||
import dev.horizonlab.render.CloudBatchStatus;
|
||||
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
/** Opt-in M2 Pro cloud batching on the inspected DH renderer. */
|
||||
@Mixin(BlazeDhGenericObjectRenderer.class)
|
||||
public abstract class DhCloudBatchMixin {
|
||||
@Shadow @Final private BlazeUniformBufferWrapper vertUniformBufferWrapper;
|
||||
@Shadow private RenderPipeline pipeline;
|
||||
|
||||
@WrapMethod(method = "render")
|
||||
private void horizon$cloudBatch(RenderParams params, IProfilerWrapper profiler, boolean ssao,
|
||||
Operation<Void> original) {
|
||||
var device = RenderSystem.getDevice().getDeviceInfo();
|
||||
boolean active = HorizonMixinPlugin.cloudBatchEnabled() && device.backendName().equals("Vulkan")
|
||||
&& device.name().equals("Apple M2 Pro");
|
||||
CloudBatchStatus.active = active;
|
||||
if (!active) {
|
||||
original.call(params, profiler, ssao);
|
||||
return;
|
||||
}
|
||||
var parent = DhCloudBatch.current;
|
||||
if (parent != null) {
|
||||
parent.closePass();
|
||||
DhCloudBatch.current = null;
|
||||
try { original.call(params, profiler, ssao); }
|
||||
finally { DhCloudBatch.current = parent; }
|
||||
return;
|
||||
}
|
||||
var batch = new DhCloudBatch(vertUniformBufferWrapper);
|
||||
DhCloudBatch.current = batch;
|
||||
try (batch) { original.call(params, profiler, ssao); }
|
||||
finally { DhCloudBatch.current = null; }
|
||||
}
|
||||
|
||||
@Inject(method = "renderBoxGroupInstanced", at = @At("HEAD"), cancellable = true)
|
||||
private void horizon$drawCloud(RenderParams params, RenderableBoxGroup group,
|
||||
IProfilerWrapper profiler, CallbackInfo callback) {
|
||||
var batch = DhCloudBatch.current;
|
||||
if (batch != null) {
|
||||
batch.draw(params, group, pipeline);
|
||||
callback.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package dev.horizonlab.mixin;
|
||||
|
||||
import com.mojang.renderpearl.api.buffers.GpuBufferSlice;
|
||||
import com.mojang.renderpearl.api.commands.CommandEncoder;
|
||||
import com.seibel.distanthorizons.common.render.blaze.wrappers.uniform.BlazeUniformBufferWrapper;
|
||||
import java.nio.ByteBuffer;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
/** Give each draw its own transient uniform snapshot. */
|
||||
@Mixin(BlazeUniformBufferWrapper.class)
|
||||
public abstract class DhCloudUniformMixin {
|
||||
@Redirect(method = "finishAndUpload", at = @At(value = "INVOKE", target =
|
||||
"Lcom/mojang/renderpearl/api/commands/CommandEncoder;writeToBuffer(Lcom/mojang/renderpearl/api/buffers/GpuBufferSlice;Ljava/nio/ByteBuffer;)V"))
|
||||
private void horizon$snapshot(CommandEncoder encoder, GpuBufferSlice destination, ByteBuffer data) {
|
||||
var batch = dev.horizonlab.render.DhCloudBatch.current;
|
||||
if (batch != null && batch.uniformWrapper == (Object) this) batch.captureUniform(data);
|
||||
else encoder.writeToBuffer(destination, data);
|
||||
}
|
||||
}
|
||||
@@ -21,9 +21,32 @@ public final class HorizonMixinPlugin implements IMixinConfigPlugin {
|
||||
private static boolean cullingEmpty;
|
||||
private static boolean terrainReadWait;
|
||||
private static boolean cullingRayScratch;
|
||||
private static boolean cloudBatch;
|
||||
|
||||
@Override public void onLoad(String mixinPackage) {
|
||||
var loader = FabricLoader.getInstance();
|
||||
try {
|
||||
var properties = new Properties();
|
||||
var cloudFile = loader.getConfigDir().resolve("horizon-cloud-batch.properties");
|
||||
if (Files.exists(cloudFile)) {
|
||||
try (var reader = Files.newBufferedReader(cloudFile)) { properties.load(reader); }
|
||||
}
|
||||
boolean requested = Boolean.parseBoolean(properties.getProperty("enabled", "false"));
|
||||
dev.horizonlab.render.CloudBatchStatus.requested = requested;
|
||||
boolean supported = loader.getModContainer("distanthorizons")
|
||||
.map(mod -> mod.getMetadata().getVersion().getFriendlyString().equals("3.3.4")).orElse(false)
|
||||
&& loader.getModContainer("minecraft")
|
||||
.map(mod -> mod.getMetadata().getVersion().getFriendlyString().equals("26.3")).orElse(false)
|
||||
&& System.getProperty("os.name", "").startsWith("Mac")
|
||||
&& Set.of("aarch64", "arm64").contains(System.getProperty("os.arch", ""))
|
||||
&& !loader.isModLoaded("iris");
|
||||
cloudBatch = requested && supported;
|
||||
if (requested) LoggerFactory.getLogger("horizonlab").info(
|
||||
"DH cloud batching: {} (requires Mac ARM, Minecraft 26.3, DH 3.3.4, no Iris; runtime requires M2 Pro Vulkan)",
|
||||
cloudBatch ? "hooks enabled" : "unavailable");
|
||||
} catch (Exception error) {
|
||||
LoggerFactory.getLogger("horizonlab").warn("Cloud batching disabled: invalid configuration", error);
|
||||
}
|
||||
var file = loader.getConfigDir().resolve("horizon-worker-pacing.properties");
|
||||
try {
|
||||
Properties properties = new Properties();
|
||||
@@ -173,7 +196,10 @@ public final class HorizonMixinPlugin implements IMixinConfigPlugin {
|
||||
public static boolean cullingEmptyEnabled() { return cullingEmpty; }
|
||||
public static boolean cullingRayScratchEnabled() { return cullingRayScratch; }
|
||||
public static boolean terrainReadWaitEnabled() { return terrainReadWait; }
|
||||
public static boolean cloudBatchEnabled() { return cloudBatch; }
|
||||
@Override public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
|
||||
if (mixinClassName.endsWith("DhCloudBatchMixin") || mixinClassName.endsWith("DhCloudUniformMixin")
|
||||
|| mixinClassName.endsWith("DhCloudBarrierMixin")) return cloudBatch;
|
||||
if (mixinClassName.endsWith("DhWorkerPacingMixin")) return workerPacing;
|
||||
if (mixinClassName.endsWith("DhDebounceWaitMixin")) return debounceWait;
|
||||
if (mixinClassName.endsWith("DhPriorityDispatchMixin")) return dispatchRetry;
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package dev.horizonlab.render;
|
||||
|
||||
/** Render-thread counters kept independent of optional DH classes. */
|
||||
public final class CloudBatchStatus {
|
||||
public static boolean requested;
|
||||
public static boolean active;
|
||||
public static long draws;
|
||||
public static long passes;
|
||||
|
||||
private CloudBatchStatus() {}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package dev.horizonlab.render;
|
||||
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.mojang.renderpearl.api.buffers.GpuBuffer;
|
||||
import com.mojang.renderpearl.api.buffers.GpuBufferSlice;
|
||||
import com.mojang.renderpearl.api.commands.RenderPass;
|
||||
import com.mojang.renderpearl.api.pipeline.IndexType;
|
||||
import com.mojang.renderpearl.api.pipeline.RenderPipeline;
|
||||
import com.seibel.distanthorizons.common.render.blaze.BlazeDhMetaRenderer;
|
||||
import com.seibel.distanthorizons.common.render.blaze.objects.BlazeGenericObjectVertexContainer;
|
||||
import com.seibel.distanthorizons.common.render.blaze.wrappers.uniform.BlazeUniformBufferWrapper;
|
||||
import com.seibel.distanthorizons.common.wrappers.misc.LightMapWrapper;
|
||||
import com.seibel.distanthorizons.core.render.RenderParams;
|
||||
import com.seibel.distanthorizons.core.render.renderer.RenderableBoxGroup;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalDouble;
|
||||
|
||||
/** Render-thread-only state, scoped to one original generic-render invocation. */
|
||||
public final class DhCloudBatch implements AutoCloseable {
|
||||
public static DhCloudBatch current;
|
||||
public final BlazeUniformBufferWrapper uniformWrapper;
|
||||
private GpuBufferSlice uniform;
|
||||
private RenderPass pass;
|
||||
private Object lightmap;
|
||||
|
||||
public DhCloudBatch(BlazeUniformBufferWrapper wrapper) {
|
||||
uniformWrapper = wrapper;
|
||||
}
|
||||
|
||||
public void captureUniform(ByteBuffer data) {
|
||||
var copy = data.duplicate();
|
||||
copy.position(0);
|
||||
var encoder = RenderSystem.getDevice().createCommandEncoder();
|
||||
try (var mapped = encoder.transientMemory().allocateGpuMapped(copy.remaining(), 256, GpuBuffer.USAGE_UNIFORM)) {
|
||||
mapped.data().put(copy);
|
||||
uniform = mapped.slice();
|
||||
}
|
||||
}
|
||||
|
||||
public void draw(RenderParams params, RenderableBoxGroup group, RenderPipeline pipeline) {
|
||||
if (uniform == null) throw new AssertionError("Missing DH uniform snapshot");
|
||||
var vertices = (BlazeGenericObjectVertexContainer) group.vertexBufferContainer;
|
||||
boolean cloud = group.getResourceLocationNamespace().equals("DistantHorizons")
|
||||
&& group.getResourceLocationPath().equals("Clouds");
|
||||
if (!cloud || lightmap != params.lightmap) closePass();
|
||||
if (pass == null) {
|
||||
var targets = BlazeDhMetaRenderer.INSTANCE;
|
||||
pass = RenderSystem.getDevice().createCommandEncoder().createRenderPass(
|
||||
() -> "horizon:DH cloud batch", targets.dhColorTextureWrapper.getTextureView(),
|
||||
Optional.empty(), targets.dhDepthTextureWrapper.getTextureView(), OptionalDouble.empty());
|
||||
pass.setPipeline(RenderSystem.getCompiledPipeline(pipeline));
|
||||
var light = ((LightMapWrapper) params.lightmap).getTextureViewWrapper();
|
||||
pass.setUniform("uLightMap", light.getTextureView(), light.getTextureSampler());
|
||||
lightmap = params.lightmap;
|
||||
CloudBatchStatus.passes++;
|
||||
}
|
||||
pass.setUniform("vertUniformBlock", uniform);
|
||||
pass.setIndexBuffer(vertices.indexGpuBuffer, IndexType.INT);
|
||||
pass.setVertexBuffer(0, vertices.vboGpuBuffer.slice());
|
||||
if (vertices.uploadedBoxCount > 0) {
|
||||
pass.drawIndexed(Math.multiplyExact(vertices.uploadedBoxCount, 36), 1, 0, 0, 0);
|
||||
CloudBatchStatus.draws++;
|
||||
}
|
||||
uniform = null;
|
||||
if (!cloud) closePass();
|
||||
}
|
||||
|
||||
public void closePass() {
|
||||
var closing = pass;
|
||||
pass = null;
|
||||
lightmap = null;
|
||||
if (closing != null) closing.close();
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
closePass();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "horizonlab",
|
||||
"version": "0.21.0",
|
||||
"version": "0.22.0",
|
||||
"name": "Horizon Lab",
|
||||
"description": "Client frame pacing and server tick measurements for reproducible optimization experiments.",
|
||||
"authors": [
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
"DhTerrainReadWaitMixin",
|
||||
"CullingRayScratchMixin",
|
||||
"DhRelativePositionMixin",
|
||||
"DhQuadReuseMixin"
|
||||
"DhQuadReuseMixin",
|
||||
"DhCloudBatchMixin",
|
||||
"DhCloudUniformMixin",
|
||||
"DhCloudBarrierMixin"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package dev.horizonlab.dispatch.fixture;
|
||||
|
||||
import com.mojang.renderpearl.frontend.FrontendCommandEncoder;
|
||||
import dev.horizonlab.metrics.MacBenchmarkIntegration.MacCloudBatchState;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
/** Preserve command order when uploads or callbacks interrupt a private batch. */
|
||||
@Mixin(FrontendCommandEncoder.class)
|
||||
public abstract class MacDhCloudBarrierMixin {
|
||||
@Inject(method = {"submit", "clearColorTexture", "clearColorAndDepthTextures", "clearDepthTexture",
|
||||
"writeToBuffer", "copyToBuffer", "writeToTexture", "copyBufferToTexture", "copyTextureToBuffer",
|
||||
"copyTextureToTexture", "writeTimestamp"}, at = @At("HEAD"))
|
||||
private void horizon$beforeCommand(CallbackInfo callback) {
|
||||
var batch = MacCloudBatchState.current;
|
||||
if (batch != null) batch.closePass();
|
||||
}
|
||||
|
||||
@Inject(method = {"createRenderPass", "createFence"}, at = @At("HEAD"))
|
||||
private void horizon$beforeResource(CallbackInfoReturnable<?> callback) {
|
||||
var batch = MacCloudBatchState.current;
|
||||
if (batch != null) batch.closePass();
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package dev.horizonlab.dispatch.fixture;
|
||||
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.mojang.renderpearl.api.pipeline.RenderPipeline;
|
||||
import com.seibel.distanthorizons.common.render.blaze.BlazeDhGenericObjectRenderer;
|
||||
import com.seibel.distanthorizons.common.render.blaze.wrappers.uniform.BlazeUniformBufferWrapper;
|
||||
import com.seibel.distanthorizons.core.render.RenderParams;
|
||||
import com.seibel.distanthorizons.core.render.renderer.RenderableBoxGroup;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
|
||||
import dev.horizonlab.metrics.MacBenchmarkIntegration;
|
||||
import dev.horizonlab.metrics.MacBenchmarkIntegration.MacCloudBatchState;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
/** Private experiment on the pinned DH renderer; not part of playable packs. */
|
||||
@Mixin(BlazeDhGenericObjectRenderer.class)
|
||||
public abstract class MacDhCloudBatchMixin {
|
||||
@Shadow @Final private BlazeUniformBufferWrapper vertUniformBufferWrapper;
|
||||
@Shadow private RenderPipeline pipeline;
|
||||
|
||||
@Inject(method = "render", at = @At("HEAD"))
|
||||
private void horizon$cloudBatch(RenderParams params, IProfilerWrapper profiler, boolean ssao, CallbackInfo callback) {
|
||||
var device = RenderSystem.getDevice().getDeviceInfo();
|
||||
if (!MacBenchmarkIntegration.cloudBatchEnabled || !device.backendName().equals("Vulkan") || !device.name().contains("Apple M2 Pro")) return;
|
||||
if (MacCloudBatchState.current != null) throw new AssertionError("Nested private DH batch");
|
||||
MacCloudBatchState.current = new MacCloudBatchState(vertUniformBufferWrapper);
|
||||
}
|
||||
|
||||
@Inject(method = "render", at = @At("RETURN"))
|
||||
private void horizon$endBatch(RenderParams params, IProfilerWrapper profiler, boolean ssao, CallbackInfo callback) {
|
||||
var batch = MacCloudBatchState.current;
|
||||
if (batch != null) {
|
||||
try { batch.close(); }
|
||||
finally { MacCloudBatchState.current = null; }
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "renderBoxGroupInstanced", at = @At("HEAD"), cancellable = true)
|
||||
private void horizon$drawCloud(RenderParams params, RenderableBoxGroup group,
|
||||
IProfilerWrapper profiler, CallbackInfo callback) {
|
||||
var batch = MacCloudBatchState.current;
|
||||
if (batch != null) {
|
||||
batch.draw(params, group, pipeline);
|
||||
callback.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package dev.horizonlab.dispatch.fixture;
|
||||
|
||||
import com.seibel.distanthorizons.core.render.renderer.CloudRenderHandler;
|
||||
import dev.horizonlab.metrics.MacBenchmarkIntegration;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
/** Freeze wall-clock cloud movement only during private image validation. */
|
||||
@Mixin(CloudRenderHandler.class)
|
||||
public abstract class MacDhCloudClockMixin {
|
||||
@Redirect(method = "preRender", at = @At(value = "INVOKE", target = "Ljava/lang/System;currentTimeMillis()J"))
|
||||
private long horizon$validationClock() {
|
||||
long frozen = MacBenchmarkIntegration.cloudValidationClock;
|
||||
return frozen == 0 ? System.currentTimeMillis() : frozen;
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package dev.horizonlab.dispatch.fixture;
|
||||
|
||||
import com.mojang.renderpearl.api.buffers.GpuBufferSlice;
|
||||
import com.mojang.renderpearl.api.commands.CommandEncoder;
|
||||
import com.seibel.distanthorizons.common.render.blaze.wrappers.uniform.BlazeUniformBufferWrapper;
|
||||
import java.nio.ByteBuffer;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
/** Give each experimental draw its own transient uniform snapshot. */
|
||||
@Mixin(BlazeUniformBufferWrapper.class)
|
||||
public abstract class MacDhCloudUniformMixin {
|
||||
@Redirect(method = "finishAndUpload", at = @At(value = "INVOKE", target =
|
||||
"Lcom/mojang/renderpearl/api/commands/CommandEncoder;writeToBuffer(Lcom/mojang/renderpearl/api/buffers/GpuBufferSlice;Ljava/nio/ByteBuffer;)V"))
|
||||
private void horizon$snapshot(CommandEncoder encoder, GpuBufferSlice destination, ByteBuffer data) {
|
||||
var batch = dev.horizonlab.metrics.MacBenchmarkIntegration.MacCloudBatchState.current;
|
||||
if (batch != null && batch.uniformWrapper == (Object) this) batch.captureUniform(data);
|
||||
else encoder.writeToBuffer(destination, data);
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package dev.horizonlab.dispatch.fixture;
|
||||
|
||||
import com.mojang.renderpearl.api.buffers.GpuBuffer;
|
||||
import com.mojang.renderpearl.api.buffers.GpuBufferSlice;
|
||||
import com.mojang.renderpearl.api.commands.CommandEncoder;
|
||||
import com.mojang.renderpearl.api.device.GpuDevice;
|
||||
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.seibel.distanthorizons.common.render.blaze.wrappers.buffer.BlazeVertexBufferWrapper;
|
||||
import dev.horizonlab.metrics.MacBenchmarkIntegration;
|
||||
import java.util.function.Supplier;
|
||||
import java.nio.ByteBuffer;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
/** Private allocation timings; the original allocation and upload remain intact. */
|
||||
@Mixin(BlazeVertexBufferWrapper.class)
|
||||
public abstract class MacDhVertexAllocationMixin {
|
||||
@Shadow public GpuBuffer vertexGpuBuffer;
|
||||
|
||||
@WrapMethod(method = "uploadVertexBuffer")
|
||||
private void horizon$upload(ByteBuffer data, int vertices, Operation<Void> original) {
|
||||
long bytes = data.remaining();
|
||||
long started = System.nanoTime();
|
||||
var event = MacBenchmarkIntegration.beginTerrainUpload(bytes);
|
||||
try { original.call(data, vertices); }
|
||||
finally {
|
||||
MacBenchmarkIntegration.profileVboUpload(bytes, started, System.nanoTime() - started, false);
|
||||
if (event != null) { event.end(); event.commit(); }
|
||||
}
|
||||
}
|
||||
|
||||
@Redirect(method = "uploadVertexBuffer", at = @At(value = "INVOKE", target =
|
||||
"Lcom/mojang/renderpearl/api/commands/CommandEncoder;writeToBuffer(Lcom/mojang/renderpearl/api/buffers/GpuBufferSlice;Ljava/nio/ByteBuffer;)V"))
|
||||
private void horizon$copy(CommandEncoder encoder, GpuBufferSlice destination, ByteBuffer data) {
|
||||
long bytes = data.remaining();
|
||||
long started = System.nanoTime();
|
||||
try { encoder.writeToBuffer(destination, data); }
|
||||
finally { MacBenchmarkIntegration.profileVboUpload(bytes, started, System.nanoTime() - started, true); }
|
||||
}
|
||||
|
||||
@Redirect(method = "uploadVertexBuffer", at = @At(value = "INVOKE", target =
|
||||
"Lcom/mojang/renderpearl/api/device/GpuDevice;createBuffer(Ljava/util/function/Supplier;IJ)Lcom/mojang/renderpearl/api/buffers/GpuBuffer;"))
|
||||
private GpuBuffer horizon$allocation(GpuDevice device, Supplier<String> label, int usage, long bytes) {
|
||||
boolean capacityFits = vertexGpuBuffer != null && vertexGpuBuffer.size() >= bytes;
|
||||
long started = System.nanoTime();
|
||||
var buffer = device.createBuffer(label, usage, bytes);
|
||||
MacBenchmarkIntegration.profileVboAllocation(bytes, started, System.nanoTime() - started, capacityFits);
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package dev.horizonlab.dispatch.fixture;
|
||||
|
||||
import dev.horizonlab.metrics.MacBenchmarkIntegration;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
/** Private diagnostic intervals on the same boundary as the frame recorder. */
|
||||
@Mixin(Minecraft.class)
|
||||
public abstract class MacFrameIntervalMixin {
|
||||
@Inject(method = "runTick", at = @At("HEAD"))
|
||||
private void horizon$frameInterval(boolean advanceGameTime, CallbackInfo callback) {
|
||||
MacBenchmarkIntegration.profileFrameInterval();
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package dev.horizonlab.dispatch.fixture;
|
||||
|
||||
import dev.horizonlab.metrics.MacBenchmarkIntegration;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(Minecraft.class)
|
||||
public abstract class MacGpuPassFrameMixin {
|
||||
@Inject(method = "runTick", at = @At("HEAD"), require = 1)
|
||||
private void horizon$beginPassFrame(boolean advanceGameTime, CallbackInfo callback) {
|
||||
MacBenchmarkIntegration.profileFrameStart();
|
||||
}
|
||||
|
||||
@Inject(method = "runTick", at = @At("RETURN"), require = 1)
|
||||
private void horizon$endPassFrame(boolean advanceGameTime, CallbackInfo callback) {
|
||||
MacBenchmarkIntegration.profileFrameEnd();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package dev.horizonlab.dispatch.fixture;
|
||||
|
||||
import dev.horizonlab.metrics.MacBenchmarkIntegration;
|
||||
import net.minecraft.client.renderer.GameRenderer;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
/** Optional private asynchronous device timestamps; absent from timing-only runs. */
|
||||
@Mixin(GameRenderer.class)
|
||||
public abstract class MacGpuTimingMixin {
|
||||
@Inject(method = "render", at = @At("HEAD"), require = 1)
|
||||
private void horizon$beginGpuProfile(CallbackInfo callback) {
|
||||
MacBenchmarkIntegration.profileRenderStart();
|
||||
}
|
||||
|
||||
@Inject(method = "render", at = @At("RETURN"), require = 1)
|
||||
private void horizon$endGpuProfile(CallbackInfo callback) {
|
||||
MacBenchmarkIntegration.profileRenderEnd();
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package dev.horizonlab.dispatch.fixture;
|
||||
|
||||
import com.mojang.renderpearl.api.commands.RenderPassDescriptor;
|
||||
import com.mojang.renderpearl.backend.api.CommandEncoderBackend;
|
||||
import com.mojang.renderpearl.backend.api.RenderPassBackend;
|
||||
import com.mojang.renderpearl.backend.vulkan.VulkanCommandEncoder;
|
||||
import dev.horizonlab.metrics.MacBenchmarkIntegration;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
/** Private pass-boundary queries around the original operations. */
|
||||
@Mixin(VulkanCommandEncoder.class)
|
||||
public abstract class MacVulkanPassTimingMixin {
|
||||
@Inject(method = "createRenderPass", at = @At("HEAD"), require = 1)
|
||||
private void horizon$beginPass(RenderPassDescriptor descriptor, CallbackInfoReturnable<RenderPassBackend> callback) {
|
||||
MacBenchmarkIntegration.profilePassStart((CommandEncoderBackend) (Object) this, descriptor);
|
||||
}
|
||||
|
||||
@Inject(method = "submitRenderPass", at = @At("RETURN"), require = 1)
|
||||
private void horizon$endPass(CallbackInfo callback) {
|
||||
MacBenchmarkIntegration.profilePassEnd((CommandEncoderBackend) (Object) this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,702 @@
|
||||
package dev.horizonlab.metrics;
|
||||
|
||||
import com.mojang.renderpearl.api.buffers.GpuBuffer;
|
||||
import com.mojang.renderpearl.api.buffers.GpuBufferSlice;
|
||||
import com.mojang.renderpearl.api.commands.RenderPass;
|
||||
import com.mojang.renderpearl.api.pipeline.IndexType;
|
||||
import com.mojang.renderpearl.api.pipeline.RenderPipeline;
|
||||
import com.seibel.distanthorizons.common.render.blaze.BlazeDhMetaRenderer;
|
||||
import com.seibel.distanthorizons.common.render.blaze.objects.BlazeGenericObjectVertexContainer;
|
||||
import com.seibel.distanthorizons.common.render.blaze.wrappers.uniform.BlazeUniformBufferWrapper;
|
||||
import com.seibel.distanthorizons.common.wrappers.misc.LightMapWrapper;
|
||||
import com.seibel.distanthorizons.core.render.RenderParams;
|
||||
import com.seibel.distanthorizons.core.render.renderer.RenderableBoxGroup;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalDouble;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.mojang.renderpearl.api.commands.GpuQueryPool;
|
||||
import com.mojang.renderpearl.api.commands.RenderPassDescriptor;
|
||||
import com.mojang.renderpearl.backend.api.CommandEncoderBackend;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import net.fabricmc.api.ClientModInitializer;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.client.Screenshot;
|
||||
import net.minecraft.client.gui.screens.TitleScreen;
|
||||
import net.minecraft.network.chat.contents.TranslatableContents;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.world.level.levelgen.WorldOptions;
|
||||
import net.minecraft.world.level.levelgen.presets.WorldPresets;
|
||||
import net.minecraft.world.level.GameType;
|
||||
|
||||
/** Private physical-Mac measurement fixture; never included in playable packs. */
|
||||
public final class MacBenchmarkIntegration implements ClientModInitializer {
|
||||
private boolean opening, finished, readbackRequested;
|
||||
private long ready, started;
|
||||
private int warmupSeconds, seconds, postRecordSeconds;
|
||||
private float yaw, pitch;
|
||||
private String label;
|
||||
private boolean gpuProfile;
|
||||
private boolean jfrProfile;
|
||||
private boolean vboProfile;
|
||||
private boolean renderClassPrewarm;
|
||||
private static final String[] PREWARM_RENDER_CLASSES = {
|
||||
"net.minecraft.client.renderer.extract.TransientBlock",
|
||||
"net.minecraft.client.renderer.block.MovingBlockRenderState",
|
||||
"net.caffeinemc.mods.sodium.client.render.frapi.render.NonTerrainBlockRenderContext",
|
||||
"net.caffeinemc.mods.sodium.client.model.light.data.SingleBlockLightDataCache",
|
||||
"it.unimi.dsi.fastutil.longs.Long2IntLinkedOpenHashMap"
|
||||
};
|
||||
private static VboTiming vboTiming;
|
||||
private jdk.jfr.Recording flightRecording;
|
||||
private static long flightDeadline, previousFlightFrame;
|
||||
private static FrameIntervalEvent flightFrame;
|
||||
private boolean passProfile;
|
||||
private boolean sceneRequested;
|
||||
private String scene;
|
||||
public static boolean cloudBatchEnabled;
|
||||
public static long cloudValidationClock;
|
||||
private boolean cloudImageCheck, imagePending, productionCloudBatch;
|
||||
private long imageDrawsStart, imagePassesStart;
|
||||
private int imageStage;
|
||||
private long imageReady;
|
||||
private static GpuTiming gpuTiming;
|
||||
private static PassTiming passTiming;
|
||||
|
||||
public static void profileVboAllocation(long bytes, long start, long duration, boolean capacityFits) {
|
||||
if (vboTiming != null) vboTiming.add(bytes, start, duration, capacityFits);
|
||||
}
|
||||
|
||||
public static void profileVboUpload(long bytes, long start, long duration, boolean copy) {
|
||||
if (vboTiming != null && vboTiming.started != 0 && start >= vboTiming.started && start < vboTiming.deadline) {
|
||||
(copy ? vboTiming.copy : vboTiming.upload).add(bytes, start - vboTiming.started, duration);
|
||||
}
|
||||
}
|
||||
|
||||
@jdk.jfr.Name("dev.horizonlab.TerrainUpload")
|
||||
@jdk.jfr.Label("Private DH terrain upload")
|
||||
@jdk.jfr.StackTrace(false)
|
||||
public static final class TerrainUploadEvent extends jdk.jfr.Event {
|
||||
public long bytes;
|
||||
}
|
||||
|
||||
public static TerrainUploadEvent beginTerrainUpload(long bytes) {
|
||||
if (flightDeadline == 0 || System.nanoTime() >= flightDeadline) return null;
|
||||
var event = new TerrainUploadEvent();
|
||||
event.bytes = bytes;
|
||||
event.begin();
|
||||
return event;
|
||||
}
|
||||
|
||||
private static final class UploadTiming {
|
||||
private final long[] durations = new long[300_000];
|
||||
private final com.google.gson.JsonArray slow = new com.google.gson.JsonArray();
|
||||
private int count, dropped, slowDropped;
|
||||
private long bytes;
|
||||
|
||||
void add(long size, long offset, long duration) {
|
||||
bytes += size;
|
||||
if (count == durations.length) { dropped++; return; }
|
||||
durations[count++] = duration;
|
||||
if (duration < 1_000_000) return;
|
||||
if (slow.size() >= 128) { slowDropped++; return; }
|
||||
var event = new JsonObject();
|
||||
event.addProperty("recording_offset_ns", offset);
|
||||
event.addProperty("duration_ns", duration);
|
||||
event.addProperty("bytes", size);
|
||||
slow.add(event);
|
||||
}
|
||||
|
||||
JsonObject summary() {
|
||||
var result = new JsonObject();
|
||||
result.addProperty("bytes", bytes);
|
||||
result.addProperty("dropped_samples", dropped);
|
||||
result.addProperty("dropped_slow_events", slowDropped);
|
||||
result.add("duration", new com.google.gson.Gson().toJsonTree(
|
||||
SampleStats.of(java.util.Arrays.copyOf(durations, count), count)));
|
||||
result.add("slow_events", slow);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class VboTiming {
|
||||
private final UploadTiming upload = new UploadTiming(), copy = new UploadTiming();
|
||||
private final long[] durations = new long[300_000];
|
||||
private final com.google.gson.JsonArray slow = new com.google.gson.JsonArray();
|
||||
private long started, deadline, totalCalls, bytesAllocated, capacityReuseCandidates;
|
||||
private int count, dropped, slowDropped;
|
||||
|
||||
void add(long bytes, long start, long duration, boolean capacityFits) {
|
||||
totalCalls++;
|
||||
if (started == 0 || start < started || start >= deadline) return;
|
||||
bytesAllocated += bytes;
|
||||
if (capacityFits) capacityReuseCandidates++;
|
||||
if (count == durations.length) { dropped++; return; }
|
||||
durations[count++] = duration;
|
||||
if (duration < 5_000_000) return;
|
||||
if (slow.size() >= 128) { slowDropped++; return; }
|
||||
var event = new JsonObject();
|
||||
event.addProperty("recording_offset_ns", start - started);
|
||||
event.addProperty("duration_ns", duration);
|
||||
event.addProperty("bytes", bytes);
|
||||
event.addProperty("existing_capacity_fits", capacityFits);
|
||||
slow.add(event);
|
||||
}
|
||||
|
||||
JsonObject summary() {
|
||||
var result = new JsonObject();
|
||||
result.addProperty("instrumented_calls_total", totalCalls);
|
||||
result.addProperty("bytes_allocated_during_recording", bytesAllocated);
|
||||
result.addProperty("capacity_reuse_candidates", capacityReuseCandidates);
|
||||
result.addProperty("dropped_samples", dropped);
|
||||
result.addProperty("dropped_slow_events", slowDropped);
|
||||
result.add("allocation_duration", new com.google.gson.Gson().toJsonTree(
|
||||
SampleStats.of(java.util.Arrays.copyOf(durations, count), count)));
|
||||
result.add("slow_allocations", slow);
|
||||
result.add("whole_upload", upload.summary());
|
||||
result.add("staging_copy_and_barrier", copy.summary());
|
||||
result.addProperty("note", "Original DH destination allocation, complete upload, and staging/copy/barrier timings. Nested durations are not additive. Profiling is separate from ordinary FPS measurements.");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@jdk.jfr.Name("dev.horizonlab.FrameInterval")
|
||||
@jdk.jfr.Label("Private Minecraft frame interval")
|
||||
@jdk.jfr.StackTrace(false)
|
||||
public static final class FrameIntervalEvent extends jdk.jfr.Event {
|
||||
@jdk.jfr.Timespan(jdk.jfr.Timespan.NANOSECONDS)
|
||||
public long intervalNs;
|
||||
}
|
||||
|
||||
public static void profileFrameInterval() {
|
||||
long now = System.nanoTime();
|
||||
if (flightDeadline == 0 || now >= flightDeadline) { flightFrame = null; return; }
|
||||
if (flightFrame != null) {
|
||||
flightFrame.end();
|
||||
flightFrame.intervalNs = now - previousFlightFrame;
|
||||
flightFrame.commit();
|
||||
}
|
||||
flightFrame = new FrameIntervalEvent();
|
||||
flightFrame.begin();
|
||||
previousFlightFrame = now;
|
||||
}
|
||||
|
||||
public static void profileFrameStart() { if (passTiming != null) passTiming.beginFrame(); }
|
||||
public static void profileFrameEnd() { if (passTiming != null) passTiming.endFrame(); }
|
||||
public static void profilePassStart(CommandEncoderBackend encoder, RenderPassDescriptor descriptor) {
|
||||
if (passTiming != null) passTiming.begin(encoder, descriptor.label().get());
|
||||
}
|
||||
public static void profilePassEnd(CommandEncoderBackend encoder) {
|
||||
if (passTiming != null) passTiming.end(encoder);
|
||||
}
|
||||
|
||||
private static final class PassTiming implements AutoCloseable {
|
||||
private final GpuQueryPool pool = RenderSystem.getDevice().createTimestampQueryPool(512);
|
||||
private final String[] pending = new String[256];
|
||||
private final int[] pendingFrames = new int[256];
|
||||
private final java.util.Map<String, java.util.ArrayList<Long>> durations = new java.util.LinkedHashMap<>();
|
||||
private final java.util.Map<String, java.util.Map<Integer, Long>> frameDurations = new java.util.LinkedHashMap<>();
|
||||
private final java.util.Map<String, Long> passCounts = new java.util.LinkedHashMap<>();
|
||||
private final long deadlineNs;
|
||||
private final double period = RenderSystem.getDevice().getDeviceInfo().timestampPeriod();
|
||||
private int frame, active = -1, skipped, invalid;
|
||||
private boolean sampling;
|
||||
private String activeLabel;
|
||||
|
||||
PassTiming(long deadlineNs) { this.deadlineNs = deadlineNs; }
|
||||
|
||||
private void collect() {
|
||||
for (int slot = 0; slot < pending.length; slot++) {
|
||||
String label = pending[slot];
|
||||
if (label == null) continue;
|
||||
var values = pool.getValues(slot * 2, 2);
|
||||
if (values[0].isEmpty() || values[1].isEmpty()) continue;
|
||||
pending[slot] = null;
|
||||
long duration = (long) ((values[1].getAsLong() - values[0].getAsLong()) * period);
|
||||
if (duration <= 0) { invalid++; continue; }
|
||||
if (!durations.containsKey(label) && durations.size() >= 64) { skipped++; continue; }
|
||||
var samples = durations.computeIfAbsent(label, key -> new java.util.ArrayList<>());
|
||||
if (samples.size() < 4096) samples.add(duration);
|
||||
var frames = frameDurations.computeIfAbsent(label, key -> new java.util.LinkedHashMap<>());
|
||||
int sampleFrame = pendingFrames[slot];
|
||||
if (frames.containsKey(sampleFrame) || frames.size() < 2048) {
|
||||
frames.merge(sampleFrame, duration, Long::sum);
|
||||
passCounts.merge(label, 1L, Long::sum);
|
||||
} else skipped++;
|
||||
}
|
||||
}
|
||||
|
||||
void beginFrame() {
|
||||
collect();
|
||||
sampling = System.nanoTime() < deadlineNs && frame++ % 20 == 0;
|
||||
}
|
||||
|
||||
void endFrame() {
|
||||
sampling = false;
|
||||
if (active >= 0) throw new AssertionError("Unclosed private GPU pass timing");
|
||||
}
|
||||
|
||||
void begin(CommandEncoderBackend encoder, String label) {
|
||||
if (!sampling) return;
|
||||
if (active >= 0) throw new AssertionError("Overlapping private GPU pass timing");
|
||||
for (int slot = 0; slot < pending.length; slot++) {
|
||||
if (pending[slot] != null) continue;
|
||||
encoder.writeTimestamp(pool, slot * 2);
|
||||
activeLabel = label;
|
||||
pendingFrames[slot] = frame;
|
||||
active = slot;
|
||||
return;
|
||||
}
|
||||
skipped++;
|
||||
}
|
||||
|
||||
void end(CommandEncoderBackend encoder) {
|
||||
if (active < 0) return;
|
||||
int slot = active;
|
||||
active = -1;
|
||||
encoder.writeTimestamp(pool, slot * 2 + 1);
|
||||
pending[slot] = activeLabel;
|
||||
}
|
||||
|
||||
JsonObject summary() {
|
||||
collect();
|
||||
var result = new JsonObject();
|
||||
var passes = new JsonObject();
|
||||
var gson = new com.google.gson.Gson();
|
||||
durations.forEach((label, values) -> {
|
||||
long[] samples = values.stream().mapToLong(Long::longValue).toArray();
|
||||
passes.add(label, gson.toJsonTree(SampleStats.of(samples, samples.length)));
|
||||
});
|
||||
result.add("passes", passes);
|
||||
var totals = new JsonObject();
|
||||
frameDurations.forEach((label, frames) -> {
|
||||
long[] samples = frames.values().stream().mapToLong(Long::longValue).toArray();
|
||||
var total = gson.toJsonTree(SampleStats.of(samples, samples.length)).getAsJsonObject();
|
||||
total.addProperty("pass_invocations", passCounts.get(label));
|
||||
totals.add(label, total);
|
||||
});
|
||||
result.add("per_sampled_frame", totals);
|
||||
result.addProperty("sampling_frame_stride", 20);
|
||||
result.addProperty("skipped_queries", skipped);
|
||||
result.addProperty("invalid_queries", invalid);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override public void close() { pool.close(); }
|
||||
}
|
||||
|
||||
/** Called only by the optional private GPU timing mixin. */
|
||||
public static void profileRenderStart() {
|
||||
if (gpuTiming != null) gpuTiming.begin();
|
||||
}
|
||||
|
||||
public static void profileRenderEnd() {
|
||||
if (gpuTiming != null) gpuTiming.end();
|
||||
}
|
||||
|
||||
private static final class GpuTiming implements AutoCloseable {
|
||||
private final GpuQueryPool pool = RenderSystem.getDevice().createTimestampQueryPool(16);
|
||||
private final boolean[] pending = new boolean[8];
|
||||
private final long[] gpuSamples = new long[120_000];
|
||||
private final long[] cpuSamples = new long[120_000];
|
||||
private final float period = RenderSystem.getDevice().getDeviceInfo().timestampPeriod();
|
||||
private final long deadlineNs;
|
||||
private int active = -1, gpuCount, cpuCount, skipped, invalid;
|
||||
private long cpuStart;
|
||||
private boolean recordingFrame;
|
||||
|
||||
GpuTiming(long deadlineNs) { this.deadlineNs = deadlineNs; }
|
||||
|
||||
private void collect() {
|
||||
for (int slot = 0; slot < pending.length; slot++) {
|
||||
if (!pending[slot]) continue;
|
||||
var values = pool.getValues(slot * 2, 2);
|
||||
if (values[0].isEmpty() || values[1].isEmpty()) continue;
|
||||
pending[slot] = false;
|
||||
long duration = (long) ((values[1].getAsLong() - values[0].getAsLong()) * (double) period);
|
||||
if (duration > 0 && gpuCount < gpuSamples.length) gpuSamples[gpuCount++] = duration;
|
||||
else invalid++;
|
||||
}
|
||||
}
|
||||
|
||||
void begin() {
|
||||
recordingFrame = System.nanoTime() < deadlineNs;
|
||||
if (!recordingFrame) return;
|
||||
collect();
|
||||
cpuStart = System.nanoTime();
|
||||
active = -1;
|
||||
for (int slot = 0; slot < pending.length; slot++) {
|
||||
if (pending[slot]) continue;
|
||||
active = slot;
|
||||
RenderSystem.getDevice().createCommandEncoder().writeTimestamp(pool, slot * 2);
|
||||
return;
|
||||
}
|
||||
skipped++;
|
||||
}
|
||||
|
||||
void end() {
|
||||
if (!recordingFrame) return;
|
||||
long duration = System.nanoTime() - cpuStart;
|
||||
if (duration > 0 && cpuCount < cpuSamples.length) cpuSamples[cpuCount++] = duration;
|
||||
if (active < 0) return;
|
||||
RenderSystem.getDevice().createCommandEncoder().writeTimestamp(pool, active * 2 + 1);
|
||||
pending[active] = true;
|
||||
active = -1;
|
||||
}
|
||||
|
||||
JsonObject summary() {
|
||||
collect();
|
||||
var gson = new com.google.gson.Gson();
|
||||
var result = new JsonObject();
|
||||
result.addProperty("status", gpuCount > 0 ? "available" : "unavailable");
|
||||
result.add("gpu_render_duration", gson.toJsonTree(SampleStats.of(gpuSamples, gpuCount)));
|
||||
result.add("cpu_render_call_duration", gson.toJsonTree(SampleStats.of(cpuSamples, cpuCount)));
|
||||
result.addProperty("skipped_queries", skipped);
|
||||
result.addProperty("invalid_or_overflowed_queries", invalid);
|
||||
int pendingCount = 0;
|
||||
for (boolean value : pending) if (value) pendingCount++;
|
||||
result.addProperty("pending_queries_at_end", pendingCount);
|
||||
result.addProperty("timestamp_period_ns", period);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override public void close() { pool.close(); }
|
||||
}
|
||||
|
||||
@Override public void onInitializeClient() {
|
||||
try {
|
||||
var config = new java.util.Properties();
|
||||
try (var reader = Files.newBufferedReader(FabricLoader.getInstance().getConfigDir()
|
||||
.resolve("mac-benchmark.properties"))) { config.load(reader); }
|
||||
warmupSeconds = Integer.parseInt(config.getProperty("warmupSeconds", "60"));
|
||||
seconds = Integer.parseInt(config.getProperty("seconds", "120"));
|
||||
yaw = Float.parseFloat(config.getProperty("yaw", "141.5"));
|
||||
pitch = Float.parseFloat(config.getProperty("pitch", "16.95"));
|
||||
label = config.getProperty("label", "mac-stationary");
|
||||
productionCloudBatch = Boolean.parseBoolean(config.getProperty("productionCloudBatch", "false"));
|
||||
cloudImageCheck = Boolean.parseBoolean(config.getProperty("cloudImageCheck", "false"));
|
||||
cloudBatchEnabled = !cloudImageCheck && Boolean.parseBoolean(config.getProperty("cloudBatch", "false"));
|
||||
gpuProfile = Boolean.parseBoolean(config.getProperty("gpuProfile", "false"));
|
||||
jfrProfile = Boolean.parseBoolean(config.getProperty("jfrProfile", "false"));
|
||||
vboProfile = Boolean.parseBoolean(config.getProperty("vboProfile", "false"));
|
||||
if (vboProfile) vboTiming = new VboTiming();
|
||||
renderClassPrewarm = Boolean.parseBoolean(config.getProperty("renderClassPrewarm", "false"));
|
||||
postRecordSeconds = Integer.parseInt(config.getProperty("postRecordSeconds", "3"));
|
||||
if (renderClassPrewarm) {
|
||||
for (String type : PREWARM_RENDER_CLASSES) {
|
||||
Class.forName(type, false, MacBenchmarkIntegration.class.getClassLoader());
|
||||
}
|
||||
System.out.println("HORIZON_MAC_RENDER_CLASSES_PREWARMED " + PREWARM_RENDER_CLASSES.length);
|
||||
}
|
||||
passProfile = Boolean.parseBoolean(config.getProperty("passProfile", "false"));
|
||||
scene = config.getProperty("scene", "spawn");
|
||||
if (warmupSeconds < 0 || warmupSeconds > 300 || seconds < 10 || seconds > 120
|
||||
|| !Float.isFinite(yaw) || !Float.isFinite(pitch)
|
||||
|| !label.matches("[a-zA-Z0-9_-]{1,64}")
|
||||
|| !(scene.equals("spawn") || scene.equals("overlook") || scene.equals("flyover"))) throw new IllegalArgumentException("Invalid fixture settings");
|
||||
} catch (Exception error) { throw new IllegalStateException("Private Mac benchmark configuration failed", error); }
|
||||
ClientTickEvents.END_CLIENT_TICK.register(client -> {
|
||||
if (finished || client.gui.overlay() != null) return;
|
||||
if (!opening && client.gui.screen() instanceof TitleScreen screen) {
|
||||
opening = true;
|
||||
if (Files.isRegularFile(client.gameDirectory.toPath().resolve("saves/Demo_World/level.dat"))) {
|
||||
client.createWorldOpenFlows().openWorld("Demo_World", () -> {
|
||||
throw new AssertionError("Private benchmark world did not open");
|
||||
});
|
||||
} else {
|
||||
client.createWorldOpenFlows().createFreshLevel("Demo_World", MinecraftServer.DEMO_SETTINGS,
|
||||
WorldOptions.DEMO_OPTIONS, WorldPresets::createNormalWorldDimensions, screen);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (client.gui.screen() != null && client.gui.screen().getTitle().getContents() instanceof TranslatableContents title
|
||||
&& title.getKey().equals("demo.help.title")) { client.gui.setScreen(null); return; }
|
||||
if (client.level == null || client.player == null || client.gui.screen() != null
|
||||
|| client.isPaused() || !client.isWindowActive()) {
|
||||
if (started == 0) ready = 0;
|
||||
return;
|
||||
}
|
||||
if (!scene.equals("spawn")) {
|
||||
if (!sceneRequested) {
|
||||
sceneRequested = true;
|
||||
var server = client.getSingleplayerServer();
|
||||
var playerId = client.player.getUUID();
|
||||
server.execute(() -> {
|
||||
var player = server.getPlayerList().getPlayer(playerId);
|
||||
player.setGameMode(GameType.SPECTATOR);
|
||||
player.teleportTo(-41.5, 100.0, 72.5);
|
||||
player.setDeltaMovement(0, 0, 0);
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!client.player.isSpectator()
|
||||
|| (started == 0 && Math.abs(client.player.getY() - 100.0) > 0.01)) return;
|
||||
if (scene.equals("overlook")) {
|
||||
client.player.setPos(-41.5, 100.0, 72.5);
|
||||
client.player.setDeltaMovement(0, 0, 0);
|
||||
}
|
||||
}
|
||||
// Control only this private workload's camera; simulation runs normally.
|
||||
client.player.setYRot(yaw);
|
||||
client.player.setXRot(pitch);
|
||||
if (scene.equals("overlook")) client.player.setOldPosAndRot();
|
||||
long now = System.nanoTime();
|
||||
if (scene.equals("flyover")) client.options.keyUp.setDown(
|
||||
started != 0 && now - started < seconds * 1_000_000_000L);
|
||||
if (ready == 0) {
|
||||
ready = now;
|
||||
System.out.println("HORIZON_MAC_BENCHMARK_READY");
|
||||
}
|
||||
if (jfrProfile && started == 0 && flightRecording == null
|
||||
&& now - ready >= Math.max(0, warmupSeconds - 15L) * 1_000_000_000L) {
|
||||
try {
|
||||
flightRecording = new jdk.jfr.Recording(jdk.jfr.Configuration.getConfiguration("profile"));
|
||||
flightRecording.setName("Private M2 Pro diagnostics");
|
||||
flightRecording.setMaxSize(128L * 1024 * 1024);
|
||||
flightRecording.enable(FrameIntervalEvent.class).withThreshold(java.time.Duration.ZERO);
|
||||
if (vboProfile) flightRecording.enable(TerrainUploadEvent.class).withThreshold(java.time.Duration.ofMillis(1));
|
||||
flightRecording.enable("jdk.ThreadPark").withThreshold(java.time.Duration.ofMillis(1));
|
||||
flightRecording.enable("jdk.JavaMonitorEnter").withThreshold(java.time.Duration.ofMillis(1));
|
||||
flightRecording.enable("jdk.ClassLoad").withThreshold(java.time.Duration.ofMillis(1));
|
||||
flightRecording.start();
|
||||
var frameEvent = new FrameIntervalEvent();
|
||||
frameEvent.begin(); frameEvent.end();
|
||||
if (vboProfile) {
|
||||
// Initialize the private event machinery during warmup, before timing uploads.
|
||||
var event = new TerrainUploadEvent();
|
||||
event.begin(); event.end(); event.commit();
|
||||
}
|
||||
} catch (Exception error) { throw new AssertionError("Private JFR start failed", error); }
|
||||
}
|
||||
if (cloudImageCheck) {
|
||||
if (now - ready >= warmupSeconds * 1_000_000_000L) checkCloudImages(client, now);
|
||||
return;
|
||||
}
|
||||
if (started == 0 && now - ready >= warmupSeconds * 1_000_000_000L) {
|
||||
try {
|
||||
var method = Class.forName("dev.horizonlab.client.HorizonClient")
|
||||
.getDeclaredMethod("start", String.class, int.class);
|
||||
method.setAccessible(true);
|
||||
if (!method.invoke(null, label, seconds).equals(1)) throw new AssertionError("Recording failed to start");
|
||||
for (String[] target : new String[][]{{"dev.horizonlab.client.HorizonClient", "frames"},
|
||||
{"dev.horizonlab.HorizonLab", "ticks"}}) {
|
||||
var field = Class.forName(target[0]).getDeclaredField(target[1]);
|
||||
field.setAccessible(true);
|
||||
var recording = (Recording) field.get(null);
|
||||
if (recording != null) synchronized (recording) {
|
||||
recording.context.put("private_render_class_prewarm", renderClassPrewarm);
|
||||
if (gpuProfile || passProfile || jfrProfile || vboProfile) {
|
||||
recording.context.put("private_profiling_enabled", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
started = now;
|
||||
if (vboTiming != null) {
|
||||
vboTiming.started = now;
|
||||
vboTiming.deadline = now + seconds * 1_000_000_000L;
|
||||
}
|
||||
if (jfrProfile) flightDeadline = now + seconds * 1_000_000_000L;
|
||||
if (gpuProfile) gpuTiming = new GpuTiming(now + seconds * 1_000_000_000L);
|
||||
if (passProfile) passTiming = new PassTiming(now + seconds * 1_000_000_000L);
|
||||
System.out.println("HORIZON_MAC_BENCHMARK_STARTED");
|
||||
} catch (ReflectiveOperationException error) { throw new AssertionError("Private recorder start failed", error); }
|
||||
}
|
||||
if (started == 0 || now - started < (seconds + (long) postRecordSeconds) * 1_000_000_000L || readbackRequested) return;
|
||||
Path output = client.gameDirectory.toPath().resolve("horizon-benchmarks");
|
||||
try (var paths = Files.list(output)) {
|
||||
var reports = paths.filter(p -> p.getFileName().toString().startsWith(label + "-")
|
||||
&& p.toString().endsWith(".json")).toList();
|
||||
if (reports.size() != 2) throw new AssertionError("Expected completed frame and tick reports");
|
||||
var result = new JsonObject();
|
||||
result.add("cloud_batch", MacCloudBatchState.summary());
|
||||
result.addProperty("warmup_seconds", warmupSeconds);
|
||||
result.addProperty("post_record_seconds", postRecordSeconds);
|
||||
result.addProperty("scene", scene);
|
||||
result.addProperty("render_class_prewarm", renderClassPrewarm);
|
||||
if (renderClassPrewarm) result.add("prewarmed_render_classes",
|
||||
new com.google.gson.Gson().toJsonTree(PREWARM_RENDER_CLASSES));
|
||||
result.addProperty("camera_yaw", yaw);
|
||||
result.addProperty("camera_pitch", pitch);
|
||||
result.addProperty("player_x", client.player.getX());
|
||||
result.addProperty("player_y", client.player.getY());
|
||||
result.addProperty("player_z", client.player.getZ());
|
||||
var device = RenderSystem.getDevice().getDeviceInfo();
|
||||
result.addProperty("graphics_backend", device.backendName());
|
||||
result.addProperty("gpu", device.name());
|
||||
if (vboTiming != null) {
|
||||
result.add("vbo_allocation_profile", vboTiming.summary());
|
||||
vboTiming = null;
|
||||
}
|
||||
if (flightRecording != null) {
|
||||
flightDeadline = 0;
|
||||
flightFrame = null;
|
||||
flightRecording.stop();
|
||||
flightRecording.dump(client.gameDirectory.toPath().resolve("mac-benchmark.jfr"));
|
||||
var jfr = new JsonObject();
|
||||
jfr.addProperty("file", "mac-benchmark.jfr");
|
||||
jfr.addProperty("frame_event", jdk.jfr.EventType.getEventType(FrameIntervalEvent.class).getName());
|
||||
jfr.addProperty("max_recording_bytes", 128L * 1024 * 1024);
|
||||
result.add("jfr_profile", jfr);
|
||||
flightRecording.close();
|
||||
flightRecording = null;
|
||||
}
|
||||
if (gpuTiming != null) {
|
||||
result.add("gpu_profile", gpuTiming.summary());
|
||||
gpuTiming.close();
|
||||
gpuTiming = null;
|
||||
}
|
||||
if (passTiming != null) {
|
||||
result.add("gpu_pass_profile", passTiming.summary());
|
||||
passTiming.close();
|
||||
passTiming = null;
|
||||
}
|
||||
for (Path path : reports) {
|
||||
var report = JsonParser.parseString(Files.readString(path)).getAsJsonObject();
|
||||
if (report.getAsJsonObject("stats").get("count").getAsInt() < 100)
|
||||
throw new AssertionError("Too few active measurement samples");
|
||||
result.add(report.get("kind").getAsString(), report);
|
||||
}
|
||||
readbackRequested = true;
|
||||
Screenshot.takeScreenshot(client.gameRenderer.mainRenderTarget(), image -> {
|
||||
try (image) {
|
||||
image.writeToFile(client.gameDirectory.toPath().resolve("mac-benchmark-private.png"));
|
||||
result.addProperty("framebuffer_width", image.getWidth());
|
||||
result.addProperty("framebuffer_height", image.getHeight());
|
||||
Files.writeString(client.gameDirectory.toPath().resolve("mac-benchmark-result.json"), result.toString());
|
||||
System.out.println("HORIZON_MAC_BENCHMARK_RESULT " + result);
|
||||
finished = true;
|
||||
client.execute(client::stop);
|
||||
} catch (Exception error) { throw new AssertionError("Private Mac readback failed", error); }
|
||||
});
|
||||
} catch (Exception error) { throw new AssertionError("Private Mac benchmark failed", error); }
|
||||
});
|
||||
}
|
||||
|
||||
private void setImageBatchEnabled(boolean enabled) {
|
||||
if (!productionCloudBatch) { cloudBatchEnabled = enabled; return; }
|
||||
try {
|
||||
var flag = Class.forName("dev.horizonlab.mixin.HorizonMixinPlugin").getDeclaredField("cloudBatch");
|
||||
flag.setAccessible(true);
|
||||
flag.setBoolean(null, enabled);
|
||||
} catch (ReflectiveOperationException error) { throw new AssertionError("Private production batch toggle failed", error); }
|
||||
}
|
||||
|
||||
/** Frozen-scene A/B/A readbacks are separate from ordinary performance runs. */
|
||||
private void checkCloudImages(net.minecraft.client.Minecraft client, long now) {
|
||||
if (imageReady == 0) {
|
||||
setImageBatchEnabled(false);
|
||||
imageDrawsStart = productionCloudBatch ? dev.horizonlab.render.CloudBatchStatus.draws : MacCloudBatchState.totalDraws;
|
||||
imagePassesStart = productionCloudBatch ? dev.horizonlab.render.CloudBatchStatus.passes : MacCloudBatchState.totalPasses;
|
||||
cloudValidationClock = System.currentTimeMillis();
|
||||
if (!client.gui.hud.isHidden()) client.gui.hud.toggle();
|
||||
client.getSingleplayerServer().execute(() -> client.getSingleplayerServer().tickRateManager().setFrozen(true));
|
||||
imageReady = now;
|
||||
return;
|
||||
}
|
||||
if (!client.level.tickRateManager().isFrozen() || imagePending || now - imageReady < 5_000_000_000L) return;
|
||||
String name = new String[]{"cloud-image-control-a.png", "cloud-image-batched.png", "cloud-image-control-b.png"}[imageStage];
|
||||
imagePending = true;
|
||||
Screenshot.takeScreenshot(client.gameRenderer.mainRenderTarget(), image -> {
|
||||
try (image) {
|
||||
image.writeToFile(client.gameDirectory.toPath().resolve(name));
|
||||
imageStage++;
|
||||
setImageBatchEnabled(imageStage == 1);
|
||||
imageReady = System.nanoTime();
|
||||
imagePending = false;
|
||||
if (imageStage < 3) return;
|
||||
var result = new JsonObject();
|
||||
var device = RenderSystem.getDevice().getDeviceInfo();
|
||||
result.addProperty("image_check_only", true);
|
||||
result.addProperty("graphics_backend", device.backendName());
|
||||
result.addProperty("gpu", device.name());
|
||||
result.addProperty("frozen_scene", client.level.tickRateManager().isFrozen());
|
||||
result.addProperty("framebuffer_width", image.getWidth());
|
||||
result.addProperty("framebuffer_height", image.getHeight());
|
||||
result.addProperty("production_cloud_batch", productionCloudBatch);
|
||||
var batch = new JsonObject();
|
||||
batch.addProperty("draws", (productionCloudBatch ? dev.horizonlab.render.CloudBatchStatus.draws : MacCloudBatchState.totalDraws) - imageDrawsStart);
|
||||
batch.addProperty("passes", (productionCloudBatch ? dev.horizonlab.render.CloudBatchStatus.passes : MacCloudBatchState.totalPasses) - imagePassesStart);
|
||||
result.add("cloud_batch", batch);
|
||||
if (productionCloudBatch) setImageBatchEnabled(true);
|
||||
Files.writeString(client.gameDirectory.toPath().resolve("mac-benchmark-result.json"), result.toString());
|
||||
finished = true;
|
||||
client.execute(client::stop);
|
||||
} catch (Exception error) { throw new AssertionError("Private cloud image validation failed", error); }
|
||||
});
|
||||
}
|
||||
|
||||
/** Render-thread-only state, scoped to one original generic-render invocation. */
|
||||
public static final class MacCloudBatchState implements AutoCloseable {
|
||||
public static MacCloudBatchState current;
|
||||
public final BlazeUniformBufferWrapper uniformWrapper;
|
||||
private GpuBufferSlice uniform;
|
||||
private RenderPass pass;
|
||||
private Object lightmap;
|
||||
private static long totalDraws, totalPasses;
|
||||
|
||||
public MacCloudBatchState(BlazeUniformBufferWrapper wrapper) {
|
||||
uniformWrapper = wrapper;
|
||||
}
|
||||
|
||||
public void captureUniform(ByteBuffer data) {
|
||||
var copy = data.duplicate();
|
||||
copy.position(0);
|
||||
var encoder = RenderSystem.getDevice().createCommandEncoder();
|
||||
try (var mapped = encoder.transientMemory().allocateGpuMapped(copy.remaining(), 256, GpuBuffer.USAGE_UNIFORM)) {
|
||||
mapped.data().put(copy);
|
||||
uniform = mapped.slice();
|
||||
}
|
||||
}
|
||||
|
||||
public void draw(RenderParams params, RenderableBoxGroup group, RenderPipeline pipeline) {
|
||||
if (uniform == null) throw new AssertionError("Missing private DH uniform snapshot");
|
||||
var vertices = (BlazeGenericObjectVertexContainer) group.vertexBufferContainer;
|
||||
boolean cloud = group.getResourceLocationNamespace().equals("DistantHorizons")
|
||||
&& group.getResourceLocationPath().equals("Clouds");
|
||||
if (!cloud || lightmap != params.lightmap) closePass();
|
||||
if (pass == null) {
|
||||
var targets = BlazeDhMetaRenderer.INSTANCE;
|
||||
pass = RenderSystem.getDevice().createCommandEncoder().createRenderPass(
|
||||
() -> "horizon-private:DH cloud batch", targets.dhColorTextureWrapper.getTextureView(),
|
||||
Optional.empty(), targets.dhDepthTextureWrapper.getTextureView(), OptionalDouble.empty());
|
||||
pass.setPipeline(RenderSystem.getCompiledPipeline(pipeline));
|
||||
var light = ((LightMapWrapper) params.lightmap).getTextureViewWrapper();
|
||||
pass.setUniform("uLightMap", light.getTextureView(), light.getTextureSampler());
|
||||
lightmap = params.lightmap;
|
||||
totalPasses++;
|
||||
}
|
||||
pass.setUniform("vertUniformBlock", uniform);
|
||||
pass.setIndexBuffer(vertices.indexGpuBuffer, IndexType.INT);
|
||||
pass.setVertexBuffer(0, vertices.vboGpuBuffer.slice());
|
||||
if (vertices.uploadedBoxCount > 0) {
|
||||
pass.drawIndexed(Math.multiplyExact(vertices.uploadedBoxCount, 36), 1, 0, 0, 0);
|
||||
totalDraws++;
|
||||
}
|
||||
uniform = null;
|
||||
if (!cloud) closePass();
|
||||
}
|
||||
|
||||
public void closePass() {
|
||||
var closing = pass;
|
||||
pass = null;
|
||||
lightmap = null;
|
||||
if (closing != null) closing.close();
|
||||
}
|
||||
|
||||
public static JsonObject summary() {
|
||||
var result = new JsonObject();
|
||||
result.addProperty("requested", cloudBatchEnabled);
|
||||
result.addProperty("draws", totalDraws);
|
||||
result.addProperty("passes", totalPasses);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
closePass();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package dev.horizonlab.metrics;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public final class VisualSettingsChangesTest {
|
||||
public static void main(String[] args) {
|
||||
var visual = Map.<String, Object>of("render_distance", 32, "dh_runtime_ratio", 0.5,
|
||||
"dh_cloud_batch_enabled", true, "iris_capture_status", "not_installed");
|
||||
var snapshot = new LinkedHashMap<String, Object>(visual);
|
||||
if (!VisualSettingsChanges.between(snapshot, visual).isEmpty())
|
||||
throw new AssertionError("Identical visual settings were treated as changed");
|
||||
var end = new LinkedHashMap<String, Object>(visual);
|
||||
end.put("dh_runtime_ratio", 0.25);
|
||||
if (!VisualSettingsChanges.between(snapshot, end).isEmpty())
|
||||
throw new AssertionError("Expected adaptive duty-cycle change was treated as visual quality");
|
||||
end.put("dh_cloud_batch_enabled", false);
|
||||
end.put("render_distance", 12);
|
||||
end.remove("iris_capture_status");
|
||||
var changed = VisualSettingsChanges.between(snapshot, end);
|
||||
if (!changed.containsAll(java.util.List.of("dh_cloud_batch_enabled", "render_distance", "iris_capture_status"))
|
||||
|| changed.size() != 3) throw new AssertionError("Actual visual changes were missed: " + changed);
|
||||
System.out.println("Visual snapshot equality, adaptive duty cycle and actual changes checked.");
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
"minecraft": "26.3",
|
||||
"java_major": 25,
|
||||
"fabric_loader": "0.19.5",
|
||||
"pack_version": "0.21.0",
|
||||
"pack_version": "0.22.0",
|
||||
"options_data_version": 5023,
|
||||
"projects": {
|
||||
"sodium": {
|
||||
|
||||
@@ -5,7 +5,37 @@ import jdk.jfr.Recording;
|
||||
/** Produces real JDK counter/GC events for the reader's independent integration check. */
|
||||
public final class JfrFixture {
|
||||
private static volatile Object[] retained;
|
||||
@jdk.jfr.Name("dev.horizonlab.FrameInterval")
|
||||
@jdk.jfr.StackTrace(false)
|
||||
public static final class FrameInterval extends jdk.jfr.Event {
|
||||
public long intervalNs;
|
||||
}
|
||||
@jdk.jfr.Name("dev.horizonlab.TerrainUpload")
|
||||
@jdk.jfr.StackTrace(false)
|
||||
public static final class TerrainUpload extends jdk.jfr.Event {}
|
||||
public static final class LoadPayload {}
|
||||
|
||||
static void checkOverlapCoverage() {
|
||||
var random = new java.util.Random(0x4d3250524fL);
|
||||
for (int test = 0; test < 10_000; test++) {
|
||||
var spans = new java.util.ArrayList<JfrSummary.Span>();
|
||||
var occupied = new boolean[128];
|
||||
for (int i = random.nextInt(16); i > 0; i--) {
|
||||
int start = random.nextInt(128), end = start + random.nextInt(129 - start);
|
||||
spans.add(new JfrSummary.Span(start, end));
|
||||
java.util.Arrays.fill(occupied, start, end, true);
|
||||
}
|
||||
long expected = 0;
|
||||
for (boolean present : occupied) if (present) expected++;
|
||||
if (JfrSummary.coveredNs(spans) != expected)
|
||||
throw new AssertionError("Overlapping event intervals were double-counted");
|
||||
java.util.Collections.shuffle(spans, random);
|
||||
if (JfrSummary.coveredNs(spans) != expected)
|
||||
throw new AssertionError("Coverage depends on event order");
|
||||
}
|
||||
}
|
||||
public static void main(String[] args) throws Exception {
|
||||
checkOverlapCoverage();
|
||||
Thread.currentThread().setName("Horizon JFR fixture \"quoted\"\tthread");
|
||||
try (var recording = new Recording()) {
|
||||
recording.enable("jdk.ThreadAllocationStatistics").withPeriod(Duration.ofMillis(100));
|
||||
@@ -14,6 +44,9 @@ public final class JfrFixture {
|
||||
recording.enable("jdk.GarbageCollection");
|
||||
recording.enable("jdk.GCPhasePause");
|
||||
recording.enable("jdk.GCHeapSummary");
|
||||
recording.enable(FrameInterval.class).withThreshold(Duration.ZERO);
|
||||
recording.enable(TerrainUpload.class).withThreshold(Duration.ZERO);
|
||||
recording.enable("jdk.ClassLoad").withThreshold(Duration.ZERO);
|
||||
recording.start();
|
||||
for (int batch = 0; batch < 12; batch++) {
|
||||
Object[] objects = new Object[100_000];
|
||||
@@ -21,7 +54,36 @@ public final class JfrFixture {
|
||||
retained = objects;
|
||||
Thread.sleep(120);
|
||||
}
|
||||
System.gc();
|
||||
var renderer = new Thread(() -> {
|
||||
var frame = new FrameInterval();
|
||||
long started = System.nanoTime();
|
||||
frame.begin();
|
||||
System.gc();
|
||||
try (var input = JfrFixture.class.getResourceAsStream("JfrFixture$LoadPayload.class")) {
|
||||
byte[] bytes = input.readAllBytes();
|
||||
var slowLoader = new ClassLoader(null) {
|
||||
@Override protected Class<?> findClass(String name) throws ClassNotFoundException {
|
||||
if (!name.equals("JfrFixture$LoadPayload")) throw new ClassNotFoundException(name);
|
||||
java.util.concurrent.locks.LockSupport.parkNanos(10_000_000);
|
||||
return defineClass(name, bytes, 0, bytes.length);
|
||||
}
|
||||
};
|
||||
Class.forName("JfrFixture$LoadPayload", false, slowLoader);
|
||||
} catch (Exception failure) { throw new AssertionError(failure); }
|
||||
var outer = new TerrainUpload();
|
||||
outer.begin();
|
||||
var inner = new TerrainUpload();
|
||||
inner.begin();
|
||||
java.util.concurrent.locks.LockSupport.parkNanos(20_000_000);
|
||||
inner.end(); inner.commit();
|
||||
java.util.concurrent.locks.LockSupport.parkNanos(20_000_000);
|
||||
outer.end(); outer.commit();
|
||||
frame.end();
|
||||
frame.intervalNs = System.nanoTime() - started;
|
||||
frame.commit();
|
||||
}, "Render thread");
|
||||
renderer.start();
|
||||
renderer.join();
|
||||
Thread.sleep(250);
|
||||
recording.stop();
|
||||
recording.dump(Path.of(args[0]));
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import copy
|
||||
import csv
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
from tools.mac_benchmark import validate_capture
|
||||
|
||||
|
||||
class MacCaptureTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.directory = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.directory.cleanup)
|
||||
self.instance = Path(self.directory.name)
|
||||
output = self.instance / "horizon-benchmarks"
|
||||
output.mkdir()
|
||||
self.result = {"gpu": "Apple M2 Pro", "graphics_backend": "Vulkan"}
|
||||
for kind, duration in [("frames", 120_000_000), ("ticks", 5_000_000)]:
|
||||
filename = kind + ".csv"
|
||||
with (output / filename).open("w") as stream:
|
||||
writer = csv.writer(stream)
|
||||
writer.writerow(["sample", "duration_ns"])
|
||||
writer.writerows((index, duration) for index in range(1000))
|
||||
self.result[kind] = {"kind": kind, "gpu": "Apple M2 Pro", "graphics_backend": "Vulkan",
|
||||
"elapsed_seconds": 120, "dropped_samples": 0, "excluded_frames": 0,
|
||||
"samples_file": filename, "stats": {"count": 1000, "meanMs": duration / 1e6}}
|
||||
self.result["frames"].update(visual_settings_changed=[], render_distance=32, simulation_distance=12,
|
||||
inactivity_fps_limit="minimized", frame_limiter_throttled_observations=0)
|
||||
self.result["ticks"].update(tick_state_changed=[])
|
||||
for field in ["frozen", "stepping", "sprinting", "suppressed", "nonstandard_rate"]:
|
||||
self.result["ticks"]["tick_state_" + field + "_samples"] = 0
|
||||
|
||||
def test_complete_active_capture_is_accepted(self):
|
||||
validate_capture(self.result, self.instance, 32, 120)
|
||||
|
||||
def test_idle_focused_capture_and_missing_limiter_measurements_are_rejected(self):
|
||||
for value in [None, 1, 3600]:
|
||||
report = copy.deepcopy(self.result)
|
||||
report["frames"]["frame_limiter_throttled_observations"] = value
|
||||
with self.subTest(value=value), self.assertRaises(AssertionError):
|
||||
validate_capture(report, self.instance, 32, 120)
|
||||
|
||||
def test_incomplete_files_changed_graphics_and_nonstandard_simulation_are_rejected(self):
|
||||
changes = [("frames", "visual_settings_changed", ["render_distance"]),
|
||||
("frames", "excluded_frames", 1), ("frames", "samples_file", "../private.csv"),
|
||||
("frames", "graphics_backend", "OpenGL"), ("ticks", "tick_state_frozen_samples", 1),
|
||||
("ticks", "dropped_samples", 1), ("ticks", "elapsed_seconds", 60)]
|
||||
for kind, key, value in changes:
|
||||
report = copy.deepcopy(self.result)
|
||||
report[kind][key] = value
|
||||
with self.subTest(kind=kind, key=key), self.assertRaises(AssertionError):
|
||||
validate_capture(report, self.instance, 32, 120)
|
||||
report = copy.deepcopy(self.result)
|
||||
report["frames"]["stats"]["count"] = 999
|
||||
with self.assertRaises(AssertionError):
|
||||
validate_capture(report, self.instance, 32, 120)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+55
-1
@@ -62,6 +62,30 @@ class PackTests(unittest.TestCase):
|
||||
self.assertNotIn("distanthorizons", candidate["mods"])
|
||||
self.assertNotIn("iris", candidate["mods"])
|
||||
|
||||
def test_m2_metal_retains_supplied_detail_and_balanced_terrain_quality(self):
|
||||
files = build_pack.overrides("m2-metal", b"fixture")
|
||||
balanced = build_pack.overrides("m2-balanced", b"fixture")
|
||||
options = dict(line.split(":", 1) for line in files["options.txt"].decode().splitlines())
|
||||
self.assertEqual(options["renderDistance"], "32")
|
||||
self.assertEqual(options["simulationDistance"], "12")
|
||||
self.assertEqual(float(options["fov"]), 1.0)
|
||||
self.assertEqual(json.loads(options["renderClouds"]), "false")
|
||||
self.assertEqual(json.loads(options["preferredGraphicsBackend"]), "vulkan")
|
||||
self.assertEqual(files["config/DistantHorizons.toml"], balanced["config/DistantHorizons.toml"])
|
||||
for name in balanced:
|
||||
if name.startswith("config/horizon-") and name != "config/horizon-profile.txt":
|
||||
self.assertEqual(files[name], balanced[name])
|
||||
|
||||
def test_cloud_batch_is_opt_in_and_keeps_metal_visual_settings(self):
|
||||
metal = build_pack.overrides("m2-metal", b"fixture")
|
||||
batch = build_pack.overrides("m2-metal-batched", b"fixture")
|
||||
self.assertEqual(batch["options.txt"], metal["options.txt"])
|
||||
self.assertEqual(batch["config/DistantHorizons.toml"], metal["config/DistantHorizons.toml"])
|
||||
self.assertEqual(batch["config/horizon-cloud-batch.properties"], b"enabled=true\n")
|
||||
for profile in build_pack.PROFILES:
|
||||
if profile != "m2-metal-batched":
|
||||
self.assertEqual(build_pack.overrides(profile, b"fixture")["config/horizon-cloud-batch.properties"], b"enabled=false\n")
|
||||
|
||||
def test_missing_exact_iris_sodium_dependency_is_rejected(self):
|
||||
with self.assertRaisesRegex(ValueError, "Missing pinned dependency"):
|
||||
build_pack.validate_selection(build_pack.read_lock(), ["iris"])
|
||||
@@ -232,13 +256,17 @@ class PackTests(unittest.TestCase):
|
||||
with mock.patch.object(runtime, "prepare_client", return_value=(manifest, [root / "game.jar"], root / "natives")), \
|
||||
mock.patch.object(runtime.platform, "system", return_value="Darwin"), \
|
||||
mock.patch.object(runtime.subprocess, "run") as run, \
|
||||
mock.patch("sys.argv", ["runtime.py", "demo", "--instance", str(root / "instance")]):
|
||||
mock.patch("sys.argv", ["runtime.py", "demo", "--instance", str(root / "instance"),
|
||||
"--width", "1728", "--height", "1117", "--fullscreen"]):
|
||||
runtime.main()
|
||||
command = run.call_args.args[0]
|
||||
self.assertEqual(command.count("-XstartOnFirstThread"), 1)
|
||||
self.assertLess(command.index("-XstartOnFirstThread"),
|
||||
command.index("net.fabricmc.loader.impl.launch.knot.KnotClient"))
|
||||
self.assertEqual(command.count("-cp"), 1)
|
||||
self.assertEqual(command[command.index("--width") + 1], "1728")
|
||||
self.assertEqual(command[command.index("--height") + 1], "1117")
|
||||
self.assertEqual(command.count("--fullscreen"), 1)
|
||||
self.assertTrue(run.call_args.kwargs["check"])
|
||||
|
||||
def test_native_architecture_is_selected_even_with_os_only_rules(self):
|
||||
@@ -251,6 +279,32 @@ class PackTests(unittest.TestCase):
|
||||
|
||||
|
||||
class ComparisonTests(unittest.TestCase):
|
||||
def test_private_profiling_is_visible_in_frame_and_tick_comparisons(self):
|
||||
for kind in ["frames", "ticks"]:
|
||||
baseline = self.report(kind)
|
||||
candidate = {**baseline, "private_profiling_enabled": True}
|
||||
warnings = compare_benchmarks.compare(baseline, candidate)["warnings"]
|
||||
self.assertTrue(any("candidate includes private profiling overhead" in item for item in warnings))
|
||||
|
||||
def test_afk_cap_is_explicit_even_with_unlimited_configured_fps(self):
|
||||
baseline = self.report()
|
||||
baseline.update(fps_limit=260, fps_limit_unlimited=True, inactivity_fps_limit="afk",
|
||||
frame_limiter_throttled_observations=3600, frame_limiter_afk_observations=3600)
|
||||
candidate = {**baseline, "inactivity_fps_limit": "minimized",
|
||||
"frame_limiter_throttled_observations": 0, "frame_limiter_afk_observations": 0}
|
||||
result = compare_benchmarks.compare(baseline, candidate)
|
||||
self.assertTrue(any("inactivity_fps_limit" in item for item in result["mismatches"]))
|
||||
self.assertTrue(any("3600 AFK" in item for item in result["warnings"]))
|
||||
self.assertFalse(any("candidate includes" in item for item in result["warnings"]))
|
||||
legacy = self.report()
|
||||
self.assertTrue(any("AFK throttling is unknown" in item for item in compare_benchmarks.compare(legacy, legacy)["warnings"]))
|
||||
|
||||
def test_cloud_batch_experiment_is_visible_in_comparison(self):
|
||||
baseline = {**self.report(), "dh_cloud_batch_requested": False, "dh_cloud_batch_enabled": False}
|
||||
candidate = {**baseline, "dh_cloud_batch_requested": True, "dh_cloud_batch_enabled": True}
|
||||
result = compare_benchmarks.compare(baseline, candidate)
|
||||
self.assertTrue(any("dh_cloud_batch_enabled" in warning for warning in result["warnings"]))
|
||||
|
||||
def report(self, kind="frames"):
|
||||
return {"schema": 1, "kind": kind, "elapsed_seconds": 120, "framebuffer_width": 1920,
|
||||
"stats": {"count": 1000, "meanRate": 100, "lowOnePercentRate": 50,
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
</main>
|
||||
<script>
|
||||
'use strict';
|
||||
const SETTINGS=['minecraft','kind','os','os_version','architecture','gpu','graphics_backend','graphics_driver','server_runtime_kind','client_graphics_capture_status','java_version','heap_max_bytes','framebuffer_width','framebuffer_height','render_distance','simulation_distance','vsync','fps_limit','graphics_preset','ambient_occlusion','improved_transparency','mipmap_levels','entity_distance_scaling','entity_shadows','particles','clouds','cloud_range','fov','world_difficulty','dh_capture_status','dh_rendering_enabled','dh_renderer_mode','dh_lod_radius_chunks','dh_horizontal_quality','dh_vertical_quality','dh_max_horizontal_resolution','dh_transparency','dh_lod_shading','dh_lod_only_mode','dh_camera_quality_dropoff','dh_ambient_occlusion','iris_capture_status','iris_shaders_enabled','iris_shader_pack'];
|
||||
const SETTINGS=['minecraft','kind','os','os_version','architecture','gpu','graphics_backend','graphics_driver','server_runtime_kind','client_graphics_capture_status','java_version','heap_max_bytes','framebuffer_width','framebuffer_height','render_distance','simulation_distance','vsync','fps_limit','inactivity_fps_limit','graphics_preset','ambient_occlusion','improved_transparency','mipmap_levels','entity_distance_scaling','entity_shadows','particles','clouds','cloud_range','fov','world_difficulty','dh_capture_status','dh_rendering_enabled','dh_renderer_mode','dh_lod_radius_chunks','dh_horizontal_quality','dh_vertical_quality','dh_max_horizontal_resolution','dh_transparency','dh_lod_shading','dh_lod_only_mode','dh_camera_quality_dropoff','dh_ambient_occlusion','iris_capture_status','iris_shaders_enabled','iris_shader_pack'];
|
||||
const state={baseline:null,candidate:null},generation={baseline:0,candidate:0};
|
||||
let comparison=null,chartData=null;
|
||||
const $=id=>document.getElementById(id);
|
||||
@@ -117,6 +117,7 @@ function compareReports(a,b){
|
||||
if(a.kind==='ticks'&&a.peak_players!==b.peak_players)differences.push({setting:'peak_players',baseline:a.peak_players??null,candidate:b.peak_players??null});
|
||||
const warnings=['World, camera route, shaders and terrain-cache warm-up still need to match. These are not verified by the captured settings.'];
|
||||
warnings.push(...softwareWarnings(a,b));
|
||||
for(const [name,report]of [['Baseline',a],['Candidate',b]])if(report.private_profiling_enabled)warnings.push(name+' includes private profiling overhead; keep separate from ordinary performance comparisons.');
|
||||
if(a.kind==='ticks'){
|
||||
warnings.push(...serverContextWarnings(a,b));
|
||||
const tick=compareTickStates(a,b);differences.push(...tick.differences);warnings.push(...tick.warnings);
|
||||
@@ -126,10 +127,12 @@ function compareReports(a,b){
|
||||
else if(JSON.stringify(a[key])!==JSON.stringify(b[key]))warnings.push('JVM '+key+' differs. Treat it as an explicit experiment variable.');
|
||||
}
|
||||
if(a.kind==='frames'){
|
||||
for(const key of ['adaptive_budget_enabled','adaptive_target_fps','precise_worker_pacing','debounce_queue_wait','wakeable_idle_wait','dispatch_retry_backoff','tick_membership_dedup','empty_culling_whitelist','culling_ray_scratch','terrain_position_arithmetic','terrain_quad_reuse','interruptible_terrain_read','dh_thread_count','dh_runtime_ratio']){
|
||||
for(const key of ['adaptive_budget_enabled','adaptive_target_fps','precise_worker_pacing','debounce_queue_wait','wakeable_idle_wait','dispatch_retry_backoff','tick_membership_dedup','empty_culling_whitelist','culling_ray_scratch','terrain_position_arithmetic','dh_cloud_batch_requested','dh_cloud_batch_enabled','private_render_class_prewarm','terrain_quad_reuse','interruptible_terrain_read','dh_thread_count','dh_runtime_ratio']){
|
||||
if(key in a&&key in b&&JSON.stringify(a[key])!==JSON.stringify(b[key]))warnings.push('Experiment '+key+' differs.');
|
||||
}
|
||||
for(const [label,r] of [['Baseline',a],['Candidate',b]]){
|
||||
if(!('frame_limiter_throttled_observations' in r))warnings.push(label+' effective frame-limiter state was not captured; AFK throttling is unknown.');
|
||||
else if(r.frame_limiter_throttled_observations)warnings.push(label+' includes '+r.frame_limiter_throttled_observations+' frame-limiter throttled observations, including '+(r.frame_limiter_afk_observations??'unknown')+' AFK observations. Configured unlimited FPS does not override this limiter.');
|
||||
if(!['available','not_installed'].includes(r.dh_capture_status))warnings.push(label+' distant-terrain configuration was not captured; quality equivalence is unknown.');
|
||||
if(!['available','not_installed'].includes(r.iris_capture_status))warnings.push(label+' Iris state was not captured; shader equivalence is unknown.');
|
||||
if(Array.isArray(r.visual_settings_changed)&&r.visual_settings_changed.length){
|
||||
@@ -197,6 +200,7 @@ function renderDetails(side){
|
||||
fields.push(['Rejected dispatch retry', r.dispatch_retry_backoff===true?'on':r.dispatch_retry_backoff===false?'off':'not captured']);
|
||||
fields.push(['Wakeable queue idle', r.wakeable_idle_wait===true?'on':r.wakeable_idle_wait===false?'off':'not captured']);
|
||||
if(r.kind==='frames')fields.push(['Tick membership deduplication',r.tick_membership_dedup===true?'on':r.tick_membership_dedup===false?'off':'not captured']);
|
||||
if(r.kind==='frames')fields.push(['DH cloud batching',r.dh_cloud_batch_enabled===true?'on':r.dh_cloud_batch_enabled===false?'off':'not captured']);
|
||||
if(r.kind==='frames')fields.push(['Terrain quad reuse',r.terrain_quad_reuse===true?'on':r.terrain_quad_reuse===false?'off':'not captured']);
|
||||
if(r.kind==='frames')fields.push(['Terrain position arithmetic',r.terrain_position_arithmetic===true?'on':r.terrain_position_arithmetic===false?'off':'not captured']);
|
||||
if(r.kind==='frames')fields.push(['Culling ray scratch',r.culling_ray_scratch===true?'on':r.culling_ray_scratch===false?'off':'not captured']);
|
||||
|
||||
@@ -150,6 +150,123 @@ public final class JfrSummary {
|
||||
.reversed().thenComparingLong(Map.Entry::getKey)).map(e -> e.getValue().result(e.getKey())).toList();
|
||||
}
|
||||
|
||||
record Span(long start, long end) {}
|
||||
|
||||
static long coveredNs(List<Span> spans) {
|
||||
var ordered = spans.stream().filter(s -> s.end > s.start)
|
||||
.sorted(Comparator.comparingLong(Span::start)).toList();
|
||||
long total = 0, start = 0, end = 0;
|
||||
for (Span span : ordered) {
|
||||
if (span.start > end) {
|
||||
total += end - start;
|
||||
start = span.start;
|
||||
}
|
||||
end = Math.max(end, span.end);
|
||||
}
|
||||
return total + end - start;
|
||||
}
|
||||
|
||||
static final class Stall {
|
||||
final Instant start, end;
|
||||
final long intervalNs;
|
||||
final List<Span> pauses = new ArrayList<>(), waits = new ArrayList<>(), uploads = new ArrayList<>(), classLoads = new ArrayList<>();
|
||||
final List<Map<String, Object>> loadedClasses = new ArrayList<>();
|
||||
final Map<String, Long> samples = new HashMap<>();
|
||||
final Map<String, Long> inclusiveSamples = new HashMap<>();
|
||||
|
||||
Stall(RecordedEvent event) {
|
||||
start = event.getStartTime(); end = event.getEndTime();
|
||||
intervalNs = event.getLong("intervalNs");
|
||||
}
|
||||
|
||||
void overlap(RecordedEvent event, List<Span> spans) {
|
||||
Instant first = event.getStartTime().isAfter(start) ? event.getStartTime() : start;
|
||||
Instant last = event.getEndTime().isBefore(end) ? event.getEndTime() : end;
|
||||
if (!first.isBefore(last)) return;
|
||||
if (spans.size() >= 4096) throw new IllegalArgumentException("Too many overlapping diagnostic events");
|
||||
spans.add(new Span(Duration.between(start, first).toNanos(), Duration.between(start, last).toNanos()));
|
||||
}
|
||||
|
||||
Map<String, Object> result() {
|
||||
return Map.of("start", start.toString(), "interval_ms", intervalNs / 1e6,
|
||||
"gc_pause_overlap_ms", coveredNs(pauses) / 1e6,
|
||||
"render_thread_wait_overlap_ms", coveredNs(waits) / 1e6,
|
||||
"terrain_upload_overlap_ms", coveredNs(uploads) / 1e6,
|
||||
"render_thread_class_load_overlap_ms", coveredNs(classLoads) / 1e6,
|
||||
"render_thread_class_loads", loadedClasses,
|
||||
"render_thread_sample_leaf_methods", top(samples),
|
||||
"render_thread_sample_inclusive_methods", top(inclusiveSamples));
|
||||
}
|
||||
}
|
||||
|
||||
/** Two bounded streaming passes, without expanding JFR class-loader graphs. */
|
||||
static Map<String, Object> frameStalls(Path path) throws Exception {
|
||||
var stalls = new ArrayList<Stall>();
|
||||
long frames = 0;
|
||||
try (var file = new RecordingFile(path)) {
|
||||
while (file.hasMoreEvents()) {
|
||||
var event = file.readEvent();
|
||||
String type = event.getEventType().getName();
|
||||
if (!type.equals("dev.horizonlab.FrameInterval")
|
||||
&& !type.equals("dev.horizonlab.metrics.MacBenchmarkIntegration$FrameIntervalEvent")) continue;
|
||||
frames++;
|
||||
if (event.getLong("intervalNs") < 25_000_000) continue;
|
||||
if (stalls.size() >= 4096) throw new IllegalArgumentException("Too many slow frames for bounded analysis");
|
||||
stalls.add(new Stall(event));
|
||||
}
|
||||
}
|
||||
if (frames == 0) throw new IllegalArgumentException("No private frame-interval events captured");
|
||||
try (var file = new RecordingFile(path)) {
|
||||
while (file.hasMoreEvents()) {
|
||||
var event = file.readEvent();
|
||||
String type = event.getEventType().getName();
|
||||
boolean pause = type.equals("jdk.GCPhasePause");
|
||||
boolean wait = type.equals("jdk.ThreadPark") || type.equals("jdk.JavaMonitorEnter")
|
||||
|| type.equals("jdk.JavaMonitorWait");
|
||||
boolean sample = type.equals("jdk.ExecutionSample") || type.equals("jdk.NativeMethodSample");
|
||||
boolean upload = type.equals("dev.horizonlab.TerrainUpload");
|
||||
boolean classLoad = type.equals("jdk.ClassLoad");
|
||||
if (!pause && !wait && !sample && !upload && !classLoad) continue;
|
||||
if (!pause) {
|
||||
var thread = event.getThread(sample ? "sampledThread" : "eventThread");
|
||||
if (thread == null || !threadName(thread).equals("Render thread")) continue;
|
||||
}
|
||||
for (Stall stall : stalls) {
|
||||
if (pause) stall.overlap(event, stall.pauses);
|
||||
else if (wait) stall.overlap(event, stall.waits);
|
||||
else if (upload) stall.overlap(event, stall.uploads);
|
||||
else if (classLoad) {
|
||||
int previous = stall.classLoads.size();
|
||||
stall.overlap(event, stall.classLoads);
|
||||
if (stall.classLoads.size() == previous) continue;
|
||||
if (stall.loadedClasses.size() >= 128)
|
||||
throw new IllegalArgumentException("Too many overlapping class loads");
|
||||
var span = stall.classLoads.getLast();
|
||||
stall.loadedClasses.add(Map.of("class_name", event.getClass("loadedClass").getName(),
|
||||
"duration_ms", event.getDuration().toNanos() / 1e6,
|
||||
"overlap_ms", (span.end - span.start) / 1e6));
|
||||
}
|
||||
else if (!event.getStartTime().isBefore(stall.start) && event.getStartTime().isBefore(stall.end)) {
|
||||
var stack = event.getStackTrace();
|
||||
if (stack != null && !stack.getFrames().isEmpty()) {
|
||||
var method = stack.getFrames().getFirst().getMethod();
|
||||
stall.samples.merge(method.getType().getName() + "." + method.getName(), 1L, Long::sum);
|
||||
var methods = new java.util.HashSet<String>();
|
||||
for (var frame : stack.getFrames()) {
|
||||
var inclusive = frame.getMethod();
|
||||
methods.add(inclusive.getType().getName() + "." + inclusive.getName());
|
||||
}
|
||||
for (String inclusive : methods) stall.inclusiveSamples.merge(inclusive, 1L, Long::sum);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Map.of("frame_events", frames, "slow_frame_threshold_ms", 25,
|
||||
"slow_frames", stalls.stream().sorted(Comparator.comparing(s -> s.start)).map(Stall::result).toList(),
|
||||
"note", "Diagnostic overlap is not causation. GC and thread waits can overlap each other; sampled stacks can miss short work. Profiling intervals are not ordinary FPS measurements.");
|
||||
}
|
||||
|
||||
static String json(Object value) {
|
||||
if (value == null) return "null";
|
||||
if (value instanceof String string) {
|
||||
@@ -172,6 +289,10 @@ public final class JfrSummary {
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (args.length == 2 && args[0].equals("--frame-stalls")) {
|
||||
System.out.println(json(frameStalls(Path.of(args[1]))));
|
||||
return;
|
||||
}
|
||||
if (args.length != 1) throw new IllegalArgumentException("Usage: java tools/JfrSummary.java private-recording.jfr");
|
||||
System.out.println(json(summarize(Path.of(args[0]))));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build a local Prism import containing the measured M2 Pro launch settings."""
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
from . import build_pack, runtime
|
||||
except ImportError:
|
||||
import build_pack
|
||||
import runtime
|
||||
|
||||
|
||||
def build(collector, mod_jar):
|
||||
lock = build_pack.read_lock()
|
||||
if collector not in {"g1", "zgc"}:
|
||||
raise ValueError("Choose the measured G1 or ZGC launch configuration")
|
||||
if lock["minecraft"] != "26.3" or lock["fabric_loader"] != "0.19.5":
|
||||
raise ValueError("Revalidate the Prism components before exporting a new game version")
|
||||
profile = "m2-metal-batched"
|
||||
selected = build_pack.PROFILES[profile]["mods"]
|
||||
build_pack.validate_selection(lock, selected)
|
||||
mod_bytes = mod_jar.read_bytes()
|
||||
name = f"horizon-m2-metal-batched-{collector}-{lock['pack_version']}"
|
||||
entries = {"minecraft/" + path: data for path, data in build_pack.overrides(profile, mod_bytes).items()}
|
||||
for project in selected:
|
||||
pinned = lock["projects"][project]
|
||||
source = runtime.fetch(pinned["url"], build_pack.ROOT / ".local/pack-cache" / pinned["filename"],
|
||||
pinned["hashes"]["sha512"], "sha512")
|
||||
entries["minecraft/" + build_pack.mod_path(pinned)] = source.read_bytes()
|
||||
entries["mmc-pack.json"] = json.dumps({"formatVersion": 1, "components": [
|
||||
{"uid": "org.lwjgl3", "version": "3.4.3", "dependencyOnly": True},
|
||||
{"uid": "net.minecraft", "version": lock["minecraft"], "important": True},
|
||||
{"uid": "net.fabricmc.intermediary", "version": lock["minecraft"], "dependencyOnly": True},
|
||||
{"uid": "net.fabricmc.fabric-loader", "version": lock["fabric_loader"]},
|
||||
]}, indent=2).encode()
|
||||
flag = {"g1": "-XX:+UseG1GC", "zgc": "-XX:+UseZGC"}[collector]
|
||||
entries["instance.cfg"] = (f"InstanceType=OneSix\nname={name}\niconKey=default\n"
|
||||
"AutomaticJava=true\nOverrideJavaLocation=false\nOverrideMemory=true\n"
|
||||
f"MinMemAlloc=512\nMaxMemAlloc=6144\nOverrideJavaArgs=true\nJvmArgs={flag}\n").encode()
|
||||
entries["HORIZON-BUILD.json"] = json.dumps({"profile": profile, "collector": collector,
|
||||
"pack_version": lock["pack_version"], "original_mod_sha256": hashlib.sha256(mod_bytes).hexdigest(),
|
||||
"mods": {project: lock["projects"][project]["hashes"] for project in selected}}, indent=2).encode()
|
||||
output = build_pack.ROOT / "dist" / f"{name}-prism.zip"
|
||||
build_pack.write_zip(output, entries)
|
||||
script = output.with_name(f"Mac-Import-{collector}.command")
|
||||
script.write_text('#!/bin/bash\nset -euo pipefail\ncd "$(dirname "$0")"\n'
|
||||
f'open -a "Prism Launcher" --args --import "$PWD/{output.name}"\n')
|
||||
script.chmod(0o755)
|
||||
print(f"Built local Prism import: {output}\nCollector: {flag}; heap: 512–6144 MiB")
|
||||
return output
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--gc", choices=["g1", "zgc"], required=True)
|
||||
parser.add_argument("--mod", type=Path,
|
||||
default=build_pack.ROOT / "dist" / f"horizon-lab-{build_pack.read_lock()['pack_version']}.jar")
|
||||
args = parser.parse_args()
|
||||
build(args.gc, args.mod)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -33,6 +33,10 @@ def test_metrics():
|
||||
"dev.horizonlab.metrics.MetricsTest"], check=True)
|
||||
subprocess.run(["java", "-Xms32M", "-Xmx256M", "-XX:+UseZGC", "-XX:+UseCompactObjectHeaders", "-cp", str(destination),
|
||||
"dev.horizonlab.metrics.MetricsTest"], check=True)
|
||||
subprocess.run(["javac", "--release", "25", "-d", str(destination),
|
||||
str(SOURCE / "main/java/dev/horizonlab/metrics/VisualSettingsChanges.java"),
|
||||
str(SOURCE / "test/java/dev/horizonlab/metrics/VisualSettingsChangesTest.java")], check=True)
|
||||
subprocess.run(["java", "-cp", str(destination), "dev.horizonlab.metrics.VisualSettingsChangesTest"], check=True)
|
||||
budget_sources = [SOURCE / "main/java/dev/horizonlab/budget/TerrainBudget.java",
|
||||
SOURCE / "test/java/dev/horizonlab/budget/TerrainBudgetTest.java"]
|
||||
subprocess.run(["javac", "--release", "25", "-d", str(destination), *map(str, budget_sources)], check=True)
|
||||
@@ -65,6 +69,14 @@ def build():
|
||||
dh = lock["projects"]["distanthorizons"]
|
||||
jars.append(runtime.fetch(dh["url"], ROOT / ".local/pack-cache" / dh["filename"],
|
||||
dh["hashes"]["sha512"], "sha512"))
|
||||
# Fabric Loader provides MixinExtras at runtime; compile against that exact embedded JAR.
|
||||
loader = next(jar for jar in jars if jar.name == f"fabric-loader-{lock['fabric_loader']}.jar")
|
||||
with zipfile.ZipFile(loader) as archive:
|
||||
embedded = "META-INF/jars/mixinextras-fabric-0.5.5.jar"
|
||||
extras = BUILD / Path(embedded).name
|
||||
extras.parent.mkdir(parents=True, exist_ok=True)
|
||||
extras.write_bytes(archive.read(embedded))
|
||||
jars.append(extras)
|
||||
culling = lock["projects"]["entityculling"]
|
||||
jars.append(runtime.fetch(culling["url"], ROOT / ".local/pack-cache" / culling["filename"],
|
||||
culling["hashes"]["sha512"], "sha512"))
|
||||
|
||||
+8
-1
@@ -19,6 +19,8 @@ PROFILES = {
|
||||
"baseline": {"title": "Vanilla baseline", "mods": ["fabric-api"], "render": 12, "lod": 0, "heap": 6144},
|
||||
"core-performance": {"title": "Full-detail performance comparison", "mods": CORE, "render": 12, "lod": 0, "heap": 6144},
|
||||
"m2-balanced": {"title": "M2 Pro balanced", "mods": CORE + ["distanthorizons"], "render": 12, "lod": 128, "heap": 6144},
|
||||
"m2-metal": {"title": "M2 Pro Metal-backed renderer", "mods": CORE + ["distanthorizons"], "render": 32, "lod": 128, "heap": 6144, "backend": "vulkan", "fov": 110, "clouds": "false"},
|
||||
"m2-metal-batched": {"title": "M2 Pro cloud batching experiment", "mods": CORE + ["distanthorizons"], "render": 32, "lod": 128, "heap": 6144, "backend": "vulkan", "fov": 110, "clouds": "false", "cloud_batch": True},
|
||||
"adaptive": {"title": "Adaptive terrain experiment", "mods": CORE + ["distanthorizons"], "render": 12, "lod": 128, "heap": 6144, "adaptive": True},
|
||||
"worker-pacing": {"title": "Precise worker pacing experiment", "mods": CORE + ["distanthorizons"], "render": 12, "lod": 128, "heap": 6144, "pacing": True},
|
||||
"adaptive-pacing": {"title": "Adaptive precise pacing experiment", "mods": CORE + ["distanthorizons"], "render": 12, "lod": 128, "heap": 6144, "adaptive": True, "pacing": True},
|
||||
@@ -77,6 +79,10 @@ def options(profile):
|
||||
"renderDistance": profile["render"], "simulationDistance": 12, "enableVsync": "false",
|
||||
"maxFps": 260, "guiScale": 2, "onboardAccessibility": "false",
|
||||
"preferredGraphicsBackend": json.dumps(profile.get("backend", "opengl"))}
|
||||
if "fov" in profile:
|
||||
values["fov"] = (profile["fov"] - 70) / 40
|
||||
if "clouds" in profile:
|
||||
values["renderClouds"] = json.dumps(profile["clouds"])
|
||||
return "".join(f"{key}:{value}\n" for key, value in values.items())
|
||||
|
||||
|
||||
@@ -86,7 +92,8 @@ def overrides(profile_name, mod_bytes):
|
||||
"config/horizon-profile.txt": (profile_name + "\n").encode(),
|
||||
"options.txt": options(profile).encode()}
|
||||
experiment = any(profile.get(key, False) for key in ["adaptive", "pacing", "queue_wait", "dispatch_retry", "tick_dedup", "culling_empty", "culling_rays", "terrain_position", "quad_reuse"])
|
||||
config = ROOT / "packs/config" / f"{'m2-balanced' if experiment else profile_name}.toml"
|
||||
config = ROOT / "packs/config" / f"{'m2-balanced' if experiment or profile_name in {'m2-metal', 'm2-metal-batched'} else profile_name}.toml"
|
||||
files["config/horizon-cloud-batch.properties"] = f"enabled={str(profile.get('cloud_batch', False)).lower()}\n".encode()
|
||||
files["config/horizon-adaptive.properties"] = (
|
||||
f"enabled={str(profile.get('adaptive', False)).lower()}\ntargetFps={profile.get('target_fps', 60)}\n").encode()
|
||||
files["config/horizon-worker-pacing.properties"] = (
|
||||
|
||||
@@ -8,7 +8,7 @@ from pathlib import Path
|
||||
|
||||
MATCH_FIELDS = ("minecraft", "kind", "os", "os_version", "architecture", "gpu", "graphics_backend", "graphics_driver",
|
||||
"server_runtime_kind", "client_graphics_capture_status", "java_version", "heap_max_bytes", "framebuffer_width", "framebuffer_height", "render_distance",
|
||||
"simulation_distance", "vsync", "fps_limit", "graphics_preset", "ambient_occlusion",
|
||||
"simulation_distance", "vsync", "fps_limit", "inactivity_fps_limit", "graphics_preset", "ambient_occlusion",
|
||||
"improved_transparency", "mipmap_levels", "entity_distance_scaling", "entity_shadows", "particles",
|
||||
"clouds", "cloud_range", "fov", "world_difficulty", "dh_capture_status", "dh_rendering_enabled", "dh_renderer_mode",
|
||||
"dh_lod_radius_chunks", "dh_horizontal_quality", "dh_vertical_quality", "dh_max_horizontal_resolution",
|
||||
@@ -118,7 +118,12 @@ def compare(baseline, candidate):
|
||||
mismatches = [f"{key}: {baseline.get(key)!r} vs {candidate.get(key)!r}" for key in MATCH_FIELDS
|
||||
if baseline.get(key) != candidate.get(key)]
|
||||
warnings = ["World, route, warm-up, shader state and LOD cache must also match; these are not verified by this tool."]
|
||||
warnings.append("Memory peaks are sampled and can miss short spikes. RSS includes resident JVM and native pages, but is not a complete GPU/unified-memory measurement. Heap/nonheap measure JVM pools only. CPU includes the whole process and integrated server.")
|
||||
warnings.append("GC collection time comes from collector MXBean counters. It may include concurrent cycles and overlapping collector phases; it is not a measured stop-the-world pause total. Use JFR pause events for that comparison.")
|
||||
warnings.extend(software_warnings(baseline, candidate))
|
||||
for name, report in [("baseline", baseline), ("candidate", candidate)]:
|
||||
if report.get("private_profiling_enabled"):
|
||||
warnings.append(f"{name} includes private profiling overhead; keep separate from ordinary performance comparisons.")
|
||||
if baseline["kind"] == "ticks":
|
||||
warnings.extend(server_context_warnings(baseline, candidate))
|
||||
tick_mismatches, tick_warnings = compare_tick_states(baseline, candidate)
|
||||
@@ -127,10 +132,14 @@ def compare(baseline, candidate):
|
||||
warnings.append("Tick durations exclude scheduler sleep. Observed TPS counts measured invocations, including frozen ticks, and includes recording time spent paused; tick execution capacity is not TPS.")
|
||||
if baseline["kind"] == "frames":
|
||||
for field in ["adaptive_budget_enabled", "adaptive_target_fps", "precise_worker_pacing", "debounce_queue_wait",
|
||||
"wakeable_idle_wait", "dispatch_retry_backoff", "tick_membership_dedup", "empty_culling_whitelist", "culling_ray_scratch", "terrain_position_arithmetic", "terrain_quad_reuse", "interruptible_terrain_read", "dh_thread_count", "dh_runtime_ratio"]:
|
||||
"wakeable_idle_wait", "dispatch_retry_backoff", "tick_membership_dedup", "empty_culling_whitelist", "culling_ray_scratch", "terrain_position_arithmetic", "dh_cloud_batch_requested", "dh_cloud_batch_enabled", "private_render_class_prewarm", "terrain_quad_reuse", "interruptible_terrain_read", "dh_thread_count", "dh_runtime_ratio"]:
|
||||
if field in baseline and field in candidate and baseline[field] != candidate[field]:
|
||||
warnings.append(f"Experiment {field} differs: {baseline[field]!r} vs {candidate[field]!r}.")
|
||||
for name, report in [("baseline", baseline), ("candidate", candidate)]:
|
||||
if "frame_limiter_throttled_observations" not in report:
|
||||
warnings.append(f"{name} effective frame-limiter state was not captured; AFK throttling is unknown.")
|
||||
elif report["frame_limiter_throttled_observations"]:
|
||||
warnings.append(f"{name} includes {report['frame_limiter_throttled_observations']} frame-limiter throttled observations, including {report.get('frame_limiter_afk_observations', 'unknown')} AFK observations. Configured unlimited FPS does not override this limiter.")
|
||||
if report.get("dh_capture_status") not in ["available", "not_installed"]:
|
||||
warnings.append(f"{name} distant-terrain configuration was not captured; quality equivalence is unknown.")
|
||||
if report.get("iris_capture_status") not in ["available", "not_installed"]:
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run an isolated physical-Mac benchmark using copied demo-world data."""
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
from pathlib import Path
|
||||
import platform
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
try:
|
||||
from . import build_mod, build_pack
|
||||
from .test_culling_rays import run_fixture
|
||||
except ImportError:
|
||||
import build_mod
|
||||
import build_pack
|
||||
from test_culling_rays import run_fixture
|
||||
|
||||
|
||||
def validate_capture(result, instance, render, seconds):
|
||||
"""Reject changed workloads and truncated files before interpreting throughput."""
|
||||
frames, ticks = result["frames"], result["ticks"]
|
||||
if frames.get("inactivity_fps_limit") != "minimized" or frames.get("frame_limiter_throttled_observations") != 0:
|
||||
raise AssertionError("The stationary workload was frame-throttled or limiter state was not captured")
|
||||
if frames["visual_settings_changed"]:
|
||||
raise AssertionError("Visual settings changed during measurement")
|
||||
if frames["render_distance"] != render or frames["simulation_distance"] != 12:
|
||||
raise AssertionError("Effective view or simulation settings differ")
|
||||
for kind, report in [("frames", frames), ("ticks", ticks)]:
|
||||
if report["kind"] != kind or report["dropped_samples"] or report["excluded_frames"]:
|
||||
raise AssertionError("Lost, excluded or misidentified measurement samples")
|
||||
if abs(report["elapsed_seconds"] - seconds) > 1:
|
||||
raise AssertionError("Measurement duration differs")
|
||||
if any(report[field] != result[field] for field in ["gpu", "graphics_backend"]):
|
||||
raise AssertionError("Captured device differs from the actual runtime device")
|
||||
sample_name = report["samples_file"]
|
||||
if Path(sample_name).name != sample_name:
|
||||
raise AssertionError("Invalid sample filename")
|
||||
with (instance / "horizon-benchmarks" / sample_name).open() as source:
|
||||
durations = [int(row["duration_ns"]) for row in csv.DictReader(source)]
|
||||
stats = report["stats"]
|
||||
if len(durations) != stats["count"] or len(durations) < 100 or min(durations) <= 0:
|
||||
raise AssertionError("CSV sample counts or durations are invalid")
|
||||
if not math.isclose(sum(durations) / len(durations) / 1e6, stats["meanMs"], rel_tol=1e-10):
|
||||
raise AssertionError("CSV timings differ from the summary")
|
||||
if kind == "frames" and abs(sum(durations) / 1e9 - report["elapsed_seconds"]) > 1:
|
||||
raise AssertionError("Active frame intervals do not cover the recording")
|
||||
if ticks["tick_state_changed"] or any(ticks["tick_state_" + field + "_samples"] for field in
|
||||
["frozen", "stepping", "sprinting", "suppressed", "nonstandard_rate"]):
|
||||
raise AssertionError("Simulation tick-manager state changed or is nonstandard")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--profile", choices=build_pack.PROFILES, default="m2-balanced")
|
||||
parser.add_argument("--backend", choices=["opengl", "vulkan"], required=True)
|
||||
parser.add_argument("--label", required=True)
|
||||
parser.add_argument("--seed-world", type=Path, required=True)
|
||||
parser.add_argument("--warmup", type=int, default=60)
|
||||
parser.add_argument("--seconds", type=int, default=120)
|
||||
parser.add_argument("--post-record-seconds", type=int, default=3, help="Keep the private world alive after capture for external profiler shutdown")
|
||||
parser.add_argument("--render", type=int, default=32)
|
||||
parser.add_argument("--scene", choices=["spawn", "overlook", "flyover"], default="spawn",
|
||||
help="Use survival spawn, a fixed spectator view, or a vanilla-input flyover")
|
||||
parser.add_argument("--width", type=int, default=1280)
|
||||
parser.add_argument("--height", type=int, default=800)
|
||||
parser.add_argument("--fullscreen", action="store_true")
|
||||
parser.add_argument("--heap", default="6G")
|
||||
parser.add_argument("--compact-headers", action="store_true")
|
||||
parser.add_argument("--cloud-image-check", action="store_true", help="Frozen-scene image validation only; no FPS measurement")
|
||||
parser.add_argument("--cloud-batch", action="store_true", help="Test private M2 Pro cloud batching; not part of playable packs")
|
||||
parser.add_argument("--gpu-profile", action="store_true", help="Add asynchronous GPU timings; keep separate from ordinary benchmarks")
|
||||
parser.add_argument("--jfr-profile", action="store_true", help="Capture JVM events and frame intervals separately from ordinary benchmarks")
|
||||
parser.add_argument("--vbo-profile", action="store_true", help="Measure original DH allocation, staging/copy and complete terrain uploads")
|
||||
parser.add_argument("--render-prewarm", action="store_true", help="Privately preload five measured rendering classes without initializing them")
|
||||
parser.add_argument("--pass-profile", action="store_true", help="Sample native Vulkan render-pass times separately from normal benchmarks")
|
||||
parser.add_argument("--fixture", type=Path, help="Reuse an earlier private fixture JAR for an identical-code comparison")
|
||||
parser.add_argument("--gc", choices=["default", "g1", "zgc"], default="default")
|
||||
args = parser.parse_args()
|
||||
if platform.system() != "Darwin" or platform.machine() not in {"arm64", "aarch64"}:
|
||||
parser.error("This benchmark targets the physical Apple Silicon Mac")
|
||||
if not re.fullmatch(r"[a-zA-Z0-9_-]{1,64}", args.label):
|
||||
parser.error("--label must contain 1 to 64 letters, digits, underscores or hyphens")
|
||||
if not (0 <= args.warmup <= 300 and 10 <= args.seconds <= 120 and 2 <= args.render <= 32):
|
||||
parser.error("Invalid warmup, measurement duration or view distance")
|
||||
if not 3 <= args.post_record_seconds <= 120:
|
||||
parser.error("Post-recording delay must be between 3 and 120 seconds")
|
||||
production_cloud_batch = bool(build_pack.PROFILES[args.profile].get("cloud_batch"))
|
||||
if args.cloud_batch and production_cloud_batch:
|
||||
parser.error("Do not combine private and production cloud batch mixins")
|
||||
if args.cloud_image_check and (not (args.cloud_batch or production_cloud_batch) or args.scene != "overlook" or args.gpu_profile or args.pass_profile or args.jfr_profile or args.vbo_profile):
|
||||
parser.error("--cloud-image-check requires cloud batching and --scene overlook, without profiling")
|
||||
if args.cloud_batch and (args.backend != "vulkan" or "distanthorizons" not in build_pack.PROFILES[args.profile]["mods"]):
|
||||
parser.error("--cloud-batch requires Vulkan and a Distant Horizons profile")
|
||||
if args.pass_profile and args.backend != "vulkan":
|
||||
parser.error("--pass-profile requires the Vulkan backend")
|
||||
if args.vbo_profile and (args.backend != "vulkan" or "distanthorizons" not in build_pack.PROFILES[args.profile]["mods"]):
|
||||
parser.error("--vbo-profile requires Vulkan and Distant Horizons")
|
||||
if args.render_prewarm and (args.cloud_image_check or args.backend != "vulkan" or "sodium" not in build_pack.PROFILES[args.profile]["mods"]):
|
||||
parser.error("--render-prewarm requires Vulkan and Sodium, without cloud image checks")
|
||||
if not (args.seed_world / "level.dat").is_file():
|
||||
parser.error("--seed-world must be an existing disposable demo-world directory")
|
||||
root = build_pack.ROOT
|
||||
instance = root / ".local/runtime/mac-benchmarks" / args.label
|
||||
if instance.exists():
|
||||
parser.error("This label already exists; use a new label to retain prior evidence")
|
||||
mixins = ["MacGpuTimingMixin"] if args.gpu_profile else []
|
||||
if args.jfr_profile:
|
||||
mixins.append("MacFrameIntervalMixin")
|
||||
if args.vbo_profile:
|
||||
mixins.append("MacDhVertexAllocationMixin")
|
||||
if args.cloud_image_check and production_cloud_batch:
|
||||
mixins.append("MacDhCloudClockMixin")
|
||||
if args.cloud_batch:
|
||||
mixins.extend(["MacDhCloudBatchMixin", "MacDhCloudUniformMixin", "MacDhCloudBarrierMixin", "MacDhCloudClockMixin"])
|
||||
if args.pass_profile:
|
||||
mixins.extend(["MacGpuPassFrameMixin", "MacVulkanPassTimingMixin"])
|
||||
fixture = args.fixture or build_mod.build_integration_fixture("mac-benchmark",
|
||||
"dev.horizonlab.metrics.MacBenchmarkIntegration", fixture_dependencies={},
|
||||
fixture_mixins=mixins)
|
||||
mod = root / "dist" / f"horizon-lab-{build_pack.read_lock()['pack_version']}.jar"
|
||||
hashes = {"original_mod_sha256": hashlib.sha256(mod.read_bytes()).hexdigest(),
|
||||
"fixture_sha256": hashlib.sha256(fixture.read_bytes()).hexdigest()}
|
||||
build_pack.prepare_instance(args.profile, instance, mod)
|
||||
shutil.copytree(args.seed_world, instance / "saves/Demo_World")
|
||||
shutil.copy2(fixture, instance / "mods/mac-benchmark-fixture.jar")
|
||||
options = instance / "options.txt"
|
||||
text = options.read_text()
|
||||
text = re.sub(r"^renderDistance:.*$", f"renderDistance:{args.render}", text, flags=re.MULTILINE)
|
||||
text = re.sub(r"^preferredGraphicsBackend:.*$", "preferredGraphicsBackend:" + json.dumps(args.backend), text, flags=re.MULTILINE)
|
||||
# Match the supplied capture's expensive visual workload. Simulation remains 12.
|
||||
options.write_text(text + 'fov:1.0\nrenderClouds:"false"\ninactivityFpsLimit:"minimized"\n')
|
||||
(instance / "config/mac-benchmark.properties").write_text(
|
||||
f"label={args.label}\nwarmupSeconds={args.warmup}\nseconds={args.seconds}\n"
|
||||
f"gpuProfile={str(args.gpu_profile).lower()}\nscene={args.scene}\n"
|
||||
f"jfrProfile={str(args.jfr_profile).lower()}\n"
|
||||
f"vboProfile={str(args.vbo_profile).lower()}\n"
|
||||
f"renderClassPrewarm={str(args.render_prewarm).lower()}\n"
|
||||
f"postRecordSeconds={args.post_record_seconds}\n"
|
||||
f"passProfile={str(args.pass_profile).lower()}\ncloudBatch={str(args.cloud_batch).lower()}\n"
|
||||
f"cloudImageCheck={str(args.cloud_image_check).lower()}\n"
|
||||
f"productionCloudBatch={str(production_cloud_batch).lower()}\n")
|
||||
command = [sys.executable, str(root / "tools/runtime.py"), "demo", "--instance", str(instance),
|
||||
"--heap", args.heap, "--width", str(args.width), "--height", str(args.height), "--gc", args.gc]
|
||||
if args.compact_headers:
|
||||
command.append("--compact-headers")
|
||||
if args.fullscreen:
|
||||
command.append("--fullscreen")
|
||||
run = run_fixture(command, instance, os.environ.copy(), timeout=args.warmup + args.seconds + args.post_record_seconds + 180)
|
||||
result_file = instance / "mac-benchmark-result.json"
|
||||
if run.returncode or not result_file.is_file():
|
||||
raise RuntimeError(f"Mac benchmark failed; inspect the private log in {instance}")
|
||||
result = json.loads(result_file.read_text())
|
||||
if args.cloud_image_check:
|
||||
if not result.get("image_check_only") or not result.get("frozen_scene") or result.get("cloud_batch", {}).get("draws", 0) < 100:
|
||||
raise AssertionError("Frozen cloud image check was not exercised")
|
||||
if not all((instance / name).is_file() for name in ["cloud-image-control-a.png", "cloud-image-batched.png", "cloud-image-control-b.png"]):
|
||||
raise AssertionError("Cloud image readbacks are missing")
|
||||
result.update(hashes)
|
||||
result_file.write_text(json.dumps(result, indent=2) + "\n")
|
||||
print(json.dumps(result), flush=True)
|
||||
return
|
||||
frames = result["frames"]
|
||||
validate_capture(result, instance, args.render, args.seconds)
|
||||
if result.get("post_record_seconds", 3) != args.post_record_seconds:
|
||||
raise AssertionError("This fixture does not provide the requested profiler shutdown delay")
|
||||
if bool(result.get("render_class_prewarm")) != args.render_prewarm:
|
||||
raise AssertionError("Requested rendering-class prewarm differs from the fixture")
|
||||
if args.render_prewarm and len(result.get("prewarmed_render_classes", [])) != 5:
|
||||
raise AssertionError("Measured rendering classes were not all preloaded")
|
||||
if production_cloud_batch and (not frames.get("dh_cloud_batch_enabled") or frames.get("dh_cloud_batch_draws_recorded", 0) < 100):
|
||||
raise AssertionError("Production cloud batching was not active in the recording")
|
||||
if args.scene == "flyover" and math.hypot(result["player_x"] + 41.5, result["player_z"] - 72.5) < args.seconds:
|
||||
raise AssertionError("Private flyover did not move far enough to exercise terrain")
|
||||
if result.get("scene", "spawn") != args.scene:
|
||||
raise AssertionError("Fixture did not render the requested scene")
|
||||
batch = result.get("cloud_batch", {})
|
||||
if args.cloud_batch and (not batch.get("requested") or batch.get("draws", 0) < 100 or batch.get("passes", 0) < 1):
|
||||
raise AssertionError("Requested private cloud batch was not exercised")
|
||||
if not args.cloud_batch and batch.get("requested"):
|
||||
raise AssertionError("Unexpected private cloud batch")
|
||||
if args.gpu_profile:
|
||||
if result.get("gpu_profile", {}).get("gpu_render_duration", {}).get("count", 0) < 100:
|
||||
raise AssertionError("Requested GPU timings were not captured")
|
||||
elif "gpu_profile" in result:
|
||||
raise AssertionError("Unexpected GPU profiling in a timing-only run")
|
||||
if args.pass_profile:
|
||||
passes = result.get("gpu_pass_profile", {}).get("passes", {})
|
||||
if not passes or result["graphics_backend"] != "Vulkan":
|
||||
raise AssertionError("Requested native Vulkan pass timings were not captured")
|
||||
elif "gpu_pass_profile" in result:
|
||||
raise AssertionError("Unexpected GPU pass profiling in a timing-only run")
|
||||
if args.jfr_profile:
|
||||
if "jfr_profile" not in result or (instance / "mac-benchmark.jfr").stat().st_size < 1024:
|
||||
raise AssertionError("Requested JFR diagnostics were not captured")
|
||||
elif "jfr_profile" in result:
|
||||
raise AssertionError("Unexpected JFR profiling in a timing-only run")
|
||||
if args.vbo_profile:
|
||||
if result.get("vbo_allocation_profile", {}).get("instrumented_calls_total", 0) < 1:
|
||||
raise AssertionError("Requested terrain allocation profiling was not exercised")
|
||||
timings = result["vbo_allocation_profile"]
|
||||
if any(key not in timings for key in ["whole_upload", "staging_copy_and_barrier"]):
|
||||
raise AssertionError("This fixture does not capture the complete terrain upload path")
|
||||
if any(timing["dropped_samples"] or timing["dropped_slow_events"] for timing in
|
||||
[timings, timings["whole_upload"], timings["staging_copy_and_barrier"]]):
|
||||
raise AssertionError("Terrain allocation profiling lost samples")
|
||||
elif "vbo_allocation_profile" in result:
|
||||
raise AssertionError("Unexpected allocation profiling in a timing-only run")
|
||||
result["requested_backend"] = args.backend
|
||||
result["backend_fallback"] = result["graphics_backend"].lower() != args.backend
|
||||
result.update(hashes)
|
||||
result["profiling_enabled"] = args.gpu_profile or args.pass_profile or args.jfr_profile or args.vbo_profile
|
||||
result_file.write_text(json.dumps(result, indent=2) + "\n")
|
||||
print(json.dumps({"label": args.label, "gpu": result["gpu"], "backend": result["graphics_backend"],
|
||||
"fallback": result["backend_fallback"], "frames": frames["stats"],
|
||||
"process_cpu_mean_cores": frames["process_cpu_mean_cores"],
|
||||
"resolution": [frames["framebuffer_width"], frames["framebuffer_height"]]}), flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+8
-1
@@ -160,11 +160,16 @@ def main():
|
||||
parser.add_argument("command", choices=["prepare", "demo"])
|
||||
parser.add_argument("--instance", type=Path, default=CACHE / "demo")
|
||||
parser.add_argument("--heap", default="4G")
|
||||
parser.add_argument("--width", type=int, default=1280, help="Logical window width; Retina framebuffer can be larger")
|
||||
parser.add_argument("--height", type=int, default=800, help="Logical window height; Retina framebuffer can be larger")
|
||||
parser.add_argument("--fullscreen", action="store_true", help="Use the game's native fullscreen mode")
|
||||
parser.add_argument("--compact-headers", action="store_true", help="Enable Java 25 compact object headers for this demo process")
|
||||
parser.add_argument("--gc", choices=["default", "g1", "zgc"], default="default", help="Select a collector for this process; default leaves JVM policy in use")
|
||||
args = parser.parse_args()
|
||||
if not re.fullmatch(r"[1-9][0-9]*[MGmg]", args.heap):
|
||||
parser.error("--heap must look like 4G")
|
||||
if not (64 <= args.width <= 8192 and 64 <= args.height <= 8192):
|
||||
parser.error("--width and --height must be between 64 and 8192")
|
||||
manifest, jars, natives = prepare_client()
|
||||
if args.command == "prepare":
|
||||
return
|
||||
@@ -179,7 +184,9 @@ def main():
|
||||
"--username", "HorizonDemo", "--version", manifest["id"], "--gameDir", str(instance),
|
||||
"--assetsDir", str(CACHE / "assets"), "--assetIndex", manifest["assetIndex"]["id"],
|
||||
"--accessToken", "0", "--uuid", "00000000000000000000000000000000", "--demo",
|
||||
"--width", "1280", "--height", "800"]
|
||||
"--width", str(args.width), "--height", str(args.height)]
|
||||
if args.fullscreen:
|
||||
command.append("--fullscreen")
|
||||
subprocess.run(command, cwd=instance, check=True)
|
||||
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ class FixtureTimeout(RuntimeError):
|
||||
self.run = run
|
||||
|
||||
|
||||
def run_fixture(command, instance, environment):
|
||||
def run_fixture(command, instance, environment, timeout=240):
|
||||
# Keep all children of this private fixture in a controllable process group.
|
||||
process = subprocess.Popen(command, cwd=build_pack.ROOT, env=environment, text=True,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||
@@ -37,7 +37,7 @@ def run_fixture(command, instance, environment):
|
||||
try: os.killpg(process.pid, signal.SIGKILL if force else signal.SIGTERM)
|
||||
except ProcessLookupError: pass
|
||||
try:
|
||||
output, _ = process.communicate(timeout=240)
|
||||
output, _ = process.communicate(timeout=timeout)
|
||||
except subprocess.TimeoutExpired:
|
||||
stop_tree()
|
||||
try: output, _ = process.communicate(timeout=10)
|
||||
|
||||
+24
-1
@@ -3,7 +3,9 @@
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
@@ -14,7 +16,11 @@ def main(collector="default"):
|
||||
BUILD.mkdir(parents=True, exist_ok=True)
|
||||
fixture = BUILD / "fixture.jfr"
|
||||
gc_flags = ["-XX:+UseZGC"] if collector == "zgc" else []
|
||||
subprocess.run(["java", "-Xmx128M", *gc_flags, str(ROOT / "tests/java/JfrFixture.java"), str(fixture)], check=True)
|
||||
classes = BUILD / "classes"
|
||||
classes.mkdir(exist_ok=True)
|
||||
subprocess.run(["javac", "--release", "25", "-d", str(classes),
|
||||
str(ROOT / "tools/JfrSummary.java"), str(ROOT / "tests/java/JfrFixture.java")], check=True)
|
||||
subprocess.run(["java", "-Xmx128M", *gc_flags, "-cp", str(classes), "JfrFixture", str(fixture)], check=True)
|
||||
summary = json.loads(subprocess.check_output(
|
||||
["java", str(ROOT / "tools/JfrSummary.java"), str(fixture)], text=True))
|
||||
events = json.loads(subprocess.check_output(
|
||||
@@ -63,7 +69,24 @@ def main(collector="default"):
|
||||
for child in value:
|
||||
no_weight_keys(child)
|
||||
no_weight_keys(summary)
|
||||
frames = json.loads(subprocess.check_output(
|
||||
["java", "-cp", str(classes), "JfrSummary", "--frame-stalls", str(fixture)], text=True))
|
||||
assert frames["frame_events"] == 1 and len(frames["slow_frames"]) == 1
|
||||
assert 0 < frames["slow_frames"][0]["gc_pause_overlap_ms"] < frames["slow_frames"][0]["interval_ms"]
|
||||
uploads = json.loads(subprocess.check_output(
|
||||
["jfr", "print", "--json", "--events", "dev.horizonlab.TerrainUpload", str(fixture)],
|
||||
text=True))["recording"]["events"]
|
||||
assert len(uploads) == 2
|
||||
durations = [float(re.fullmatch(r"PT([0-9.]+)S", event["values"]["duration"]).group(1)) * 1000
|
||||
for event in uploads]
|
||||
assert math.isclose(frames["slow_frames"][0]["terrain_upload_overlap_ms"], max(durations), abs_tol=1e-6)
|
||||
loads = frames["slow_frames"][0]["render_thread_class_loads"]
|
||||
payload = next(row for row in loads if row["class_name"] == "JfrFixture$LoadPayload")
|
||||
assert payload["duration_ms"] >= 5
|
||||
coverage = frames["slow_frames"][0]["render_thread_class_load_overlap_ms"]
|
||||
assert payload["overlap_ms"] <= coverage <= frames["slow_frames"][0]["interval_ms"]
|
||||
print("Streaming JFR summary matches real JDK allocation counters, stack-sample counts, GC event counts, heap observations and escaped thread names.")
|
||||
print("Frame/GC overlap, nested upload coverage, real slow class loading and 10,000 seeded interval-union cases checked.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in new issue
Block a user