makepad/platform/tests/metal_alloc_probe.rs
Admin 2e27ce2ff1 platform: retained draw lists, shared publications and GPU residency (DL-0..DL-5)
Squash of 27 work commits (Sep 3–12):
  aa907ca  platform: a frame trace on both desktop backends, and a window that cannot present stops ticking at 600 Hz
  e3abf4d  map: the hosted-tile cache keeps the same ~2x-visible margin as the local one — a budget pinned at the visible set evicted the trailing edge of every pan on the next frame and refetched it a moment later
  8d542fe  platform: a child pass orphaned by its attaching draw list is no longer painted — the window's gauss_scene pass stayed a live_with_parent child after the map went flat and re-encoded a frozen 400-item list every pan frame with stale geometry ids (new tile meshes × old instance counts, tens of millions of triangles into a texture nobody read); make_child_pass records the recording list + redraw id, stale passes are skipped, a cached View re-attaches on a cache hit; tests for the orphan gate and the pool generation contract
  bb49fe6  map + platform: retained per-tile draw lists — each resident tile owns one DrawList2d per carto pass (fill, casing, stroke, icon, icon-high, shadow) and the label glyph batches are retained the same way, recorded when the bake, LOD ring, fringe/icon gates, flat/tilted or clip change and re-attached otherwise; a pan/zoom/tilt frame pushes this frame's uniforms onto the retained calls (DrawVars::update_uniforms_on_area, resolved slot table) and uploads zero instance bytes; the tilted per-pass depth is a pass_depth uniform; a held list's zbias resolves at entry (zbias_hold in every backend); the shimmer heartbeat patches shiny_time in place without a redraw; a freed/reused sub-list id is skipped by every draw-tree walker and the mask list re-records empty on the flat transition (contract test). Web pan tail 1,098 → 20 MiB/s, flat pan 2.4 → 0.43 MiB/frame; Metal grabs within the run-to-run noise floor
  313265d  Studio code atlas: geometry code views, live filter, 3D size lens, lanes as terminals
  9c4e0cc  Studio code atlas: performance round — retained uploads, worker labels, exact search, no forks
  7235d7e  Studio code atlas: stall fix, GPU working set, lens hard switch, filter masks, parallel index
  56a6da5  platform: per-pass GPU counter timing on Metal; retained publications replace in place
  72e2443  platform: present-path trace (1 Hz cause histograms), bounded drawable wait and retirement on macOS
  1c5d583  platform: bounded retained maintenance on empty paint beats; republish actual backend debt
  edfed73  platform: retained residency high/low water and hysteresis; no distance eviction without pressure
  acab6a0  platform: critical upload class serves present-blocking items first; identical immediate re-records upload nothing
  906c774  platform: uniform_range on DrawVars and patch_retained_uniforms on retained draw lists
  c29031c  platform/draw/widgets: heap-keyed script resources and RecordingBuffer draw items — the files today's commits depend on
  cd0964f  platform headless: homogeneous near-plane clipping before the perspective divide
  61d424d  platform: release retained bindings of released textures so pool evictions complete
  a00287a  platform: texture-tile cache support — per-item instance ranges, painted pass receipts, display-dpi pass uniform, retained render targets, present gate on the drawable pool
  74b63be  platform: retained upload floor reverted, unconfirmed presents counted
  c03fcc5  platform: tile-cache round 3 support — O(1) demand on re-recorded lists, evictions counter, allocated_size, lost-button release, Vec2d::round
  e515d75  platform: shared instance publications — the DL-0/DL-1 contract, additive beside the retained path
  142a337  platform: shared instance publications — close the seven review items (DL-1b)
  6811a19  platform: Debug for SharedInstances, WeakSharedInstances and PublishReceipt
  c061e47  platform: Metal draws are resident by construction — the hole path and its gates are gone (DL-2)
  0bdbb29  platform: drop the unreachable InstancesNotResident present cause
  de3c868  platform: `drawlist` trace names the holder of a stale draw-list id; the per-frame upload line moves to `gpu.upload`
  721c3d8  platform: publication backends — Metal per-publication backings, lease-keyed uniform ring, receipts on every backend, Vulkan draws attached items (DL-3)
  393de54  platform: integrated deletion — the draw-list system is generic again (DL-5)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 13:40:23 +02:00

94 lines
4.7 KiB
Rust

//! Cleanup DL-0 micro-probe: what a Metal buffer costs on this machine, as the
//! contract's "measured" section cites it. Cold allocation, first touch, a
//! CPU copy into shared storage, a warm re-allocation after a release, an
//! empty command-buffer round trip, a GPU blit of the same bytes, and the
//! release itself. Prints one line per size; asserts only sanity (non-null,
//! copied bytes readable back). Run with
//! `cargo test --release -p makepad-platform --test metal_alloc_probe -- --nocapture`.
#[cfg(all(target_os = "macos", not(gpusim)))]
fn main() {
use makepad_platform::os::apple::apple_sys::*;
use std::time::Instant;
fn median(v: &mut Vec<f64>) -> f64 {
v.sort_by(|a, b| a.partial_cmp(b).unwrap());
v[v.len() / 2]
}
unsafe {
let pool: ObjcId = msg_send![class!(NSAutoreleasePool), new];
let device = MTLCreateSystemDefaultDevice();
assert!(!device.is_null(), "no Metal device");
let queue: ObjcId = msg_send![device, newCommandQueue];
assert!(!queue.is_null());
let page = 16384usize;
// Empty command buffer: the floor of one submission's commit + wait.
let mut empty = Vec::new();
for _ in 0..7 {
let t = Instant::now();
let cb: ObjcId = msg_send![queue, commandBuffer];
let () = msg_send![cb, commit];
let () = msg_send![cb, waitUntilCompleted];
empty.push(t.elapsed().as_secs_f64() * 1e6);
}
println!("probe: empty_cb_commit_wait_us={:.0} (median of 7)", median(&mut empty));
for &size in &[64usize << 10, 1 << 20, 4 << 20, 16 << 20] {
let source: Vec<u8> = (0..size).map(|i| (i % 251) as u8).collect();
let (mut cold, mut touch, mut copy, mut warm, mut blit, mut release) =
(Vec::new(), Vec::new(), Vec::new(), Vec::new(), Vec::new(), Vec::new());
for _ in 0..5 {
let t = Instant::now();
let buffer: ObjcId = msg_send![device, newBufferWithLength: size as u64 options: MTLResourceOptions::StorageModeShared];
cold.push(t.elapsed().as_secs_f64() * 1e6);
assert!(!buffer.is_null());
let contents: *mut u8 = msg_send![buffer, contents];
let t = Instant::now();
let mut off = 0;
while off < size {
contents.add(off).write_volatile(1);
off += page;
}
touch.push(t.elapsed().as_secs_f64() * 1e6);
let t = Instant::now();
std::ptr::copy_nonoverlapping(source.as_ptr(), contents, size);
copy.push(t.elapsed().as_secs_f64() * 1e6);
assert_eq!(*contents.add(size - 1), source[size - 1]);
// GPU blit of the same bytes into a second buffer.
let target: ObjcId = msg_send![device, newBufferWithLength: size as u64 options: MTLResourceOptions::StorageModeShared];
let t = Instant::now();
let cb: ObjcId = msg_send![queue, commandBuffer];
let enc: ObjcId = msg_send![cb, blitCommandEncoder];
let () = msg_send![enc, copyFromBuffer: buffer sourceOffset: 0u64 toBuffer: target destinationOffset: 0u64 size: size as u64];
let () = msg_send![enc, endEncoding];
let () = msg_send![cb, commit];
let () = msg_send![cb, waitUntilCompleted];
blit.push(t.elapsed().as_secs_f64() * 1e6);
let tc: *const u8 = msg_send![target, contents];
assert_eq!(*tc.add(size - 1), source[size - 1]);
let () = msg_send![target, release];
let t = Instant::now();
let () = msg_send![buffer, release];
release.push(t.elapsed().as_secs_f64() * 1e6);
// Warm: the allocator has just seen a release of this size.
let t = Instant::now();
let again: ObjcId = msg_send![device, newBufferWithLength: size as u64 options: MTLResourceOptions::StorageModeShared];
warm.push(t.elapsed().as_secs_f64() * 1e6);
let () = msg_send![again, release];
}
println!(
"probe: size={} cold_alloc_us={:.0} first_touch_us={:.0} memcpy_us={:.0} warm_alloc_us={:.0} gpu_blit_commit_wait_us={:.0} release_us={:.0} (medians of 5)",
size, median(&mut cold), median(&mut touch), median(&mut copy), median(&mut warm), median(&mut blit), median(&mut release)
);
}
let () = msg_send![queue, release];
let () = msg_send![pool, release];
}
}
#[cfg(not(all(target_os = "macos", not(gpusim))))]
fn main() {}